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..645ff0353 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,39 +50,66 @@ 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)) + // If there's no reason for blending, then avoid it. + if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush)) { - amount.Span.Fill(this.options.BlendPercentage); - 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(1f); + + 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); + }); + } } } + + 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 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 a9225eec4..0318a7068 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -9,21 +9,24 @@ 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>(); 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().MemoryManager, width, height)); + this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, backgroundColor)); } internal ImageFrameCollection(Image parent, IEnumerable> frames) @@ -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,31 @@ 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() { - var frame = new ImageFrame(this.parent.GetConfiguration().MemoryManager, this.RootFrame.Width, this.RootFrame.Height); + return this.CreateFrame(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) + { + 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/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index cf7a1ae4f..c3955c132 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; @@ -18,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; /// @@ -29,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()) { } /// @@ -52,6 +51,38 @@ 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 backgroundColor) + : this(configuration, width, height, backgroundColor, 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 backgroundColor, 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, backgroundColor); + this.MetaData = metaData; + } + /// /// Initializes a new instance of the class. /// @@ -59,8 +90,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) { } /// @@ -267,6 +297,23 @@ namespace SixLabors.ImageSharp return target; } + /// + /// Clears the bitmap. + /// + /// The parallel options. + /// The value to initialize the bitmap with. + internal 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. /// diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 78a091e41..596dc9bcd 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -37,6 +37,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 backgroundColor) + : this(configuration, width, height, backgroundColor, new ImageMetaData()) + { + } + /// /// Initializes a new instance of the class /// with the height and the width of the image. @@ -63,7 +78,25 @@ 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)); + } + + /// + /// 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. + /// The images 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.frames = new ImageFrameCollection(this, width, height, backgroundColor); } /// @@ -102,7 +135,7 @@ namespace SixLabors.ImageSharp /// /// Gets the frames. /// - public IImageFrameCollection Frames => this.frames; + public ImageFrameCollection Frames => this.frames; /// /// Gets the root frame. 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 } 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/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/Drawing/SolidFillBlendedShapesTests.cs b/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs new file mode 100644 index 000000000..7d73d1b65 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs @@ -0,0 +1,154 @@ +// 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 +{ + [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); + + 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/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/Image/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs index 4c760e681..c2ebf83ba 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] @@ -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/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/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..8955eeb63 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; } @@ -210,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)) @@ -218,7 +222,8 @@ namespace SixLabors.ImageSharp.Tests provider, testOutputDetails, extension, - appendPixelTypeToFileName)) + appendPixelTypeToFileName, + appendSourceFileOrDescription)) { firstFrameOnlyImage.Frames.AddFrame(image.Frames.RootFrame); firstFrameOnlyImage.Frames.RemoveFrame(0); @@ -255,10 +260,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)) { @@ -336,18 +346,42 @@ 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 + { + 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 imageFrame; + } + public static ImageFrame ComparePixelBufferTo( this ImageFrame image, Span expectedPixels) 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: