From 5a1569f499a314a5a8b321f519b0d50d0026a874 Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Sun, 29 Apr 2018 17:33:39 +0200 Subject: [PATCH 01/15] Added fast path for SolidBrush in FillProcessor. --- .../Drawing/Brushes/SolidBrush{TPixel}.cs | 21 ++++-- .../Drawing/Processors/FillProcessor.cs | 66 ++++++++++++------- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/SolidBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/SolidBrush{TPixel}.cs index 826f5f60a..f2054ee0d 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Brushes/SolidBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Brushes/SolidBrush{TPixel}.cs @@ -90,16 +90,23 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes MemoryManager memoryManager = this.Target.MemoryManager; - using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) + if (this.Options.BlendPercentage == 1f) { - Span amountSpan = amountBuffer.Span; - - for (int i = 0; i < scanline.Length; i++) + this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, scanline); + } + else + { + using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) { - amountSpan[i] = scanline[i] * this.Options.BlendPercentage; - } + Span amountSpan = amountBuffer.Span; + + for (int i = 0; i < scanline.Length; i++) + { + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; + } - this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan); + this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan); + } } } } diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs index e4ef44564..0ef7db419 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs @@ -3,6 +3,7 @@ using System; using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Drawing.Brushes; @@ -49,38 +50,57 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } - - if (minY > 0) - { - startY = 0; - } - int width = maxX - minX; - using (IBuffer amount = source.MemoryManager.Allocate(width)) - using (BrushApplicator applicator = this.brush.CreateApplicator( - source, - sourceRectangle, - this.options)) - { - amount.Span.Fill(this.options.BlendPercentage); + var solidBrush = this.brush as SolidBrush; + // If there's no reason for blending, then avoid it. + if (solidBrush != null && this.options.BlendPercentage == 1f && solidBrush.Color.ToVector4().Z == 1f) + { Parallel.For( minY, maxY, configuration.ParallelOptions, y => - { - int offsetY = y - startY; - int offsetX = minX - startX; + { + int offsetY = y - startY; + int offsetX = minX - startX; + source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color); + }); + } + else + { + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (IBuffer amount = source.MemoryManager.Allocate(width)) + using (BrushApplicator applicator = this.brush.CreateApplicator( + source, + sourceRectangle, + this.options)) + { + amount.Span.Fill(this.options.BlendPercentage); + + Parallel.For( + minY, + maxY, + configuration.ParallelOptions, + y => + { + int offsetY = y - startY; + int offsetX = minX - startX; - applicator.Apply(amount.Span, offsetX, offsetY); - }); + applicator.Apply(amount.Span, offsetX, offsetY); + }); + } } } } From 08b033c684c4d693310826dd1388c6da24ca12cf Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Sun, 29 Apr 2018 17:45:02 +0200 Subject: [PATCH 02/15] Oops, should be W instead of Z ofcourse. --- .../Processing/Drawing/Processors/FillProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs index 0ef7db419..c3addaf29 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors var solidBrush = this.brush as SolidBrush; // If there's no reason for blending, then avoid it. - if (solidBrush != null && this.options.BlendPercentage == 1f && solidBrush.Color.ToVector4().Z == 1f) + if (solidBrush != null && this.options.BlendPercentage == 1f && solidBrush.Color.ToVector4().W == 1f) { Parallel.For( minY, From 58384de0c7b884bd6f30fa495aba8a95217569d6 Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Sun, 29 Apr 2018 20:19:35 +0200 Subject: [PATCH 03/15] Add Image and ImageFrame constructors that take a clear color as parameter. This is good for performance because this avoids first needing to clear the buffer with zeroes. --- src/ImageSharp/ImageFrameCollection.cs | 20 ++++++++++-- src/ImageSharp/Image{TPixel}.cs | 40 +++++++++++++++++++++++ src/ImageSharp/Memory/BufferExtensions.cs | 12 +++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index a9225eec4..1c00d9e63 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -23,7 +23,14 @@ namespace SixLabors.ImageSharp this.parent = parent; // Frames are already cloned within the caller - this.frames.Add(new ImageFrame(parent.GetConfiguration().MemoryManager, width, height)); + if (parent.ClearColor.HasValue) + { + this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, parent.ClearColor.Value)); + } + else + { + this.frames.Add(new ImageFrame(parent.GetConfiguration().MemoryManager, width, height)); + } } internal ImageFrameCollection(Image parent, IEnumerable> frames) @@ -143,7 +150,16 @@ namespace SixLabors.ImageSharp /// public ImageFrame CreateFrame() { - var frame = new ImageFrame(this.parent.GetConfiguration().MemoryManager, this.RootFrame.Width, this.RootFrame.Height); + ImageFrame frame; + if (this.parent.ClearColor.HasValue) + { + frame = new ImageFrame(this.parent.GetConfiguration(), this.RootFrame.Width, this.RootFrame.Height, this.parent.ClearColor.Value); + } + else + { + frame = new ImageFrame(this.parent.GetConfiguration().MemoryManager, this.RootFrame.Width, this.RootFrame.Height); + } + this.frames.Add(frame); return frame; } diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 78a091e41..2aa503844 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -22,6 +22,7 @@ namespace SixLabors.ImageSharp { private readonly Configuration configuration; private readonly ImageFrameCollection frames; + private readonly TPixel? clearColor; /// /// Initializes a new instance of the class @@ -37,6 +38,21 @@ namespace SixLabors.ImageSharp { } + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to initialize the pixels with. + public Image(Configuration configuration, int width, int height, TPixel clearColor) + : this(configuration, width, height, clearColor, new ImageMetaData()) + { + } + /// /// Initializes a new instance of the class /// with the height and the width of the image. @@ -66,6 +82,25 @@ namespace SixLabors.ImageSharp this.frames = new ImageFrameCollection(this, width, height); } + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The clear color. + /// The images metadata. + internal Image(Configuration configuration, int width, int height, TPixel clearColor, ImageMetaData metadata) { + this.configuration = configuration ?? Configuration.Default; + this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); + this.MetaData = metadata ?? new ImageMetaData(); + this.clearColor = clearColor; + this.frames = new ImageFrameCollection(this, width, height); + } + /// /// Initializes a new instance of the class /// with the height and the width of the image. @@ -104,6 +139,11 @@ namespace SixLabors.ImageSharp /// public IImageFrameCollection Frames => this.frames; + /// + /// Gets the clear color to initialize the image frame pixels with. + /// + internal TPixel? ClearColor => this.clearColor; + /// /// Gets the root frame. /// diff --git a/src/ImageSharp/Memory/BufferExtensions.cs b/src/ImageSharp/Memory/BufferExtensions.cs index dd3114c21..1347f2882 100644 --- a/src/ImageSharp/Memory/BufferExtensions.cs +++ b/src/ImageSharp/Memory/BufferExtensions.cs @@ -52,6 +52,18 @@ namespace SixLabors.ImageSharp.Memory buffer.Span.Clear(); } + /// + /// Fills the contents of this buffer. + /// + /// The buffer + /// The value to fill the buffer with. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Fill(this IBuffer buffer, T value) + where T : struct + { + buffer.Span.Fill(value); + } + public static ref T DangerousGetPinnableReference(this IBuffer buffer) where T : struct => ref MemoryMarshal.GetReference(buffer.Span); From 8995a738aa2e98acf8e52457a35af0ba3ac5bc55 Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Sun, 29 Apr 2018 21:36:03 +0200 Subject: [PATCH 04/15] Processed Scott's review comment. --- .../Drawing/Processors/FillProcessor.cs | 6 ++++- .../PixelFormats/PixelBlenderMode.cs | 24 +++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs index c3addaf29..3417b8faa 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs @@ -55,7 +55,11 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors var solidBrush = this.brush as SolidBrush; // If there's no reason for blending, then avoid it. - if (solidBrush != null && this.options.BlendPercentage == 1f && solidBrush.Color.ToVector4().W == 1f) + if (solidBrush != null && + ( + (this.options.BlenderMode == PixelBlenderMode.Normal && this.options.BlendPercentage == 1f && solidBrush.Color.ToVector4().W == 1f) || + (this.options.BlenderMode == PixelBlenderMode.Over && this.options.BlendPercentage == 1f && solidBrush.Color.ToVector4().W == 1f) || + (this.options.BlenderMode == PixelBlenderMode.Src))) { Parallel.For( minY, diff --git a/src/ImageSharp/PixelFormats/PixelBlenderMode.cs b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs index 4b8f56d76..7a8ab6592 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenderMode.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs @@ -54,62 +54,62 @@ namespace SixLabors.ImageSharp.PixelFormats HardLight, /// - /// returns the source colors + /// returns the source colors. /// Src, /// - /// returns the source over the destination + /// returns the source over the destination. /// Atop, /// - /// returns the detination over the source + /// returns the destination over the source. /// Over, /// - /// the source where the desitnation and source overlap + /// The source where the destination and source overlap. /// In, /// - /// the destination where the desitnation and source overlap + /// The destination where the destination and source overlap. /// Out, /// - /// the destination where the source does not overlap it + /// The destination where the source does not overlap it. /// Dest, /// - /// the source where they dont overlap othersie dest in overlapping parts + /// The source where they don't overlap othersie dest in overlapping parts. /// DestAtop, /// - /// the destnation over the source + /// The destination over the source. /// DestOver, /// - /// the destination where the desitnation and source overlap + /// The destination where the destination and source overlap. /// DestIn, /// - /// the source where the desitnation and source overlap + /// The source where the destination and source overlap. /// DestOut, /// - /// the clear. + /// The clear. /// Clear, /// - /// clear where they overlap + /// Clear where they overlap. /// Xor } From 02fc4ef43b7834f4d0e7de927de0ec41a5725afe Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Sun, 29 Apr 2018 23:19:28 +0200 Subject: [PATCH 05/15] I think I forgot to commit this one. --- src/ImageSharp/ImageFrame{TPixel}.cs | 51 ++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index cf7a1ae4f..cb15fe3db 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -4,6 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -52,6 +53,39 @@ namespace SixLabors.ImageSharp this.MetaData = metaData; } + /// + /// Initializes a new instance of the class. + /// + /// The to use for buffer allocation and parallel options to clear the buffer with. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to clear the image with. + internal ImageFrame(Configuration configuration, int width, int height, TPixel clearColor) + : this(configuration, width, height, clearColor, new ImageFrameMetaData()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The to use for buffer allocation and parallel options to clear the buffer with. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to clear the image with. + /// The meta data. + internal ImageFrame(Configuration configuration, int width, int height, TPixel clearColor, ImageFrameMetaData metaData) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + Guard.NotNull(metaData, nameof(metaData)); + + this.MemoryManager = configuration.MemoryManager; + this.PixelBuffer = this.MemoryManager.Allocate2D(width, height, false); + this.Clear(configuration.ParallelOptions, clearColor); + this.MetaData = metaData; + } + /// /// Initializes a new instance of the class. /// @@ -267,6 +301,23 @@ namespace SixLabors.ImageSharp return target; } + /// + /// Clears the bitmap. + /// + /// The parallel options. + /// The value to initialize the bitmap with. + public void Clear(ParallelOptions parallelOptions, TPixel value) { + Parallel.For( + 0, + this.Height, + parallelOptions, + (int y) => + { + Span targetRow = this.GetPixelRowSpan(y); + targetRow.Fill(value); + }); + } + /// /// Clones the current instance. /// From 994f5fc4a170300319f99f4a5e395bafc6523ac2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 29 Apr 2018 23:44:39 +0200 Subject: [PATCH 06/15] "BlendedShapes" -> SolidFillBlendedShapesTests --- .../ImageSharp.Tests/Drawing/BlendedShapes.cs | 92 ----------- .../Drawing/SolidFillBlendedShapesTests.cs | 149 ++++++++++++++++++ .../Formats/Png/PngEncoderTests.cs | 2 +- .../TestUtilities/ImagingTestCaseUtility.cs | 52 ++++-- .../TestUtilities/TestImageExtensions.cs | 16 +- 5 files changed, 199 insertions(+), 112 deletions(-) delete mode 100644 tests/ImageSharp.Tests/Drawing/BlendedShapes.cs create mode 100644 tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs diff --git a/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs b/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs deleted file mode 100644 index c39b5bc34..000000000 --- a/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Generic; -using System.Linq; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Drawing; -using SixLabors.Primitives; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Drawing -{ - using SixLabors.ImageSharp.Processing; - - public class BlendedShapes - { - public static IEnumerable modes = ((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode))) - .Select(x => new object[] { x }); - - [Theory] - [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] - public void DrawBlendedValues(TestImageProvider provider, PixelBlenderMode mode) - where TPixel : struct, IPixel - { - using (var img = provider.GetImage()) - { - var scaleX = (img.Width / 100); - var scaleY = (img.Height / 100); - img.Mutate(x => x - .Fill(NamedColors.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)) - .Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY) - )); - img.DebugSave(provider, new { mode }); - } - } - - [Theory] - [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] - public void DrawBlendedValues_transparent(TestImageProvider provider, PixelBlenderMode mode) - where TPixel : struct, IPixel - { - using (var img = provider.GetImage()) - { - var scaleX = (img.Width / 100); - var scaleY = (img.Height / 100); - img.Mutate(x => x.Fill(NamedColors.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); - img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))); - img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors.Transparent, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); - img.DebugSave(provider, new { mode }); - } - } - - [Theory] - [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] - public void DrawBlendedValues_transparent50Percent(TestImageProvider provider, PixelBlenderMode mode) - where TPixel : struct, IPixel - { - using (var img = provider.GetImage()) - { - var scaleX = (img.Width / 100); - var scaleY = (img.Height / 100); - img.Mutate(x => x.Fill(NamedColors.DarkBlue, new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY))); - img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors.HotPink, new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY))); - var c = NamedColors.Red.ToVector4(); - c.W *= 0.5f; - TPixel pixel = default(TPixel); - pixel.PackFromVector4(c); - - img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, pixel, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); - img.DebugSave(provider, new { mode }); - } - } - - - - [Theory] - [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] - public void DrawBlendedValues_doldidEllips(TestImageProvider provider, PixelBlenderMode mode) - where TPixel : struct, IPixel - { - using (var img = provider.GetImage()) - { - var scaleX = (img.Width / 100); - var scaleY = (img.Height / 100); - img.Mutate(x => x.Fill(NamedColors.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); - img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors.Black, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); - img.DebugSave(provider, new { mode }); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs b/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs new file mode 100644 index 000000000..e23a09d52 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs @@ -0,0 +1,149 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Drawing +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Processing.Drawing; + using SixLabors.Primitives; + + using Xunit; + + [GroupOutput("Drawing")] + public class SolidFillBlendedShapesTests + { + public static IEnumerable modes = + ((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode))).Select(x => new object[] { x }); + + [Theory] + [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] + public void _1DarkBlueRect_2BlendHotPinkRect( + TestImageProvider provider, + PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + int scaleX = img.Width / 100; + int scaleY = img.Height / 100; + img.Mutate( + x => x.Fill( + NamedColors.DarkBlue, + new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY) + ) + .Fill(new GraphicsOptions(true) { BlenderMode = mode }, + NamedColors.HotPink, + new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)) + ); + + VerifyImage(provider, mode, img); + } + } + + [Theory] + [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] + public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse( + TestImageProvider provider, + PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + int scaleX = img.Width / 100; + int scaleY = img.Height / 100; + img.Mutate( + x => x.Fill( + NamedColors.DarkBlue, + new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); + img.Mutate( + x => x.Fill( + new GraphicsOptions(true) { BlenderMode = mode }, + NamedColors.HotPink, + new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))); + img.Mutate( + x => x.Fill( + new GraphicsOptions(true) { BlenderMode = mode }, + NamedColors.Transparent, + new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) + ); + + VerifyImage(provider, mode, img); + } + } + + [Theory] + [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] + public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse( + TestImageProvider provider, + PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + int scaleX = (img.Width / 100); + int scaleY = (img.Height / 100); + img.Mutate( + x => x.Fill( + NamedColors.DarkBlue, + new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY))); + img.Mutate( + x => x.Fill( + new GraphicsOptions(true) { BlenderMode = mode }, + NamedColors.HotPink, + new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY))); + var c = NamedColors.Red.ToVector4(); + c.W *= 0.5f; + var pixel = default(TPixel); + pixel.PackFromVector4(c); + + img.Mutate( + x => x.Fill( + new GraphicsOptions(true) { BlenderMode = mode }, + pixel, + new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) + ); + + VerifyImage(provider, mode, img); ; + } + } + + [Theory] + [WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] + public void _1DarkBlueRect_2BlendBlackEllipse(TestImageProvider provider, PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + int scaleX = (img.Width / 100); + int scaleY = (img.Height / 100); + img.Mutate( + x => x.Fill( + NamedColors.DarkBlue, + new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); + img.Mutate( + x => x.Fill( + new GraphicsOptions(true) { BlenderMode = mode }, + NamedColors.Black, + new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); + + VerifyImage(provider, mode, img); + } + } + + private static void VerifyImage(TestImageProvider provider, PixelBlenderMode mode, Image img) + where TPixel : struct, IPixel + { + img.DebugSave( + provider, + new { mode }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index e9ae98d10..11124ad03 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests } IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); - string referenceOutputFile = ((ITestImageProvider)provider).Utility.GetReferenceOutputFileName("png", debugInfo, appendPixelType); + string referenceOutputFile = ((ITestImageProvider)provider).Utility.GetReferenceOutputFileName("png", debugInfo, appendPixelType, true); using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) using (var referenceImage = Image.Load(referenceOutputFile, referenceDecoder)) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index cde8ec9e4..340fc600a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -41,16 +41,20 @@ namespace SixLabors.ImageSharp.Tests /// public string TestName { get; set; } = string.Empty; - private string GetTestOutputFileNameImpl(string extension, string details, bool appendPixelTypeToFileName) + private string GetTestOutputFileNameImpl( + string extension, + string details, + bool appendPixelTypeToFileName, + bool appendSourceFileOrDescription) { - string fn = string.Empty; - if (string.IsNullOrWhiteSpace(extension)) { extension = null; } - fn = Path.GetFileNameWithoutExtension(this.SourceFileOrDescription); + string fn = appendSourceFileOrDescription + ? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription) + : ""; if (string.IsNullOrWhiteSpace(extension)) { @@ -92,20 +96,24 @@ namespace SixLabors.ImageSharp.Tests } private static string Inv(FormattableString formattable) => System.FormattableString.Invariant(formattable); - + /// /// Gets the recommended file name for the output of the test /// /// The required extension /// The settings modifying the output path /// A boolean indicating whether to append the pixel type to output file name. + /// A boolean indicating whether to append to the test output file name. /// The file test name - public string GetTestOutputFileName(string extension = null, object testOutputDetails = null, bool appendPixelTypeToFileName = true) + public string GetTestOutputFileName( + string extension = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) { string detailsString = null; - string s = testOutputDetails as string; - if (s != null) + if (testOutputDetails is string s) { detailsString = s; } @@ -128,7 +136,12 @@ namespace SixLabors.ImageSharp.Tests ); } } - return this.GetTestOutputFileNameImpl(extension, detailsString, appendPixelTypeToFileName); + + return this.GetTestOutputFileNameImpl( + extension, + detailsString, + appendPixelTypeToFileName, + appendSourceFileOrDescription); } @@ -139,15 +152,22 @@ namespace SixLabors.ImageSharp.Tests /// The image instance /// The requested extension /// Optional encoder + /// /// A boolean indicating whether to append to the test output file name. public string SaveTestOutputFile( Image image, string extension = null, IImageEncoder encoder = null, object testOutputDetails = null, - bool appendPixelTypeToFileName = true) + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { - string path = this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName); + string path = this.GetTestOutputFileName( + extension, + testOutputDetails, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + encoder = encoder ?? TestEnvironment.GetReferenceEncoder(path); using (FileStream stream = File.OpenWrite(path)) @@ -161,9 +181,10 @@ namespace SixLabors.ImageSharp.Tests int frameCount, string extension = null, object testOutputDetails = null, - bool appendPixelTypeToFileName = true) + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) { - string baseDir = this.GetTestOutputFileName("", testOutputDetails, appendPixelTypeToFileName); + string baseDir = this.GetTestOutputFileName("", testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); if (!Directory.Exists(baseDir)) { @@ -211,10 +232,11 @@ namespace SixLabors.ImageSharp.Tests internal string GetReferenceOutputFileName( string extension, object testOutputDetails, - bool appendPixelTypeToFileName) + bool appendPixelTypeToFileName, + bool appendSourceFileOrDescription) { return TestEnvironment.GetReferenceOutputFileName( - this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName) + this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription) ); } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index ee0382dbe..da335555d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -61,12 +61,14 @@ namespace SixLabors.ImageSharp.Tests /// Details to be concatenated to the test output file, describing the parameters of the test. /// The extension /// A boolean indicating whether to append the pixel type to the output file name. + /// A boolean indicating whether to append to the test output file name. public static Image DebugSave( this Image image, ITestImageProvider provider, object testOutputDetails = null, string extension = "png", - bool appendPixelTypeToFileName = true) + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { if (TestEnvironment.RunsOnCI) @@ -79,7 +81,8 @@ namespace SixLabors.ImageSharp.Tests image, extension, testOutputDetails: testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName); + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); return image; } @@ -255,10 +258,15 @@ namespace SixLabors.ImageSharp.Tests public static Image GetReferenceOutputImage(this ITestImageProvider provider, object testOutputDetails = null, string extension = "png", - bool appendPixelTypeToFileName = true) + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { - string referenceOutputFile = provider.Utility.GetReferenceOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName); + string referenceOutputFile = provider.Utility.GetReferenceOutputFileName( + extension, + testOutputDetails, + appendPixelTypeToFileName, + appendSourceFileOrDescription); if (!File.Exists(referenceOutputFile)) { From 34ed38db8895f31c54d7e900b1f305599d558019 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 29 Apr 2018 23:52:39 +0200 Subject: [PATCH 07/15] assertions for SolidFillBlendedShapesTests --- .../Drawing/SolidFillBlendedShapesTests.cs | 27 +++++++++++-------- .../TestUtilities/TestImageExtensions.cs | 6 +++-- tests/Images/External | 2 +- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs b/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs index e23a09d52..7d73d1b65 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs @@ -1,20 +1,18 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.Linq; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Drawing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; +using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Drawing { - using System; - using System.Collections.Generic; - using System.Linq; - - using SixLabors.ImageSharp.PixelFormats; - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Processing.Drawing; - using SixLabors.Primitives; - - using Xunit; - [GroupOutput("Drawing")] public class SolidFillBlendedShapesTests { @@ -144,6 +142,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing new { mode }, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + + var comparer = ImageComparer.TolerantPercentage(0.01f, 3); + img.CompareFirstFrameToReferenceOutput(comparer, + provider, + new { mode }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index da335555d..c2c0eb487 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -213,7 +213,8 @@ namespace SixLabors.ImageSharp.Tests object testOutputDetails = null, string extension = "png", bool grayscale = false, - bool appendPixelTypeToFileName = true) + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) where TPixel : struct, IPixel { using (var firstFrameOnlyImage = new Image(image.Width, image.Height)) @@ -221,7 +222,8 @@ namespace SixLabors.ImageSharp.Tests provider, testOutputDetails, extension, - appendPixelTypeToFileName)) + appendPixelTypeToFileName, + appendSourceFileOrDescription)) { firstFrameOnlyImage.Frames.AddFrame(image.Frames.RootFrame); firstFrameOnlyImage.Frames.RemoveFrame(0); diff --git a/tests/Images/External b/tests/Images/External index 558729ec8..5a9a88380 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 558729ec87bcf52f22362175842f88a81ccfc483 +Subproject commit 5a9a88380166a87521d10048f53cda7f5f761d66 From 2943df12d751fcfb67d7b5239da1041079cee871 Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Mon, 30 Apr 2018 10:27:21 +0200 Subject: [PATCH 08/15] Processed review comment, made ClearColor not nullable. --- src/ImageSharp/ImageFrameCollection.cs | 20 ++------------------ src/ImageSharp/Image{TPixel}.cs | 4 ++-- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index 1c00d9e63..f2e35812b 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -23,14 +23,7 @@ namespace SixLabors.ImageSharp this.parent = parent; // Frames are already cloned within the caller - if (parent.ClearColor.HasValue) - { - this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, parent.ClearColor.Value)); - } - else - { - this.frames.Add(new ImageFrame(parent.GetConfiguration().MemoryManager, width, height)); - } + this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, parent.ClearColor)); } internal ImageFrameCollection(Image parent, IEnumerable> frames) @@ -150,16 +143,7 @@ namespace SixLabors.ImageSharp /// public ImageFrame CreateFrame() { - ImageFrame frame; - if (this.parent.ClearColor.HasValue) - { - frame = new ImageFrame(this.parent.GetConfiguration(), this.RootFrame.Width, this.RootFrame.Height, this.parent.ClearColor.Value); - } - else - { - frame = new ImageFrame(this.parent.GetConfiguration().MemoryManager, this.RootFrame.Width, this.RootFrame.Height); - } - + ImageFrame frame = new ImageFrame(this.parent.GetConfiguration(), this.RootFrame.Width, this.RootFrame.Height, this.parent.ClearColor); this.frames.Add(frame); return frame; } diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 2aa503844..599116414 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp { private readonly Configuration configuration; private readonly ImageFrameCollection frames; - private readonly TPixel? clearColor; + private readonly TPixel clearColor; /// /// Initializes a new instance of the class @@ -142,7 +142,7 @@ namespace SixLabors.ImageSharp /// /// Gets the clear color to initialize the image frame pixels with. /// - internal TPixel? ClearColor => this.clearColor; + internal TPixel ClearColor => this.clearColor; /// /// Gets the root frame. From aa4a05719fbd011cd4a28f73eb248012b0db20e0 Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Mon, 30 Apr 2018 18:22:19 +0200 Subject: [PATCH 09/15] Processed review comments, moved Image.ClearColor to ImageFrame.BackgroundColor. --- src/ImageSharp/ImageFrameCollection.cs | 6 ++-- src/ImageSharp/ImageFrame{TPixel}.cs | 30 ++++++++++--------- src/ImageSharp/Image{TPixel}.cs | 21 +++++-------- src/ImageSharp/Memory/BufferExtensions.cs | 12 -------- .../Image/ImageFramesCollectionTests.cs | 2 +- 5 files changed, 27 insertions(+), 44 deletions(-) diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index f2e35812b..1d4735fd8 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -16,14 +16,14 @@ namespace SixLabors.ImageSharp private readonly IList> frames = new List>(); private readonly Image parent; - internal ImageFrameCollection(Image parent, int width, int height) + internal ImageFrameCollection(Image parent, int width, int height, TPixel backgroundColor) { Guard.NotNull(parent, nameof(parent)); this.parent = parent; // Frames are already cloned within the caller - this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, parent.ClearColor)); + this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, backgroundColor)); } internal ImageFrameCollection(Image parent, IEnumerable> frames) @@ -143,7 +143,7 @@ namespace SixLabors.ImageSharp /// public ImageFrame CreateFrame() { - ImageFrame frame = new ImageFrame(this.parent.GetConfiguration(), this.RootFrame.Width, this.RootFrame.Height, this.parent.ClearColor); + var frame = new ImageFrame(this.parent.GetConfiguration(), this.RootFrame.Width, this.RootFrame.Height, this.RootFrame.BackgroundColor); this.frames.Add(frame); return frame; } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index cb15fe3db..1c26bb558 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -19,8 +19,7 @@ namespace SixLabors.ImageSharp /// /// The pixel format. public sealed class ImageFrame : IPixelSource, IDisposable - where TPixel : struct, IPixel - { + where TPixel : struct, IPixel { private bool isDisposed; /// @@ -30,8 +29,7 @@ namespace SixLabors.ImageSharp /// The width of the image in pixels. /// The height of the image in pixels. internal ImageFrame(MemoryManager memoryManager, int width, int height) - : this(memoryManager, width, height, new ImageFrameMetaData()) - { + : this(memoryManager, width, height, new ImageFrameMetaData()) { } /// @@ -59,10 +57,9 @@ namespace SixLabors.ImageSharp /// The to use for buffer allocation and parallel options to clear the buffer with. /// The width of the image in pixels. /// The height of the image in pixels. - /// The color to clear the image with. - internal ImageFrame(Configuration configuration, int width, int height, TPixel clearColor) - : this(configuration, width, height, clearColor, new ImageFrameMetaData()) - { + /// The color to clear the image with. + internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor) + : this(configuration, width, height, backgroundColor, new ImageFrameMetaData()) { } /// @@ -71,9 +68,9 @@ namespace SixLabors.ImageSharp /// The to use for buffer allocation and parallel options to clear the buffer with. /// The width of the image in pixels. /// The height of the image in pixels. - /// The color to clear the image with. + /// The color to clear the image with. /// The meta data. - internal ImageFrame(Configuration configuration, int width, int height, TPixel clearColor, ImageFrameMetaData metaData) + internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor, ImageFrameMetaData metaData) { Guard.NotNull(configuration, nameof(configuration)); Guard.MustBeGreaterThan(width, 0, nameof(width)); @@ -82,7 +79,8 @@ namespace SixLabors.ImageSharp this.MemoryManager = configuration.MemoryManager; this.PixelBuffer = this.MemoryManager.Allocate2D(width, height, false); - this.Clear(configuration.ParallelOptions, clearColor); + this.BackgroundColor = backgroundColor; + this.Clear(configuration.ParallelOptions, backgroundColor); this.MetaData = metaData; } @@ -93,8 +91,7 @@ namespace SixLabors.ImageSharp /// The of the frame. /// The meta data. internal ImageFrame(MemoryManager memoryManager, Size size, ImageFrameMetaData metaData) - : this(memoryManager, size.Width, size.Height, metaData) - { + : this(memoryManager, size.Width, size.Height, metaData) { } /// @@ -133,6 +130,11 @@ namespace SixLabors.ImageSharp /// public int Height => this.PixelBuffer.Height; + /// + /// Gets the background color. + /// + public TPixel BackgroundColor { get; } + /// /// Gets the meta data of the frame. /// @@ -306,7 +308,7 @@ namespace SixLabors.ImageSharp /// /// The parallel options. /// The value to initialize the bitmap with. - public void Clear(ParallelOptions parallelOptions, TPixel value) { + internal void Clear(ParallelOptions parallelOptions, TPixel value) { Parallel.For( 0, this.Height, diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 599116414..2d9869602 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -22,7 +22,6 @@ namespace SixLabors.ImageSharp { private readonly Configuration configuration; private readonly ImageFrameCollection frames; - private readonly TPixel clearColor; /// /// Initializes a new instance of the class @@ -47,9 +46,9 @@ namespace SixLabors.ImageSharp /// /// The width of the image in pixels. /// The height of the image in pixels. - /// The color to initialize the pixels with. - public Image(Configuration configuration, int width, int height, TPixel clearColor) - : this(configuration, width, height, clearColor, new ImageMetaData()) + /// The color to initialize the pixels with. + public Image(Configuration configuration, int width, int height, TPixel backgroundColor) + : this(configuration, width, height, backgroundColor, new ImageMetaData()) { } @@ -79,7 +78,7 @@ namespace SixLabors.ImageSharp this.configuration = configuration ?? Configuration.Default; this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); this.MetaData = metadata ?? new ImageMetaData(); - this.frames = new ImageFrameCollection(this, width, height); + this.frames = new ImageFrameCollection(this, width, height, default(TPixel)); } /// @@ -91,14 +90,13 @@ namespace SixLabors.ImageSharp /// /// The width of the image in pixels. /// The height of the image in pixels. - /// The clear color. + /// The color to initialize the pixels with. /// The images metadata. - internal Image(Configuration configuration, int width, int height, TPixel clearColor, ImageMetaData metadata) { + internal Image(Configuration configuration, int width, int height, TPixel backgroundColor, ImageMetaData metadata) { this.configuration = configuration ?? Configuration.Default; this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); this.MetaData = metadata ?? new ImageMetaData(); - this.clearColor = clearColor; - this.frames = new ImageFrameCollection(this, width, height); + this.frames = new ImageFrameCollection(this, width, height, backgroundColor); } /// @@ -139,11 +137,6 @@ namespace SixLabors.ImageSharp /// public IImageFrameCollection Frames => this.frames; - /// - /// Gets the clear color to initialize the image frame pixels with. - /// - internal TPixel ClearColor => this.clearColor; - /// /// Gets the root frame. /// diff --git a/src/ImageSharp/Memory/BufferExtensions.cs b/src/ImageSharp/Memory/BufferExtensions.cs index 1347f2882..dd3114c21 100644 --- a/src/ImageSharp/Memory/BufferExtensions.cs +++ b/src/ImageSharp/Memory/BufferExtensions.cs @@ -52,18 +52,6 @@ namespace SixLabors.ImageSharp.Memory buffer.Span.Clear(); } - /// - /// Fills the contents of this buffer. - /// - /// The buffer - /// The value to fill the buffer with. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Fill(this IBuffer buffer, T value) - where T : struct - { - buffer.Span.Fill(value); - } - public static ref T DangerousGetPinnableReference(this IBuffer buffer) where T : struct => ref MemoryMarshal.GetReference(buffer.Span); diff --git a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs index 4c760e681..cb185d977 100644 --- a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); this.image = new Image(10, 10); - this.collection = new ImageFrameCollection(this.image, 10, 10); + this.collection = new ImageFrameCollection(this.image, 10, 10, default); } [Fact] From ef9b350b2712be23c87f0ee7c519ab603864c2bd Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 1 May 2018 01:10:37 +0200 Subject: [PATCH 10/15] better FillSolidBrushTests + bugfix in FillProcessor --- .../Drawing/Processors/FillProcessor.cs | 2 +- .../Drawing/FillSolidBrushTests.cs | 173 +++++++++++++----- .../WithSolidFilledImagesAttribute.cs | 2 +- .../TestUtilities/TestImageExtensions.cs | 19 +- .../TestUtilities/TestUtils.cs | 5 + 5 files changed, 152 insertions(+), 49 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs index e4ef44564..b9f9b4600 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors sourceRectangle, this.options)) { - amount.Span.Fill(this.options.BlendPercentage); + amount.Span.Fill(1f); Parallel.For( minY, diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs index 02e34092e..83f4fbde6 100644 --- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs @@ -1,79 +1,164 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Drawing; -using SixLabors.ImageSharp.Processing.Overlays; +using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing.Drawing.Brushes; +using SixLabors.Shapes; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Drawing { - public class FillSolidBrushTests : FileTestBase + + + [GroupOutput("Drawing")] + public class FillSolidBrushTests { - [Fact] - public void ImageShouldBeFloodFilledWithColorOnDefaultBackground() + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + [WithBlankImages(7, 4, PixelTypes.Rgba32)] + [WithBlankImages(16, 7, PixelTypes.Rgba32)] + [WithBlankImages(33, 32, PixelTypes.Rgba32)] + [WithBlankImages(400, 500, PixelTypes.Rgba32)] + public void DoesNotDependOnSize(TestImageProvider provider) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush"); - using (var image = new Image(500, 500)) + using (Image image = provider.GetImage()) { - image.Mutate(x => x.Fill(Rgba32.HotPink)); - image.Save($"{path}/DefaultBack.png"); - - using (PixelAccessor sourcePixels = image.Lock()) - { - Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]); + TPixel color = NamedColors.HotPink; + image.Mutate(c => c.Fill(color)); - Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]); - } + image.DebugSave(provider, appendPixelTypeToFileName: false); + image.ComparePixelBufferTo(color); } } - [Fact] - public void ImageShouldBeFloodFilledWithColor() + [Theory] + [WithBlankImages(16, 16, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector)] + public void DoesNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush"); - using (var image = new Image(500, 500)) + using (Image image = provider.GetImage()) { - image.Mutate(x => x - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink)); - image.Save($"{path}/Simple.png"); + TPixel color = NamedColors.HotPink; + image.Mutate(c => c.Fill(color)); - using (PixelAccessor sourcePixels = image.Lock()) - { - Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]); - - Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]); - } + image.DebugSave(provider, appendSourceFileOrDescription: false); + image.ComparePixelBufferTo(color); } } - [Fact] - public void ImageShouldBeFloodFilledWithColorOpacity() + [Theory] + [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")] + [WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")] + public void WhenColorIsOpaque_OverridePreviousColor(TestImageProvider provider, string newColorName) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush"); - using (var image = new Image(500, 500)) + using (Image image = provider.GetImage()) { - var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); + TPixel color = TestUtils.GetPixelOfNamedColor(newColorName); + image.Mutate(c => c.Fill(color)); + + image.DebugSave(provider, newColorName, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + image.ComparePixelBufferTo(color); + } + } + + public static readonly TheoryData BlendData = + new TheoryData() + { + { false, "Blue", 0.5f, PixelBlenderMode.Normal, 1.0f }, + { false, "Blue", 1.0f, PixelBlenderMode.Normal, 0.5f }, + { false, "Green", 0.5f, PixelBlenderMode.Normal, 0.3f }, + { false, "HotPink", 0.8f, PixelBlenderMode.Normal, 0.8f }, + + { false, "Blue", 0.5f, PixelBlenderMode.Multiply, 1.0f }, + { false, "Blue", 1.0f, PixelBlenderMode.Multiply, 0.5f }, + { false, "Green", 0.5f, PixelBlenderMode.Multiply, 0.3f }, + { false, "HotPink", 0.8f, PixelBlenderMode.Multiply, 0.8f }, + + { false, "Blue", 0.5f, PixelBlenderMode.Add, 1.0f }, + { false, "Blue", 1.0f, PixelBlenderMode.Add, 0.5f }, + { false, "Green", 0.5f, PixelBlenderMode.Add, 0.3f }, + { false, "HotPink", 0.8f, PixelBlenderMode.Add, 0.8f }, - image.Mutate(x => x - .BackgroundColor(Rgba32.Blue) - .Fill(color)); - image.Save($"{path}/Opacity.png"); + { true, "Blue", 0.5f, PixelBlenderMode.Normal, 1.0f }, + { true, "Blue", 1.0f, PixelBlenderMode.Normal, 0.5f }, + { true, "Green", 0.5f, PixelBlenderMode.Normal, 0.3f }, + { true, "HotPink", 0.8f, PixelBlenderMode.Normal, 0.8f }, - //shift background color towards forground color by the opacity amount - var mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); + { true, "Blue", 0.5f, PixelBlenderMode.Multiply, 1.0f }, + { true, "Blue", 1.0f, PixelBlenderMode.Multiply, 0.5f }, + { true, "Green", 0.5f, PixelBlenderMode.Multiply, 0.3f }, + { true, "HotPink", 0.8f, PixelBlenderMode.Multiply, 0.8f }, + { true, "Blue", 0.5f, PixelBlenderMode.Add, 1.0f }, + { true, "Blue", 1.0f, PixelBlenderMode.Add, 0.5f }, + { true, "Green", 0.5f, PixelBlenderMode.Add, 0.3f }, + { true, "HotPink", 0.8f, PixelBlenderMode.Add, 0.8f }, + }; - using (PixelAccessor sourcePixels = image.Lock()) + [Theory] + [WithSolidFilledImages(nameof(BlendData), 16, 16, "Red", PixelTypes.Rgba32)] + public void BlendFillColorOverBackround( + TestImageProvider provider, + bool triggerFillRegion, + string newColorName, + float alpha, + PixelBlenderMode blenderMode, + float blendPercentage) + where TPixel : struct, IPixel + { + var vec = TestUtils.GetPixelOfNamedColor(newColorName).ToVector4(); + vec.W = alpha; + + TPixel fillColor = default; + fillColor.PackFromVector4(vec); + + using (Image image = provider.GetImage()) + { + TPixel bgColor = image[0, 0]; + + var options = new GraphicsOptions(false) + { + BlenderMode = blenderMode, + BlendPercentage = blendPercentage + }; + + if (triggerFillRegion) + { + var region = new ShapeRegion(new RectangularPolygon(0, 0, 16, 16)); + + image.Mutate(c => c.Fill(options, new SolidBrush(fillColor), region)); + } + else { - Assert.Equal(mergedColor, sourcePixels[9, 9]); - Assert.Equal(mergedColor, sourcePixels[199, 149]); + image.Mutate(c => c.Fill(options, new SolidBrush(fillColor))); } + + var testOutputDetails = new + { + triggerFillRegion = triggerFillRegion, + newColorName = newColorName, + alpha = alpha, + blenderMode = blenderMode, + blendPercentage = blendPercentage + }; + + image.DebugSave( + provider, + testOutputDetails, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(blenderMode); + TPixel expectedPixel = blender.Blend(bgColor, fillColor, blendPercentage); + + image.ComparePixelBufferTo(expectedPixel); } } - } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs index 991f7108f..f95db45f7 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests { Guard.NotNull(colorName, nameof(colorName)); - var c = (Rgba32)typeof(Rgba32).GetTypeInfo().GetField(colorName).GetValue(null); + Rgba32 c = TestUtils.GetPixelOfNamedColor(colorName); this.R = c.R; this.G = c.G; this.B = c.B; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index c2c0eb487..f37df48dc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -346,13 +346,26 @@ namespace SixLabors.ImageSharp.Tests Span expectedPixels) where TPixel : struct, IPixel { - Span actual = image.GetPixelSpan(); + Span actualPixels = image.GetPixelSpan(); - Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!"); + Assert.True(expectedPixels.Length == actualPixels.Length, "Buffer sizes are not equal!"); for (int i = 0; i < expectedPixels.Length; i++) { - Assert.True(expectedPixels[i].Equals(actual[i]), $"Pixels are different on position {i}!"); + Assert.True(expectedPixels[i].Equals(actualPixels[i]), $"Pixels are different on position {i}!"); + } + + return image; + } + + public static Image ComparePixelBufferTo(this Image image, TPixel expectedPixel) + where TPixel : struct, IPixel + { + Span actualPixels = image.GetPixelSpan(); + + for (int i = 0; i < actualPixels.Length; i++) + { + Assert.True(expectedPixel.Equals(actualPixels[i]), $"Pixels are different on position {i}!"); } return image; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index 4f9a558d4..85729acd3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -147,6 +147,11 @@ namespace SixLabors.ImageSharp.Tests /// The pixel types internal static PixelTypes[] GetAllPixelTypes() => (PixelTypes[])Enum.GetValues(typeof(PixelTypes)); + internal static TPixel GetPixelOfNamedColor(string colorName) + where TPixel : struct, IPixel + { + return (TPixel)typeof(NamedColors).GetTypeInfo().GetField(colorName).GetValue(null); + } /// /// Utility for testing image processor extension methods: From 7c70a5efcfd4b6be1c0a4a1f4d588a8a869128a4 Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Tue, 1 May 2018 10:36:20 +0200 Subject: [PATCH 11/15] Processed review comments, removed ImageFrame.BackgroundColor property. --- src/ImageSharp/ImageFrameCollection.cs | 2 +- src/ImageSharp/ImageFrame{TPixel}.cs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index 1d4735fd8..eb118979c 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -143,7 +143,7 @@ namespace SixLabors.ImageSharp /// public ImageFrame CreateFrame() { - var frame = new ImageFrame(this.parent.GetConfiguration(), this.RootFrame.Width, this.RootFrame.Height, this.RootFrame.BackgroundColor); + var frame = new ImageFrame(this.parent.GetConfiguration(), this.RootFrame.Width, this.RootFrame.Height, default); this.frames.Add(frame); return frame; } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 1c26bb558..c3955c132 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -79,7 +79,6 @@ namespace SixLabors.ImageSharp this.MemoryManager = configuration.MemoryManager; this.PixelBuffer = this.MemoryManager.Allocate2D(width, height, false); - this.BackgroundColor = backgroundColor; this.Clear(configuration.ParallelOptions, backgroundColor); this.MetaData = metaData; } @@ -130,11 +129,6 @@ namespace SixLabors.ImageSharp /// public int Height => this.PixelBuffer.Height; - /// - /// Gets the background color. - /// - public TPixel BackgroundColor { get; } - /// /// Gets the meta data of the frame. /// From 0d81089159548ff584bf0519fa7c7ba880f5030c Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Tue, 1 May 2018 15:14:34 +0200 Subject: [PATCH 12/15] Processed review comments, removed IImageFrameCollection interface, and added backgroundColor parameter to ImageFrameCollection.CreateFrame() method with default. --- src/ImageSharp/IImageFrameCollection.cs | 115 ---------------------- src/ImageSharp/ImageFrameCollection.cs | 123 ++++++++++++++++++------ src/ImageSharp/Image{TPixel}.cs | 2 +- 3 files changed, 94 insertions(+), 146 deletions(-) delete mode 100644 src/ImageSharp/IImageFrameCollection.cs diff --git a/src/ImageSharp/IImageFrameCollection.cs b/src/ImageSharp/IImageFrameCollection.cs deleted file mode 100644 index 59c64a6af..000000000 --- a/src/ImageSharp/IImageFrameCollection.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Generic; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp -{ - /// - /// Encapsulates a collection of instances that make up an . - /// - /// The type of the pixel. - public interface IImageFrameCollection : IEnumerable> - where TPixel : struct, IPixel - { - /// - /// Gets the number of frames. - /// - int Count { get; } - - /// - /// Gets the root frame. - /// - ImageFrame RootFrame { get; } - - /// - /// Gets the at the specified index. - /// - /// - /// The . - /// - /// The index. - /// The at the specified index. - ImageFrame this[int index] { get; } - - /// - /// Creates an with only the frame at the specified index - /// with the same metadata as the original image. - /// - /// The zero-based index of the frame to clone. - /// The new with the specified frame. - Image CloneFrame(int index); - - /// - /// Removes the frame at the specified index and creates a new image with only the removed frame - /// with the same metadata as the original image. - /// - /// The zero-based index of the frame to export. - /// Cannot remove last frame. - /// The new with the specified frame. - Image ExportFrame(int index); - - /// - /// Removes the frame at the specified index and frees all freeable resources associated with it. - /// - /// The zero-based index of the frame to remove. - /// Cannot remove last frame. - void RemoveFrame(int index); - - /// - /// Creates a new and appends it to the end of the collection. - /// - /// The new . - ImageFrame CreateFrame(); - - /// - /// Clones the frame and appends the clone to the end of the collection. - /// - /// The raw pixel data to generate the from. - /// The cloned . - ImageFrame AddFrame(ImageFrame source); - - /// - /// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the - /// new frame at the end of the collection. - /// - /// The raw pixel data to generate the from. - /// The new . - ImageFrame AddFrame(TPixel[] source); - - /// - /// Clones and inserts the into the at the specified . - /// - /// The zero-based index to insert the frame at. - /// The to clone and insert into the . - /// Frame must have the same dimensions as the image. - /// The cloned . - ImageFrame InsertFrame(int index, ImageFrame source); - - /// - /// Moves an from to . - /// - /// The zero-based index of the frame to move. - /// The index to move the frame to. - void MoveFrame(int sourceIndex, int destinationIndex); - - /// - /// Determines the index of a specific in the . - /// - /// The to locate in the . - /// The index of item if found in the list; otherwise, -1. - int IndexOf(ImageFrame frame); - - /// - /// Determines whether the contains the . - /// - /// The frame. - /// - /// true if the contains the specified frame; otherwise, false. - /// - bool Contains(ImageFrame frame); - } -} \ No newline at end of file diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index eb118979c..97a8df875 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -9,8 +9,11 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp { - /// - internal sealed class ImageFrameCollection : IImageFrameCollection + /// + /// Encapsulates a collection of instances that make up an . + /// + /// The type of the pixel. + public sealed class ImageFrameCollection : IEnumerable> where TPixel : struct, IPixel { private readonly IList> frames = new List>(); @@ -41,51 +44,85 @@ namespace SixLabors.ImageSharp } } - /// + /// + /// Gets the number of frames. + /// public int Count => this.frames.Count; - /// + /// + /// Gets the root frame. + /// public ImageFrame RootFrame => this.frames.Count > 0 ? this.frames[0] : null; - /// + /// + /// Gets the at the specified index. + /// + /// + /// The . + /// + /// The index. + /// The at the specified index. public ImageFrame this[int index] => this.frames[index]; - /// + /// + /// Determines the index of a specific in the . + /// + /// The to locate in the . + /// The index of item if found in the list; otherwise, -1. public int IndexOf(ImageFrame frame) => this.frames.IndexOf(frame); - /// - public ImageFrame InsertFrame(int index, ImageFrame frame) + /// + /// Clones and inserts the into the at the specified . + /// + /// The zero-based index to insert the frame at. + /// The to clone and insert into the . + /// Frame must have the same dimensions as the image. + /// The cloned . + public ImageFrame InsertFrame(int index, ImageFrame source) { - this.ValidateFrame(frame); - ImageFrame clonedFrame = frame.Clone(); + this.ValidateFrame(source); + ImageFrame clonedFrame = source.Clone(); this.frames.Insert(index, clonedFrame); return clonedFrame; } - /// - public ImageFrame AddFrame(ImageFrame frame) + /// + /// Clones the frame and appends the clone to the end of the collection. + /// + /// The raw pixel data to generate the from. + /// The cloned . + public ImageFrame AddFrame(ImageFrame source) { - this.ValidateFrame(frame); - ImageFrame clonedFrame = frame.Clone(); + this.ValidateFrame(source); + ImageFrame clonedFrame = source.Clone(); this.frames.Add(clonedFrame); return clonedFrame; } - /// - public ImageFrame AddFrame(TPixel[] data) + /// + /// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the + /// new frame at the end of the collection. + /// + /// The raw pixel data to generate the from. + /// The new . + public ImageFrame AddFrame(TPixel[] source) { - Guard.NotNull(data, nameof(data)); + Guard.NotNull(source, nameof(source)); var frame = ImageFrame.LoadPixelData( this.parent.GetMemoryManager(), - new Span(data), + new Span(source), this.RootFrame.Width, this.RootFrame.Height); this.frames.Add(frame); return frame; } - /// + /// + /// Removes the frame at the specified index and frees all freeable resources associated with it. + /// + /// The zero-based index of the frame to remove. + /// Cannot remove last frame. public void RemoveFrame(int index) { if (index == 0 && this.Count == 1) @@ -98,26 +135,42 @@ namespace SixLabors.ImageSharp frame.Dispose(); } - /// + /// + /// Determines whether the contains the . + /// + /// The frame. + /// + /// true if the contains the specified frame; otherwise, false. + /// public bool Contains(ImageFrame frame) { return this.frames.Contains(frame); } - /// - public void MoveFrame(int sourceIndex, int destIndex) + /// + /// Moves an from to . + /// + /// The zero-based index of the frame to move. + /// The index to move the frame to. + public void MoveFrame(int sourceIndex, int destinationIndex) { - if (sourceIndex == destIndex) + if (sourceIndex == destinationIndex) { return; } ImageFrame frameAtIndex = this.frames[sourceIndex]; this.frames.RemoveAt(sourceIndex); - this.frames.Insert(destIndex, frameAtIndex); + this.frames.Insert(destinationIndex, frameAtIndex); } - /// + /// + /// Removes the frame at the specified index and creates a new image with only the removed frame + /// with the same metadata as the original image. + /// + /// The zero-based index of the frame to export. + /// Cannot remove last frame. + /// The new with the specified frame. public Image ExportFrame(int index) { ImageFrame frame = this[index]; @@ -132,7 +185,12 @@ namespace SixLabors.ImageSharp return new Image(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { frame }); } - /// + /// + /// Creates an with only the frame at the specified index + /// with the same metadata as the original image. + /// + /// The zero-based index of the frame to clone. + /// The new with the specified frame. public Image CloneFrame(int index) { ImageFrame frame = this[index]; @@ -140,10 +198,15 @@ namespace SixLabors.ImageSharp return new Image(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { clonedFrame }); } - /// - public ImageFrame CreateFrame() - { - var frame = new ImageFrame(this.parent.GetConfiguration(), this.RootFrame.Width, this.RootFrame.Height, default); + /// + /// Creates a new and appends it to the end of the collection. + /// + /// The background color to initialize the pixels with. + /// + /// The new . + /// + public ImageFrame CreateFrame(TPixel backgroundColor = default) { + var frame = new ImageFrame(this.parent.GetConfiguration(), this.RootFrame.Width, this.RootFrame.Height, backgroundColor); this.frames.Add(frame); return frame; } diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 2d9869602..596dc9bcd 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp /// /// Gets the frames. /// - public IImageFrameCollection Frames => this.frames; + public ImageFrameCollection Frames => this.frames; /// /// Gets the root frame. From 24f6ff98f8e7014169c71dc0f650a9a07393254a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 1 May 2018 18:00:43 +0200 Subject: [PATCH 13/15] minor code cleanup in FillProcessor.cs --- .../Drawing/Processors/FillProcessor.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs index 44329e359..645ff0353 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs @@ -52,14 +52,8 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors int width = maxX - minX; - var solidBrush = this.brush as SolidBrush; - // If there's no reason for blending, then avoid it. - if (solidBrush != null && - ( - (this.options.BlenderMode == PixelBlenderMode.Normal && this.options.BlendPercentage == 1f && solidBrush.Color.ToVector4().W == 1f) || - (this.options.BlenderMode == PixelBlenderMode.Over && this.options.BlendPercentage == 1f && solidBrush.Color.ToVector4().W == 1f) || - (this.options.BlenderMode == PixelBlenderMode.Src))) + if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush)) { Parallel.For( minY, @@ -67,8 +61,6 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors configuration.ParallelOptions, y => { - int offsetY = y - startY; - int offsetX = minX - startX; source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color); }); } @@ -107,5 +99,17 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors } } } + + private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush) + { + solidBrush = this.brush as SolidBrush; + + return solidBrush != null + && ((this.options.BlenderMode == PixelBlenderMode.Normal && this.options.BlendPercentage == 1f + && solidBrush.Color.ToVector4().W == 1f) + || (this.options.BlenderMode == PixelBlenderMode.Over && this.options.BlendPercentage == 1f + && solidBrush.Color.ToVector4().W == 1f) + || (this.options.BlenderMode == PixelBlenderMode.Src)); + } } } \ No newline at end of file From a9816fd937341598ad4bdd7e793ab289caf30253 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 1 May 2018 18:33:44 +0200 Subject: [PATCH 14/15] use overloading in ImageFrameCollection + more tests for API-s affected by the PR --- src/ImageSharp/ImageFrameCollection.cs | 20 ++++++- .../Image/ImageFramesCollectionTests.cs | 13 +++- tests/ImageSharp.Tests/Image/ImageTests.cs | 60 +++++++++++++++++-- .../TestUtilities/TestImageExtensions.cs | 15 ++++- 4 files changed, 98 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index 97a8df875..0318a7068 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -198,6 +198,17 @@ namespace SixLabors.ImageSharp return new Image(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { clonedFrame }); } + /// + /// Creates a new and appends it to the end of the collection. + /// + /// + /// The new . + /// + public ImageFrame CreateFrame() + { + return this.CreateFrame(default); + } + /// /// Creates a new and appends it to the end of the collection. /// @@ -205,8 +216,13 @@ namespace SixLabors.ImageSharp /// /// The new . /// - public ImageFrame CreateFrame(TPixel backgroundColor = default) { - var frame = new ImageFrame(this.parent.GetConfiguration(), this.RootFrame.Width, this.RootFrame.Height, backgroundColor); + public ImageFrame CreateFrame(TPixel backgroundColor) + { + var frame = new ImageFrame( + this.parent.GetConfiguration(), + this.RootFrame.Width, + this.RootFrame.Height, + backgroundColor); this.frames.Add(frame); return frame; } diff --git a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs index cb185d977..c2ebf83ba 100644 --- a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs @@ -225,10 +225,21 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void CreateFrame() + public void CreateFrame_Default() { this.image.Frames.CreateFrame(); + + Assert.Equal(2, this.image.Frames.Count); + this.image.Frames[1].ComparePixelBufferTo(default(Rgba32)); + } + + [Fact] + public void CreateFrame_CustomFillColor() + { + this.image.Frames.CreateFrame(Rgba32.HotPink); + Assert.Equal(2, this.image.Frames.Count); + this.image.Frames[1].ComparePixelBufferTo(Rgba32.HotPink); } [Fact] diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index da813f428..8234df24e 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -13,10 +13,60 @@ namespace SixLabors.ImageSharp.Tests /// /// Tests the class. /// - public class ImageTests : FileTestBase + public class ImageTests { + public class Constructor + { + [Fact] + public void Width_Height() + { + using (var image = new Image(11, 23)) + { + Assert.Equal(11, image.Width); + Assert.Equal(23, image.Height); + Assert.Equal(11*23, image.GetPixelSpan().Length); + image.ComparePixelBufferTo(default(Rgba32)); + + Assert.Equal(Configuration.Default, image.GetConfiguration()); + } + } + + [Fact] + public void Configuration_Width_Height() + { + Configuration configuration = Configuration.Default.ShallowCopy(); + + using (var image = new Image(configuration, 11, 23)) + { + Assert.Equal(11, image.Width); + Assert.Equal(23, image.Height); + Assert.Equal(11 * 23, image.GetPixelSpan().Length); + image.ComparePixelBufferTo(default(Rgba32)); + + Assert.Equal(configuration, image.GetConfiguration()); + } + } + + [Fact] + public void Configuration_Width_Height_BackroundColor() + { + Configuration configuration = Configuration.Default.ShallowCopy(); + Rgba32 color = Rgba32.Aquamarine; + + using (var image = new Image(configuration, 11, 23, color)) + { + Assert.Equal(11, image.Width); + Assert.Equal(23, image.Height); + Assert.Equal(11 * 23, image.GetPixelSpan().Length); + image.ComparePixelBufferTo(color); + + Assert.Equal(configuration, image.GetConfiguration()); + } + } + } + [Fact] - public void ConstructorByteArray() + public void Load_ByteArray() { Assert.Throws(() => { @@ -32,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void ConstructorFileSystem() + public void Load_FileSystemPath() { TestFile file = TestFile.Create(TestImages.Bmp.Car); using (Image image = Image.Load(file.FullPath)) @@ -43,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void ConstructorFileSystem_FileNotFound() + public void Load_FileSystemPath_FileNotFound() { System.IO.FileNotFoundException ex = Assert.Throws( () => @@ -53,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void ConstructorFileSystem_NullPath() + public void Load_FileSystemPath_NullPath() { ArgumentNullException ex = Assert.Throws( () => diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index f37df48dc..8955eeb63 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -361,14 +361,25 @@ namespace SixLabors.ImageSharp.Tests public static Image ComparePixelBufferTo(this Image image, TPixel expectedPixel) where TPixel : struct, IPixel { - Span actualPixels = image.GetPixelSpan(); + foreach (ImageFrame imageFrame in image.Frames) + { + imageFrame.ComparePixelBufferTo(expectedPixel); + } + + return image; + } + + public static ImageFrame ComparePixelBufferTo(this ImageFrame imageFrame, TPixel expectedPixel) + where TPixel : struct, IPixel + { + Span actualPixels = imageFrame.GetPixelSpan(); for (int i = 0; i < actualPixels.Length; i++) { Assert.True(expectedPixel.Equals(actualPixels[i]), $"Pixels are different on position {i}!"); } - return image; + return imageFrame; } public static ImageFrame ComparePixelBufferTo( From 72cb50c0c7e805d6e0c1d310578b4038c3ab203d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 2 May 2018 13:52:40 +1000 Subject: [PATCH 15/15] Delete merge conflict backup file. --- .../Drawing/Processors/FillProcessor.cs.orig | 121 ------------------ 1 file changed, 121 deletions(-) delete mode 100644 src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs.orig diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs.orig b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs.orig deleted file mode 100644 index 7da01b24f..000000000 --- a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs.orig +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Drawing.Brushes; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Drawing.Processors -{ - /// - /// Using the brush as a source of pixels colors blends the brush color with source. - /// - /// The pixel format. - internal class FillProcessor : ImageProcessor - where TPixel : struct, IPixel - { - /// - /// The brush. - /// - private readonly IBrush brush; - private readonly GraphicsOptions options; - - /// - /// Initializes a new instance of the class. - /// - /// The brush to source pixel colors from. - /// The options - public FillProcessor(IBrush brush, GraphicsOptions options) - { - this.brush = brush; - this.options = options; - } - - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); - - int width = maxX - minX; - -<<<<<<< HEAD - var solidBrush = this.brush as SolidBrush; -======= - using (IBuffer amount = source.MemoryManager.Allocate(width)) - using (BrushApplicator applicator = this.brush.CreateApplicator( - source, - sourceRectangle, - this.options)) - { - amount.Span.Fill(1f); ->>>>>>> master - - // If there's no reason for blending, then avoid it. - if (solidBrush != null && - ( - (this.options.BlenderMode == PixelBlenderMode.Normal && this.options.BlendPercentage == 1f && solidBrush.Color.ToVector4().W == 1f) || - (this.options.BlenderMode == PixelBlenderMode.Over && this.options.BlendPercentage == 1f && solidBrush.Color.ToVector4().W == 1f) || - (this.options.BlenderMode == PixelBlenderMode.Src))) - { - Parallel.For( - minY, - maxY, - configuration.ParallelOptions, - y => - { - int offsetY = y - startY; - int offsetX = minX - startX; - source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color); - }); - } - else - { - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } - - if (minY > 0) - { - startY = 0; - } - - using (IBuffer amount = source.MemoryManager.Allocate(width)) - using (BrushApplicator applicator = this.brush.CreateApplicator( - source, - sourceRectangle, - this.options)) - { - amount.Span.Fill(this.options.BlendPercentage); - - Parallel.For( - minY, - maxY, - configuration.ParallelOptions, - y => - { - int offsetY = y - startY; - int offsetX = minX - startX; - - applicator.Apply(amount.Span, offsetX, offsetY); - }); - } - } - } - } -} \ No newline at end of file