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);
}
}