Browse Source

Add bounded quantization and update namings.

pull/1114/head
James Jackson-South 6 years ago
parent
commit
c98ea13710
  1. 2
      src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs
  2. 27
      src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs
  3. 4
      src/ImageSharp/Processing/KnownDitherings.cs
  4. BIN
      src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf
  5. 18
      src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs
  6. 161
      src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs
  7. 4
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs
  8. 4
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  9. 12
      src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs
  10. 19
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
  11. 4
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs
  12. 2
      tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs
  13. 4
      tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs
  14. 30
      tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs
  15. 30
      tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
  16. 19
      tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs
  17. 19
      tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs
  18. 73
      tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs
  19. 19
      tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs
  20. 4
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
  21. 5
      tests/ImageSharp.Tests/TestUtilities/TestUtils.cs

2
src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Dither(this IImageProcessingContext source) => public static IImageProcessingContext Dither(this IImageProcessingContext source) =>
Dither(source, KnownDitherers.BayerDither4x4); Dither(source, KnownDitherings.BayerDither4x4);
/// <summary> /// <summary>
/// Dithers the image reducing it to a web-safe palette using ordered dithering. /// Dithers the image reducing it to a web-safe palette using ordered dithering.

27
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. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -27,5 +27,28 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer) => public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer) =>
source.ApplyProcessor(new QuantizeProcessor(quantizer)); source.ApplyProcessor(new QuantizeProcessor(quantizer));
/// <summary>
/// Applies quantization to the image using the <see cref="OctreeQuantizer"/>.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Quantize(this IImageProcessingContext source, Rectangle rectangle) =>
Quantize(source, KnownQuantizers.Octree, rectangle);
/// <summary>
/// Applies quantization to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="quantizer">The quantizer to apply to perform the operation.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer, Rectangle rectangle) =>
source.ApplyProcessor(new QuantizeProcessor(quantizer), rectangle);
} }
} }

4
src/ImageSharp/Processing/KnownDitherers.cs → src/ImageSharp/Processing/KnownDitherings.cs

@ -6,9 +6,9 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing namespace SixLabors.ImageSharp.Processing
{ {
/// <summary> /// <summary>
/// Contains reusable static instances of known ordered dither matrices /// Contains reusable static instances of known dithering algorithms.
/// </summary> /// </summary>
public static class KnownDitherers public static class KnownDitherings
{ {
/// <summary> /// <summary>
/// Gets the order ditherer using the 2x2 Bayer dithering matrix /// Gets the order ditherer using the 2x2 Bayer dithering matrix

BIN
src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf

Binary file not shown.

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

@ -182,16 +182,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (this.Dither.DitherType == DitherType.ErrorDiffusion) if (this.Dither.DitherType == DitherType.ErrorDiffusion)
{ {
int width = bounds.Width; int width = bounds.Width;
int offsetY = bounds.Top;
int offsetX = bounds.Left; int offsetX = bounds.Left;
for (int y = bounds.Top; y < bounds.Bottom; y++) for (int y = bounds.Top; y < bounds.Bottom; y++)
{ {
Span<TPixel> row = source.GetPixelRowSpan(y); Span<TPixel> row = source.GetPixelRowSpan(y);
int offset = y * width; int rowStart = (y - offsetY) * width;
for (int x = bounds.Left; x < bounds.Right; x++) for (int x = bounds.Left; x < bounds.Right; x++)
{ {
TPixel sourcePixel = row[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); this.Dither.Dither(source, bounds, sourcePixel, transformed, x, y, bitDepth);
} }
} }
@ -255,16 +256,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ReadOnlySpan<TPixel> paletteSpan = this.palette.Span; ReadOnlySpan<TPixel> paletteSpan = this.palette.Span;
Span<byte> outputSpan = this.output.Span; Span<byte> outputSpan = this.output.Span;
int width = this.bounds.Width; int width = this.bounds.Width;
int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left; int offsetX = this.bounds.Left;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> row = this.source.GetPixelRowSpan(y); Span<TPixel> 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++) 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<TPixel> paletteSpan = this.palette.Span; ReadOnlySpan<TPixel> paletteSpan = this.palette.Span;
Span<byte> outputSpan = this.output.Span; Span<byte> outputSpan = this.output.Span;
int width = this.bounds.Width; int width = this.bounds.Width;
int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left;
IDither dither = this.quantizer.Dither; IDither dither = this.quantizer.Dither;
TPixel transformed = default; TPixel transformed = default;
int offsetX = this.bounds.Left;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> row = this.source.GetPixelRowSpan(y); Span<TPixel> 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++) 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); 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 _);
} }
} }
} }

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

@ -2,11 +2,12 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
@ -68,20 +69,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
protected override void FirstPass(ImageFrame<TPixel> source, Rectangle bounds) protected override void FirstPass(ImageFrame<TPixel> source, Rectangle bounds)
{ {
using IMemoryOwner<Rgba32> buffer = this.Configuration.MemoryAllocator.Allocate<Rgba32>(bounds.Width);
Span<Rgba32> bufferSpan = buffer.GetSpan();
// Loop through each row // Loop through each row
int offset = bounds.Left;
for (int y = bounds.Top; y < bounds.Bottom; y++) for (int y = bounds.Top; y < bounds.Bottom; y++)
{ {
Span<TPixel> row = source.GetPixelRowSpan(y); Span<TPixel> row = source.GetPixelRowSpan(y).Slice(bounds.Left, bounds.Width);
ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row); PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan);
// And loop through each column for (int x = 0; x < bufferSpan.Length; x++)
for (int x = bounds.Left; x < bounds.Right; x++)
{ {
ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x - offset); Rgba32 rgba = bufferSpan[x];
// Add the color to the Octree // 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) if (!this.DoDither)
{ {
var index = (byte)this.octree.GetPaletteIndex(ref color); var index = (byte)this.octree.GetPaletteIndex(color);
match = palette[index]; match = palette[index];
return index; return index;
} }
@ -113,10 +115,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private sealed class Octree private sealed class Octree
{ {
/// <summary> /// <summary>
/// Mask used when getting the appropriate pixels for a given node /// Mask used when getting the appropriate pixels for a given node.
/// </summary> /// </summary>
// ReSharper disable once StaticMemberInGenericType private static readonly byte[] Mask = new byte[]
private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; {
0b10000000,
0b1000000,
0b100000,
0b10000,
0b1000,
0b100,
0b10,
0b1
};
/// <summary> /// <summary>
/// The root of the Octree /// The root of the Octree
@ -136,7 +147,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary> /// <summary>
/// Cache the previous color quantized /// Cache the previous color quantized
/// </summary> /// </summary>
private TPixel previousColor; private Rgba32 previousColor;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Octree"/> class. /// Initializes a new instance of the <see cref="Octree"/> class.
@ -178,29 +189,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary> /// <summary>
/// Add a given color value to the Octree /// Add a given color value to the Octree
/// </summary> /// </summary>
/// <param name="pixel">The pixel data.</param> /// <param name="color">The color to add.</param>
public void AddColor(ref TPixel pixel) public void AddColor(Rgba32 color)
{ {
// Check if this request is for the same color as the last // 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. // happens to be black, with an alpha component of zero.
if (this.previousNode is null) if (this.previousNode is null)
{ {
this.previousColor = pixel; this.previousColor = color;
this.root.AddColor(ref pixel, this.maxColorBits, 0, this); this.root.AddColor(ref color, this.maxColorBits, 0, this);
} }
else else
{ {
// Just update the previous node // Just update the previous node
this.previousNode.Increment(ref pixel); this.previousNode.Increment(ref color);
} }
} }
else else
{ {
this.previousColor = pixel; this.previousColor = color;
this.root.AddColor(ref pixel, this.maxColorBits, 0, this); this.root.AddColor(ref color, this.maxColorBits, 0, this);
} }
} }
@ -232,12 +244,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary> /// <summary>
/// Get the palette index for the passed color /// Get the palette index for the passed color
/// </summary> /// </summary>
/// <param name="pixel">The pixel data.</param> /// <param name="color">The color to match.</param>
/// <returns> /// <returns>
/// The <see cref="int"/>. /// The <see cref="int"/> index.
/// </returns> /// </returns>
[MethodImpl(InliningOptions.ShortMethod)] [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);
}
/// <summary> /// <summary>
/// Keep track of the previous node that was quantized /// Keep track of the previous node that was quantized
@ -360,16 +377,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary> /// <summary>
/// Add a color into the tree /// Add a color into the tree
/// </summary> /// </summary>
/// <param name="pixel">The pixel color</param> /// <param name="color">The color to add.</param>
/// <param name="colorBits">The number of significant color bits</param> /// <param name="colorBits">The number of significant color bits.</param>
/// <param name="level">The level in the tree</param> /// <param name="level">The level in the tree.</param>
/// <param name="octree">The tree to which this node belongs</param> /// <param name="octree">The tree to which this node belongs.</param>
public void AddColor(ref TPixel pixel, int colorBits, int level, Octree octree) public void AddColor(ref Rgba32 color, int colorBits, int level, Octree octree)
{ {
// Update the color information if this is a leaf // Update the color information if this is a leaf
if (this.leaf) if (this.leaf)
{ {
this.Increment(ref pixel); this.Increment(ref color);
// Setup the previous node // Setup the previous node
octree.TrackPrevious(this); octree.TrackPrevious(this);
@ -377,13 +394,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
else else
{ {
// Go to the next level down in the tree // Go to the next level down in the tree
int shift = 7 - level; int index = GetColorIndex(ref color, 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);
OctreeNode child = this.children[index]; OctreeNode child = this.children[index];
if (child is null) if (child is null)
@ -394,7 +405,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
// Add the color to the child node // 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 <see cref="int"/> representing the index of the pixel in the palette. /// The <see cref="int"/> representing the index of the pixel in the palette.
/// </returns> /// </returns>
[MethodImpl(InliningOptions.ColdPath)] [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; return this.paletteIndex;
Rgba32 rgba = default; }
pixel.ToRgba32(ref rgba);
int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2)) int colorIndex = GetColorIndex(ref pixel, level);
| ((rgba.G & Mask[level]) >> (shift - 1)) OctreeNode child = this.children[colorIndex];
| ((rgba.R & Mask[level]) >> shift);
OctreeNode child = this.children[pixelIndex]; int index = 0;
if (child != null) if (child != null)
{ {
index = child.GetPaletteIndex(ref pixel, level + 1); index = child.GetPaletteIndex(ref pixel, level + 1);
} }
else else
{
// Check other children.
for (int i = 0; i < this.children.Length; i++)
{ {
// TODO: Throw helper. child = this.children[i];
throw new Exception($"Cannot retrieve a pixel at the given index {pixelIndex}."); 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
} }
/// <summary> /// <summary>
/// Increment the pixel count and add to the color information /// Gets the color index at the given level.
/// </summary>
/// <param name="color">The color.</param>
/// <param name="level">The node level.</param>
/// <returns>The <see cref="int"/> index.</returns>
[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);
}
/// <summary>
/// Increment the color count and add to the color information
/// </summary> /// </summary>
/// <param name="pixel">The pixel to add.</param> /// <param name="color">The pixel to add.</param>
[MethodImpl(InliningOptions.ShortMethod)] [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.pixelCount++;
this.red += rgba.R; this.red += color.R;
this.green += rgba.G; this.green += color.G;
this.blue += rgba.B; this.blue += color.B;
} }
} }
} }

4
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. /// Allows the quantization of images pixels using Octrees.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/> /// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// <para> /// <para>
/// By default the quantizer uses <see cref="KnownDitherers.FloydSteinberg"/> dithering and a color palette of a maximum length of <value>255</value> /// By default the quantizer uses <see cref="KnownDitherings.FloydSteinberg"/> dithering and a color palette of a maximum length of <value>255</value>
/// </para> /// </para>
/// </summary> /// </summary>
public class OctreeQuantizer : IQuantizer public class OctreeQuantizer : IQuantizer
@ -93,6 +93,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return new OctreeFrameQuantizer<TPixel>(configuration, this, maxColors); return new OctreeFrameQuantizer<TPixel>(configuration, this, maxColors);
} }
private static IDither GetDiffuser(bool dither) => dither ? KnownDitherers.FloydSteinberg : null; private static IDither GetDiffuser(bool dither) => dither ? KnownDitherings.FloydSteinberg : null;
} }
} }

4
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. /// Allows the quantization of images pixels using color palettes.
/// Override this class to provide your own palette. /// Override this class to provide your own palette.
/// <para> /// <para>
/// By default the quantizer uses <see cref="KnownDitherers.FloydSteinberg"/> dithering. /// By default the quantizer uses <see cref="KnownDitherings.FloydSteinberg"/> dithering.
/// </para> /// </para>
/// </summary> /// </summary>
public class PaletteQuantizer : IQuantizer public class PaletteQuantizer : IQuantizer
@ -76,6 +76,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return new PaletteFrameQuantizer<TPixel>(configuration, this.Dither, palette); return new PaletteFrameQuantizer<TPixel>(configuration, this.Dither, palette);
} }
private static IDither GetDiffuser(bool dither) => dither ? KnownDitherers.FloydSteinberg : null; private static IDither GetDiffuser(bool dither) => dither ? KnownDitherings.FloydSteinberg : null;
} }
} }

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

@ -53,7 +53,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly IQuantizedFrame<TPixel> quantized; private readonly IQuantizedFrame<TPixel> quantized;
private readonly int maxPaletteIndex;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation( public RowIntervalOperation(
@ -64,7 +63,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.bounds = bounds; this.bounds = bounds;
this.source = source; this.source = source;
this.quantized = quantized; this.quantized = quantized;
this.maxPaletteIndex = quantized.Palette.Length - 1;
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -72,17 +70,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
ReadOnlySpan<byte> quantizedPixelSpan = this.quantized.GetPixelSpan(); ReadOnlySpan<byte> quantizedPixelSpan = this.quantized.GetPixelSpan();
ReadOnlySpan<TPixel> paletteSpan = this.quantized.Palette.Span; ReadOnlySpan<TPixel> 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++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> row = this.source.GetPixelRowSpan(y); Span<TPixel> 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++) for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{ {
int i = yy + x - offset; int i = rowStart + x - offsetX;
row[x] = paletteSpan[Math.Min(this.maxPaletteIndex, quantizedPixelSpan[i])]; row[x] = paletteSpan[quantizedPixelSpan[i]];
} }
} }
} }

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

@ -384,29 +384,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Span<Moment> momentSpan = this.moments.GetSpan(); Span<Moment> momentSpan = this.moments.GetSpan();
// Build up the 3-D color histogram // Build up the 3-D color histogram
// Loop through each row using IMemoryOwner<Rgba32> buffer = this.memoryAllocator.Allocate<Rgba32>(bounds.Width);
using IMemoryOwner<Rgba32> rgbaBuffer = this.memoryAllocator.Allocate<Rgba32>(source.Width); Span<Rgba32> bufferSpan = buffer.GetSpan();
Span<Rgba32> rgbaSpan = rgbaBuffer.GetSpan();
ref Rgba32 scanBaseRef = ref MemoryMarshal.GetReference(rgbaSpan);
int offset = bounds.Left;
for (int y = bounds.Top; y < bounds.Bottom; y++) for (int y = bounds.Top; y < bounds.Bottom; y++)
{ {
Span<TPixel> row = source.GetPixelRowSpan(y); Span<TPixel> row = source.GetPixelRowSpan(y).Slice(bounds.Left, bounds.Width);
PixelOperations<TPixel>.Instance.ToRgba32(source.GetConfiguration(), row, rgbaSpan); PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan);
// And loop through each column for (int x = 0; x < bufferSpan.Length; x++)
for (int x = bounds.Left; x < bounds.Right; x++)
{ {
ref Rgba32 rgba = ref Unsafe.Add(ref scanBaseRef, x - offset); Rgba32 rgba = bufferSpan[x];
int r = (rgba.R >> (8 - IndexBits)) + 1; int r = (rgba.R >> (8 - IndexBits)) + 1;
int g = (rgba.G >> (8 - IndexBits)) + 1; int g = (rgba.G >> (8 - IndexBits)) + 1;
int b = (rgba.B >> (8 - IndexBits)) + 1; int b = (rgba.B >> (8 - IndexBits)) + 1;
int a = (rgba.A >> (8 - IndexAlphaBits)) + 1; int a = (rgba.A >> (8 - IndexAlphaBits)) + 1;
int index = GetPaletteIndex(r, g, b, a); momentSpan[GetPaletteIndex(r, g, b, a)] += rgba;
momentSpan[index] += rgba;
} }
} }
} }

4
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary> /// <summary>
/// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/> /// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/>
/// <para> /// <para>
/// By default the quantizer uses <see cref="KnownDitherers.FloydSteinberg"/> dithering and a color palette of a maximum length of <value>255</value> /// By default the quantizer uses <see cref="KnownDitherings.FloydSteinberg"/> dithering and a color palette of a maximum length of <value>255</value>
/// </para> /// </para>
/// </summary> /// </summary>
public class WuQuantizer : IQuantizer public class WuQuantizer : IQuantizer
@ -85,6 +85,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return new WuFrameQuantizer<TPixel>(configuration, this, maxColors); return new WuFrameQuantizer<TPixel>(configuration, this, maxColors);
} }
private static IDither GetDiffuser(bool dither) => dither ? KnownDitherers.FloydSteinberg : null; private static IDither GetDiffuser(bool dither) => dither ? KnownDitherings.FloydSteinberg : null;
} }
} }

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)) using (var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond))
{ {
image.Mutate(x => x.Dither(KnownDitherers.FloydSteinberg)); image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg));
return image.Size(); return image.Size();
} }

4
tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs

@ -29,8 +29,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
public DitherTest() public DitherTest()
{ {
this.orderedDither = KnownDitherers.BayerDither4x4; this.orderedDither = KnownDitherings.BayerDither4x4;
this.errorDiffuser = KnownDitherers.FloydSteinberg; this.errorDiffuser = KnownDitherings.FloydSteinberg;
} }
[Fact] [Fact]

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

@ -20,30 +20,30 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
public static readonly TheoryData<string, IDither> OrderedDitherers = new TheoryData<string, IDither> public static readonly TheoryData<string, IDither> OrderedDitherers = new TheoryData<string, IDither>
{ {
{ "Bayer8x8", KnownDitherers.BayerDither8x8 }, { "Bayer8x8", KnownDitherings.BayerDither8x8 },
{ "Bayer4x4", KnownDitherers.BayerDither4x4 }, { "Bayer4x4", KnownDitherings.BayerDither4x4 },
{ "Ordered3x3", KnownDitherers.OrderedDither3x3 }, { "Ordered3x3", KnownDitherings.OrderedDither3x3 },
{ "Bayer2x2", KnownDitherers.BayerDither2x2 } { "Bayer2x2", KnownDitherings.BayerDither2x2 }
}; };
public static readonly TheoryData<string, IDither> ErrorDiffusers = new TheoryData<string, IDither> public static readonly TheoryData<string, IDither> ErrorDiffusers = new TheoryData<string, IDither>
{ {
{ "Atkinson", KnownDitherers.Atkinson }, { "Atkinson", KnownDitherings.Atkinson },
{ "Burks", KnownDitherers.Burks }, { "Burks", KnownDitherings.Burks },
{ "FloydSteinberg", KnownDitherers.FloydSteinberg }, { "FloydSteinberg", KnownDitherings.FloydSteinberg },
{ "JarvisJudiceNinke", KnownDitherers.JarvisJudiceNinke }, { "JarvisJudiceNinke", KnownDitherings.JarvisJudiceNinke },
{ "Sierra2", KnownDitherers.Sierra2 }, { "Sierra2", KnownDitherings.Sierra2 },
{ "Sierra3", KnownDitherers.Sierra3 }, { "Sierra3", KnownDitherings.Sierra3 },
{ "SierraLite", KnownDitherers.SierraLite }, { "SierraLite", KnownDitherings.SierraLite },
{ "StevensonArce", KnownDitherers.StevensonArce }, { "StevensonArce", KnownDitherings.StevensonArce },
{ "Stucki", KnownDitherers.Stucki }, { "Stucki", KnownDitherings.Stucki },
}; };
public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; 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] [Theory]
[WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)]

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

@ -20,31 +20,31 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
public static readonly TheoryData<IDither> ErrorDiffusers public static readonly TheoryData<IDither> ErrorDiffusers
= new TheoryData<IDither> = new TheoryData<IDither>
{ {
KnownDitherers.Atkinson, KnownDitherings.Atkinson,
KnownDitherers.Burks, KnownDitherings.Burks,
KnownDitherers.FloydSteinberg, KnownDitherings.FloydSteinberg,
KnownDitherers.JarvisJudiceNinke, KnownDitherings.JarvisJudiceNinke,
KnownDitherers.Sierra2, KnownDitherings.Sierra2,
KnownDitherers.Sierra3, KnownDitherings.Sierra3,
KnownDitherers.SierraLite, KnownDitherings.SierraLite,
KnownDitherers.StevensonArce, KnownDitherings.StevensonArce,
KnownDitherers.Stucki, KnownDitherings.Stucki,
}; };
public static readonly TheoryData<IDither> OrderedDitherers public static readonly TheoryData<IDither> OrderedDitherers
= new TheoryData<IDither> = new TheoryData<IDither>
{ {
KnownDitherers.BayerDither8x8, KnownDitherings.BayerDither8x8,
KnownDitherers.BayerDither4x4, KnownDitherings.BayerDither4x4,
KnownDitherers.OrderedDither3x3, KnownDitherings.OrderedDither3x3,
KnownDitherers.BayerDither2x2 KnownDitherings.BayerDither2x2
}; };
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); 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;
/// <summary> /// <summary>
/// The output is visually correct old 32bit runtime, /// The output is visually correct old 32bit runtime,

19
tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs

@ -16,19 +16,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
var quantizer = new OctreeQuantizer(128); var quantizer = new OctreeQuantizer(128);
Assert.Equal(128, quantizer.MaxColors); Assert.Equal(128, quantizer.MaxColors);
Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither);
quantizer = new OctreeQuantizer(false); quantizer = new OctreeQuantizer(false);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors);
Assert.Null(quantizer.Dither); Assert.Null(quantizer.Dither);
quantizer = new OctreeQuantizer(KnownDitherers.Atkinson); quantizer = new OctreeQuantizer(KnownDitherings.Atkinson);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); 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(128, quantizer.MaxColors);
Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither);
} }
[Fact] [Fact]
@ -39,7 +39,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither); Assert.True(frameQuantizer.DoDither);
Assert.Equal(KnownDitherers.FloydSteinberg, frameQuantizer.Dither); Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither);
frameQuantizer.Dispose();
quantizer = new OctreeQuantizer(false); quantizer = new OctreeQuantizer(false);
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
@ -47,12 +48,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.False(frameQuantizer.DoDither); Assert.False(frameQuantizer.DoDither);
Assert.Null(frameQuantizer.Dither); Assert.Null(frameQuantizer.Dither);
frameQuantizer.Dispose();
quantizer = new OctreeQuantizer(KnownDitherers.Atkinson); quantizer = new OctreeQuantizer(KnownDitherings.Atkinson);
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither); Assert.True(frameQuantizer.DoDither);
Assert.Equal(KnownDitherers.Atkinson, frameQuantizer.Dither); Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither);
frameQuantizer.Dispose();
} }
} }
} }

19
tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs

@ -18,15 +18,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
var quantizer = new PaletteQuantizer(Rgb); var quantizer = new PaletteQuantizer(Rgb);
Assert.Equal(Rgb, quantizer.Palette); Assert.Equal(Rgb, quantizer.Palette);
Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither);
quantizer = new PaletteQuantizer(Rgb, false); quantizer = new PaletteQuantizer(Rgb, false);
Assert.Equal(Rgb, quantizer.Palette); Assert.Equal(Rgb, quantizer.Palette);
Assert.Null(quantizer.Dither); Assert.Null(quantizer.Dither);
quantizer = new PaletteQuantizer(Rgb, KnownDitherers.Atkinson); quantizer = new PaletteQuantizer(Rgb, KnownDitherings.Atkinson);
Assert.Equal(Rgb, quantizer.Palette); Assert.Equal(Rgb, quantizer.Palette);
Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither);
} }
[Fact] [Fact]
@ -37,7 +37,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither); Assert.True(frameQuantizer.DoDither);
Assert.Equal(KnownDitherers.FloydSteinberg, frameQuantizer.Dither); Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither);
frameQuantizer.Dispose();
quantizer = new PaletteQuantizer(Rgb, false); quantizer = new PaletteQuantizer(Rgb, false);
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
@ -45,26 +46,28 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.False(frameQuantizer.DoDither); Assert.False(frameQuantizer.DoDither);
Assert.Null(frameQuantizer.Dither); Assert.Null(frameQuantizer.Dither);
frameQuantizer.Dispose();
quantizer = new PaletteQuantizer(Rgb, KnownDitherers.Atkinson); quantizer = new PaletteQuantizer(Rgb, KnownDitherings.Atkinson);
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither); Assert.True(frameQuantizer.DoDither);
Assert.Equal(KnownDitherers.Atkinson, frameQuantizer.Dither); Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither);
frameQuantizer.Dispose();
} }
[Fact] [Fact]
public void KnownQuantizersWebSafeTests() public void KnownQuantizersWebSafeTests()
{ {
IQuantizer quantizer = KnownQuantizers.WebSafe; IQuantizer quantizer = KnownQuantizers.WebSafe;
Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither);
} }
[Fact] [Fact]
public void KnownQuantizersWernerTests() public void KnownQuantizersWernerTests()
{ {
IQuantizer quantizer = KnownQuantizers.Werner; IQuantizer quantizer = KnownQuantizers.Werner;
Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither);
} }
} }
} }

73
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<IQuantizer> Quantizers
= new TheoryData<IQuantizer>
{
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<TPixel>(TestImageProvider<TPixel> provider, IQuantizer quantizer)
where TPixel : struct, IPixel<TPixel>
{
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<TPixel>(TestImageProvider<TPixel> provider, IQuantizer quantizer)
where TPixel : struct, IPixel<TPixel>
{
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);
}
}
}

19
tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs

@ -16,19 +16,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
var quantizer = new WuQuantizer(128); var quantizer = new WuQuantizer(128);
Assert.Equal(128, quantizer.MaxColors); Assert.Equal(128, quantizer.MaxColors);
Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither); Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither);
quantizer = new WuQuantizer(false); quantizer = new WuQuantizer(false);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors);
Assert.Null(quantizer.Dither); Assert.Null(quantizer.Dither);
quantizer = new WuQuantizer(KnownDitherers.Atkinson); quantizer = new WuQuantizer(KnownDitherings.Atkinson);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); 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(128, quantizer.MaxColors);
Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither); Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither);
} }
[Fact] [Fact]
@ -39,7 +39,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither); Assert.True(frameQuantizer.DoDither);
Assert.Equal(KnownDitherers.FloydSteinberg, frameQuantizer.Dither); Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither);
frameQuantizer.Dispose();
quantizer = new WuQuantizer(false); quantizer = new WuQuantizer(false);
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
@ -47,12 +48,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.False(frameQuantizer.DoDither); Assert.False(frameQuantizer.DoDither);
Assert.Null(frameQuantizer.Dither); Assert.Null(frameQuantizer.Dither);
frameQuantizer.Dispose();
quantizer = new WuQuantizer(KnownDitherers.Atkinson); quantizer = new WuQuantizer(KnownDitherings.Atkinson);
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither); Assert.True(frameQuantizer.DoDither);
Assert.Equal(KnownDitherers.Atkinson, frameQuantizer.Dither); Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither);
frameQuantizer.Dispose();
} }
} }
} }

4
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -342,10 +342,10 @@ namespace SixLabors.ImageSharp.Tests
if (!File.Exists(referenceOutputFile)) 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<TPixel>(referenceOutputFile, decoder); return Image.Load<TPixel>(referenceOutputFile, decoder);
} }

5
tests/ImageSharp.Tests/TestUtilities/TestUtils.cs

@ -294,7 +294,8 @@ namespace SixLabors.ImageSharp.Tests
this TestImageProvider<TPixel> provider, this TestImageProvider<TPixel> provider,
Action<IImageProcessingContext, Rectangle> process, Action<IImageProcessingContext, Rectangle> process,
object testOutputDetails = null, object testOutputDetails = null,
ImageComparer comparer = null) ImageComparer comparer = null,
bool appendPixelTypeToFileName = true)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (comparer == null) 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); var bounds = new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2);
image.Mutate(x => process(x, bounds)); image.Mutate(x => process(x, bounds));
image.DebugSave(provider, testOutputDetails); image.DebugSave(provider, testOutputDetails);
image.CompareToReferenceOutput(comparer, provider, testOutputDetails); image.CompareToReferenceOutput(comparer, provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName);
} }
} }

Loading…
Cancel
Save