diff --git a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs index 516bd5545..ebd2ea613 100644 --- a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Processing /// The image this method extends. /// The to allow chaining of operations. public static IImageProcessingContext Dither(this IImageProcessingContext source) => - Dither(source, KnownDitherers.BayerDither4x4); + Dither(source, KnownDitherings.BayerDither4x4); /// /// Dithers the image reducing it to a web-safe palette using ordered dithering. diff --git a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs index 3410ee6be..86ccddd85 100644 --- a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.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.Processing.Processors.Quantization; @@ -27,5 +27,28 @@ namespace SixLabors.ImageSharp.Processing /// The to allow chaining of operations. public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer) => source.ApplyProcessor(new QuantizeProcessor(quantizer)); + + /// + /// Applies quantization to the image using the . + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Quantize(this IImageProcessingContext source, Rectangle rectangle) => + Quantize(source, KnownQuantizers.Octree, rectangle); + + /// + /// Applies quantization to the image. + /// + /// The image this method extends. + /// The quantizer to apply to perform the operation. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer, Rectangle rectangle) => + source.ApplyProcessor(new QuantizeProcessor(quantizer), rectangle); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/KnownDitherers.cs b/src/ImageSharp/Processing/KnownDitherings.cs similarity index 96% rename from src/ImageSharp/Processing/KnownDitherers.cs rename to src/ImageSharp/Processing/KnownDitherings.cs index 8e3653b52..43387c55e 100644 --- a/src/ImageSharp/Processing/KnownDitherers.cs +++ b/src/ImageSharp/Processing/KnownDitherings.cs @@ -6,9 +6,9 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing { /// - /// Contains reusable static instances of known ordered dither matrices + /// Contains reusable static instances of known dithering algorithms. /// - public static class KnownDitherers + public static class KnownDitherings { /// /// Gets the order ditherer using the 2x2 Bayer dithering matrix diff --git a/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf b/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf new file mode 100644 index 000000000..42fb22c95 Binary files /dev/null and b/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf differ diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs index f8ae64d95..1914ed891 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs @@ -182,16 +182,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (this.Dither.DitherType == DitherType.ErrorDiffusion) { int width = bounds.Width; + int offsetY = bounds.Top; int offsetX = bounds.Left; for (int y = bounds.Top; y < bounds.Bottom; y++) { Span row = source.GetPixelRowSpan(y); - int offset = y * width; + int rowStart = (y - offsetY) * width; for (int x = bounds.Left; x < bounds.Right; x++) { TPixel sourcePixel = row[x]; - outputSpan[offset + x - offsetX] = this.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed); + outputSpan[rowStart + x - offsetX] = this.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed); this.Dither.Dither(source, bounds, sourcePixel, transformed, x, y, bitDepth); } } @@ -255,16 +256,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlySpan paletteSpan = this.palette.Span; Span outputSpan = this.output.Span; int width = this.bounds.Width; + int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; for (int y = rows.Min; y < rows.Max; y++) { Span row = this.source.GetPixelRowSpan(y); - int offset = y * width; + int rowStart = (y - offsetY) * width; for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - outputSpan[offset + x - offsetX] = this.quantizer.GetQuantizedColor(row[x], paletteSpan, out TPixel _); + outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(row[x], paletteSpan, out TPixel _); } } } @@ -302,18 +304,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlySpan paletteSpan = this.palette.Span; Span outputSpan = this.output.Span; int width = this.bounds.Width; + int offsetY = this.bounds.Top; + int offsetX = this.bounds.Left; IDither dither = this.quantizer.Dither; TPixel transformed = default; - int offsetX = this.bounds.Left; for (int y = rows.Min; y < rows.Max; y++) { Span row = this.source.GetPixelRowSpan(y); - int offset = y * width; + int rowStart = (y - offsetY) * 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 _); + outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 56a523f9b..b489b5e8d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -2,11 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization @@ -68,20 +69,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// protected override void FirstPass(ImageFrame source, Rectangle bounds) { + using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width); + Span bufferSpan = buffer.GetSpan(); + // Loop through each row - 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); + Span row = source.GetPixelRowSpan(y).Slice(bounds.Left, bounds.Width); + PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); - // And loop through each column - for (int x = bounds.Left; x < bounds.Right; x++) + for (int x = 0; x < bufferSpan.Length; x++) { - ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x - offset); + Rgba32 rgba = bufferSpan[x]; // Add the color to the Octree - this.octree.AddColor(ref pixel); + this.octree.AddColor(rgba); } } } @@ -92,7 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { if (!this.DoDither) { - var index = (byte)this.octree.GetPaletteIndex(ref color); + var index = (byte)this.octree.GetPaletteIndex(color); match = palette[index]; return index; } @@ -113,10 +115,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private sealed class Octree { /// - /// Mask used when getting the appropriate pixels for a given node + /// Mask used when getting the appropriate pixels for a given node. /// - // ReSharper disable once StaticMemberInGenericType - private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + private static readonly byte[] Mask = new byte[] + { + 0b10000000, + 0b1000000, + 0b100000, + 0b10000, + 0b1000, + 0b100, + 0b10, + 0b1 + }; /// /// The root of the Octree @@ -136,7 +147,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Cache the previous color quantized /// - private TPixel previousColor; + private Rgba32 previousColor; /// /// Initializes a new instance of the class. @@ -178,29 +189,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Add a given color value to the Octree /// - /// The pixel data. - public void AddColor(ref TPixel pixel) + /// The color to add. + public void AddColor(Rgba32 color) { // Check if this request is for the same color as the last - if (this.previousColor.Equals(pixel)) + if (this.previousColor.Equals(color)) { - // If so, check if I have a previous node setup. This will only occur if the first color in the image + // If so, check if I have a previous node setup. + // This will only occur if the first color in the image // happens to be black, with an alpha component of zero. if (this.previousNode is null) { - this.previousColor = pixel; - this.root.AddColor(ref pixel, this.maxColorBits, 0, this); + this.previousColor = color; + this.root.AddColor(ref color, this.maxColorBits, 0, this); } else { // Just update the previous node - this.previousNode.Increment(ref pixel); + this.previousNode.Increment(ref color); } } else { - this.previousColor = pixel; - this.root.AddColor(ref pixel, this.maxColorBits, 0, this); + this.previousColor = color; + this.root.AddColor(ref color, this.maxColorBits, 0, this); } } @@ -232,12 +244,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Get the palette index for the passed color /// - /// The pixel data. + /// The color to match. /// - /// The . + /// The index. /// [MethodImpl(InliningOptions.ShortMethod)] - public int GetPaletteIndex(ref TPixel pixel) => this.root.GetPaletteIndex(ref pixel, 0); + public int GetPaletteIndex(TPixel color) + { + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + return this.root.GetPaletteIndex(ref rgba, 0); + } /// /// Keep track of the previous node that was quantized @@ -360,16 +377,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Add a color into the tree /// - /// The pixel color - /// The number of significant color bits - /// The level in the tree - /// The tree to which this node belongs - public void AddColor(ref TPixel pixel, int colorBits, int level, Octree octree) + /// The color to add. + /// The number of significant color bits. + /// The level in the tree. + /// The tree to which this node belongs. + public void AddColor(ref Rgba32 color, int colorBits, int level, Octree octree) { // Update the color information if this is a leaf if (this.leaf) { - this.Increment(ref pixel); + this.Increment(ref color); // Setup the previous node octree.TrackPrevious(this); @@ -377,13 +394,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization else { // Go to the next level down in the tree - int shift = 7 - level; - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); - - int index = ((rgba.B & Mask[level]) >> (shift - 2)) - | ((rgba.G & Mask[level]) >> (shift - 1)) - | ((rgba.R & Mask[level]) >> shift); + int index = GetColorIndex(ref color, level); OctreeNode child = this.children[index]; if (child is null) @@ -394,7 +405,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } // Add the color to the child node - child.AddColor(ref pixel, colorBits, level + 1, octree); + child.AddColor(ref color, colorBits, level + 1, octree); } } @@ -467,29 +478,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The representing the index of the pixel in the palette. /// [MethodImpl(InliningOptions.ColdPath)] - public int GetPaletteIndex(ref TPixel pixel, int level) + public int GetPaletteIndex(ref Rgba32 pixel, int level) { - int index = this.paletteIndex; - - if (!this.leaf) + if (this.leaf) { - int shift = 7 - level; - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); + return this.paletteIndex; + } - int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2)) - | ((rgba.G & Mask[level]) >> (shift - 1)) - | ((rgba.R & Mask[level]) >> shift); + int colorIndex = GetColorIndex(ref pixel, level); + OctreeNode child = this.children[colorIndex]; - OctreeNode child = this.children[pixelIndex]; - if (child != null) - { - index = child.GetPaletteIndex(ref pixel, level + 1); - } - else + int index = 0; + if (child != null) + { + index = child.GetPaletteIndex(ref pixel, level + 1); + } + else + { + // Check other children. + for (int i = 0; i < this.children.Length; i++) { - // TODO: Throw helper. - throw new Exception($"Cannot retrieve a pixel at the given index {pixelIndex}."); + child = this.children[i]; + if (child != null) + { + var childIndex = child.GetPaletteIndex(ref pixel, level + 1); + if (childIndex != 0) + { + return childIndex; + } + } } } @@ -497,18 +514,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - /// Increment the pixel count and add to the color information + /// Gets the color index at the given level. + /// + /// The color. + /// The node level. + /// The index. + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetColorIndex(ref Rgba32 color, int level) + { + int shift = 7 - level; + byte mask = Mask[level]; + return ((color.B & mask) >> (shift - 2)) + | ((color.G & mask) >> (shift - 1)) + | ((color.R & mask) >> shift); + } + + /// + /// Increment the color count and add to the color information /// - /// The pixel to add. + /// The pixel to add. [MethodImpl(InliningOptions.ShortMethod)] - public void Increment(ref TPixel pixel) + public void Increment(ref Rgba32 color) { - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); this.pixelCount++; - this.red += rgba.R; - this.green += rgba.G; - this.blue += rgba.B; + this.red += color.R; + this.green += color.G; + this.blue += color.B; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index 2aad3c43d..06578354c 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Allows the quantization of images pixels using Octrees. /// /// - /// By default the quantizer uses dithering and a color palette of a maximum length of 255 + /// By default the quantizer uses dithering and a color palette of a maximum length of 255 /// /// public class OctreeQuantizer : IQuantizer @@ -93,6 +93,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization return new OctreeFrameQuantizer(configuration, this, maxColors); } - private static IDither GetDiffuser(bool dither) => dither ? KnownDitherers.FloydSteinberg : null; + private static IDither GetDiffuser(bool dither) => dither ? KnownDitherings.FloydSteinberg : null; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index daba7a6b7..fd2e6052e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Allows the quantization of images pixels using color palettes. /// Override this class to provide your own palette. /// - /// By default the quantizer uses dithering. + /// By default the quantizer uses dithering. /// /// public class PaletteQuantizer : IQuantizer @@ -76,6 +76,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization return new PaletteFrameQuantizer(configuration, this.Dither, palette); } - private static IDither GetDiffuser(bool dither) => dither ? KnownDitherers.FloydSteinberg : null; + private static IDither GetDiffuser(bool dither) => dither ? KnownDitherings.FloydSteinberg : null; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index b842c6362..b42e0f3e2 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -53,7 +53,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private readonly Rectangle bounds; private readonly ImageFrame source; private readonly IQuantizedFrame quantized; - private readonly int maxPaletteIndex; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperation( @@ -64,7 +63,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.bounds = bounds; this.source = source; this.quantized = quantized; - this.maxPaletteIndex = quantized.Palette.Length - 1; } [MethodImpl(InliningOptions.ShortMethod)] @@ -72,17 +70,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { ReadOnlySpan quantizedPixelSpan = this.quantized.GetPixelSpan(); ReadOnlySpan paletteSpan = this.quantized.Palette.Span; + int offsetY = this.bounds.Top; + int offsetX = this.bounds.Left; + int width = this.bounds.Width; - 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; + int rowStart = (y - offsetY) * width; for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - int i = yy + x - offset; - row[x] = paletteSpan[Math.Min(this.maxPaletteIndex, quantizedPixelSpan[i])]; + int i = rowStart + x - offsetX; + row[x] = paletteSpan[quantizedPixelSpan[i]]; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 3cf67f308..f037f63c2 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -384,29 +384,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Span momentSpan = this.moments.GetSpan(); // Build up the 3-D color histogram - // Loop through each row - using IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(source.Width); - Span rgbaSpan = rgbaBuffer.GetSpan(); - ref Rgba32 scanBaseRef = ref MemoryMarshal.GetReference(rgbaSpan); + using IMemoryOwner buffer = this.memoryAllocator.Allocate(bounds.Width); + Span bufferSpan = buffer.GetSpan(); - 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); + Span row = source.GetPixelRowSpan(y).Slice(bounds.Left, bounds.Width); + PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); - // And loop through each column - for (int x = bounds.Left; x < bounds.Right; x++) + for (int x = 0; x < bufferSpan.Length; x++) { - ref Rgba32 rgba = ref Unsafe.Add(ref scanBaseRef, x - offset); + Rgba32 rgba = bufferSpan[x]; int r = (rgba.R >> (8 - IndexBits)) + 1; int g = (rgba.G >> (8 - IndexBits)) + 1; int b = (rgba.B >> (8 - IndexBits)) + 1; int a = (rgba.A >> (8 - IndexAlphaBits)) + 1; - int index = GetPaletteIndex(r, g, b, a); - momentSpan[index] += rgba; + momentSpan[GetPaletteIndex(r, g, b, a)] += rgba; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs index 6bd432242..682b6ec64 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer /// - /// By default the quantizer uses dithering and a color palette of a maximum length of 255 + /// By default the quantizer uses dithering and a color palette of a maximum length of 255 /// /// public class WuQuantizer : IQuantizer @@ -85,6 +85,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization return new WuFrameQuantizer(configuration, this, maxColors); } - private static IDither GetDiffuser(bool dither) => dither ? KnownDitherers.FloydSteinberg : null; + private static IDither GetDiffuser(bool dither) => dither ? KnownDitherings.FloydSteinberg : null; } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs index feb447501..134b3091e 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.Dither(KnownDitherers.FloydSteinberg)); + image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg)); return image.Size(); } diff --git a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs index 3b04f216c..f343d9266 100644 --- a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs @@ -29,8 +29,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public DitherTest() { - this.orderedDither = KnownDitherers.BayerDither4x4; - this.errorDiffuser = KnownDitherers.FloydSteinberg; + this.orderedDither = KnownDitherings.BayerDither4x4; + this.errorDiffuser = KnownDitherings.FloydSteinberg; } [Fact] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs index 3b6f51a89..d57a63432 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs @@ -20,30 +20,30 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization public static readonly TheoryData OrderedDitherers = new TheoryData { - { "Bayer8x8", KnownDitherers.BayerDither8x8 }, - { "Bayer4x4", KnownDitherers.BayerDither4x4 }, - { "Ordered3x3", KnownDitherers.OrderedDither3x3 }, - { "Bayer2x2", KnownDitherers.BayerDither2x2 } + { "Bayer8x8", KnownDitherings.BayerDither8x8 }, + { "Bayer4x4", KnownDitherings.BayerDither4x4 }, + { "Ordered3x3", KnownDitherings.OrderedDither3x3 }, + { "Bayer2x2", KnownDitherings.BayerDither2x2 } }; public static readonly TheoryData ErrorDiffusers = new TheoryData { - { "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 }, + { "Atkinson", KnownDitherings.Atkinson }, + { "Burks", KnownDitherings.Burks }, + { "FloydSteinberg", KnownDitherings.FloydSteinberg }, + { "JarvisJudiceNinke", KnownDitherings.JarvisJudiceNinke }, + { "Sierra2", KnownDitherings.Sierra2 }, + { "Sierra3", KnownDitherings.Sierra3 }, + { "SierraLite", KnownDitherings.SierraLite }, + { "StevensonArce", KnownDitherings.StevensonArce }, + { "Stucki", KnownDitherings.Stucki }, }; public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; - private static IDither DefaultDitherer => KnownDitherers.BayerDither4x4; + private static IDither DefaultDitherer => KnownDitherings.BayerDither4x4; - private static IDither DefaultErrorDiffuser => KnownDitherers.Atkinson; + private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 0900d6956..2ce655a7e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -20,31 +20,31 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization public static readonly TheoryData ErrorDiffusers = new TheoryData { - KnownDitherers.Atkinson, - KnownDitherers.Burks, - KnownDitherers.FloydSteinberg, - KnownDitherers.JarvisJudiceNinke, - KnownDitherers.Sierra2, - KnownDitherers.Sierra3, - KnownDitherers.SierraLite, - KnownDitherers.StevensonArce, - KnownDitherers.Stucki, + KnownDitherings.Atkinson, + KnownDitherings.Burks, + KnownDitherings.FloydSteinberg, + KnownDitherings.JarvisJudiceNinke, + KnownDitherings.Sierra2, + KnownDitherings.Sierra3, + KnownDitherings.SierraLite, + KnownDitherings.StevensonArce, + KnownDitherings.Stucki, }; public static readonly TheoryData OrderedDitherers = new TheoryData { - KnownDitherers.BayerDither8x8, - KnownDitherers.BayerDither4x4, - KnownDitherers.OrderedDither3x3, - KnownDitherers.BayerDither2x2 + KnownDitherings.BayerDither8x8, + KnownDitherings.BayerDither4x4, + KnownDitherings.OrderedDither3x3, + KnownDitherings.BayerDither2x2 }; private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); - private static IDither DefaultDitherer => KnownDitherers.BayerDither4x4; + private static IDither DefaultDitherer => KnownDitherings.BayerDither4x4; - private static IDither DefaultErrorDiffuser => KnownDitherers.Atkinson; + private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; /// /// The output is visually correct old 32bit runtime, diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs index 5ea3d7863..69a681bb3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs @@ -16,19 +16,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization var quantizer = new OctreeQuantizer(128); Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); + Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); quantizer = new OctreeQuantizer(false); Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); Assert.Null(quantizer.Dither); - quantizer = new OctreeQuantizer(KnownDitherers.Atkinson); + quantizer = new OctreeQuantizer(KnownDitherings.Atkinson); Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); - quantizer = new OctreeQuantizer(KnownDitherers.Atkinson, 128); + quantizer = new OctreeQuantizer(KnownDitherings.Atkinson, 128); Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); } [Fact] @@ -39,7 +39,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization Assert.NotNull(frameQuantizer); Assert.True(frameQuantizer.DoDither); - Assert.Equal(KnownDitherers.FloydSteinberg, frameQuantizer.Dither); + Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither); + frameQuantizer.Dispose(); quantizer = new OctreeQuantizer(false); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); @@ -47,12 +48,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization Assert.NotNull(frameQuantizer); Assert.False(frameQuantizer.DoDither); Assert.Null(frameQuantizer.Dither); + frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(KnownDitherers.Atkinson); + quantizer = new OctreeQuantizer(KnownDitherings.Atkinson); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); Assert.True(frameQuantizer.DoDither); - Assert.Equal(KnownDitherers.Atkinson, frameQuantizer.Dither); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither); + frameQuantizer.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs index 1d5c3163c..a348deb65 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(KnownDitherers.FloydSteinberg, quantizer.Dither); + Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); quantizer = new PaletteQuantizer(Rgb, false); Assert.Equal(Rgb, quantizer.Palette); Assert.Null(quantizer.Dither); - quantizer = new PaletteQuantizer(Rgb, KnownDitherers.Atkinson); + quantizer = new PaletteQuantizer(Rgb, KnownDitherings.Atkinson); Assert.Equal(Rgb, quantizer.Palette); - Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); } [Fact] @@ -37,7 +37,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization Assert.NotNull(frameQuantizer); Assert.True(frameQuantizer.DoDither); - Assert.Equal(KnownDitherers.FloydSteinberg, frameQuantizer.Dither); + Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither); + frameQuantizer.Dispose(); quantizer = new PaletteQuantizer(Rgb, false); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); @@ -45,26 +46,28 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization Assert.NotNull(frameQuantizer); Assert.False(frameQuantizer.DoDither); Assert.Null(frameQuantizer.Dither); + frameQuantizer.Dispose(); - quantizer = new PaletteQuantizer(Rgb, KnownDitherers.Atkinson); + quantizer = new PaletteQuantizer(Rgb, KnownDitherings.Atkinson); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); Assert.True(frameQuantizer.DoDither); - Assert.Equal(KnownDitherers.Atkinson, frameQuantizer.Dither); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither); + frameQuantizer.Dispose(); } [Fact] public void KnownQuantizersWebSafeTests() { IQuantizer quantizer = KnownQuantizers.WebSafe; - Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); + Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); } [Fact] public void KnownQuantizersWernerTests() { IQuantizer quantizer = KnownQuantizers.Werner; - Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); + Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs new file mode 100644 index 000000000..efad57d5b --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization +{ + public class QuantizerTests + { + public static readonly string[] CommonTestImages = + { + TestImages.Png.CalliphoraPartial, + TestImages.Png.Bike + }; + + public static readonly TheoryData Quantizers + = new TheoryData + { + KnownQuantizers.Octree, + KnownQuantizers.WebSafe, + KnownQuantizers.Werner, + KnownQuantizers.Wu, + new OctreeQuantizer(false), + new WebSafePaletteQuantizer(false), + new WernerPaletteQuantizer(false), + new WuQuantizer(false), + new OctreeQuantizer(KnownDitherings.BayerDither8x8), + new WebSafePaletteQuantizer(KnownDitherings.BayerDither8x8), + new WernerPaletteQuantizer(KnownDitherings.BayerDither8x8), + new WuQuantizer(KnownDitherings.BayerDither8x8) + }; + + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(Quantizers), PixelTypes.Rgba32)] + public void ApplyQuantizationInBox(TestImageProvider provider, IQuantizer quantizer) + where TPixel : struct, IPixel + { + string quantizerName = quantizer.GetType().Name; + string ditherName = quantizer.Dither?.GetType()?.Name ?? "noDither"; + string ditherType = quantizer.Dither?.DitherType.ToString() ?? string.Empty; + string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}"; + + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Quantize(quantizer, rect), + comparer: ValidatorComparer, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(Quantizers), PixelTypes.Rgba32)] + public void ApplyQuantization(TestImageProvider provider, IQuantizer quantizer) + where TPixel : struct, IPixel + { + string quantizerName = quantizer.GetType().Name; + string ditherName = quantizer.Dither?.GetType()?.Name ?? "noDither"; + string ditherType = quantizer.Dither?.DitherType.ToString() ?? string.Empty; + string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}"; + + provider.RunValidatingProcessorTest( + x => x.Quantize(quantizer), + comparer: ValidatorComparer, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: false); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs index 08f51940d..e352d51f6 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs @@ -16,19 +16,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization var quantizer = new WuQuantizer(128); Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); + Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); quantizer = new WuQuantizer(false); Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); Assert.Null(quantizer.Dither); - quantizer = new WuQuantizer(KnownDitherers.Atkinson); + quantizer = new WuQuantizer(KnownDitherings.Atkinson); Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); - quantizer = new WuQuantizer(KnownDitherers.Atkinson, 128); + quantizer = new WuQuantizer(KnownDitherings.Atkinson, 128); Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); } [Fact] @@ -39,7 +39,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization Assert.NotNull(frameQuantizer); Assert.True(frameQuantizer.DoDither); - Assert.Equal(KnownDitherers.FloydSteinberg, frameQuantizer.Dither); + Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither); + frameQuantizer.Dispose(); quantizer = new WuQuantizer(false); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); @@ -47,12 +48,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization Assert.NotNull(frameQuantizer); Assert.False(frameQuantizer.DoDither); Assert.Null(frameQuantizer.Dither); + frameQuantizer.Dispose(); - quantizer = new WuQuantizer(KnownDitherers.Atkinson); + quantizer = new WuQuantizer(KnownDitherings.Atkinson); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); Assert.True(frameQuantizer.DoDither); - Assert.Equal(KnownDitherers.Atkinson, frameQuantizer.Dither); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither); + frameQuantizer.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 2ef62ed1c..92e0bf85a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -342,10 +342,10 @@ namespace SixLabors.ImageSharp.Tests if (!File.Exists(referenceOutputFile)) { - throw new System.IO.FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); + throw new FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); } - decoder = decoder ?? TestEnvironment.GetReferenceDecoder(referenceOutputFile); + decoder ??= TestEnvironment.GetReferenceDecoder(referenceOutputFile); return Image.Load(referenceOutputFile, decoder); } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index 05da31282..fd3f18359 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -294,7 +294,8 @@ namespace SixLabors.ImageSharp.Tests this TestImageProvider provider, Action process, object testOutputDetails = null, - ImageComparer comparer = null) + ImageComparer comparer = null, + bool appendPixelTypeToFileName = true) where TPixel : struct, IPixel { if (comparer == null) @@ -307,7 +308,7 @@ namespace SixLabors.ImageSharp.Tests var bounds = new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2); image.Mutate(x => process(x, bounds)); image.DebugSave(provider, testOutputDetails); - image.CompareToReferenceOutput(comparer, provider, testOutputDetails); + image.CompareToReferenceOutput(comparer, provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); } }