Browse Source

Cleanup and fix tests.

af/octree-no-pixelmap
James Jackson-South 6 years ago
parent
commit
042a6bef53
  1. 3
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 50
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  3. 10
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  4. 3
      src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs
  5. 10
      src/ImageSharp/Processing/Processors/Dithering/DitherType.cs
  6. 2
      src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
  7. 6
      src/ImageSharp/Processing/Processors/Dithering/IDither.cs
  8. 4
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  9. 2
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  10. 191
      src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs
  11. 7
      src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs
  12. 27
      src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs
  13. 2
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs
  14. 68
      src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs
  15. 11
      src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs
  16. 10
      src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
  17. 26
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
  18. 2
      tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs
  19. 106
      tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs
  20. 40
      tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs
  21. 26
      tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs
  22. 26
      tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
  23. 26
      tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs
  24. 24
      tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs
  25. 26
      tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs
  26. 55
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
  27. 113
      tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs
  28. 3
      tests/Images/Input/Png/CalliphoraPartial2.png

3
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -124,7 +124,8 @@ namespace SixLabors.ImageSharp.Advanced
{
using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default, new WuQuantizer(false)))
{
test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1));
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
test.QuantizeFrame(frame, frame.Bounds());
test.AotGetPalette();
}
}

50
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -335,36 +335,36 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
where TPixel : struct, IPixel<TPixel>
{
using (IQuantizedFrame<TPixel> quantized = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, 256).QuantizeFrame(image))
using IFrameQuantizer<TPixel> quantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, 256);
using IQuantizedFrame<TPixel> quantized = quantizer.QuantizeFrame(image, image.Bounds());
ReadOnlySpan<TPixel> 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<TPixel> 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<byte> 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<byte> pixelSpan = quantized.GetRowSpan(y);
stream.Write(pixelSpan);
for (int i = 0; i < this.padding; i++)
{
stream.WriteByte(0);
}
stream.WriteByte(0);
}
}
}

10
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Configuration bound to the encoding operation.
/// </summary>
private Configuration configuration;
private readonly Configuration configuration;
/// <summary>
/// A reusable buffer used to reduce allocations.
@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
IQuantizedFrame<TPixel> quantized;
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(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<TPixel> paletteFrameQuantizer =
new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Dither, quantized.Palette))
{
using (IQuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame))
using (IQuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{
this.WriteImageData(paletteQuantized, stream);
}
@ -173,14 +173,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, frameMetadata.ColorTableLength))
{
quantized = frameQuantizer.QuantizeFrame(frame);
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
}
else
{
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
{
quantized = frameQuantizer.QuantizeFrame(frame);
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
}
}

3
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<TPixel> frameQuantizer = options.Quantizer.CreateFrameQuantizer<TPixel>(image.GetConfiguration()))
{
return frameQuantizer.QuantizeFrame(image.Frames.RootFrame);
ImageFrame<TPixel> frame = image.Frames.RootFrame;
return frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
}

10
src/ImageSharp/Processing/Processors/Dithering/DitherTransformColorBehavior.cs → src/ImageSharp/Processing/Processors/Dithering/DitherType.cs

@ -6,16 +6,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <summary>
/// Enumerates the possible dithering algorithm transform behaviors.
/// </summary>
public enum DitherTransformColorBehavior
public enum DitherType
{
/// <summary>
/// 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.
/// </summary>
PreOperation,
ErrorDiffusion,
/// <summary>
/// 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.
/// </summary>
PostOperation
OrderedDither
}
}

2
src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
}
/// <inheritdoc/>
public DitherTransformColorBehavior TransformColorBehavior { get; } = DitherTransformColorBehavior.PreOperation;
public DitherType DitherType { get; } = DitherType.ErrorDiffusion;
/// <inheritdoc/>
public TPixel Dither<TPixel>(

6
src/ImageSharp/Processing/Processors/Dithering/IDither.cs

@ -11,14 +11,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
public interface IDither
{
/// <summary>
/// Gets the <see cref="DitherTransformColorBehavior"/> which determines whether the
/// Gets the <see cref="Dithering.DitherType"/> which determines whether the
/// transformed color should be calculated and supplied to the algorithm.
/// </summary>
public DitherTransformColorBehavior TransformColorBehavior { get; }
public DitherType DitherType { get; }
/// <summary>
/// Transforms the image applying a dither matrix.
/// When <see cref="TransformColorBehavior"/> is <see cref="DitherTransformColorBehavior.PreOperation"/> this
/// When <see cref="DitherType"/> is <see cref="DitherType.ErrorDiffusion"/> this
/// this method is destructive and will alter the input pixels.
/// </summary>
/// <param name="image">The image.</param>

4
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
}
/// <inheritdoc/>
public DitherTransformColorBehavior TransformColorBehavior { get; } = DitherTransformColorBehavior.PostOperation;
public DitherType DitherType { get; } = DitherType.OrderedDither;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]

2
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++)
{

191
src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs

@ -29,14 +29,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="FrameQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="quantizer">The quantizer</param>
/// <param name="quantizer">The quantizer.</param>
/// <param name="singlePass">
/// 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.
/// </param>
/// <remarks>
/// If you construct this class with a <value>true</value> for <paramref name="singlePass"/>, then the code will
/// only call the <see cref="SecondPass(ImageFrame{TPixel}, Span{byte}, ReadOnlySpan{TPixel}, int, int)"/> method.
/// If two passes are required, the code will also call <see cref="FirstPass(ImageFrame{TPixel}, int, int)"/>.
/// only call the <see cref="SecondPass(ImageFrame{TPixel}, Rectangle, Memory{byte}, ReadOnlyMemory{TPixel})"/> method.
/// If two passes are required, the code will also call <see cref="FirstPass(ImageFrame{TPixel}, Rectangle)"/>.
/// </remarks>
protected FrameQuantizer(Configuration configuration, IQuantizer quantizer, bool singlePass)
{
@ -58,8 +58,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </param>
/// <remarks>
/// If you construct this class with a <value>true</value> for <paramref name="singlePass"/>, then the code will
/// only call the <see cref="SecondPass(ImageFrame{TPixel}, Span{byte}, ReadOnlySpan{TPixel}, int, int)"/> method.
/// If two passes are required, the code will also call <see cref="FirstPass(ImageFrame{TPixel}, int, int)"/>.
/// only call the <see cref="SecondPass(ImageFrame{TPixel}, Rectangle, Memory{byte}, ReadOnlyMemory{TPixel})"/> method.
/// If two passes are required, the code will also call <see cref="FirstPass(ImageFrame{TPixel}, Rectangle)"/>.
/// </remarks>
protected FrameQuantizer(Configuration configuration, IDither diffuser, bool singlePass)
{
@ -88,41 +88,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
/// <inheritdoc/>
public IQuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> image)
public IQuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> 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<TPixel> palette = this.GetPalette();
ReadOnlyMemory<TPixel> palette = this.GenerateQuantizedPalette();
MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator;
this.pixelMap = new EuclideanPixelMap<TPixel>(palette);
var quantizedFrame = new QuantizedFrame<TPixel>(memoryAllocator, width, height, palette);
var quantizedFrame = new QuantizedFrame<TPixel>(memoryAllocator, interest.Width, interest.Height, palette);
Span<byte> pixelSpan = quantizedFrame.GetWritablePixelSpan();
Memory<byte> 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<TPixel> 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.
/// </summary>
/// <param name="source">The source data.</param>
/// <param name="width">The width in pixels of the image.</param>
/// <param name="height">The height in pixels of the image.</param>
protected virtual void FirstPass(ImageFrame<TPixel> source, int width, int height)
/// <param name="bounds">The bounds within the source image to quantize.</param>
protected virtual void FirstPass(ImageFrame<TPixel> 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.
/// </summary>
/// <param name="color">The color to match.</param>
/// <param name="palette">The output color palette.</param>
/// <param name="match">The matched color.</param>
/// <returns>The <see cref="int"/></returns>
/// <returns>The <see cref="byte"/> index.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
protected virtual byte GetQuantizedColor(TPixel color, out TPixel match)
protected virtual byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
=> this.pixelMap.GetClosestColor(color, out match);
/// <summary>
/// Retrieve the palette for the quantized image.
/// Generates the palette for the quantized image.
/// </summary>
/// <returns>
/// <see cref="ReadOnlyMemory{TPixel}"/>
/// </returns>
protected abstract ReadOnlyMemory<TPixel> GetPalette();
protected abstract ReadOnlyMemory<TPixel> GenerateQuantizedPalette();
/// <summary>
/// Execute a second pass through the image to assign the pixels to a palette entry.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="bounds">The bounds within the source image to quantize.</param>
/// <param name="output">The output pixel array.</param>
/// <param name="palette">The output color palette.</param>
/// <param name="width">The width in pixels of the image.</param>
/// <param name="height">The height in pixels of the image.</param>
protected virtual void SecondPass(
ImageFrame<TPixel> source,
Span<byte> output,
ReadOnlySpan<TPixel> palette,
int width,
int height)
Rectangle bounds,
Memory<byte> output,
ReadOnlyMemory<TPixel> palette)
{
Rectangle interest = source.Bounds();
int bitDepth = ImageMaths.GetBitsNeededForColorDepth(palette.Length);
ReadOnlySpan<TPixel> 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<byte> 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<TPixel> 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<TPixel> source;
private readonly Memory<byte> output;
private readonly Rectangle bounds;
private readonly FrameQuantizer<TPixel> quantizer;
private readonly ReadOnlyMemory<TPixel> palette;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
ImageFrame<TPixel> source,
Memory<byte> output,
Rectangle bounds,
FrameQuantizer<TPixel> quantizer,
ReadOnlyMemory<TPixel> 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<TPixel> paletteSpan = this.palette.Span;
Span<byte> outputSpan = this.output.Span;
int width = this.bounds.Width;
int offsetX = this.bounds.Left;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
Span<TPixel> 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<TPixel> source;
private readonly Memory<byte> output;
private readonly Rectangle bounds;
private readonly FrameQuantizer<TPixel> quantizer;
private readonly ReadOnlyMemory<TPixel> palette;
private readonly int bitDepth;
[MethodImpl(InliningOptions.ShortMethod)]
public DitherRowIntervalOperation(
ImageFrame<TPixel> source,
Memory<byte> output,
Rectangle bounds,
FrameQuantizer<TPixel> quantizer,
ReadOnlyMemory<TPixel> 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<TPixel> row = source.GetPixelRowSpan(y);
int offset = y * width;
ReadOnlySpan<TPixel> paletteSpan = this.palette.Span;
Span<byte> 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<TPixel> 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 _);
}
}
}
}

7
src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs

@ -27,10 +27,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// Quantize an image frame and return the resulting output pixels.
/// </summary>
/// <param name="image">The image to quantize.</param>
/// <param name="source">The image to quantize.</param>
/// <param name="bounds">The bounds within the source image to quantize.</param>
/// <returns>
/// A <see cref="QuantizedFrame{TPixel}"/> representing a quantized version of the image pixels.
/// A <see cref="QuantizedFrame{TPixel}"/> representing a quantized version of the source image pixels.
/// </returns>
IQuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> image);
IQuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds);
}
}

27
src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs

@ -29,6 +29,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
private readonly Octree octree;
/// <summary>
/// The reduced image palette
/// </summary>
private TPixel[] palette;
/// <summary>
@ -63,18 +66,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
/// <inheritdoc/>
protected override void FirstPass(ImageFrame<TPixel> source, int width, int height)
protected override void FirstPass(ImageFrame<TPixel> 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<TPixel> 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
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
protected override byte GetQuantizedColor(TPixel color, out TPixel match)
protected override byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> 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<TPixel> AotGetPalette() => this.GetPalette();
internal ReadOnlyMemory<TPixel> AotGetPalette() => this.GenerateQuantizedPalette();
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
protected override ReadOnlyMemory<TPixel> GetPalette()
protected override ReadOnlyMemory<TPixel> GenerateQuantizedPalette()
=> this.palette ?? (this.palette = this.octree.Palletize(this.colors));
/// <summary>
@ -430,7 +434,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
/// <param name="palette">The palette</param>
/// <param name="index">The current palette index</param>
[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
/// <returns>
/// The <see cref="int"/> representing the index of the pixel in the palette.
/// </returns>
[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)

2
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs

@ -44,8 +44,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
/// <param name="dither">Whether to apply dithering to the output image.</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
public OctreeQuantizer(bool dither, int maxColors)
: this(GetDiffuser(dither), maxColors)
{

68
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;
/// <inheritdoc/>
protected override void SecondPass(
ImageFrame<TPixel> source,
Span<byte> output,
ReadOnlySpan<TPixel> 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<TPixel> 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<TPixel> 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<TPixel> 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 _);
}
}
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override ReadOnlyMemory<TPixel> GetPalette() => this.palette;
[MethodImpl(InliningOptions.ShortMethod)]
protected override ReadOnlyMemory<TPixel> GenerateQuantizedPalette() => this.palette;
}
}

11
src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs

@ -35,14 +35,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle);
Configuration configuration = this.Configuration;
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(configuration);
using IQuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(source);
using IQuantizedFrame<TPixel> 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<byte> quantizedPixelSpan = this.quantized.GetPixelSpan();
ReadOnlySpan<TPixel> paletteSpan = this.quantized.Palette.Span;
int offset = this.bounds.Left;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> 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])];
}
}

10
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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public class QuantizedFrame<TPixel> : IQuantizedFrame<TPixel>
public sealed class QuantizedFrame<TPixel> : IQuantizedFrame<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private IMemoryOwner<byte> pixels;
@ -67,8 +67,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
/// <summary>
/// Get the non-readonly span of pixel data so <see cref="FrameQuantizer{TPixel}"/> can fill it.
/// Get the non-readonly memory of pixel data so <see cref="FrameQuantizer{TPixel}"/> can fill it.
/// </summary>
internal Span<byte> GetWritablePixelSpan() => this.pixels.GetSpan();
internal Memory<byte> GetWritablePixelMemory() => this.pixels.Memory;
}
}
}

26
src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

@ -147,10 +147,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
base.Dispose(true);
}
internal ReadOnlyMemory<TPixel> AotGetPalette() => this.GetPalette();
internal ReadOnlyMemory<TPixel> AotGetPalette() => this.GenerateQuantizedPalette();
/// <inheritdoc/>
protected override ReadOnlyMemory<TPixel> GetPalette()
protected override ReadOnlyMemory<TPixel> GenerateQuantizedPalette()
{
if (this.palette is null)
{
@ -175,16 +175,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
/// <inheritdoc/>
protected override void FirstPass(ImageFrame<TPixel> source, int width, int height)
protected override void FirstPass(ImageFrame<TPixel> source, Rectangle bounds)
{
this.Build3DHistogram(source, width, height);
this.Build3DHistogram(source, bounds);
this.Get3DMoments(this.memoryAllocator);
this.BuildCube();
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
protected override byte GetQuantizedColor(TPixel color, out TPixel match)
protected override byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
{
if (!this.DoDither)
{
@ -199,11 +199,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ReadOnlySpan<byte> 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);
}
/// <summary>
@ -378,9 +378,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Builds a 3-D color histogram of <c>counts, r/g/b, c^2</c>.
/// </summary>
/// <param name="source">The source data.</param>
/// <param name="width">The width in pixels of the image.</param>
/// <param name="height">The height in pixels of the image.</param>
private void Build3DHistogram(ImageFrame<TPixel> source, int width, int height)
/// <param name="bounds">The bounds within the source image to quantize.</param>
private void Build3DHistogram(ImageFrame<TPixel> source, Rectangle bounds)
{
Span<Moment> momentSpan = this.moments.GetSpan();
@ -390,15 +389,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Span<Rgba32> 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<TPixel> row = source.GetPixelRowSpan(y);
PixelOperations<TPixel>.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;

2
tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
{
using (var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond))
{
image.Mutate(x => x.Diffuse());
image.Mutate(x => x.Dither());
return image.Size();
}

106
tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs

@ -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<BinaryOrderedDitherProcessor>();
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<BinaryOrderedDitherProcessor>(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<BinaryOrderedDitherProcessor>();
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<BinaryOrderedDitherProcessor>(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<BinaryErrorDiffusionProcessor>();
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<BinaryErrorDiffusionProcessor>(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<BinaryErrorDiffusionProcessor>();
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<BinaryErrorDiffusionProcessor>(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);
}
}
}

40
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<OrderedDitherPaletteProcessor>();
PaletteDitherProcessor p = this.Verify<PaletteDitherProcessor>();
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<OrderedDitherPaletteProcessor>(this.rect);
PaletteDitherProcessor p = this.Verify<PaletteDitherProcessor>(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<OrderedDitherPaletteProcessor>();
PaletteDitherProcessor p = this.Verify<PaletteDitherProcessor>();
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<OrderedDitherPaletteProcessor>(this.rect);
PaletteDitherProcessor p = this.Verify<PaletteDitherProcessor>(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<ErrorDiffusionPaletteProcessor>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.4F, p.Threshold);
this.operations.Dither(this.errorDiffuser);
PaletteDitherProcessor p = this.Verify<PaletteDitherProcessor>();
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<ErrorDiffusionPaletteProcessor>(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.3F, p.Threshold);
this.operations.Dither(this.errorDiffuser, this.rect);
PaletteDitherProcessor p = this.Verify<PaletteDitherProcessor>(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<ErrorDiffusionPaletteProcessor>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.5F, p.Threshold);
this.operations.Dither(this.errorDiffuser, this.testPalette);
PaletteDitherProcessor p = this.Verify<PaletteDitherProcessor>();
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<ErrorDiffusionPaletteProcessor>(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<PaletteDitherProcessor>(this.rect);
Assert.Equal(this.errorDiffuser, p.Dither);
Assert.Equal(this.testPalette, p.Palette);
}
}

26
tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs

@ -28,22 +28,22 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
public static readonly TheoryData<string, IDither> ErrorDiffusers = new TheoryData<string, IDither>
{
{ "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<TPixel> 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<TPixel> 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);

26
tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs

@ -20,15 +20,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
public static readonly TheoryData<IDither> ErrorDiffusers
= new TheoryData<IDither>
{
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<IDither> 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;
/// <summary>
/// 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);

26
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<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(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<Rgba32>(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<Rgba32>(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);
}
}
}

24
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<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(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<Rgba32>(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<Rgba32>(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);
}
}
}

26
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<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(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<Rgba32>(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<Rgba32>(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);
}
}
}

55
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<Rgba32>(this.Configuration).Dither);
Assert.True(webSafe.CreateFrameQuantizer<Rgba32>(this.Configuration).Dither);
Assert.True(octree.CreateFrameQuantizer<Rgba32>(this.Configuration).Dither);
Assert.True(wu.CreateFrameQuantizer<Rgba32>(this.Configuration).Dither);
Assert.NotNull(werner.Dither);
Assert.NotNull(webSafe.Dither);
Assert.NotNull(octree.Dither);
Assert.NotNull(wu.Dither);
using (IFrameQuantizer<Rgba32> quantizer = werner.CreateFrameQuantizer<Rgba32>(this.Configuration))
{
Assert.True(quantizer.DoDither);
}
using (IFrameQuantizer<Rgba32> quantizer = webSafe.CreateFrameQuantizer<Rgba32>(this.Configuration))
{
Assert.True(quantizer.DoDither);
}
using (IFrameQuantizer<Rgba32> quantizer = octree.CreateFrameQuantizer<Rgba32>(this.Configuration))
{
Assert.True(quantizer.DoDither);
}
using (IFrameQuantizer<Rgba32> quantizer = wu.CreateFrameQuantizer<Rgba32>(this.Configuration))
{
Assert.True(quantizer.DoDither);
}
}
[Theory]
@ -49,11 +64,12 @@ namespace SixLabors.ImageSharp.Tests
foreach (ImageFrame<TPixel> frame in image.Frames)
{
IQuantizedFrame<TPixel> quantized =
quantizer.CreateFrameQuantizer<TPixel>(this.Configuration).QuantizeFrame(frame);
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.GetPixelSpan()[0]);
using (IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(this.Configuration))
using (IQuantizedFrame<TPixel> 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<TPixel> frame in image.Frames)
{
IQuantizedFrame<TPixel> quantized =
quantizer.CreateFrameQuantizer<TPixel>(this.Configuration).QuantizeFrame(frame);
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.GetPixelSpan()[0]);
using (IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(this.Configuration))
using (IQuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.GetPixelSpan()[0]);
}
}
}
}

113
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<Rgba32>(config, 1, 1, Color.Black))
using (IQuantizedFrame<Rgba32> result = quantizer.CreateFrameQuantizer<Rgba32>(config).QuantizeFrame(image.Frames[0]))
{
Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.GetPixelSpan().Length);
using var image = new Image<Rgba32>(config, 1, 1, Color.Black);
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
Assert.Equal(Color.Black, (Color)result.Palette.Span[0]);
Assert.Equal(0, result.GetPixelSpan()[0]);
}
using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config);
using IQuantizedFrame<Rgba32> 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<Rgba32>(config, 1, 1, default(Rgba32)))
using (IQuantizedFrame<Rgba32> result = quantizer.CreateFrameQuantizer<Rgba32>(config).QuantizeFrame(image.Frames[0]))
{
Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.GetPixelSpan().Length);
using var image = new Image<Rgba32>(config, 1, 1, default(Rgba32));
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
Assert.Equal(default, result.Palette.Span[0]);
Assert.Equal(0, result.GetPixelSpan()[0]);
}
using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config);
using IQuantizedFrame<Rgba32> 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<Rgba32>(1, 256))
using var image = new Image<Rgba32>(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<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config))
using (IQuantizedFrame<Rgba32> 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<Rgba32>(1, 256);
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span;
int paletteCount = result.Palette.Length - 1;
for (int y = 0; y < actualImage.Height; y++)
{
Span<Rgba32> row = actualImage.GetPixelRowSpan(y);
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelSpan();
int yy = y * actualImage.Width;
using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config);
using IQuantizedFrame<Rgba32> 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<Rgba32>(1, 256);
Assert.True(image.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan()));
ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span;
int paletteCount = result.Palette.Length - 1;
for (int y = 0; y < actualImage.Height; y++)
{
Span<Rgba32> row = actualImage.GetPixelRowSpan(y);
ReadOnlySpan<byte> 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<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(config))
using (IQuantizedFrame<TPixel> result = frameQuantizer.QuantizeFrame(image.Frames[0]))
{
Assert.Equal(48, result.Palette.Length);
}
ImageFrame<TPixel> frame = image.Frames.RootFrame;
using IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(config);
using IQuantizedFrame<TPixel> 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<Rgba32> frame = image.Frames.RootFrame;
using (IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config))
using (IQuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(image.Frames[0]))
using (IQuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{
Assert.Equal(4 * 8, result.Palette.Length);
Assert.Equal(256, result.GetPixelSpan().Length);

3
tests/Images/Input/Png/CalliphoraPartial2.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2fb48e3c495d7834df09a17d6a6cadbce047a0e791b0cb78ca3a6d334d309b13
size 75628
Loading…
Cancel
Save