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>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Dither(this IImageProcessingContext source) =>
Dither(source, KnownDitherers.BayerDither4x4);
Dither(source, KnownDitherings.BayerDither4x4);
/// <summary>
/// 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.
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>
public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer 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
{
/// <summary>
/// Contains reusable static instances of known ordered dither matrices
/// Contains reusable static instances of known dithering algorithms.
/// </summary>
public static class KnownDitherers
public static class KnownDitherings
{
/// <summary>
/// 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)
{
int width = bounds.Width;
int offsetY = bounds.Top;
int offsetX = bounds.Left;
for (int y = bounds.Top; y < bounds.Bottom; 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++)
{
TPixel sourcePixel = row[x];
outputSpan[offset + x - offsetX] = this.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed);
outputSpan[rowStart + x - offsetX] = this.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed);
this.Dither.Dither(source, bounds, sourcePixel, transformed, x, y, bitDepth);
}
}
@ -255,16 +256,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ReadOnlySpan<TPixel> paletteSpan = this.palette.Span;
Span<byte> outputSpan = this.output.Span;
int width = this.bounds.Width;
int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<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++)
{
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;
Span<byte> outputSpan = this.output.Span;
int width = this.bounds.Width;
int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left;
IDither dither = this.quantizer.Dither;
TPixel transformed = default;
int offsetX = this.bounds.Left;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<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++)
{
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.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
@ -68,20 +69,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/>
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
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);
Span<TPixel> row = source.GetPixelRowSpan(y).Slice(bounds.Left, bounds.Width);
PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan);
// And loop through each column
for (int x = bounds.Left; x < bounds.Right; x++)
for (int x = 0; x < bufferSpan.Length; x++)
{
ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x - offset);
Rgba32 rgba = bufferSpan[x];
// Add the color to the Octree
this.octree.AddColor(ref pixel);
this.octree.AddColor(rgba);
}
}
}
@ -92,7 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
if (!this.DoDither)
{
var index = (byte)this.octree.GetPaletteIndex(ref color);
var index = (byte)this.octree.GetPaletteIndex(color);
match = palette[index];
return index;
}
@ -113,10 +115,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private sealed class Octree
{
/// <summary>
/// Mask used when getting the appropriate pixels for a given node
/// Mask used when getting the appropriate pixels for a given node.
/// </summary>
// ReSharper disable once StaticMemberInGenericType
private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
private static readonly byte[] Mask = new byte[]
{
0b10000000,
0b1000000,
0b100000,
0b10000,
0b1000,
0b100,
0b10,
0b1
};
/// <summary>
/// The root of the Octree
@ -136,7 +147,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// Cache the previous color quantized
/// </summary>
private TPixel previousColor;
private Rgba32 previousColor;
/// <summary>
/// Initializes a new instance of the <see cref="Octree"/> class.
@ -178,29 +189,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// Add a given color value to the Octree
/// </summary>
/// <param name="pixel">The pixel data.</param>
public void AddColor(ref TPixel pixel)
/// <param name="color">The color to add.</param>
public void AddColor(Rgba32 color)
{
// Check if this request is for the same color as the last
if (this.previousColor.Equals(pixel))
if (this.previousColor.Equals(color))
{
// If so, check if I have a previous node setup. This will only occur if the first color in the image
// If so, check if I have a previous node setup.
// This will only occur if the first color in the image
// happens to be black, with an alpha component of zero.
if (this.previousNode is null)
{
this.previousColor = pixel;
this.root.AddColor(ref pixel, this.maxColorBits, 0, this);
this.previousColor = color;
this.root.AddColor(ref color, this.maxColorBits, 0, this);
}
else
{
// Just update the previous node
this.previousNode.Increment(ref pixel);
this.previousNode.Increment(ref color);
}
}
else
{
this.previousColor = pixel;
this.root.AddColor(ref pixel, this.maxColorBits, 0, this);
this.previousColor = color;
this.root.AddColor(ref color, this.maxColorBits, 0, this);
}
}
@ -232,12 +244,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// Get the palette index for the passed color
/// </summary>
/// <param name="pixel">The pixel data.</param>
/// <param name="color">The color to match.</param>
/// <returns>
/// The <see cref="int"/>.
/// The <see cref="int"/> index.
/// </returns>
[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>
/// Keep track of the previous node that was quantized
@ -360,16 +377,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// Add a color into the tree
/// </summary>
/// <param name="pixel">The pixel color</param>
/// <param name="colorBits">The number of significant color bits</param>
/// <param name="level">The level in the tree</param>
/// <param name="octree">The tree to which this node belongs</param>
public void AddColor(ref TPixel pixel, int colorBits, int level, Octree octree)
/// <param name="color">The color to add.</param>
/// <param name="colorBits">The number of significant color bits.</param>
/// <param name="level">The level in the tree.</param>
/// <param name="octree">The tree to which this node belongs.</param>
public void AddColor(ref Rgba32 color, int colorBits, int level, Octree octree)
{
// Update the color information if this is a leaf
if (this.leaf)
{
this.Increment(ref pixel);
this.Increment(ref color);
// Setup the previous node
octree.TrackPrevious(this);
@ -377,13 +394,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
else
{
// Go to the next level down in the tree
int shift = 7 - level;
Rgba32 rgba = default;
pixel.ToRgba32(ref rgba);
int index = ((rgba.B & Mask[level]) >> (shift - 2))
| ((rgba.G & Mask[level]) >> (shift - 1))
| ((rgba.R & Mask[level]) >> shift);
int index = GetColorIndex(ref color, level);
OctreeNode child = this.children[index];
if (child is null)
@ -394,7 +405,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
// Add the color to the child node
child.AddColor(ref pixel, colorBits, level + 1, octree);
child.AddColor(ref color, colorBits, level + 1, octree);
}
}
@ -467,29 +478,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The <see cref="int"/> representing the index of the pixel in the palette.
/// </returns>
[MethodImpl(InliningOptions.ColdPath)]
public int GetPaletteIndex(ref TPixel pixel, int level)
public int GetPaletteIndex(ref Rgba32 pixel, int level)
{
int index = this.paletteIndex;
if (!this.leaf)
if (this.leaf)
{
int shift = 7 - level;
Rgba32 rgba = default;
pixel.ToRgba32(ref rgba);
return this.paletteIndex;
}
int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2))
| ((rgba.G & Mask[level]) >> (shift - 1))
| ((rgba.R & Mask[level]) >> shift);
int colorIndex = GetColorIndex(ref pixel, level);
OctreeNode child = this.children[colorIndex];
OctreeNode child = this.children[pixelIndex];
if (child != null)
{
index = child.GetPaletteIndex(ref pixel, level + 1);
}
else
int index = 0;
if (child != null)
{
index = child.GetPaletteIndex(ref pixel, level + 1);
}
else
{
// Check other children.
for (int i = 0; i < this.children.Length; i++)
{
// TODO: Throw helper.
throw new Exception($"Cannot retrieve a pixel at the given index {pixelIndex}.");
child = this.children[i];
if (child != null)
{
var childIndex = child.GetPaletteIndex(ref pixel, level + 1);
if (childIndex != 0)
{
return childIndex;
}
}
}
}
@ -497,18 +514,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
/// <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>
/// <param name="pixel">The pixel to add.</param>
/// <param name="color">The pixel to add.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void Increment(ref TPixel pixel)
public void Increment(ref Rgba32 color)
{
Rgba32 rgba = default;
pixel.ToRgba32(ref rgba);
this.pixelCount++;
this.red += rgba.R;
this.green += rgba.G;
this.blue += rgba.B;
this.red += color.R;
this.green += color.G;
this.blue += color.B;
}
}
}

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.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// <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>
/// </summary>
public class OctreeQuantizer : IQuantizer
@ -93,6 +93,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
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.
/// Override this class to provide your own palette.
/// <para>
/// By default the quantizer uses <see cref="KnownDitherers.FloydSteinberg"/> dithering.
/// By default the quantizer uses <see cref="KnownDitherings.FloydSteinberg"/> dithering.
/// </para>
/// </summary>
public class PaletteQuantizer : IQuantizer
@ -76,6 +76,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
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 ImageFrame<TPixel> source;
private readonly IQuantizedFrame<TPixel> quantized;
private readonly int maxPaletteIndex;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
@ -64,7 +63,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.bounds = bounds;
this.source = source;
this.quantized = quantized;
this.maxPaletteIndex = quantized.Palette.Length - 1;
}
[MethodImpl(InliningOptions.ShortMethod)]
@ -72,17 +70,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
ReadOnlySpan<byte> quantizedPixelSpan = this.quantized.GetPixelSpan();
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++)
{
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++)
{
int i = yy + x - offset;
row[x] = paletteSpan[Math.Min(this.maxPaletteIndex, quantizedPixelSpan[i])];
int i = rowStart + x - offsetX;
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();
// Build up the 3-D color histogram
// Loop through each row
using IMemoryOwner<Rgba32> rgbaBuffer = this.memoryAllocator.Allocate<Rgba32>(source.Width);
Span<Rgba32> rgbaSpan = rgbaBuffer.GetSpan();
ref Rgba32 scanBaseRef = ref MemoryMarshal.GetReference(rgbaSpan);
using IMemoryOwner<Rgba32> buffer = this.memoryAllocator.Allocate<Rgba32>(bounds.Width);
Span<Rgba32> bufferSpan = buffer.GetSpan();
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);
Span<TPixel> row = source.GetPixelRowSpan(y).Slice(bounds.Left, bounds.Width);
PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan);
// And loop through each column
for (int x = bounds.Left; x < bounds.Right; x++)
for (int x = 0; x < bufferSpan.Length; x++)
{
ref Rgba32 rgba = ref Unsafe.Add(ref scanBaseRef, x - offset);
Rgba32 rgba = bufferSpan[x];
int r = (rgba.R >> (8 - IndexBits)) + 1;
int g = (rgba.G >> (8 - IndexBits)) + 1;
int b = (rgba.B >> (8 - IndexBits)) + 1;
int a = (rgba.A >> (8 - IndexAlphaBits)) + 1;
int index = GetPaletteIndex(r, g, b, a);
momentSpan[index] += rgba;
momentSpan[GetPaletteIndex(r, g, b, a)] += rgba;
}
}
}

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

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/>
/// <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>
/// </summary>
public class WuQuantizer : IQuantizer
@ -85,6 +85,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
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))
{
image.Mutate(x => x.Dither(KnownDitherers.FloydSteinberg));
image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg));
return image.Size();
}

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

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

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

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
= new TheoryData<IDither>
{
KnownDitherers.Atkinson,
KnownDitherers.Burks,
KnownDitherers.FloydSteinberg,
KnownDitherers.JarvisJudiceNinke,
KnownDitherers.Sierra2,
KnownDitherers.Sierra3,
KnownDitherers.SierraLite,
KnownDitherers.StevensonArce,
KnownDitherers.Stucki,
KnownDitherings.Atkinson,
KnownDitherings.Burks,
KnownDitherings.FloydSteinberg,
KnownDitherings.JarvisJudiceNinke,
KnownDitherings.Sierra2,
KnownDitherings.Sierra3,
KnownDitherings.SierraLite,
KnownDitherings.StevensonArce,
KnownDitherings.Stucki,
};
public static readonly TheoryData<IDither> OrderedDitherers
= new TheoryData<IDither>
{
KnownDitherers.BayerDither8x8,
KnownDitherers.BayerDither4x4,
KnownDitherers.OrderedDither3x3,
KnownDitherers.BayerDither2x2
KnownDitherings.BayerDither8x8,
KnownDitherings.BayerDither4x4,
KnownDitherings.OrderedDither3x3,
KnownDitherings.BayerDither2x2
};
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f);
private static IDither DefaultDitherer => KnownDitherers.BayerDither4x4;
private static IDither DefaultDitherer => KnownDitherings.BayerDither4x4;
private static IDither DefaultErrorDiffuser => KnownDitherers.Atkinson;
private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson;
/// <summary>
/// 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);
Assert.Equal(128, quantizer.MaxColors);
Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither);
Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither);
quantizer = new OctreeQuantizer(false);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors);
Assert.Null(quantizer.Dither);
quantizer = new OctreeQuantizer(KnownDitherers.Atkinson);
quantizer = new OctreeQuantizer(KnownDitherings.Atkinson);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors);
Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither);
quantizer = new OctreeQuantizer(KnownDitherers.Atkinson, 128);
quantizer = new OctreeQuantizer(KnownDitherings.Atkinson, 128);
Assert.Equal(128, quantizer.MaxColors);
Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither);
}
[Fact]
@ -39,7 +39,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither);
Assert.Equal(KnownDitherers.FloydSteinberg, frameQuantizer.Dither);
Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither);
frameQuantizer.Dispose();
quantizer = new OctreeQuantizer(false);
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
@ -47,12 +48,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
Assert.NotNull(frameQuantizer);
Assert.False(frameQuantizer.DoDither);
Assert.Null(frameQuantizer.Dither);
frameQuantizer.Dispose();
quantizer = new OctreeQuantizer(KnownDitherers.Atkinson);
quantizer = new OctreeQuantizer(KnownDitherings.Atkinson);
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer);
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);
Assert.Equal(Rgb, quantizer.Palette);
Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither);
Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither);
quantizer = new PaletteQuantizer(Rgb, false);
Assert.Equal(Rgb, quantizer.Palette);
Assert.Null(quantizer.Dither);
quantizer = new PaletteQuantizer(Rgb, KnownDitherers.Atkinson);
quantizer = new PaletteQuantizer(Rgb, KnownDitherings.Atkinson);
Assert.Equal(Rgb, quantizer.Palette);
Assert.Equal(KnownDitherers.Atkinson, quantizer.Dither);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither);
}
[Fact]
@ -37,7 +37,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither);
Assert.Equal(KnownDitherers.FloydSteinberg, frameQuantizer.Dither);
Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither);
frameQuantizer.Dispose();
quantizer = new PaletteQuantizer(Rgb, false);
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
@ -45,26 +46,28 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
Assert.NotNull(frameQuantizer);
Assert.False(frameQuantizer.DoDither);
Assert.Null(frameQuantizer.Dither);
frameQuantizer.Dispose();
quantizer = new PaletteQuantizer(Rgb, KnownDitherers.Atkinson);
quantizer = new PaletteQuantizer(Rgb, KnownDitherings.Atkinson);
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither);
Assert.Equal(KnownDitherers.Atkinson, frameQuantizer.Dither);
Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither);
frameQuantizer.Dispose();
}
[Fact]
public void KnownQuantizersWebSafeTests()
{
IQuantizer quantizer = KnownQuantizers.WebSafe;
Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither);
Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither);
}
[Fact]
public void KnownQuantizersWernerTests()
{
IQuantizer quantizer = KnownQuantizers.Werner;
Assert.Equal(KnownDitherers.FloydSteinberg, quantizer.Dither);
Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither);
}
}
}

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

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

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

Loading…
Cancel
Save