From 05098868d6a4a257033c1f64339d13fdf55bbbe9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 15 Feb 2020 01:04:39 +1100 Subject: [PATCH] Cleanup and fix tests. --- src/ImageSharp/Advanced/AotCompilerTools.cs | 3 +- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 50 ++--- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 10 +- .../Formats/Png/PngEncoderOptionsHelpers.cs | 3 +- ...ransformColorBehavior.cs => DitherType.cs} | 10 +- .../Processors/Dithering/ErrorDither.cs | 2 +- .../Processors/Dithering/IDither.cs | 6 +- .../Processors/Dithering/OrderedDither.cs | 4 +- .../PaletteDitherProcessor{TPixel}.cs | 2 +- .../Quantization/FrameQuantizer{TPixel}.cs | 191 +++++++++++++----- .../Quantization/IFrameQuantizer{TPixel}.cs | 7 +- .../OctreeFrameQuantizer{TPixel}.cs | 27 +-- .../Quantization/OctreeQuantizer.cs | 2 +- .../PaletteFrameQuantizer{TPixel}.cs | 68 +------ .../Quantization/QuantizeProcessor{TPixel}.cs | 11 +- .../Quantization/QuantizedFrame{TPixel}.cs | 10 +- .../Quantization/WuFrameQuantizer{TPixel}.cs | 26 +-- .../ImageSharp.Benchmarks/Samplers/Diffuse.cs | 2 +- .../Binarization/BinaryDitherTest.cs | 106 ---------- .../Processing/Dithering/DitherTest.cs | 40 ++-- .../Binarization/BinaryDitherTests.cs | 26 +-- .../Processors/Dithering/DitherTests.cs | 26 +-- .../Quantization/OctreeQuantizerTests.cs | 26 +-- .../Quantization/PaletteQuantizerTests.cs | 24 +-- .../Quantization/WuQuantizerTests.cs | 26 +-- .../Quantization/QuantizedImageTests.cs | 55 +++-- .../Quantization/WuQuantizerTests.cs | 113 ++++++----- tests/Images/Input/Png/CalliphoraPartial2.png | Bin 0 -> 75628 bytes 28 files changed, 406 insertions(+), 470 deletions(-) rename src/ImageSharp/Processing/Processors/Dithering/{DitherTransformColorBehavior.cs => DitherType.cs} (55%) delete mode 100644 tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs create mode 100644 tests/Images/Input/Png/CalliphoraPartial2.png diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index e02afc83e6..995aee91d5 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -124,7 +124,8 @@ namespace SixLabors.ImageSharp.Advanced { using (var test = new WuFrameQuantizer(Configuration.Default, new WuQuantizer(false))) { - test.QuantizeFrame(new ImageFrame(Configuration.Default, 1, 1)); + var frame = new ImageFrame(Configuration.Default, 1, 1); + test.QuantizeFrame(frame, frame.Bounds()); test.AotGetPalette(); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 1c7c606ca6..a1c415f76e 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -335,36 +335,36 @@ namespace SixLabors.ImageSharp.Formats.Bmp private void Write8BitColor(Stream stream, ImageFrame image, Span colorPalette) where TPixel : struct, IPixel { - using (IQuantizedFrame quantized = this.quantizer.CreateFrameQuantizer(this.configuration, 256).QuantizeFrame(image)) + using IFrameQuantizer quantizer = this.quantizer.CreateFrameQuantizer(this.configuration, 256); + using IQuantizedFrame quantized = quantizer.QuantizeFrame(image, image.Bounds()); + + ReadOnlySpan quantizedColors = quantized.Palette.Span; + var color = default(Rgba32); + + // TODO: Use bulk conversion here for better perf + int idx = 0; + foreach (TPixel quantizedColor in quantizedColors) { - ReadOnlySpan quantizedColors = quantized.Palette.Span; - var color = default(Rgba32); + quantizedColor.ToRgba32(ref color); + colorPalette[idx] = color.B; + colorPalette[idx + 1] = color.G; + colorPalette[idx + 2] = color.R; - // TODO: Use bulk conversion here for better perf - int idx = 0; - foreach (TPixel quantizedColor in quantizedColors) - { - quantizedColor.ToRgba32(ref color); - colorPalette[idx] = color.B; - colorPalette[idx + 1] = color.G; - colorPalette[idx + 2] = color.R; - - // Padding byte, always 0. - colorPalette[idx + 3] = 0; - idx += 4; - } + // Padding byte, always 0. + colorPalette[idx + 3] = 0; + idx += 4; + } + + stream.Write(colorPalette); - stream.Write(colorPalette); + for (int y = image.Height - 1; y >= 0; y--) + { + ReadOnlySpan pixelSpan = quantized.GetRowSpan(y); + stream.Write(pixelSpan); - for (int y = image.Height - 1; y >= 0; y--) + for (int i = 0; i < this.padding; i++) { - ReadOnlySpan pixelSpan = quantized.GetRowSpan(y); - stream.Write(pixelSpan); - - for (int i = 0; i < this.padding; i++) - { - stream.WriteByte(0); - } + stream.WriteByte(0); } } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index df79532308..8577ab4768 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Configuration bound to the encoding operation. /// - private Configuration configuration; + private readonly Configuration configuration; /// /// A reusable buffer used to reduce allocations. @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Gif IQuantizedFrame quantized; using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration)) { - quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame); + quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); } // Get the number of bits. @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Gif using (IFrameQuantizer paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Dither, quantized.Palette)) { - using (IQuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame)) + using (IQuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds())) { this.WriteImageData(paletteQuantized, stream); } @@ -173,14 +173,14 @@ namespace SixLabors.ImageSharp.Formats.Gif { using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration, frameMetadata.ColorTableLength)) { - quantized = frameQuantizer.QuantizeFrame(frame); + quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } } else { using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration)) { - quantized = frameQuantizer.QuantizeFrame(frame); + quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs index b494c164f5..dc3d9d3ce6 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs @@ -78,7 +78,8 @@ namespace SixLabors.ImageSharp.Formats.Png // Create quantized frame returning the palette and set the bit depth. using (IFrameQuantizer frameQuantizer = options.Quantizer.CreateFrameQuantizer(image.GetConfiguration())) { - return frameQuantizer.QuantizeFrame(image.Frames.RootFrame); + ImageFrame frame = image.Frames.RootFrame; + return frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/DitherTransformColorBehavior.cs b/src/ImageSharp/Processing/Processors/Dithering/DitherType.cs similarity index 55% rename from src/ImageSharp/Processing/Processors/Dithering/DitherTransformColorBehavior.cs rename to src/ImageSharp/Processing/Processors/Dithering/DitherType.cs index 6823630644..0dac157873 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/DitherTransformColorBehavior.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/DitherType.cs @@ -6,16 +6,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// /// Enumerates the possible dithering algorithm transform behaviors. /// - public enum DitherTransformColorBehavior + public enum DitherType { /// - /// The transformed color should be precalulated and passed to the dithering algorithm. + /// Error diffusion. Spreads the difference between source and quanized color values as distributed error. /// - PreOperation, + ErrorDiffusion, /// - /// The transformed color should be calculated as a result of the dithering algorithm. + /// Ordered dithering. Applies thresholding matrices agains the source to determine the quantized color. /// - PostOperation + OrderedDither } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 2ab570610b..91ca4e95ef 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } /// - public DitherTransformColorBehavior TransformColorBehavior { get; } = DitherTransformColorBehavior.PreOperation; + public DitherType DitherType { get; } = DitherType.ErrorDiffusion; /// public TPixel Dither( diff --git a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs index 45c9d4b588..0d7841884b 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs @@ -11,14 +11,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering public interface IDither { /// - /// Gets the which determines whether the + /// Gets the which determines whether the /// transformed color should be calculated and supplied to the algorithm. /// - public DitherTransformColorBehavior TransformColorBehavior { get; } + public DitherType DitherType { get; } /// /// Transforms the image applying a dither matrix. - /// When is this + /// When is this /// this method is destructive and will alter the input pixels. /// /// The image. diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 0e15c700fc..c3277e3266 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering float m2 = length * length; for (int y = 0; y < length; y++) { - for (int x = 0; x < length; y++) + for (int x = 0; x < length; x++) { thresholdMatrix[y, x] = ((ditherMatrix[y, x] + 1) / m2) - .5F; } @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } /// - public DitherTransformColorBehavior TransformColorBehavior { get; } = DitherTransformColorBehavior.PostOperation; + public DitherType DitherType { get; } = DitherType.OrderedDither; /// [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index ed7e3a3530..041404f394 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering // Error diffusion. The difference between the source and transformed color // is spread to neighboring pixels. - if (this.dither.TransformColorBehavior == DitherTransformColorBehavior.PreOperation) + if (this.dither.DitherType == DitherType.ErrorDiffusion) { for (int y = interest.Top; y < interest.Bottom; y++) { diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs index c5c729300e..63d6875d83 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs @@ -29,14 +29,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// The quantizer + /// The quantizer. /// - /// If true, the quantization process only needs to loop through the source pixels once + /// If true, the quantization process only needs to loop through the source pixels once. /// /// /// If you construct this class with a true for , then the code will - /// only call the method. - /// If two passes are required, the code will also call . + /// only call the method. + /// If two passes are required, the code will also call . /// protected FrameQuantizer(Configuration configuration, IQuantizer quantizer, bool singlePass) { @@ -58,8 +58,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// If you construct this class with a true for , then the code will - /// only call the method. - /// If two passes are required, the code will also call . + /// only call the method. + /// If two passes are required, the code will also call . /// protected FrameQuantizer(Configuration configuration, IDither diffuser, bool singlePass) { @@ -88,41 +88,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - public IQuantizedFrame QuantizeFrame(ImageFrame image) + public IQuantizedFrame QuantizeFrame(ImageFrame image, Rectangle bounds) { Guard.NotNull(image, nameof(image)); - - // Get the size of the source image - int height = image.Height; - int width = image.Width; + var interest = Rectangle.Intersect(image.Bounds(), bounds); // Call the FirstPass function if not a single pass algorithm. // For something like an Octree quantizer, this will run through // all image pixels, build a data structure, and create a palette. if (!this.singlePass) { - this.FirstPass(image, width, height); + this.FirstPass(image, interest); } // Collect the palette. Required before the second pass runs. - ReadOnlyMemory palette = this.GetPalette(); + ReadOnlyMemory palette = this.GenerateQuantizedPalette(); MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; this.pixelMap = new EuclideanPixelMap(palette); - var quantizedFrame = new QuantizedFrame(memoryAllocator, width, height, palette); + var quantizedFrame = new QuantizedFrame(memoryAllocator, interest.Width, interest.Height, palette); - Span pixelSpan = quantizedFrame.GetWritablePixelSpan(); + Memory output = quantizedFrame.GetWritablePixelMemory(); if (this.DoDither) { - // We clone the image as we don't want to alter the original via dithering. + // We clone the image as we don't want to alter the original via error diffusion based dithering. using (ImageFrame clone = image.Clone()) { - this.SecondPass(clone, pixelSpan, palette.Span, width, height); + this.SecondPass(clone, interest, output, palette); } } else { - this.SecondPass(image, pixelSpan, palette.Span, width, height); + this.SecondPass(image, interest, output, palette); } return quantizedFrame; @@ -146,9 +143,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Execute the first pass through the pixels in the image to create the palette. /// /// The source data. - /// The width in pixels of the image. - /// The height in pixels of the image. - protected virtual void FirstPass(ImageFrame source, int width, int height) + /// The bounds within the source image to quantize. + protected virtual void FirstPass(ImageFrame source, Rectangle bounds) { } @@ -156,86 +152,169 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Returns the index and color from the quantized palette corresponding to the give to the given color. /// /// The color to match. + /// The output color palette. /// The matched color. - /// The + /// The index. [MethodImpl(InliningOptions.ShortMethod)] - protected virtual byte GetQuantizedColor(TPixel color, out TPixel match) + protected virtual byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) => this.pixelMap.GetClosestColor(color, out match); /// - /// Retrieve the palette for the quantized image. + /// Generates the palette for the quantized image. /// /// /// /// - protected abstract ReadOnlyMemory GetPalette(); + protected abstract ReadOnlyMemory GenerateQuantizedPalette(); /// /// Execute a second pass through the image to assign the pixels to a palette entry. /// /// The source image. + /// The bounds within the source image to quantize. /// The output pixel array. /// The output color palette. - /// The width in pixels of the image. - /// The height in pixels of the image. protected virtual void SecondPass( ImageFrame source, - Span output, - ReadOnlySpan palette, - int width, - int height) + Rectangle bounds, + Memory output, + ReadOnlyMemory palette) { - Rectangle interest = source.Bounds(); - int bitDepth = ImageMaths.GetBitsNeededForColorDepth(palette.Length); - + ReadOnlySpan paletteSpan = palette.Span; if (!this.DoDither) { - // TODO: This can be parallel. - for (int y = interest.Top; y < interest.Bottom; y++) + var operation = new RowIntervalOperation(source, output, bounds, this, palette); + ParallelRowIterator.IterateRows( + this.Configuration, + bounds, + in operation); + + return; + } + + // Error diffusion. + // The difference between the source and transformed color is spread to neighboring pixels. + // TODO: Investigate parallel strategy. + Span outputSpan = output.Span; + + int bitDepth = ImageMaths.GetBitsNeededForColorDepth(paletteSpan.Length); + if (this.Dither.DitherType == DitherType.ErrorDiffusion) + { + int width = bounds.Width; + int offsetX = bounds.Left; + for (int y = bounds.Top; y < bounds.Bottom; y++) { Span row = source.GetPixelRowSpan(y); int offset = y * width; - for (int x = interest.Left; x < interest.Right; x++) + for (int x = bounds.Left; x < bounds.Right; x++) { - output[offset + x] = this.GetQuantizedColor(row[x], out TPixel _); + TPixel sourcePixel = row[x]; + outputSpan[offset + x - offsetX] = this.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed); + this.Dither.Dither(source, bounds, sourcePixel, transformed, x, y, bitDepth); } } return; } - // Error diffusion. The difference between the source and transformed color - // is spread to neighboring pixels. - if (this.Dither.TransformColorBehavior == DitherTransformColorBehavior.PreOperation) + // Ordered dithering. We are only operating on a single pixel so we can work in parallel. + var ditherOperation = new DitherRowIntervalOperation(source, output, bounds, this, palette, bitDepth); + ParallelRowIterator.IterateRows( + this.Configuration, + bounds, + in ditherOperation); + } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly ImageFrame source; + private readonly Memory output; + private readonly Rectangle bounds; + private readonly FrameQuantizer quantizer; + private readonly ReadOnlyMemory palette; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + ImageFrame source, + Memory output, + Rectangle bounds, + FrameQuantizer quantizer, + ReadOnlyMemory palette) + { + this.source = source; + this.output = output; + this.bounds = bounds; + this.quantizer = quantizer; + this.palette = palette; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) { - for (int y = interest.Top; y < interest.Bottom; y++) + ReadOnlySpan paletteSpan = this.palette.Span; + Span outputSpan = this.output.Span; + int width = this.bounds.Width; + int offsetX = this.bounds.Left; + + for (int y = rows.Min; y < rows.Max; y++) { - Span row = source.GetPixelRowSpan(y); + Span row = this.source.GetPixelRowSpan(y); int offset = y * width; - for (int x = interest.Left; x < interest.Right; x++) + for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - TPixel sourcePixel = row[x]; - output[offset + x] = this.GetQuantizedColor(sourcePixel, out TPixel transformed); - this.Dither.Dither(source, interest, sourcePixel, transformed, x, y, bitDepth); + outputSpan[offset + x - offsetX] = this.quantizer.GetQuantizedColor(row[x], paletteSpan, out TPixel _); } } + } + } - return; + private readonly struct DitherRowIntervalOperation : IRowIntervalOperation + { + private readonly ImageFrame source; + private readonly Memory output; + private readonly Rectangle bounds; + private readonly FrameQuantizer quantizer; + private readonly ReadOnlyMemory palette; + private readonly int bitDepth; + + [MethodImpl(InliningOptions.ShortMethod)] + public DitherRowIntervalOperation( + ImageFrame source, + Memory output, + Rectangle bounds, + FrameQuantizer quantizer, + ReadOnlyMemory palette, + int bitDepth) + { + this.source = source; + this.output = output; + this.bounds = bounds; + this.quantizer = quantizer; + this.palette = palette; + this.bitDepth = bitDepth; } - // TODO: This can be parallel. - // Ordered dithering. We are only operating on a single pixel. - for (int y = interest.Top; y < interest.Bottom; y++) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) { - Span row = source.GetPixelRowSpan(y); - int offset = y * width; + ReadOnlySpan paletteSpan = this.palette.Span; + Span outputSpan = this.output.Span; + int width = this.bounds.Width; + IDither dither = this.quantizer.Dither; + TPixel transformed = default; + int offsetX = this.bounds.Left; - for (int x = interest.Left; x < interest.Right; x++) + for (int y = rows.Min; y < rows.Max; y++) { - TPixel dithered = this.Dither.Dither(source, interest, row[x], default, x, y, bitDepth); - output[offset + x] = this.GetQuantizedColor(dithered, out TPixel _); + Span row = this.source.GetPixelRowSpan(y); + int offset = y * width; + for (int x = this.bounds.Left; x < this.bounds.Right; x++) + { + TPixel dithered = dither.Dither(this.source, this.bounds, row[x], transformed, x, y, this.bitDepth); + outputSpan[offset + x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs index 4561727fb6..30d58ab0b1 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -27,10 +27,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Quantize an image frame and return the resulting output pixels. /// - /// The image to quantize. + /// The image to quantize. + /// The bounds within the source image to quantize. /// - /// A representing a quantized version of the image pixels. + /// A representing a quantized version of the source image pixels. /// - IQuantizedFrame QuantizeFrame(ImageFrame image); + IQuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 20b276c747..56a523f9bb 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -29,6 +29,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private readonly Octree octree; + /// + /// The reduced image palette + /// private TPixel[] palette; /// @@ -63,18 +66,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - protected override void FirstPass(ImageFrame source, int width, int height) + protected override void FirstPass(ImageFrame source, Rectangle bounds) { // Loop through each row - for (int y = 0; y < height; y++) + int offset = bounds.Left; + for (int y = bounds.Top; y < bounds.Bottom; y++) { Span row = source.GetPixelRowSpan(y); ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row); // And loop through each column - for (int x = 0; x < width; x++) + for (int x = bounds.Left; x < bounds.Right; x++) { - ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x); + ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x - offset); // Add the color to the Octree this.octree.AddColor(ref pixel); @@ -84,23 +88,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// [MethodImpl(InliningOptions.ShortMethod)] - protected override byte GetQuantizedColor(TPixel color, out TPixel match) + protected override byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) { if (!this.DoDither) { var index = (byte)this.octree.GetPaletteIndex(ref color); - match = this.GetPalette().Span[index]; + match = palette[index]; return index; } - return base.GetQuantizedColor(color, out match); + return base.GetQuantizedColor(color, palette, out match); } - internal ReadOnlyMemory AotGetPalette() => this.GetPalette(); + internal ReadOnlyMemory AotGetPalette() => this.GenerateQuantizedPalette(); /// [MethodImpl(InliningOptions.ShortMethod)] - protected override ReadOnlyMemory GetPalette() + protected override ReadOnlyMemory GenerateQuantizedPalette() => this.palette ?? (this.palette = this.octree.Palletize(this.colors)); /// @@ -430,7 +434,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The palette /// The current palette index - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(InliningOptions.ColdPath)] public void ConstructPalette(TPixel[] palette, ref int index) { if (this.leaf) @@ -462,10 +466,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The representing the index of the pixel in the palette. /// - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(InliningOptions.ColdPath)] public int GetPaletteIndex(ref TPixel pixel, int level) { - // TODO: pass index around so we can do this in parallel. int index = this.paletteIndex; if (!this.leaf) diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index 0a932b13fc..2aad3c43d5 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -44,8 +44,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Initializes a new instance of the class. /// - /// The maximum number of colors to hold in the color palette. /// Whether to apply dithering to the output image. + /// The maximum number of colors to hold in the color palette. public OctreeQuantizer(bool dither, int maxColors) : this(GetDiffuser(dither), maxColors) { diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index 1c9c224810..f60e6d79a7 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -3,7 +3,6 @@ using System; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Dithering; @@ -32,70 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization : base(configuration, diffuser, true) => this.palette = colors; /// - protected override void SecondPass( - ImageFrame source, - Span output, - ReadOnlySpan palette, - int width, - int height) - { - Rectangle interest = source.Bounds(); - int bitDepth = ImageMaths.GetBitsNeededForColorDepth(palette.Length); - - if (!this.DoDither) - { - // TODO: This can be parallel. - for (int y = interest.Top; y < interest.Bottom; y++) - { - Span row = source.GetPixelRowSpan(y); - int offset = y * width; - - for (int x = interest.Left; x < interest.Right; x++) - { - output[offset + x] = this.GetQuantizedColor(row[x], out TPixel _); - } - } - - return; - } - - // Error diffusion. The difference between the source and transformed color - // is spread to neighboring pixels. - if (this.Dither.TransformColorBehavior == DitherTransformColorBehavior.PreOperation) - { - for (int y = interest.Top; y < interest.Bottom; y++) - { - Span row = source.GetPixelRowSpan(y); - int offset = y * width; - - for (int x = interest.Left; x < interest.Right; x++) - { - TPixel sourcePixel = row[x]; - output[offset + x] = this.GetQuantizedColor(sourcePixel, out TPixel transformed); - this.Dither.Dither(source, interest, sourcePixel, transformed, x, y, bitDepth); - } - } - - return; - } - - // TODO: This can be parallel. - // Ordered dithering. We are only operating on a single pixel. - for (int y = interest.Top; y < interest.Bottom; y++) - { - Span row = source.GetPixelRowSpan(y); - int offset = y * width; - - for (int x = interest.Left; x < interest.Right; x++) - { - TPixel dithered = this.Dither.Dither(source, interest, row[x], default, x, y, bitDepth); - output[offset + x] = this.GetQuantizedColor(dithered, out TPixel _); - } - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override ReadOnlyMemory GetPalette() => this.palette; + [MethodImpl(InliningOptions.ShortMethod)] + protected override ReadOnlyMemory GenerateQuantizedPalette() => this.palette; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index 276919d605..b842c6362c 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -35,14 +35,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// protected override void OnFrameApply(ImageFrame source) { + var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle); + Configuration configuration = this.Configuration; using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(configuration); - using IQuantizedFrame quantized = frameQuantizer.QuantizeFrame(source); + using IQuantizedFrame quantized = frameQuantizer.QuantizeFrame(source, interest); var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); ParallelRowIterator.IterateRows( configuration, - this.SourceRectangle, + interest, in operation); } @@ -71,14 +73,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlySpan quantizedPixelSpan = this.quantized.GetPixelSpan(); ReadOnlySpan paletteSpan = this.quantized.Palette.Span; + int offset = this.bounds.Left; for (int y = rows.Min; y < rows.Max; y++) { Span row = this.source.GetPixelRowSpan(y); int yy = y * this.bounds.Width; - for (int x = this.bounds.X; x < this.bounds.Right; x++) + for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - int i = x + yy; + int i = yy + x - offset; row[x] = paletteSpan[Math.Min(this.maxPaletteIndex, quantizedPixelSpan[i])]; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs index 4938f0e127..90183473b3 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Represents a quantized image frame where the pixels indexed by a color palette. /// /// The pixel format. - public class QuantizedFrame : IQuantizedFrame + public sealed class QuantizedFrame : IQuantizedFrame where TPixel : struct, IPixel { private IMemoryOwner pixels; @@ -67,8 +67,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - /// Get the non-readonly span of pixel data so can fill it. + /// Get the non-readonly memory of pixel data so can fill it. /// - internal Span GetWritablePixelSpan() => this.pixels.GetSpan(); + internal Memory GetWritablePixelMemory() => this.pixels.Memory; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index bf37a7755b..3cf67f3080 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -147,10 +147,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization base.Dispose(true); } - internal ReadOnlyMemory AotGetPalette() => this.GetPalette(); + internal ReadOnlyMemory AotGetPalette() => this.GenerateQuantizedPalette(); /// - protected override ReadOnlyMemory GetPalette() + protected override ReadOnlyMemory GenerateQuantizedPalette() { if (this.palette is null) { @@ -175,16 +175,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - protected override void FirstPass(ImageFrame source, int width, int height) + protected override void FirstPass(ImageFrame source, Rectangle bounds) { - this.Build3DHistogram(source, width, height); + this.Build3DHistogram(source, bounds); this.Get3DMoments(this.memoryAllocator); this.BuildCube(); } /// [MethodImpl(InliningOptions.ShortMethod)] - protected override byte GetQuantizedColor(TPixel color, out TPixel match) + protected override byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) { if (!this.DoDither) { @@ -199,11 +199,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlySpan tagSpan = this.tag.GetSpan(); var index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; - match = this.GetPalette().Span[index]; + match = palette[index]; return index; } - return base.GetQuantizedColor(color, out match); + return base.GetQuantizedColor(color, palette, out match); } /// @@ -378,9 +378,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Builds a 3-D color histogram of counts, r/g/b, c^2. /// /// The source data. - /// The width in pixels of the image. - /// The height in pixels of the image. - private void Build3DHistogram(ImageFrame source, int width, int height) + /// The bounds within the source image to quantize. + private void Build3DHistogram(ImageFrame source, Rectangle bounds) { Span momentSpan = this.moments.GetSpan(); @@ -390,15 +389,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Span rgbaSpan = rgbaBuffer.GetSpan(); ref Rgba32 scanBaseRef = ref MemoryMarshal.GetReference(rgbaSpan); - for (int y = 0; y < height; y++) + int offset = bounds.Left; + for (int y = bounds.Top; y < bounds.Bottom; y++) { Span row = source.GetPixelRowSpan(y); PixelOperations.Instance.ToRgba32(source.GetConfiguration(), row, rgbaSpan); // And loop through each column - for (int x = 0; x < width; x++) + for (int x = bounds.Left; x < bounds.Right; x++) { - ref Rgba32 rgba = ref Unsafe.Add(ref scanBaseRef, x); + ref Rgba32 rgba = ref Unsafe.Add(ref scanBaseRef, x - offset); int r = (rgba.R >> (8 - IndexBits)) + 1; int g = (rgba.G >> (8 - IndexBits)) + 1; diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs index 1676197d41..35a05b8016 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers { using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) { - image.Mutate(x => x.Diffuse()); + image.Mutate(x => x.Dither()); return image.Size(); } diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs deleted file mode 100644 index d20407be92..0000000000 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Binarization; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Processing.Binarization -{ - public class BinaryDitherTest : BaseImageOperationsExtensionTest - { - private readonly IDither orderedDither; - private readonly IDither errorDiffuser; - - public BinaryDitherTest() - { - this.orderedDither = KnownDitherers.BayerDither4x4; - this.errorDiffuser = KnownDiffusers.FloydSteinberg; - } - - [Fact] - public void BinaryDither_CorrectProcessor() - { - this.operations.BinaryDither(this.orderedDither); - BinaryOrderedDitherProcessor p = this.Verify(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryDither_rect_CorrectProcessor() - { - this.operations.BinaryDither(this.orderedDither, this.rect); - BinaryOrderedDitherProcessor p = this.Verify(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryDither_index_CorrectProcessor() - { - this.operations.BinaryDither(this.orderedDither, Color.Yellow, Color.HotPink); - BinaryOrderedDitherProcessor p = this.Verify(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.Yellow, p.UpperColor); - Assert.Equal(Color.HotPink, p.LowerColor); - } - - [Fact] - public void BinaryDither_index_rect_CorrectProcessor() - { - this.operations.BinaryDither(this.orderedDither, Color.Yellow, Color.HotPink, this.rect); - BinaryOrderedDitherProcessor p = this.Verify(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.HotPink, p.LowerColor); - } - - [Fact] - public void BinaryDither_ErrorDiffuser_CorrectProcessor() - { - this.operations.BinaryDiffuse(this.errorDiffuser, .4F); - BinaryErrorDiffusionProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.4F, p.Threshold); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryDither_ErrorDiffuser_rect_CorrectProcessor() - { - this.operations.BinaryDiffuse(this.errorDiffuser, .3F, this.rect); - BinaryErrorDiffusionProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.3F, p.Threshold); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryDither_ErrorDiffuser_CorrectProcessorWithColors() - { - this.operations.BinaryDiffuse(this.errorDiffuser, .5F, Color.HotPink, Color.Yellow); - BinaryErrorDiffusionProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.5F, p.Threshold); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } - - [Fact] - public void BinaryDither_ErrorDiffuser_rect_CorrectProcessorWithColors() - { - this.operations.BinaryDiffuse(this.errorDiffuser, .5F, Color.HotPink, Color.Yellow, this.rect); - BinaryErrorDiffusionProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.5F, p.Threshold); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } - } -} diff --git a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs index 3bdbd8e522..3b04f216cb 100644 --- a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs @@ -2,10 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; - using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Binarization @@ -32,14 +30,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public DitherTest() { this.orderedDither = KnownDitherers.BayerDither4x4; - this.errorDiffuser = KnownDiffusers.FloydSteinberg; + this.errorDiffuser = KnownDitherers.FloydSteinberg; } [Fact] public void Dither_CorrectProcessor() { this.operations.Dither(this.orderedDither); - OrderedDitherPaletteProcessor p = this.Verify(); + PaletteDitherProcessor p = this.Verify(); Assert.Equal(this.orderedDither, p.Dither); Assert.Equal(Color.WebSafePalette, p.Palette); } @@ -48,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void Dither_rect_CorrectProcessor() { this.operations.Dither(this.orderedDither, this.rect); - OrderedDitherPaletteProcessor p = this.Verify(this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); Assert.Equal(this.orderedDither, p.Dither); Assert.Equal(Color.WebSafePalette, p.Palette); } @@ -57,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void Dither_index_CorrectProcessor() { this.operations.Dither(this.orderedDither, this.testPalette); - OrderedDitherPaletteProcessor p = this.Verify(); + PaletteDitherProcessor p = this.Verify(); Assert.Equal(this.orderedDither, p.Dither); Assert.Equal(this.testPalette, p.Palette); } @@ -66,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void Dither_index_rect_CorrectProcessor() { this.operations.Dither(this.orderedDither, this.testPalette, this.rect); - OrderedDitherPaletteProcessor p = this.Verify(this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); Assert.Equal(this.orderedDither, p.Dither); Assert.Equal(this.testPalette, p.Palette); } @@ -74,40 +72,36 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization [Fact] public void Dither_ErrorDiffuser_CorrectProcessor() { - this.operations.Diffuse(this.errorDiffuser, .4F); - ErrorDiffusionPaletteProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.4F, p.Threshold); + this.operations.Dither(this.errorDiffuser); + PaletteDitherProcessor p = this.Verify(); + Assert.Equal(this.errorDiffuser, p.Dither); Assert.Equal(Color.WebSafePalette, p.Palette); } [Fact] public void Dither_ErrorDiffuser_rect_CorrectProcessor() { - this.operations.Diffuse(this.errorDiffuser, .3F, this.rect); - ErrorDiffusionPaletteProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.3F, p.Threshold); + this.operations.Dither(this.errorDiffuser, this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); + Assert.Equal(this.errorDiffuser, p.Dither); Assert.Equal(Color.WebSafePalette, p.Palette); } [Fact] public void Dither_ErrorDiffuser_CorrectProcessorWithColors() { - this.operations.Diffuse(this.errorDiffuser, .5F, this.testPalette); - ErrorDiffusionPaletteProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.5F, p.Threshold); + this.operations.Dither(this.errorDiffuser, this.testPalette); + PaletteDitherProcessor p = this.Verify(); + Assert.Equal(this.errorDiffuser, p.Dither); Assert.Equal(this.testPalette, p.Palette); } [Fact] public void Dither_ErrorDiffuser_rect_CorrectProcessorWithColors() { - this.operations.Diffuse(this.errorDiffuser, .5F, this.testPalette, this.rect); - ErrorDiffusionPaletteProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.5F, p.Threshold); + this.operations.Dither(this.errorDiffuser, this.testPalette, this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); + Assert.Equal(this.errorDiffuser, p.Dither); Assert.Equal(this.testPalette, p.Palette); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs index 00eacdaf54..3b6f51a89a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs @@ -28,22 +28,22 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization public static readonly TheoryData ErrorDiffusers = new TheoryData { - { "Atkinson", KnownDiffusers.Atkinson }, - { "Burks", KnownDiffusers.Burks }, - { "FloydSteinberg", KnownDiffusers.FloydSteinberg }, - { "JarvisJudiceNinke", KnownDiffusers.JarvisJudiceNinke }, - { "Sierra2", KnownDiffusers.Sierra2 }, - { "Sierra3", KnownDiffusers.Sierra3 }, - { "SierraLite", KnownDiffusers.SierraLite }, - { "StevensonArce", KnownDiffusers.StevensonArce }, - { "Stucki", KnownDiffusers.Stucki }, + { "Atkinson", KnownDitherers.Atkinson }, + { "Burks", KnownDitherers.Burks }, + { "FloydSteinberg", KnownDitherers.FloydSteinberg }, + { "JarvisJudiceNinke", KnownDitherers.JarvisJudiceNinke }, + { "Sierra2", KnownDitherers.Sierra2 }, + { "Sierra3", KnownDitherers.Sierra3 }, + { "SierraLite", KnownDitherers.SierraLite }, + { "StevensonArce", KnownDitherers.StevensonArce }, + { "Stucki", KnownDitherers.Stucki }, }; public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; private static IDither DefaultDitherer => KnownDitherers.BayerDither4x4; - private static IDither DefaultErrorDiffuser => KnownDiffusers.Atkinson; + private static IDither DefaultErrorDiffuser => KnownDitherers.Atkinson; [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { using (Image image = provider.GetImage()) { - image.Mutate(x => x.BinaryDiffuse(diffuser, .5F)); + image.Mutate(x => x.BinaryDither(diffuser)); image.DebugSave(provider, name); } } @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { using (Image image = provider.GetImage()) { - image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, 0.5f)); + image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser)); image.DebugSave(provider); } } @@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, .5F, bounds)); + image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser, bounds)); image.DebugSave(provider); ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 94a2ec824d..0900d69565 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -20,15 +20,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization public static readonly TheoryData ErrorDiffusers = new TheoryData { - KnownDiffusers.Atkinson, - KnownDiffusers.Burks, - KnownDiffusers.FloydSteinberg, - KnownDiffusers.JarvisJudiceNinke, - KnownDiffusers.Sierra2, - KnownDiffusers.Sierra3, - KnownDiffusers.SierraLite, - KnownDiffusers.StevensonArce, - KnownDiffusers.Stucki, + KnownDitherers.Atkinson, + KnownDitherers.Burks, + KnownDitherers.FloydSteinberg, + KnownDitherers.JarvisJudiceNinke, + KnownDitherers.Sierra2, + KnownDitherers.Sierra3, + KnownDitherers.SierraLite, + KnownDitherers.StevensonArce, + KnownDitherers.Stucki, }; public static readonly TheoryData OrderedDitherers @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization private static IDither DefaultDitherer => KnownDitherers.BayerDither4x4; - private static IDither DefaultErrorDiffuser => KnownDiffusers.Atkinson; + private static IDither DefaultErrorDiffuser => KnownDitherers.Atkinson; /// /// The output is visually correct old 32bit runtime, @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization } provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.Diffuse(DefaultErrorDiffuser, .5F, rect), + (x, rect) => x.Dither(DefaultErrorDiffuser, rect), comparer: ValidatorComparer); } @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization // Increased tolerance because of compatibility issues on .NET 4.6.2: var comparer = ImageComparer.TolerantPercentage(1f); - provider.RunValidatingProcessorTest(x => x.Diffuse(DefaultErrorDiffuser, 0.5f), comparer: comparer); + provider.RunValidatingProcessorTest(x => x.Dither(DefaultErrorDiffuser), comparer: comparer); } [Theory] @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization } provider.RunValidatingProcessorTest( - x => x.Diffuse(diffuser, 0.5f), + x => x.Dither(diffuser), testOutputDetails: diffuser.GetType().Name, comparer: ValidatorComparer, appendPixelTypeToFileName: false); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs index bd1efaa64e..5ea3d78633 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,19 +16,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization var quantizer = new OctreeQuantizer(128); Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); quantizer = new OctreeQuantizer(false); Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Null(quantizer.Diffuser); + Assert.Null(quantizer.Dither); - quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson); + quantizer = new OctreeQuantizer(KnownDitherers.Atkinson); Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); - quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson, 128); + quantizer = new OctreeQuantizer(KnownDitherers.Atkinson, 128); Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); } [Fact] @@ -38,21 +38,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Dither); + Assert.True(frameQuantizer.DoDither); + Assert.Equal(KnownDitherers.FloydSteinberg, frameQuantizer.Dither); quantizer = new OctreeQuantizer(false); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.Dither); + Assert.False(frameQuantizer.DoDither); Assert.Null(frameQuantizer.Dither); - quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson); + quantizer = new OctreeQuantizer(KnownDitherers.Atkinson); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Dither); + Assert.True(frameQuantizer.DoDither); + Assert.Equal(KnownDitherers.Atkinson, frameQuantizer.Dither); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs index c21e6dc129..1d5c3163c7 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs @@ -18,15 +18,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization var quantizer = new PaletteQuantizer(Rgb); Assert.Equal(Rgb, quantizer.Palette); - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); quantizer = new PaletteQuantizer(Rgb, false); Assert.Equal(Rgb, quantizer.Palette); - Assert.Null(quantizer.Diffuser); + Assert.Null(quantizer.Dither); - quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); + quantizer = new PaletteQuantizer(Rgb, KnownDitherers.Atkinson); Assert.Equal(Rgb, quantizer.Palette); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); } [Fact] @@ -36,35 +36,35 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Dither); + Assert.True(frameQuantizer.DoDither); + Assert.Equal(KnownDitherers.FloydSteinberg, frameQuantizer.Dither); quantizer = new PaletteQuantizer(Rgb, false); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.Dither); + Assert.False(frameQuantizer.DoDither); Assert.Null(frameQuantizer.Dither); - quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); + quantizer = new PaletteQuantizer(Rgb, KnownDitherers.Atkinson); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Dither); + Assert.True(frameQuantizer.DoDither); + Assert.Equal(KnownDitherers.Atkinson, frameQuantizer.Dither); } [Fact] public void KnownQuantizersWebSafeTests() { IQuantizer quantizer = KnownQuantizers.WebSafe; - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); } [Fact] public void KnownQuantizersWernerTests() { IQuantizer quantizer = KnownQuantizers.Werner; - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs index 8287e6e442..08f51940d0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,19 +16,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization var quantizer = new WuQuantizer(128); Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); quantizer = new WuQuantizer(false); Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Null(quantizer.Diffuser); + Assert.Null(quantizer.Dither); - quantizer = new WuQuantizer(KnownDiffusers.Atkinson); + quantizer = new WuQuantizer(KnownDitherers.Atkinson); Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); - quantizer = new WuQuantizer(KnownDiffusers.Atkinson, 128); + quantizer = new WuQuantizer(KnownDitherers.Atkinson, 128); Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); } [Fact] @@ -38,21 +38,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Dither); + Assert.True(frameQuantizer.DoDither); + Assert.Equal(KnownDitherers.FloydSteinberg, frameQuantizer.Dither); quantizer = new WuQuantizer(false); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.Dither); + Assert.False(frameQuantizer.DoDither); Assert.Null(frameQuantizer.Dither); - quantizer = new WuQuantizer(KnownDiffusers.Atkinson); + quantizer = new WuQuantizer(KnownDitherers.Atkinson); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Dither); + Assert.True(frameQuantizer.DoDither); + Assert.Equal(KnownDitherers.Atkinson, frameQuantizer.Dither); } } } diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index 7750017095..0b11395a87 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -22,15 +22,30 @@ namespace SixLabors.ImageSharp.Tests var octree = new OctreeQuantizer(); var wu = new WuQuantizer(); - Assert.NotNull(werner.Diffuser); - Assert.NotNull(webSafe.Diffuser); - Assert.NotNull(octree.Diffuser); - Assert.NotNull(wu.Diffuser); - - Assert.True(werner.CreateFrameQuantizer(this.Configuration).Dither); - Assert.True(webSafe.CreateFrameQuantizer(this.Configuration).Dither); - Assert.True(octree.CreateFrameQuantizer(this.Configuration).Dither); - Assert.True(wu.CreateFrameQuantizer(this.Configuration).Dither); + Assert.NotNull(werner.Dither); + Assert.NotNull(webSafe.Dither); + Assert.NotNull(octree.Dither); + Assert.NotNull(wu.Dither); + + using (IFrameQuantizer quantizer = werner.CreateFrameQuantizer(this.Configuration)) + { + Assert.True(quantizer.DoDither); + } + + using (IFrameQuantizer quantizer = webSafe.CreateFrameQuantizer(this.Configuration)) + { + Assert.True(quantizer.DoDither); + } + + using (IFrameQuantizer quantizer = octree.CreateFrameQuantizer(this.Configuration)) + { + Assert.True(quantizer.DoDither); + } + + using (IFrameQuantizer quantizer = wu.CreateFrameQuantizer(this.Configuration)) + { + Assert.True(quantizer.DoDither); + } } [Theory] @@ -49,11 +64,12 @@ namespace SixLabors.ImageSharp.Tests foreach (ImageFrame frame in image.Frames) { - IQuantizedFrame quantized = - quantizer.CreateFrameQuantizer(this.Configuration).QuantizeFrame(frame); - - int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelSpan()[0]); + using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(this.Configuration)) + using (IQuantizedFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + { + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.GetPixelSpan()[0]); + } } } } @@ -72,11 +88,12 @@ namespace SixLabors.ImageSharp.Tests foreach (ImageFrame frame in image.Frames) { - IQuantizedFrame quantized = - quantizer.CreateFrameQuantizer(this.Configuration).QuantizeFrame(frame); - - int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelSpan()[0]); + using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(this.Configuration)) + using (IQuantizedFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + { + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.GetPixelSpan()[0]); + } } } } diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index c83adea91d..f0ee576235 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -17,15 +17,17 @@ namespace SixLabors.ImageSharp.Tests.Quantization Configuration config = Configuration.Default; var quantizer = new WuQuantizer(false); - using (var image = new Image(config, 1, 1, Color.Black)) - using (IQuantizedFrame result = quantizer.CreateFrameQuantizer(config).QuantizeFrame(image.Frames[0])) - { - Assert.Equal(1, result.Palette.Length); - Assert.Equal(1, result.GetPixelSpan().Length); + using var image = new Image(config, 1, 1, Color.Black); + ImageFrame frame = image.Frames.RootFrame; - Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); - Assert.Equal(0, result.GetPixelSpan()[0]); - } + using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); + using IQuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + + Assert.Equal(1, result.Palette.Length); + Assert.Equal(1, result.GetPixelSpan().Length); + + Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); + Assert.Equal(0, result.GetPixelSpan()[0]); } [Fact] @@ -34,15 +36,17 @@ namespace SixLabors.ImageSharp.Tests.Quantization Configuration config = Configuration.Default; var quantizer = new WuQuantizer(false); - using (var image = new Image(config, 1, 1, default(Rgba32))) - using (IQuantizedFrame result = quantizer.CreateFrameQuantizer(config).QuantizeFrame(image.Frames[0])) - { - Assert.Equal(1, result.Palette.Length); - Assert.Equal(1, result.GetPixelSpan().Length); + using var image = new Image(config, 1, 1, default(Rgba32)); + ImageFrame frame = image.Frames.RootFrame; - Assert.Equal(default, result.Palette.Span[0]); - Assert.Equal(0, result.GetPixelSpan()[0]); - } + using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); + using IQuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + + Assert.Equal(1, result.Palette.Length); + Assert.Equal(1, result.GetPixelSpan().Length); + + Assert.Equal(default, result.Palette.Span[0]); + Assert.Equal(0, result.GetPixelSpan()[0]); } [Fact] @@ -63,46 +67,47 @@ namespace SixLabors.ImageSharp.Tests.Quantization [Fact] public void Palette256() { - using (var image = new Image(1, 256)) + using var image = new Image(1, 256); + + for (int i = 0; i < 256; i++) { - for (int i = 0; i < 256; i++) - { - byte r = (byte)((i % 4) * 85); - byte g = (byte)(((i / 4) % 4) * 85); - byte b = (byte)(((i / 16) % 4) * 85); - byte a = (byte)((i / 64) * 85); + byte r = (byte)((i % 4) * 85); + byte g = (byte)(((i / 4) % 4) * 85); + byte b = (byte)(((i / 16) % 4) * 85); + byte a = (byte)((i / 64) * 85); - image[0, i] = new Rgba32(r, g, b, a); - } + image[0, i] = new Rgba32(r, g, b, a); + } - Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); - using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) - using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) - { - Assert.Equal(256, result.Palette.Length); - Assert.Equal(256, result.GetPixelSpan().Length); + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(false); - var actualImage = new Image(1, 256); + ImageFrame frame = image.Frames.RootFrame; - ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = result.Palette.Length - 1; - for (int y = 0; y < actualImage.Height; y++) - { - Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); - int yy = y * actualImage.Width; + using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); + using IQuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); - for (int x = 0; x < actualImage.Width; x++) - { - int i = x + yy; - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])]; - } - } + Assert.Equal(256, result.Palette.Length); + Assert.Equal(256, result.GetPixelSpan().Length); + + var actualImage = new Image(1, 256); - Assert.True(image.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan())); + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = result.Palette.Length - 1; + for (int y = 0; y < actualImage.Height; y++) + { + Span row = actualImage.GetPixelRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); + int yy = y * actualImage.Width; + + for (int x = 0; x < actualImage.Width; x++) + { + int i = x + yy; + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])]; } } + + Assert.True(image.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan())); } [Theory] @@ -115,11 +120,12 @@ namespace SixLabors.ImageSharp.Tests.Quantization { Configuration config = Configuration.Default; var quantizer = new WuQuantizer(false); - using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) - using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) - { - Assert.Equal(48, result.Palette.Length); - } + ImageFrame frame = image.Frames.RootFrame; + + using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); + using IQuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + + Assert.Equal(48, result.Palette.Length); } } @@ -144,8 +150,9 @@ namespace SixLabors.ImageSharp.Tests.Quantization Configuration config = Configuration.Default; var quantizer = new WuQuantizer(false); + ImageFrame frame = image.Frames.RootFrame; using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) - using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) + using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) { Assert.Equal(4 * 8, result.Palette.Length); Assert.Equal(256, result.GetPixelSpan().Length); diff --git a/tests/Images/Input/Png/CalliphoraPartial2.png b/tests/Images/Input/Png/CalliphoraPartial2.png new file mode 100644 index 0000000000000000000000000000000000000000..8597e68e991287b46d70903591577eaa66035d11 GIT binary patch literal 75628 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�-RF~ctC1ze&n+ma+tf6rL*&e7}ast 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

Ab3{!?see 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