Browse Source

Refactor to inline based on feedback.

af/octree-no-pixelmap
James Jackson-South 6 years ago
parent
commit
ad8d7757b4
  1. 12
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 2
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  3. 20
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  4. 14
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  5. 4
      src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs
  6. 8
      src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs
  7. 26
      src/ImageSharp/Processing/KnownDitherings.cs
  8. 34
      src/ImageSharp/Processing/Processors/Dithering/AtkinsonDither.cs
  9. 19
      src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs
  10. 19
      src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs
  11. 19
      src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs
  12. 33
      src/ImageSharp/Processing/Processors/Dithering/BurksDither.cs
  13. 21
      src/ImageSharp/Processing/Processors/Dithering/DitherType.cs
  14. 188
      src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs
  15. 84
      src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
  16. 33
      src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDither.cs
  17. 50
      src/ImageSharp/Processing/Processors/Dithering/IDither.cs
  18. 34
      src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDither.cs
  19. 31
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs
  20. 172
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  21. 19
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs
  22. 90
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  23. 48
      src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs
  24. 33
      src/ImageSharp/Processing/Processors/Dithering/Sierra2Dither.cs
  25. 34
      src/ImageSharp/Processing/Processors/Dithering/Sierra3Dither.cs
  26. 33
      src/ImageSharp/Processing/Processors/Dithering/SierraLiteDither.cs
  27. 34
      src/ImageSharp/Processing/Processors/Dithering/StevensonArceDither.cs
  28. 34
      src/ImageSharp/Processing/Processors/Dithering/StuckiDither.cs
  29. 51
      src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
  30. 136
      src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs
  31. 308
      src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs
  32. 38
      src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs
  33. 30
      src/ImageSharp/Processing/Processors/Quantization/IPixelMap{TPixel}.cs
  34. 38
      src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs
  35. 65
      src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs
  36. 44
      src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs
  37. 4
      src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs
  38. 6
      src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs
  39. 29
      src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs
  40. 21
      src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
  41. 121
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
  42. 2
      tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs
  43. 2
      tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs
  44. 18
      tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs
  45. 2
      tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs
  46. 10
      tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs
  47. 46
      tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
  48. 23
      tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs
  49. 6
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
  50. 10
      tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs
  51. 2
      tests/Images/External

12
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -8,6 +8,7 @@ using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -113,7 +114,8 @@ namespace SixLabors.ImageSharp.Advanced
{
using (var test = new OctreeFrameQuantizer<TPixel>(Configuration.Default, new OctreeQuantizer().Options))
{
test.AotGetPalette();
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
test.QuantizeFrame(frame, frame.Bounds());
}
}
@ -128,7 +130,6 @@ namespace SixLabors.ImageSharp.Advanced
{
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
test.QuantizeFrame(frame, frame.Bounds());
test.AotGetPalette();
}
}
@ -143,7 +144,6 @@ namespace SixLabors.ImageSharp.Advanced
{
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
test.QuantizeFrame(frame, frame.Bounds());
test.AotGetPalette();
}
}
@ -154,11 +154,13 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileDithering<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
var test = new FloydSteinbergDither();
ErrorDither errorDither = ErrorDither.FloydSteinberg;
OrderedDither orderedDither = OrderedDither.Bayer2x2;
TPixel pixel = default;
using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1))
{
test.Dither(image, default, pixel, pixel, 0, 0, 0, 0);
errorDither.Dither(image, image.Bounds(), pixel, pixel, 0, 0, 0);
orderedDither.Dither(pixel, 0, 0, 0, 0);
}
}

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

@ -337,7 +337,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : struct, IPixel<TPixel>
{
using IFrameQuantizer<TPixel> quantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
using IQuantizedFrame<TPixel> quantized = quantizer.QuantizeFrame(image, image.Bounds());
using QuantizedFrame<TPixel> quantized = quantizer.QuantizeFrame(image, image.Bounds());
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span;
var color = default(Rgba32);

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

@ -6,8 +6,6 @@ using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -81,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global;
// Quantize the image returning a palette.
IQuantizedFrame<TPixel> quantized;
QuantizedFrame<TPixel> quantized;
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
{
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
@ -127,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
stream.WriteByte(GifConstants.EndIntroducer);
}
private void EncodeGlobal<TPixel>(Image<TPixel> image, IQuantizedFrame<TPixel> quantized, int transparencyIndex, Stream stream)
private void EncodeGlobal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, int transparencyIndex, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
for (int i = 0; i < image.Frames.Count; i++)
@ -144,8 +142,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
using (IFrameQuantizer<TPixel> paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, quantized.Palette))
using (IQuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()))
using (var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, quantized.Palette))
using (QuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{
this.WriteImageData(paletteQuantized, stream);
}
@ -153,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
}
private void EncodeLocal<TPixel>(Image<TPixel> image, IQuantizedFrame<TPixel> quantized, Stream stream)
private void EncodeLocal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
ImageFrame<TPixel> previousFrame = null;
@ -210,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <returns>
/// The <see cref="int"/>.
/// </returns>
private int GetTransparentIndex<TPixel>(IQuantizedFrame<TPixel> quantized)
private int GetTransparentIndex<TPixel>(QuantizedFrame<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
{
// Transparent pixels are much more likely to be found at the end of a palette
@ -439,7 +437,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteColorTable<TPixel>(IQuantizedFrame<TPixel> image, Stream stream)
private void WriteColorTable<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
// The maximum number of colors for the bit depth
@ -461,9 +459,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Writes the image pixel data to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="IQuantizedFrame{TPixel}"/> containing indexed pixels.</param>
/// <param name="image">The <see cref="QuantizedFrame{TPixel}"/> containing indexed pixels.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteImageData<TPixel>(IQuantizedFrame<TPixel> image, Stream stream)
private void WriteImageData<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth))

14
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Png
ImageMetadata metadata = image.Metadata;
PngMetadata pngMetadata = metadata.GetPngMetadata();
PngEncoderOptionsHelpers.AdjustOptions<TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
IQuantizedFrame<TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
QuantizedFrame<TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized);
stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);
@ -371,7 +371,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="rowSpan">The row span.</param>
/// <param name="quantized">The quantized pixels. Can be null.</param>
/// <param name="row">The row.</param>
private void CollectPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan, IQuantizedFrame<TPixel> quantized, int row)
private void CollectPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan, QuantizedFrame<TPixel> quantized, int row)
where TPixel : struct, IPixel<TPixel>
{
switch (this.options.ColorType)
@ -440,7 +440,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="quantized">The quantized pixels. Can be null.</param>
/// <param name="row">The row.</param>
/// <returns>The <see cref="IManagedByteBuffer"/></returns>
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, IQuantizedFrame<TPixel> quantized, int row)
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, QuantizedFrame<TPixel> quantized, int row)
where TPixel : struct, IPixel<TPixel>
{
this.CollectPixelBytes(rowSpan, quantized, row);
@ -546,7 +546,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="quantized">The quantized frame.</param>
private void WritePaletteChunk<TPixel>(Stream stream, IQuantizedFrame<TPixel> quantized)
private void WritePaletteChunk<TPixel>(Stream stream, QuantizedFrame<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
{
if (quantized == null)
@ -783,7 +783,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="pixels">The image.</param>
/// <param name="quantized">The quantized pixel data. Can be null.</param>
/// <param name="stream">The stream.</param>
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, IQuantizedFrame<TPixel> quantized, Stream stream)
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, QuantizedFrame<TPixel> quantized, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
byte[] buffer;
@ -881,7 +881,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="pixels">The pixels.</param>
/// <param name="quantized">The quantized pixels span.</param>
/// <param name="deflateStream">The deflate stream.</param>
private void EncodePixels<TPixel>(ImageFrame<TPixel> pixels, IQuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
private void EncodePixels<TPixel>(ImageFrame<TPixel> pixels, QuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : struct, IPixel<TPixel>
{
int bytesPerScanline = this.CalculateScanlineLength(this.width);
@ -960,7 +960,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="quantized">The quantized.</param>
/// <param name="deflateStream">The deflate stream.</param>
private void EncodeAdam7IndexedPixels<TPixel>(IQuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
private void EncodeAdam7IndexedPixels<TPixel>(QuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : struct, IPixel<TPixel>
{
int width = quantized.Width;

4
src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs

@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="options">The options.</param>
/// <param name="image">The image.</param>
public static IQuantizedFrame<TPixel> CreateQuantizedFrame<TPixel>(
public static QuantizedFrame<TPixel> CreateQuantizedFrame<TPixel>(
PngEncoderOptions options,
Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public static byte CalculateBitDepth<TPixel>(
PngEncoderOptions options,
Image<TPixel> image,
IQuantizedFrame<TPixel> quantizedFrame)
QuantizedFrame<TPixel> quantizedFrame)
where TPixel : struct, IPixel<TPixel>
{
byte bitDepth;

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

@ -13,12 +13,12 @@ namespace SixLabors.ImageSharp.Processing
public static class DitherExtensions
{
/// <summary>
/// Dithers the image reducing it to a web-safe palette using <see cref="KnownDitherings.BayerDither4x4"/>.
/// Dithers the image reducing it to a web-safe palette using <see cref="KnownDitherings.Bayer8x8"/>.
/// </summary>
/// <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, KnownDitherings.BayerDither4x4);
Dither(source, KnownDitherings.Bayer8x8);
/// <summary>
/// Dithers the image reducing it to a web-safe palette using ordered dithering.
@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale, palette));
/// <summary>
/// Dithers the image reducing it to a web-safe palette using <see cref="KnownDitherings.BayerDither4x4"/>.
/// Dithers the image reducing it to a web-safe palette using <see cref="KnownDitherings.Bayer4x4"/>.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="rectangle">
@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Dither(this IImageProcessingContext source, Rectangle rectangle) =>
Dither(source, KnownDitherings.BayerDither4x4, rectangle);
Dither(source, KnownDitherings.Bayer4x4, rectangle);
/// <summary>
/// Dithers the image reducing it to a web-safe palette using ordered dithering.

26
src/ImageSharp/Processing/KnownDitherings.cs

@ -13,66 +13,66 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Gets the order ditherer using the 2x2 Bayer dithering matrix
/// </summary>
public static IDither BayerDither2x2 { get; } = new BayerDither2x2();
public static IDither Bayer2x2 { get; } = OrderedDither.Bayer2x2;
/// <summary>
/// Gets the order ditherer using the 3x3 dithering matrix
/// </summary>
public static IDither OrderedDither3x3 { get; } = new OrderedDither3x3();
public static IDither Ordered3x3 { get; } = OrderedDither.Ordered3x3;
/// <summary>
/// Gets the order ditherer using the 4x4 Bayer dithering matrix
/// </summary>
public static IDither BayerDither4x4 { get; } = new BayerDither4x4();
public static IDither Bayer4x4 { get; } = OrderedDither.Bayer4x4;
/// <summary>
/// Gets the order ditherer using the 8x8 Bayer dithering matrix
/// </summary>
public static IDither BayerDither8x8 { get; } = new BayerDither8x8();
public static IDither Bayer8x8 { get; } = OrderedDither.Bayer8x8;
/// <summary>
/// Gets the error Dither that implements the Atkinson algorithm.
/// </summary>
public static IDither Atkinson { get; } = new AtkinsonDither();
public static IDither Atkinson { get; } = ErrorDither.Atkinson;
/// <summary>
/// Gets the error Dither that implements the Burks algorithm.
/// </summary>
public static IDither Burks { get; } = new BurksDither();
public static IDither Burks { get; } = ErrorDither.Burkes;
/// <summary>
/// Gets the error Dither that implements the Floyd-Steinberg algorithm.
/// </summary>
public static IDither FloydSteinberg { get; } = new FloydSteinbergDither();
public static IDither FloydSteinberg { get; } = ErrorDither.FloydSteinberg;
/// <summary>
/// Gets the error Dither that implements the Jarvis-Judice-Ninke algorithm.
/// </summary>
public static IDither JarvisJudiceNinke { get; } = new JarvisJudiceNinkeDither();
public static IDither JarvisJudiceNinke { get; } = ErrorDither.JarvisJudiceNinke;
/// <summary>
/// Gets the error Dither that implements the Sierra-2 algorithm.
/// </summary>
public static IDither Sierra2 { get; } = new Sierra2Dither();
public static IDither Sierra2 { get; } = ErrorDither.Sierra2;
/// <summary>
/// Gets the error Dither that implements the Sierra-3 algorithm.
/// </summary>
public static IDither Sierra3 { get; } = new Sierra3Dither();
public static IDither Sierra3 { get; } = ErrorDither.Sierra3;
/// <summary>
/// Gets the error Dither that implements the Sierra-Lite algorithm.
/// </summary>
public static IDither SierraLite { get; } = new SierraLiteDither();
public static IDither SierraLite { get; } = ErrorDither.SierraLite;
/// <summary>
/// Gets the error Dither that implements the Stevenson-Arce algorithm.
/// </summary>
public static IDither StevensonArce { get; } = new StevensonArceDither();
public static IDither StevensonArce { get; } = ErrorDither.StevensonArce;
/// <summary>
/// Gets the error Dither that implements the Stucki algorithm.
/// </summary>
public static IDither Stucki { get; } = new StuckiDither();
public static IDither Stucki { get; } = ErrorDither.Stucki;
}
}

34
src/ImageSharp/Processing/Processors/Dithering/AtkinsonDither.cs

@ -1,34 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Atkinson image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public sealed class AtkinsonDither : ErrorDither
{
private const float Divisor = 8F;
private const int Offset = 1;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> AtkinsonMatrix =
new float[,]
{
{ 0, 0, 1 / Divisor, 1 / Divisor },
{ 1 / Divisor, 1 / Divisor, 1 / Divisor, 0 },
{ 0, 1 / Divisor, 0, 0 }
};
/// <summary>
/// Initializes a new instance of the <see cref="AtkinsonDither"/> class.
/// </summary>
public AtkinsonDither()
: base(AtkinsonMatrix, Offset)
{
}
}
}

19
src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs

@ -1,19 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies order dithering using the 2x2 Bayer dithering matrix.
/// </summary>
public sealed class BayerDither2x2 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither2x2"/> class.
/// </summary>
public BayerDither2x2()
: base(2)
{
}
}
}

19
src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs

@ -1,19 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies order dithering using the 4x4 Bayer dithering matrix.
/// </summary>
public sealed class BayerDither4x4 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither4x4"/> class.
/// </summary>
public BayerDither4x4()
: base(4)
{
}
}
}

19
src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs

@ -1,19 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies order dithering using the 8x8 Bayer dithering matrix.
/// </summary>
public sealed class BayerDither8x8 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither8x8"/> class.
/// </summary>
public BayerDither8x8()
: base(8)
{
}
}
}

33
src/ImageSharp/Processing/Processors/Dithering/BurksDither.cs

@ -1,33 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Burks image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public sealed class BurksDither : ErrorDither
{
private const float Divisor = 32F;
private const int Offset = 2;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> BurksMatrix =
new float[,]
{
{ 0, 0, 0, 8 / Divisor, 4 / Divisor },
{ 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor }
};
/// <summary>
/// Initializes a new instance of the <see cref="BurksDither"/> class.
/// </summary>
public BurksDither()
: base(BurksMatrix, Offset)
{
}
}
}

21
src/ImageSharp/Processing/Processors/Dithering/DitherType.cs

@ -1,21 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Enumerates the possible dithering algorithm transform behaviors.
/// </summary>
public enum DitherType
{
/// <summary>
/// Error diffusion. Spreads the difference between source and quanized color values as distributed error.
/// </summary>
ErrorDiffusion,
/// <summary>
/// Ordered dithering. Applies thresholding matrices agains the source to determine the quantized color.
/// </summary>
OrderedDither
}
}

188
src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs

@ -0,0 +1,188 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// An error diffusion dithering implementation.
/// </summary>
public readonly partial struct ErrorDither
{
/// <summary>
/// Applies error diffusion based dithering using the Atkinson image dithering algorithm.
/// </summary>
public static ErrorDither Atkinson = CreateAtkinson();
/// <summary>
/// Applies error diffusion based dithering using the Burks image dithering algorithm.
/// </summary>
public static ErrorDither Burkes = CreateBurks();
/// <summary>
/// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm.
/// </summary>
public static ErrorDither FloydSteinberg = CreateFloydSteinberg();
/// <summary>
/// Applies error diffusion based dithering using the Jarvis, Judice, Ninke image dithering algorithm.
/// </summary>
public static ErrorDither JarvisJudiceNinke = CreateJarvisJudiceNinke();
/// <summary>
/// Applies error diffusion based dithering using the Sierra2 image dithering algorithm.
/// </summary>
public static ErrorDither Sierra2 = CreateSierra2();
/// <summary>
/// Applies error diffusion based dithering using the Sierra3 image dithering algorithm.
/// </summary>
public static ErrorDither Sierra3 = CreateSierra3();
/// <summary>
/// Applies error diffusion based dithering using the Sierra Lite image dithering algorithm.
/// </summary>
public static ErrorDither SierraLite = CreateSierraLite();
/// <summary>
/// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm.
/// </summary>
public static ErrorDither StevensonArce = CreateStevensonArce();
/// <summary>
/// Applies error diffusion based dithering using the Stucki image dithering algorithm.
/// </summary>
public static ErrorDither Stucki = CreateStucki();
private static ErrorDither CreateAtkinson()
{
const float Divisor = 8F;
const int Offset = 1;
var matrix = new float[,]
{
{ 0, 0, 1 / Divisor, 1 / Divisor },
{ 1 / Divisor, 1 / Divisor, 1 / Divisor, 0 },
{ 0, 1 / Divisor, 0, 0 }
};
return new ErrorDither(matrix, Offset);
}
private static ErrorDither CreateBurks()
{
const float Divisor = 32F;
const int Offset = 2;
var matrix = new float[,]
{
{ 0, 0, 0, 8 / Divisor, 4 / Divisor },
{ 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor }
};
return new ErrorDither(matrix, Offset);
}
private static ErrorDither CreateFloydSteinberg()
{
const float Divisor = 16F;
const int Offset = 1;
var matrix = new float[,]
{
{ 0, 0, 7 / Divisor },
{ 3 / Divisor, 5 / Divisor, 1 / Divisor }
};
return new ErrorDither(matrix, Offset);
}
private static ErrorDither CreateJarvisJudiceNinke()
{
const float Divisor = 48F;
const int Offset = 2;
var matrix = new float[,]
{
{ 0, 0, 0, 7 / Divisor, 5 / Divisor },
{ 3 / Divisor, 5 / Divisor, 7 / Divisor, 5 / Divisor, 3 / Divisor },
{ 1 / Divisor, 3 / Divisor, 5 / Divisor, 3 / Divisor, 1 / Divisor }
};
return new ErrorDither(matrix, Offset);
}
private static ErrorDither CreateSierra2()
{
const float Divisor = 16F;
const int Offset = 2;
var matrix = new float[,]
{
{ 0, 0, 0, 4 / Divisor, 3 / Divisor },
{ 1 / Divisor, 2 / Divisor, 3 / Divisor, 2 / Divisor, 1 / Divisor }
};
return new ErrorDither(matrix, Offset);
}
private static ErrorDither CreateSierra3()
{
const float Divisor = 32F;
const int Offset = 2;
var matrix = new float[,]
{
{ 0, 0, 0, 5 / Divisor, 3 / Divisor },
{ 2 / Divisor, 4 / Divisor, 5 / Divisor, 4 / Divisor, 2 / Divisor },
{ 0, 2 / Divisor, 3 / Divisor, 2 / Divisor, 0 }
};
return new ErrorDither(matrix, Offset);
}
private static ErrorDither CreateSierraLite()
{
const float Divisor = 4F;
const int Offset = 1;
var matrix = new float[,]
{
{ 0, 0, 2 / Divisor },
{ 1 / Divisor, 1 / Divisor, 0 }
};
return new ErrorDither(matrix, Offset);
}
private static ErrorDither CreateStevensonArce()
{
const float Divisor = 200F;
const int Offset = 3;
var matrix = new float[,]
{
{ 0, 0, 0, 0, 0, 32 / Divisor, 0 },
{ 12 / Divisor, 0, 26 / Divisor, 0, 30 / Divisor, 0, 16 / Divisor },
{ 0, 12 / Divisor, 0, 26 / Divisor, 0, 12 / Divisor, 0 },
{ 5 / Divisor, 0, 12 / Divisor, 0, 12 / Divisor, 0, 5 / Divisor }
};
return new ErrorDither(matrix, Offset);
}
private static ErrorDither CreateStucki()
{
const float Divisor = 42F;
const int Offset = 2;
var matrix = new float[,]
{
{ 0, 0, 0, 8 / Divisor, 4 / Divisor },
{ 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor },
{ 1 / Divisor, 2 / Divisor, 4 / Divisor, 2 / Divisor, 1 / Divisor }
};
return new ErrorDither(matrix, Offset);
}
}
}

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

@ -3,42 +3,100 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// The base class of all error diffusion dithering implementations.
/// An error diffusion dithering implementation.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public abstract class ErrorDither : IDither
public readonly partial struct ErrorDither : IDither, IEquatable<ErrorDither>
{
private readonly int offset;
private readonly DenseMatrix<float> matrix;
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDither"/> class.
/// Initializes a new instance of the <see cref="ErrorDither"/> struct.
/// </summary>
/// <param name="matrix">The diffusion matrix.</param>
/// <param name="offset">The starting offset within the matrix.</param>
protected ErrorDither(in DenseMatrix<float> matrix, int offset)
[MethodImpl(InliningOptions.ShortMethod)]
public ErrorDither(in DenseMatrix<float> matrix, int offset)
{
this.matrix = matrix;
this.offset = offset;
}
/// <inheritdoc/>
public DitherType DitherType { get; } = DitherType.ErrorDiffusion;
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyQuantizationDither<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer,
ReadOnlyMemory<TPixel> palette,
ImageFrame<TPixel> source,
Memory<byte> output,
Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
Span<byte> outputSpan = output.Span;
ReadOnlySpan<TPixel> paletteSpan = palette.Span;
int width = bounds.Width;
int offsetY = bounds.Top;
int offsetX = bounds.Left;
float scale = quantizer.Options.DitherScale;
for (int y = bounds.Top; y < bounds.Bottom; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
int rowStart = (y - offsetY) * width;
for (int x = bounds.Left; x < bounds.Right; x++)
{
TPixel sourcePixel = row[x];
outputSpan[rowStart + x - offsetX] = quantizer.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed);
this.Dither(source, bounds, sourcePixel, transformed, x, y, scale);
}
}
}
/// <inheritdoc/>
public TPixel Dither<TPixel>(
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyPaletteDither<TPixel>(
Configuration configuration,
ReadOnlyMemory<TPixel> palette,
ImageFrame<TPixel> source,
Rectangle bounds,
float scale)
where TPixel : struct, IPixel<TPixel>
{
var pixelMap = new EuclideanPixelMap<TPixel>(palette);
for (int y = bounds.Top; y < bounds.Bottom; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
for (int x = bounds.Left; x < bounds.Right; x++)
{
TPixel sourcePixel = row[x];
pixelMap.GetClosestColor(sourcePixel, out TPixel transformed);
this.Dither(source, bounds, sourcePixel, transformed, x, y, scale);
row[x] = transformed;
}
}
}
// Internal for AOT
[MethodImpl(InliningOptions.ShortMethod)]
internal TPixel Dither<TPixel>(
ImageFrame<TPixel> image,
Rectangle bounds,
TPixel source,
TPixel transformed,
int x,
int y,
int bitDepth,
float scale)
where TPixel : struct, IPixel<TPixel>
{
@ -88,5 +146,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
return transformed;
}
/// <inheritdoc/>
public override bool Equals(object obj)
=> obj is ErrorDither dither && this.Equals(dither);
/// <inheritdoc/>
public bool Equals(ErrorDither other)
=> this.offset == other.offset && this.matrix.Equals(other.matrix);
/// <inheritdoc/>
public override int GetHashCode()
=> HashCode.Combine(this.offset, this.matrix);
}
}

33
src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDither.cs

@ -1,33 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public sealed class FloydSteinbergDither : ErrorDither
{
private const float Divisor = 16F;
private const int Offset = 1;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> FloydSteinbergMatrix =
new float[,]
{
{ 0, 0, 7 / Divisor },
{ 3 / Divisor, 5 / Divisor, 1 / Divisor }
};
/// <summary>
/// Initializes a new instance of the <see cref="FloydSteinbergDither"/> class.
/// </summary>
public FloydSteinbergDither()
: base(FloydSteinbergMatrix, Offset)
{
}
}
}

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

@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
@ -11,34 +13,40 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
public interface IDither
{
/// <summary>
/// Gets the <see cref="Dithering.DitherType"/> which determines whether the
/// transformed color should be calculated and supplied to the algorithm.
/// Transforms the quantized image frame applying a dither matrix.
/// This method should be treated as destructive, altering the input pixels.
/// </summary>
public DitherType DitherType { get; }
/// <typeparam name="TFrameQuantizer">The type of frame quantizer.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="quantizer">The frame quantizer.</param>
/// <param name="palette">The quantized palette.</param>
/// <param name="source">The source image.</param>
/// <param name="output">The output target</param>
/// <param name="bounds">The region of interest bounds.</param>
void ApplyQuantizationDither<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer,
ReadOnlyMemory<TPixel> palette,
ImageFrame<TPixel> source,
Memory<byte> output,
Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>;
/// <summary>
/// Transforms the image applying a dither matrix.
/// When <see cref="DitherType"/> is <see cref="DitherType.ErrorDiffusion"/> this
/// this method is destructive and will alter the input pixels.
/// Transforms the image frame applying a dither matrix.
/// This method should be treated as destructive, altering the input pixels.
/// </summary>
/// <param name="image">The image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="palette">The quantized palette.</param>
/// <param name="source">The source image.</param>
/// <param name="bounds">The region of interest bounds.</param>
/// <param name="source">The source pixel</param>
/// <param name="transformed">The transformed pixel</param>
/// <param name="x">The column index.</param>
/// <param name="y">The row index.</param>
/// <param name="bitDepth">The bit depth of the target palette.</param>
/// <param name="scale">The dithering scale used to adjust the amount of dither. Range 0..1.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The dithered result for the source pixel.</returns>
TPixel Dither<TPixel>(
ImageFrame<TPixel> image,
void ApplyPaletteDither<TPixel>(
Configuration configuration,
ReadOnlyMemory<TPixel> palette,
ImageFrame<TPixel> source,
Rectangle bounds,
TPixel source,
TPixel transformed,
int x,
int y,
int bitDepth,
float scale)
where TPixel : struct, IPixel<TPixel>;
}

34
src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDither.cs

@ -1,34 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the JarvisJudiceNinke image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public sealed class JarvisJudiceNinkeDither : ErrorDither
{
private const float Divisor = 48F;
private const int Offset = 2;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> JarvisJudiceNinkeMatrix =
new float[,]
{
{ 0, 0, 0, 7 / Divisor, 5 / Divisor },
{ 3 / Divisor, 5 / Divisor, 7 / Divisor, 5 / Divisor, 3 / Divisor },
{ 1 / Divisor, 3 / Divisor, 5 / Divisor, 3 / Divisor, 1 / Divisor }
};
/// <summary>
/// Initializes a new instance of the <see cref="JarvisJudiceNinkeDither"/> class.
/// </summary>
public JarvisJudiceNinkeDither()
: base(JarvisJudiceNinkeMatrix, Offset)
{
}
}
}

31
src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <content>
/// An ordered dithering matrix with equal sides of arbitrary length
/// </content>
public readonly partial struct OrderedDither : IDither
{
/// <summary>
/// Applies order dithering using the 2x2 Bayer dithering matrix.
/// </summary>
public static OrderedDither Bayer2x2 = new OrderedDither(2);
/// <summary>
/// Applies order dithering using the 4x4 Bayer dithering matrix.
/// </summary>
public static OrderedDither Bayer4x4 = new OrderedDither(4);
/// <summary>
/// Applies order dithering using the 8x8 Bayer dithering matrix.
/// </summary>
public static OrderedDither Bayer8x8 = new OrderedDither(8);
/// <summary>
/// Applies order dithering using the 3x3 ordered dithering matrix.
/// </summary>
public static OrderedDither Ordered3x3 = new OrderedDither(3);
}
}

172
src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs

@ -1,24 +1,29 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// An ordered dithering matrix with equal sides of arbitrary length
/// </summary>
public class OrderedDither : IDither
public readonly partial struct OrderedDither : IDither, IEquatable<OrderedDither>
{
private readonly DenseMatrix<float> thresholdMatrix;
private readonly int modulusX;
private readonly int modulusY;
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDither"/> class.
/// Initializes a new instance of the <see cref="OrderedDither"/> struct.
/// </summary>
/// <param name="length">The length of the matrix sides</param>
[MethodImpl(InliningOptions.ShortMethod)]
public OrderedDither(uint length)
{
DenseMatrix<uint> ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length);
@ -43,15 +48,58 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
}
/// <inheritdoc/>
public DitherType DitherType { get; } = DitherType.OrderedDither;
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyQuantizationDither<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer,
ReadOnlyMemory<TPixel> palette,
ImageFrame<TPixel> source,
Memory<byte> output,
Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
var ditherOperation = new QuantizeDitherRowIntervalOperation<TFrameQuantizer, TPixel>(
ref quantizer,
in Unsafe.AsRef(this),
source,
output,
bounds,
palette,
ImageMaths.GetBitsNeededForColorDepth(palette.Span.Length));
ParallelRowIterator.IterateRows(
quantizer.Configuration,
bounds,
in ditherOperation);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public TPixel Dither<TPixel>(
ImageFrame<TPixel> image,
public void ApplyPaletteDither<TPixel>(
Configuration configuration,
ReadOnlyMemory<TPixel> palette,
ImageFrame<TPixel> source,
Rectangle bounds,
float scale)
where TPixel : struct, IPixel<TPixel>
{
var ditherOperation = new PaletteDitherRowIntervalOperation<TPixel>(
in Unsafe.AsRef(this),
source,
bounds,
palette,
scale,
ImageMaths.GetBitsNeededForColorDepth(palette.Span.Length));
ParallelRowIterator.IterateRows(
configuration,
bounds,
in ditherOperation);
}
[MethodImpl(InliningOptions.ShortMethod)]
internal TPixel Dither<TPixel>(
TPixel source,
TPixel transformed,
int x,
int y,
int bitDepth,
@ -79,5 +127,117 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
return result;
}
/// <inheritdoc/>
public override bool Equals(object obj)
=> obj is OrderedDither dither && this.Equals(dither);
/// <inheritdoc/>
public bool Equals(OrderedDither other)
=> this.thresholdMatrix.Equals(other.thresholdMatrix) && this.modulusX == other.modulusX && this.modulusY == other.modulusY;
/// <inheritdoc/>
public override int GetHashCode()
=> HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY);
private readonly struct QuantizeDitherRowIntervalOperation<TFrameQuantizer, TPixel> : IRowIntervalOperation
where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly TFrameQuantizer quantizer;
private readonly OrderedDither dither;
private readonly ImageFrame<TPixel> source;
private readonly Memory<byte> output;
private readonly Rectangle bounds;
private readonly ReadOnlyMemory<TPixel> palette;
private readonly int bitDepth;
[MethodImpl(InliningOptions.ShortMethod)]
public QuantizeDitherRowIntervalOperation(
ref TFrameQuantizer quantizer,
in OrderedDither dither,
ImageFrame<TPixel> source,
Memory<byte> output,
Rectangle bounds,
ReadOnlyMemory<TPixel> palette,
int bitDepth)
{
this.quantizer = quantizer;
this.dither = dither;
this.source = source;
this.output = output;
this.bounds = bounds;
this.palette = palette;
this.bitDepth = bitDepth;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
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;
float scale = this.quantizer.Options.DitherScale;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> row = this.source.GetPixelRowSpan(y);
int rowStart = (y - offsetY) * width;
// TODO: This can be a bulk operation.
for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{
TPixel dithered = this.dither.Dither(row[x], x, y, this.bitDepth, scale);
outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _);
}
}
}
}
private readonly struct PaletteDitherRowIntervalOperation<TPixel> : IRowIntervalOperation
where TPixel : struct, IPixel<TPixel>
{
private readonly OrderedDither dither;
private readonly ImageFrame<TPixel> source;
private readonly Rectangle bounds;
private readonly EuclideanPixelMap<TPixel> pixelMap;
private readonly float scale;
private readonly int bitDepth;
[MethodImpl(InliningOptions.ShortMethod)]
public PaletteDitherRowIntervalOperation(
in OrderedDither dither,
ImageFrame<TPixel> source,
Rectangle bounds,
ReadOnlyMemory<TPixel> palette,
float scale,
int bitDepth)
{
this.dither = dither;
this.source = source;
this.bounds = bounds;
this.pixelMap = new EuclideanPixelMap<TPixel>(palette);
this.scale = scale;
this.bitDepth = bitDepth;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> row = this.source.GetPixelRowSpan(y);
for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{
TPixel dithered = this.dither.Dither(row[x], x, y, this.bitDepth, this.scale);
this.pixelMap.GetClosestColor(dithered, out TPixel transformed);
row[x] = transformed;
}
}
}
}
}
}

19
src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs

@ -1,19 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies order dithering using the 3x3 dithering matrix.
/// </summary>
public sealed class OrderedDither3x3 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDither3x3"/> class.
/// </summary>
public OrderedDither3x3()
: base(3)
{
}
}
}

90
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs

@ -3,9 +3,6 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
@ -18,12 +15,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TPixel : struct, IPixel<TPixel>
{
private readonly int paletteLength;
private readonly int bitDepth;
private readonly IDither dither;
private readonly float ditherScale;
private readonly ReadOnlyMemory<Color> sourcePalette;
private IMemoryOwner<TPixel> palette;
private EuclideanPixelMap<TPixel> pixelMap;
private bool isDisposed;
/// <summary>
@ -37,7 +32,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
: base(configuration, source, sourceRectangle)
{
this.paletteLength = definition.Palette.Span.Length;
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.paletteLength);
this.dither = definition.Dither;
this.ditherScale = definition.DitherScale;
this.sourcePalette = definition.Palette;
@ -48,51 +42,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
// Error diffusion. The difference between the source and transformed color
// is spread to neighboring pixels.
if (this.dither.DitherType == DitherType.ErrorDiffusion)
{
for (int y = interest.Top; y < interest.Bottom; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
for (int x = interest.Left; x < interest.Right; x++)
{
TPixel sourcePixel = row[x];
this.pixelMap.GetClosestColor(sourcePixel, out TPixel transformed);
this.dither.Dither(source, interest, sourcePixel, transformed, x, y, this.bitDepth, this.ditherScale);
row[x] = transformed;
}
}
return;
}
// Ordered dithering. We are only operating on a single pixel so we can work in parallel.
var ditherOperation = new DitherRowIntervalOperation(
source,
interest,
this.pixelMap,
this.dither,
this.ditherScale,
this.bitDepth);
ParallelRowIterator.IterateRows(
this.dither.ApplyPaletteDither(
this.Configuration,
this.palette.Memory,
source,
interest,
in ditherOperation);
this.ditherScale);
}
/// <inheritdoc/>
protected override void BeforeFrameApply(ImageFrame<TPixel> source)
{
// Lazy init palettes:
if (this.pixelMap is null)
if (this.palette is null)
{
this.palette = this.Configuration.MemoryAllocator.Allocate<TPixel>(this.paletteLength);
ReadOnlySpan<Color> sourcePalette = this.sourcePalette.Span;
Color.ToPixel(this.Configuration, sourcePalette, this.palette.Memory.Span);
this.pixelMap = new EuclideanPixelMap<TPixel>(this.palette.Memory);
}
base.BeforeFrameApply(source);
@ -116,51 +82,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
this.isDisposed = true;
base.Dispose(disposing);
}
private readonly struct DitherRowIntervalOperation : IRowIntervalOperation
{
private readonly ImageFrame<TPixel> source;
private readonly Rectangle bounds;
private readonly EuclideanPixelMap<TPixel> pixelMap;
private readonly IDither dither;
private readonly float scale;
private readonly int bitDepth;
[MethodImpl(InliningOptions.ShortMethod)]
public DitherRowIntervalOperation(
ImageFrame<TPixel> source,
Rectangle bounds,
EuclideanPixelMap<TPixel> pixelMap,
IDither dither,
float scale,
int bitDepth)
{
this.source = source;
this.bounds = bounds;
this.pixelMap = pixelMap;
this.dither = dither;
this.scale = scale;
this.bitDepth = bitDepth;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
IDither dither = this.dither;
TPixel transformed = default;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> row = this.source.GetPixelRowSpan(y);
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, this.scale);
this.pixelMap.GetClosestColor(dithered, out transformed);
row[x] = transformed;
}
}
}
}
}
}

48
src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs

@ -1,48 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Represents a composite pair of pixels. Used for caching color distance lookups.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal readonly struct PixelPair<TPixel> : IEquatable<PixelPair<TPixel>>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="PixelPair{TPixel}"/> struct.
/// </summary>
/// <param name="first">The first pixel color</param>
/// <param name="second">The second pixel color</param>
public PixelPair(TPixel first, TPixel second)
{
this.First = first;
this.Second = second;
}
/// <summary>
/// Gets the first pixel color
/// </summary>
public TPixel First { get; }
/// <summary>
/// Gets the second pixel color
/// </summary>
public TPixel Second { get; }
/// <inheritdoc/>
public bool Equals(PixelPair<TPixel> other)
=> this.First.Equals(other.First) && this.Second.Equals(other.Second);
/// <inheritdoc/>
public override bool Equals(object obj)
=> obj is PixelPair<TPixel> other && this.First.Equals(other.First) && this.Second.Equals(other.Second);
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(this.First, this.Second);
}
}

33
src/ImageSharp/Processing/Processors/Dithering/Sierra2Dither.cs

@ -1,33 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Sierra2 image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public sealed class Sierra2Dither : ErrorDither
{
private const float Divisor = 16F;
private const int Offset = 2;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> Sierra2Matrix =
new float[,]
{
{ 0, 0, 0, 4 / Divisor, 3 / Divisor },
{ 1 / Divisor, 2 / Divisor, 3 / Divisor, 2 / Divisor, 1 / Divisor }
};
/// <summary>
/// Initializes a new instance of the <see cref="Sierra2Dither"/> class.
/// </summary>
public Sierra2Dither()
: base(Sierra2Matrix, Offset)
{
}
}
}

34
src/ImageSharp/Processing/Processors/Dithering/Sierra3Dither.cs

@ -1,34 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Sierra3 image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public sealed class Sierra3Dither : ErrorDither
{
private const float Divisor = 32F;
private const int Offset = 2;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> Sierra3Matrix =
new float[,]
{
{ 0, 0, 0, 5 / Divisor, 3 / Divisor },
{ 2 / Divisor, 4 / Divisor, 5 / Divisor, 4 / Divisor, 2 / Divisor },
{ 0, 2 / Divisor, 3 / Divisor, 2 / Divisor, 0 }
};
/// <summary>
/// Initializes a new instance of the <see cref="Sierra3Dither"/> class.
/// </summary>
public Sierra3Dither()
: base(Sierra3Matrix, Offset)
{
}
}
}

33
src/ImageSharp/Processing/Processors/Dithering/SierraLiteDither.cs

@ -1,33 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the SierraLite image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public sealed class SierraLiteDither : ErrorDither
{
private const float Divisor = 4F;
private const int Offset = 1;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> SierraLiteMatrix =
new float[,]
{
{ 0, 0, 2 / Divisor },
{ 1 / Divisor, 1 / Divisor, 0 }
};
/// <summary>
/// Initializes a new instance of the <see cref="SierraLiteDither"/> class.
/// </summary>
public SierraLiteDither()
: base(SierraLiteMatrix, Offset)
{
}
}
}

34
src/ImageSharp/Processing/Processors/Dithering/StevensonArceDither.cs

@ -1,34 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm.
/// </summary>
public sealed class StevensonArceDither : ErrorDither
{
private const float Divisor = 200F;
private const int Offset = 3;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> StevensonArceMatrix =
new float[,]
{
{ 0, 0, 0, 0, 0, 32 / Divisor, 0 },
{ 12 / Divisor, 0, 26 / Divisor, 0, 30 / Divisor, 0, 16 / Divisor },
{ 0, 12 / Divisor, 0, 26 / Divisor, 0, 12 / Divisor, 0 },
{ 5 / Divisor, 0, 12 / Divisor, 0, 12 / Divisor, 0, 5 / Divisor }
};
/// <summary>
/// Initializes a new instance of the <see cref="StevensonArceDither"/> class.
/// </summary>
public StevensonArceDither()
: base(StevensonArceMatrix, Offset)
{
}
}
}

34
src/ImageSharp/Processing/Processors/Dithering/StuckiDither.cs

@ -1,34 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Stucki image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public sealed class StuckiDither : ErrorDither
{
private const float Divisor = 42F;
private const int Offset = 2;
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly DenseMatrix<float> StuckiMatrix =
new float[,]
{
{ 0, 0, 0, 8 / Divisor, 4 / Divisor },
{ 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor },
{ 1 / Divisor, 2 / Divisor, 4 / Divisor, 2 / Divisor, 1 / Divisor }
};
/// <summary>
/// Initializes a new instance of the <see cref="StuckiDither"/> class.
/// </summary>
public StuckiDither()
: base(StuckiMatrix, Offset)
{
}
}
}

51
src/ImageSharp/Processing/Processors/Dithering/EuclideanPixelMap{TPixel}.cs → src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs

@ -7,23 +7,31 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// Gets the closest color to the supplied color based upon the Eucladean distance.
/// TODO: Expose this somehow.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal sealed class EuclideanPixelMap<TPixel>
internal readonly struct EuclideanPixelMap<TPixel> : IPixelMap<TPixel>, IEquatable<EuclideanPixelMap<TPixel>>
where TPixel : struct, IPixel<TPixel>
{
private readonly ReadOnlyMemory<TPixel> palette;
private readonly ConcurrentDictionary<int, Vector4> vectorCache = new ConcurrentDictionary<int, Vector4>();
private readonly ConcurrentDictionary<TPixel, byte> distanceCache = new ConcurrentDictionary<TPixel, byte>();
private readonly ConcurrentDictionary<int, Vector4> vectorCache;
private readonly ConcurrentDictionary<TPixel, int> distanceCache;
/// <summary>
/// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> struct.
/// </summary>
/// <param name="palette">The color palette to map from.</param>
public EuclideanPixelMap(ReadOnlyMemory<TPixel> palette)
{
this.palette = palette;
ReadOnlySpan<TPixel> paletteSpan = this.palette.Span;
Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette));
this.Palette = palette;
ReadOnlySpan<TPixel> paletteSpan = this.Palette.Span;
this.vectorCache = new ConcurrentDictionary<int, Vector4>();
this.distanceCache = new ConcurrentDictionary<TPixel, int>();
for (int i = 0; i < paletteSpan.Length; i++)
{
@ -31,13 +39,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
}
}
/// <inheritdoc/>
public ReadOnlyMemory<TPixel> Palette { get; }
/// <inheritdoc/>
public override bool Equals(object obj)
=> obj is EuclideanPixelMap<TPixel> map && this.Equals(map);
/// <inheritdoc/>
public bool Equals(EuclideanPixelMap<TPixel> other)
=> this.Palette.Equals(other.Palette);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public byte GetClosestColor(TPixel color, out TPixel match)
public int GetClosestColor(TPixel color, out TPixel match)
{
ReadOnlySpan<TPixel> paletteSpan = this.palette.Span;
ReadOnlySpan<TPixel> paletteSpan = this.Palette.Span;
// Check if the color is in the lookup table
if (this.distanceCache.TryGetValue(color, out byte index))
if (this.distanceCache.TryGetValue(color, out int index))
{
match = paletteSpan[index];
return index;
@ -46,8 +66,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
return this.GetClosestColorSlow(color, paletteSpan, out match);
}
/// <inheritdoc/>
public override int GetHashCode()
=> this.vectorCache.GetHashCode();
[MethodImpl(InliningOptions.ShortMethod)]
private byte GetClosestColorSlow(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
private int GetClosestColorSlow(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
{
// Loop through the palette and find the nearest match.
int index = 0;
@ -74,10 +98,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
}
// Now I have the index, pop it into the cache for next time
var result = (byte)index;
this.distanceCache[color] = result;
this.distanceCache[color] = index;
match = palette[index];
return result;
return index;
}
}
}

136
src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs

@ -0,0 +1,136 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// Contains extension methods for frame quantizers.
/// </summary>
public static class FrameQuantizerExtensions
{
/// <summary>
/// Quantizes an image frame and return the resulting output pixels.
/// </summary>
/// <typeparam name="TFrameQuantizer">The type of frame quantizer.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="quantizer">The frame </param>
/// <param name="source">The source image frame to quantize.</param>
/// <param name="bounds">The bounds within the frame to quantize.</param>
/// <returns>
/// A <see cref="QuantizedFrame{TPixel}"/> representing a quantized version of the source frame pixels.
/// </returns>
public static QuantizedFrame<TPixel> QuantizeFrame<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source,
Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));
var interest = Rectangle.Intersect(source.Bounds(), bounds);
// Collect the palette. Required before the second pass runs.
ReadOnlyMemory<TPixel> palette = quantizer.BuildPalette(source, interest);
MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator;
var quantizedFrame = new QuantizedFrame<TPixel>(memoryAllocator, interest.Width, interest.Height, palette);
Memory<byte> output = quantizedFrame.GetWritablePixelMemory();
if (quantizer.Options.Dither is null)
{
SecondPass(ref quantizer, source, interest, output, palette);
}
else
{
// We clone the image as we don't want to alter the original via error diffusion based dithering.
using (ImageFrame<TPixel> clone = source.Clone())
{
SecondPass(ref quantizer, clone, interest, output, palette);
}
}
return quantizedFrame;
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void SecondPass<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source,
Rectangle bounds,
Memory<byte> output,
ReadOnlyMemory<TPixel> palette)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
IDither dither = quantizer.Options.Dither;
if (dither is null)
{
var operation = new RowIntervalOperation<TFrameQuantizer, TPixel>(quantizer, source, output, bounds, palette);
ParallelRowIterator.IterateRows(
quantizer.Configuration,
bounds,
in operation);
return;
}
dither.ApplyQuantizationDither(ref quantizer, palette, source, output, bounds);
}
private readonly struct RowIntervalOperation<TFrameQuantizer, TPixel> : IRowIntervalOperation
where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly TFrameQuantizer quantizer;
private readonly ImageFrame<TPixel> source;
private readonly Memory<byte> output;
private readonly Rectangle bounds;
private readonly ReadOnlyMemory<TPixel> palette;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
in TFrameQuantizer quantizer,
ImageFrame<TPixel> source,
Memory<byte> output,
Rectangle bounds,
ReadOnlyMemory<TPixel> palette)
{
this.quantizer = quantizer;
this.source = source;
this.output = output;
this.bounds = bounds;
this.palette = palette;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
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 rowStart = (y - offsetY) * width;
// TODO: This can be a bulk operation.
for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{
outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(row[x], paletteSpan, out TPixel _);
}
}
}
}
}
}

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

@ -1,308 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// The base class for all <see cref="IFrameQuantizer{TPixel}"/> implementations
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public abstract class FrameQuantizer<TPixel> : IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly bool singlePass;
private EuclideanPixelMap<TPixel> pixelMap;
private bool isDisposed;
/// <summary>
/// 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="options">The quantizer options defining quantization rules.</param>
/// <param name="singlePass">
/// If <see langword="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}, 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, QuantizerOptions options, bool singlePass)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
this.Configuration = configuration;
this.Options = options;
this.IsDitheringQuantizer = options.Dither != null;
this.singlePass = singlePass;
}
/// <inheritdoc/>
public QuantizerOptions Options { get; }
/// <summary>
/// Gets the configuration which allows altering default behaviour or extending the library.
/// </summary>
protected Configuration Configuration { get; }
/// <summary>
/// Gets a value indicating whether the frame quantizer utilizes a dithering method.
/// </summary>
protected bool IsDitheringQuantizer { get; }
/// <inheritdoc/>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <inheritdoc/>
public IQuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> image, Rectangle bounds)
{
Guard.NotNull(image, nameof(image));
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, interest);
}
// Collect the palette. Required before the second pass runs.
ReadOnlyMemory<TPixel> palette = this.GenerateQuantizedPalette();
MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator;
this.pixelMap = new EuclideanPixelMap<TPixel>(palette);
var quantizedFrame = new QuantizedFrame<TPixel>(memoryAllocator, interest.Width, interest.Height, palette);
Memory<byte> output = quantizedFrame.GetWritablePixelMemory();
if (this.Options.Dither is null)
{
this.SecondPass(image, interest, output, palette);
}
else
{
// 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, interest, output, palette);
}
}
return quantizedFrame;
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose managed and unmanaged objects.</param>
protected virtual void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
this.isDisposed = true;
}
/// <summary>
/// Execute the first pass through the pixels in the image to create the palette.
/// </summary>
/// <param name="source">The source data.</param>
/// <param name="bounds">The bounds within the source image to quantize.</param>
protected virtual void FirstPass(ImageFrame<TPixel> source, Rectangle bounds)
{
}
/// <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>
protected virtual void SecondPass(
ImageFrame<TPixel> source,
Rectangle bounds,
Memory<byte> output,
ReadOnlyMemory<TPixel> palette)
{
ReadOnlySpan<TPixel> paletteSpan = palette.Span;
IDither dither = this.Options.Dither;
if (dither is null)
{
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 (dither.DitherType == DitherType.ErrorDiffusion)
{
float ditherScale = this.Options.DitherScale;
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 rowStart = (y - offsetY) * width;
for (int x = bounds.Left; x < bounds.Right; x++)
{
TPixel sourcePixel = row[x];
outputSpan[rowStart + x - offsetX] = this.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed);
dither.Dither(source, bounds, sourcePixel, transformed, x, y, bitDepth, ditherScale);
}
}
return;
}
// 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);
}
/// <summary>
/// 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="byte"/> index.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
protected virtual byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
=> this.pixelMap.GetClosestColor(color, out match);
/// <summary>
/// Generates the palette for the quantized image.
/// </summary>
/// <returns>
/// <see cref="ReadOnlyMemory{TPixel}"/>
/// </returns>
protected abstract ReadOnlyMemory<TPixel> GenerateQuantizedPalette();
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)
{
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 rowStart = (y - offsetY) * width;
for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{
outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(row[x], paletteSpan, out TPixel _);
}
}
}
}
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;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
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.Options.Dither;
float scale = this.quantizer.Options.DitherScale;
TPixel transformed = default;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> row = this.source.GetPixelRowSpan(y);
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, scale);
outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _);
}
}
}
}
}
}

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

@ -3,7 +3,6 @@
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
@ -14,19 +13,46 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public interface IFrameQuantizer<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Gets the configuration.
/// </summary>
Configuration Configuration { get; }
/// <summary>
/// Gets the quantizer options defining quantization rules.
/// </summary>
QuantizerOptions Options { get; }
/// <summary>
/// Quantize an image frame and return the resulting output pixels.
/// Quantizes an image frame and return the resulting output pixels.
/// </summary>
/// <param name="source">The image to quantize.</param>
/// <param name="bounds">The bounds within the source image to quantize.</param>
/// <param name="source">The source image frame to quantize.</param>
/// <param name="bounds">The bounds within the frame to quantize.</param>
/// <returns>
/// A <see cref="QuantizedFrame{TPixel}"/> representing a quantized version of the source image pixels.
/// A <see cref="QuantizedFrame{TPixel}"/> representing a quantized version of the source frame pixels.
/// </returns>
IQuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds);
QuantizedFrame<TPixel> QuantizeFrame(
ImageFrame<TPixel> source,
Rectangle bounds);
/// <summary>
/// Builds the quantized palette from the given image frame and bounds.
/// </summary>
/// <param name="source">The source image frame.</param>
/// <param name="bounds">The region of interest bounds.</param>
/// <returns>The <see cref="ReadOnlyMemory{TPixel}"/> palette.</returns>
ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds);
/// <summary>
/// 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="byte"/> index.</returns>
public byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match);
// TODO: Enable bulk operations.
// void GetQuantizedColors(ReadOnlySpan<TPixel> colors, ReadOnlySpan<TPixel> palette, Span<byte> indices, Span<TPixel> matches);
}
}

30
src/ImageSharp/Processing/Processors/Quantization/IPixelMap{TPixel}.cs

@ -0,0 +1,30 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// Allows the mapping of input colors to colors within a given palette.
/// TODO: Expose this somehow.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal interface IPixelMap<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Gets the color palette containing colors to match.
/// </summary>
ReadOnlyMemory<TPixel> Palette { get; }
/// <summary>
/// Returns the closest color in the palette and the index of that pixel.
/// </summary>
/// <param name="color">The color to match.</param>
/// <param name="match">The matched color.</param>
/// <returns>The <see cref="int"/> index.</returns>
int GetClosestColor(TPixel color, out TPixel match);
}
}

38
src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs

@ -1,38 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// Defines an abstraction to represent a quantized image frame where the pixels indexed by a color palette.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IQuantizedFrame<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Gets the width of this <see cref="QuantizedFrame{TPixel}"/>.
/// </summary>
int Width { get; }
/// <summary>
/// Gets the height of this <see cref="QuantizedFrame{TPixel}"/>.
/// </summary>
int Height { get; }
/// <summary>
/// Gets the color palette of this <see cref="QuantizedFrame{TPixel}"/>.
/// </summary>
ReadOnlyMemory<TPixel> Palette { get; }
/// <summary>
/// Gets the pixels of this <see cref="QuantizedFrame{TPixel}"/>.
/// </summary>
/// <returns>The <see cref="Span{T}"/>The pixel span.</returns>
ReadOnlySpan<byte> GetPixelSpan();
}
}

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

@ -17,42 +17,48 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal sealed class OctreeFrameQuantizer<TPixel> : FrameQuantizer<TPixel>
public struct OctreeFrameQuantizer<TPixel> : IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Maximum allowed color depth
/// </summary>
private readonly int colors;
/// <summary>
/// Stores the tree
/// </summary>
private readonly Octree octree;
private EuclideanPixelMap<TPixel> pixelMap;
private readonly bool isDithering;
/// <summary>
/// The reduced image palette
/// </summary>
private TPixel[] palette;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeFrameQuantizer{TPixel}"/> class.
/// Initializes a new instance of the <see cref="OctreeFrameQuantizer{TPixel}"/> struct.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="options">The quantizer options defining quantization rules.</param>
/// <remarks>
/// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree,
/// the second pass quantizes a color based on the nodes in the tree
/// </remarks>
[MethodImpl(InliningOptions.ShortMethod)]
public OctreeFrameQuantizer(Configuration configuration, QuantizerOptions options)
: base(configuration, options, false)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
this.Configuration = configuration;
this.Options = options;
this.colors = this.Options.MaxColors;
this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8));
this.pixelMap = default;
this.isDithering = !(this.Options.Dither is null);
}
/// <inheritdoc/>
protected override void FirstPass(ImageFrame<TPixel> source, Rectangle bounds)
public Configuration Configuration { get; }
/// <inheritdoc/>
public QuantizerOptions Options { get; }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> FrameQuantizerExtensions.QuantizeFrame(ref this, source, bounds);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
{
using IMemoryOwner<Rgba32> buffer = this.Configuration.MemoryAllocator.Allocate<Rgba32>(bounds.Width);
Span<Rgba32> bufferSpan = buffer.GetSpan();
@ -71,31 +77,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.octree.AddColor(rgba);
}
}
TPixel[] palette = this.octree.Palletize(this.colors);
this.pixelMap = new EuclideanPixelMap<TPixel>(palette);
return palette;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
protected override byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
public byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
{
// Octree only maps the RGB component of a color
// so cannot tell the difference between a fully transparent
// pixel and a black one.
if (!this.IsDitheringQuantizer && !color.Equals(default))
if (!this.isDithering && !color.Equals(default))
{
var index = (byte)this.octree.GetPaletteIndex(color);
match = palette[index];
return index;
}
return base.GetQuantizedColor(color, palette, out match);
return (byte)this.pixelMap.GetClosestColor(color, out match);
}
internal ReadOnlyMemory<TPixel> AotGetPalette() => this.GenerateQuantizedPalette();
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
protected override ReadOnlyMemory<TPixel> GenerateQuantizedPalette()
=> this.palette ?? (this.palette = this.octree.Palletize(this.colors));
public void Dispose()
{
}
/// <summary>
/// Class which does the actual quantization.

44
src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs

@ -12,27 +12,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal sealed class PaletteFrameQuantizer<TPixel> : FrameQuantizer<TPixel>
internal struct PaletteFrameQuantizer<TPixel> : IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The reduced image palette.
/// </summary>
private readonly ReadOnlyMemory<TPixel> palette;
private readonly EuclideanPixelMap<TPixel> pixelMap;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteFrameQuantizer{TPixel}"/> class.
/// Initializes a new instance of the <see cref="PaletteFrameQuantizer{TPixel}"/> struct.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="options">The quantizer options defining quantization rules.</param>
/// <param name="colors">A <see cref="ReadOnlyMemory{TPixel}"/> containing all colors in the palette.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public PaletteFrameQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory<TPixel> colors)
: base(configuration, options, true) => this.palette = colors;
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
this.Configuration = configuration;
this.Options = options;
this.palette = colors;
this.pixelMap = new EuclideanPixelMap<TPixel>(colors);
}
/// <inheritdoc/>
public Configuration Configuration { get; }
/// <inheritdoc/>
public QuantizerOptions Options { get; }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> FrameQuantizerExtensions.QuantizeFrame(ref this, source, bounds);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
protected override ReadOnlyMemory<TPixel> GenerateQuantizedPalette() => this.palette;
public ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
=> this.palette;
internal ReadOnlyMemory<TPixel> AotGetPalette() => this.GenerateQuantizedPalette();
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
=> (byte)this.pixelMap.GetClosestColor(color, out match);
/// <inheritdoc/>
public void Dispose()
{
}
}
}

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

@ -15,9 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
/// <param name="quantizer">The quantizer used to reduce the color palette.</param>
public QuantizeProcessor(IQuantizer quantizer)
{
this.Quantizer = quantizer;
}
=> this.Quantizer = quantizer;
/// <summary>
/// Gets the quantizer.

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

@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Configuration configuration = this.Configuration;
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(configuration);
using IQuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(source, interest);
using QuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(source, interest);
var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized);
ParallelRowIterator.IterateRows(
@ -52,13 +52,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
private readonly Rectangle bounds;
private readonly ImageFrame<TPixel> source;
private readonly IQuantizedFrame<TPixel> quantized;
private readonly QuantizedFrame<TPixel> quantized;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Rectangle bounds,
ImageFrame<TPixel> source,
IQuantizedFrame<TPixel> quantized)
QuantizedFrame<TPixel> quantized)
{
this.bounds = bounds;
this.source = source;

29
src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs

@ -1,29 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// Contains extension methods for <see cref="IQuantizedFrame{TPixel}"/>.
/// </summary>
public static class QuantizedFrameExtensions
{
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
/// </summary>
/// <param name="frame">The <see cref="IQuantizedFrame{TPixel}"/>.</param>
/// <param name="rowIndex">The row.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <returns>The pixel row as a <see cref="ReadOnlySpan{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static ReadOnlySpan<byte> GetRowSpan<TPixel>(this IQuantizedFrame<TPixel> frame, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> frame.GetPixelSpan().Slice(rowIndex * frame.Width, frame.Width);
}
}

21
src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs

@ -13,10 +13,11 @@ 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 sealed class QuantizedFrame<TPixel> : IQuantizedFrame<TPixel>
public sealed class QuantizedFrame<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel>
{
private IMemoryOwner<byte> pixels;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="QuantizedFrame{TPixel}"/> class.
@ -58,16 +59,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<byte> GetPixelSpan() => this.pixels.GetSpan();
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
/// </summary>
/// <param name="rowIndex">The row.</param>
/// <returns>The pixel row as a <see cref="ReadOnlySpan{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<byte> GetRowSpan(int rowIndex)
=> this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width);
/// <inheritdoc/>
public void Dispose()
{
if (this.isDisposed)
{
return;
}
this.isDisposed = true;
this.pixels?.Dispose();
this.pixels = null;
this.Palette = null;
}
/// <summary>
/// Get the non-readonly memory of pixel data so <see cref="FrameQuantizer{TPixel}"/> can fill it.
/// Get the non-readonly memory of pixel data so <see cref="IFrameQuantizer{TPixel}"/> can fill it.
/// </summary>
internal Memory<byte> GetWritablePixelMemory() => this.pixels.Memory;
}

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

@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </para>
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal sealed class WuFrameQuantizer<TPixel> : FrameQuantizer<TPixel>
internal struct WuFrameQuantizer<TPixel> : IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly MemoryAllocator memoryAllocator;
@ -80,97 +80,82 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
private int colors;
/// <summary>
/// The reduced image palette
/// </summary>
private TPixel[] palette;
/// <summary>
/// The color cube representing the image palette
/// </summary>
private Box[] colorCube;
private readonly Box[] colorCube;
private EuclideanPixelMap<TPixel> pixelMap;
private readonly bool isDithering;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="WuFrameQuantizer{TPixel}"/> class.
/// Initializes a new instance of the <see cref="WuFrameQuantizer{TPixel}"/> struct.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="options">The quantizer options defining quantization rules.</param>
/// <remarks>
/// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram,
/// the second pass quantizes a color based on the position in the histogram.
/// </remarks>
[MethodImpl(InliningOptions.ShortMethod)]
public WuFrameQuantizer(Configuration configuration, QuantizerOptions options)
: base(configuration, options, false)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
this.Configuration = configuration;
this.Options = options;
this.memoryAllocator = this.Configuration.MemoryAllocator;
this.moments = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean);
this.tag = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean);
this.colors = this.Options.MaxColors;
this.colorCube = new Box[this.colors];
this.isDisposed = false;
this.pixelMap = default;
this.isDithering = this.isDithering = !(this.Options.Dither is null);
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.moments?.Dispose();
this.tag?.Dispose();
}
public Configuration Configuration { get; }
this.moments = null;
this.tag = null;
this.isDisposed = true;
base.Dispose(true);
}
/// <inheritdoc/>
public QuantizerOptions Options { get; }
internal ReadOnlyMemory<TPixel> AotGetPalette() => this.GenerateQuantizedPalette();
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> FrameQuantizerExtensions.QuantizeFrame(ref this, source, bounds);
/// <inheritdoc/>
protected override ReadOnlyMemory<TPixel> GenerateQuantizedPalette()
public ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
{
if (this.palette is null)
{
this.palette = new TPixel[this.colors];
ReadOnlySpan<Moment> momentsSpan = this.moments.GetSpan();
this.Build3DHistogram(source, bounds);
this.Get3DMoments(this.memoryAllocator);
this.BuildCube();
for (int k = 0; k < this.colors; k++)
{
this.Mark(ref this.colorCube[k], (byte)k);
var palette = new TPixel[this.colors];
ReadOnlySpan<Moment> momentsSpan = this.moments.GetSpan();
Moment moment = Volume(ref this.colorCube[k], momentsSpan);
for (int k = 0; k < this.colors; k++)
{
this.Mark(ref this.colorCube[k], (byte)k);
if (moment.Weight > 0)
{
ref TPixel color = ref this.palette[k];
color.FromScaledVector4(moment.Normalize());
}
Moment moment = Volume(ref this.colorCube[k], momentsSpan);
if (moment.Weight > 0)
{
ref TPixel color = ref palette[k];
color.FromScaledVector4(moment.Normalize());
}
}
return this.palette;
}
/// <inheritdoc/>
protected override void FirstPass(ImageFrame<TPixel> source, Rectangle bounds)
{
this.Build3DHistogram(source, bounds);
this.Get3DMoments(this.memoryAllocator);
this.BuildCube();
this.pixelMap = new EuclideanPixelMap<TPixel>(palette);
return palette;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
protected override byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
public byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
{
if (!this.IsDitheringQuantizer)
if (!this.isDithering)
{
Rgba32 rgba = default;
color.ToRgba32(ref rgba);
@ -181,12 +166,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
int a = rgba.A >> (8 - IndexAlphaBits);
ReadOnlySpan<byte> tagSpan = this.tag.GetSpan();
var index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)];
byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)];
match = palette[index];
return index;
}
return base.GetQuantizedColor(color, palette, out match);
return (byte)this.pixelMap.GetClosestColor(color, out match);
}
/// <inheritdoc/>
public void Dispose()
{
if (this.isDisposed)
{
return;
}
this.isDisposed = true;
this.moments?.Dispose();
this.tag?.Dispose();
this.moments = null;
this.tag = null;
}
/// <summary>
@ -634,7 +634,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
private void BuildCube()
{
this.colorCube = new Box[this.colors];
Span<double> vv = stackalloc double[this.colors];
ref Box cube = ref this.colorCube[0];

2
tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs

@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
// Try to get as close to System.Drawing's output as possible
var options = new GifEncoder
{
Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.BayerDither4x4 })
Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 })
};
using (var memoryStream = new MemoryStream())

2
tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
// Try to get as close to System.Drawing's output as possible
var options = new GifEncoder
{
Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.BayerDither4x4 })
Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 })
};
img.Save(ms, options);

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

@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
// | DoDiffuse | Clr | Clr | 124.93 ms | 33.297 ms | 1.8251 ms | - | - | - | 2 KB |
// | DoDiffuse | Core | Core | 89.63 ms | 9.895 ms | 0.5424 ms | - | - | - | 1.91 KB |
// #### 15th February 2020 ####
// #### 20th February 2020 ####
//
// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
@ -73,11 +73,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
//
// IterationCount=3 LaunchCount=1 WarmupCount=3
//
// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------- |-------------- |---------:|----------:|---------:|------:|------:|------:|----------:|
// | DoDiffuse | .NET 4.7.2 | 40.32 ms | 16.788 ms | 0.920 ms | - | - | - | 26.46 KB |
// | DoDither | .NET 4.7.2 | 12.86 ms | 3.066 ms | 0.168 ms | - | - | - | 30.75 KB |
// | DoDiffuse | .NET Core 2.1 | 27.09 ms | 3.180 ms | 0.174 ms | - | - | - | 26.04 KB |
// | DoDither | .NET Core 2.1 | 12.89 ms | 34.535 ms | 1.893 ms | - | - | - | 29.26 KB |
// | DoDiffuse | .NET Core 3.1 | 27.39 ms | 2.699 ms | 0.148 ms | - | - | - | 26.02 KB |
// | DoDither | .NET Core 3.1 | 12.50 ms | 5.083 ms | 0.279 ms | - | - | - | 30.96 KB |
// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------- |-------------- |----------:|----------:|----------:|------:|------:|------:|----------:|
// | DoDiffuse | .NET 4.7.2 | 30.535 ms | 19.217 ms | 1.0534 ms | - | - | - | 26.25 KB |
// | DoDither | .NET 4.7.2 | 14.174 ms | 1.625 ms | 0.0891 ms | - | - | - | 31.38 KB |
// | DoDiffuse | .NET Core 2.1 | 15.984 ms | 3.686 ms | 0.2020 ms | - | - | - | 25.98 KB |
// | DoDither | .NET Core 2.1 | 8.646 ms | 1.635 ms | 0.0896 ms | - | - | - | 28.99 KB |
// | DoDiffuse | .NET Core 3.1 | 16.235 ms | 9.612 ms | 0.5269 ms | - | - | - | 25.96 KB |
// | DoDither | .NET Core 3.1 | 8.429 ms | 1.270 ms | 0.0696 ms | - | - | - | 31.61 KB |

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

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
public DitherTest()
{
this.orderedDither = KnownDitherings.BayerDither4x4;
this.orderedDither = KnownDitherings.Bayer4x4;
this.errorDiffuser = KnownDitherings.FloydSteinberg;
}

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

@ -20,10 +20,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
public static readonly TheoryData<string, IDither> OrderedDitherers = new TheoryData<string, IDither>
{
{ "Bayer8x8", KnownDitherings.BayerDither8x8 },
{ "Bayer4x4", KnownDitherings.BayerDither4x4 },
{ "Ordered3x3", KnownDitherings.OrderedDither3x3 },
{ "Bayer2x2", KnownDitherings.BayerDither2x2 }
{ "Bayer8x8", KnownDitherings.Bayer8x8 },
{ "Bayer4x4", KnownDitherings.Bayer4x4 },
{ "Ordered3x3", KnownDitherings.Ordered3x3 },
{ "Bayer2x2", KnownDitherings.Bayer2x2 }
};
public static readonly TheoryData<string, IDither> ErrorDiffusers = new TheoryData<string, IDither>
@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24;
private static IDither DefaultDitherer => KnownDitherings.BayerDither4x4;
private static IDither DefaultDitherer => KnownDitherings.Bayer4x4;
private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson;

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

@ -17,32 +17,32 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial, TestImages.Png.Bike };
public static readonly TheoryData<IDither> ErrorDiffusers
= new TheoryData<IDither>
public static readonly TheoryData<IDither, string> ErrorDiffusers
= new TheoryData<IDither, string>
{
KnownDitherings.Atkinson,
KnownDitherings.Burks,
KnownDitherings.FloydSteinberg,
KnownDitherings.JarvisJudiceNinke,
KnownDitherings.Sierra2,
KnownDitherings.Sierra3,
KnownDitherings.SierraLite,
KnownDitherings.StevensonArce,
KnownDitherings.Stucki,
{ KnownDitherings.Atkinson, nameof(KnownDitherings.Atkinson) },
{ KnownDitherings.Burks, nameof(KnownDitherings.Burks) },
{ KnownDitherings.FloydSteinberg, nameof(KnownDitherings.FloydSteinberg) },
{ KnownDitherings.JarvisJudiceNinke, nameof(KnownDitherings.JarvisJudiceNinke) },
{ KnownDitherings.Sierra2, nameof(KnownDitherings.Sierra2) },
{ KnownDitherings.Sierra3, nameof(KnownDitherings.Sierra3) },
{ KnownDitherings.SierraLite, nameof(KnownDitherings.SierraLite) },
{ KnownDitherings.StevensonArce, nameof(KnownDitherings.StevensonArce) },
{ KnownDitherings.Stucki, nameof(KnownDitherings.Stucki) },
};
public static readonly TheoryData<IDither> OrderedDitherers
= new TheoryData<IDither>
public static readonly TheoryData<IDither, string> OrderedDitherers
= new TheoryData<IDither,string>
{
KnownDitherings.BayerDither8x8,
KnownDitherings.BayerDither4x4,
KnownDitherings.OrderedDither3x3,
KnownDitherings.BayerDither2x2
{ KnownDitherings.Bayer2x2, nameof(KnownDitherings.Bayer2x2) },
{ KnownDitherings.Bayer4x4, nameof(KnownDitherings.Bayer4x4) },
{ KnownDitherings.Bayer8x8, nameof(KnownDitherings.Bayer8x8) },
{ KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) }
};
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f);
private static IDither DefaultDitherer => KnownDitherings.BayerDither4x4;
private static IDither DefaultDitherer => KnownDitherings.Bayer4x4;
private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson;
@ -102,7 +102,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
[WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)]
public void DiffusionFilter_WorksWithAllErrorDiffusers<TPixel>(
TestImageProvider<TPixel> provider,
IDither diffuser)
IDither diffuser,
string name)
where TPixel : struct, IPixel<TPixel>
{
if (SkipAllDitherTests)
@ -112,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
provider.RunValidatingProcessorTest(
x => x.Dither(diffuser),
testOutputDetails: diffuser.GetType().Name,
testOutputDetails: name,
comparer: ValidatorComparer,
appendPixelTypeToFileName: false);
}
@ -136,7 +137,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
[WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)]
public void DitherFilter_WorksWithAllDitherers<TPixel>(
TestImageProvider<TPixel> provider,
IDither ditherer)
IDither ditherer,
string name)
where TPixel : struct, IPixel<TPixel>
{
if (SkipAllDitherTests)
@ -146,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
provider.RunValidatingProcessorTest(
x => x.Dither(ditherer),
testOutputDetails: ditherer.GetType().Name,
testOutputDetails: name,
comparer: ValidatorComparer,
appendPixelTypeToFileName: false);
}

23
tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
private static readonly QuantizerOptions NoDitherOptions = new QuantizerOptions { Dither = null };
private static readonly QuantizerOptions DiffuserDitherOptions = new QuantizerOptions { Dither = KnownDitherings.FloydSteinberg };
private static readonly QuantizerOptions OrderedDitherOptions = new QuantizerOptions { Dither = KnownDitherings.BayerDither8x8 };
private static readonly QuantizerOptions OrderedDitherOptions = new QuantizerOptions { Dither = KnownDitherings.Bayer8x8 };
private static readonly QuantizerOptions Diffuser0_ScaleDitherOptions = new QuantizerOptions
{
@ -57,25 +57,25 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
private static readonly QuantizerOptions Ordered0_ScaleDitherOptions = new QuantizerOptions
{
Dither = KnownDitherings.BayerDither8x8,
Dither = KnownDitherings.Bayer8x8,
DitherScale = 0F
};
private static readonly QuantizerOptions Ordered0_25_ScaleDitherOptions = new QuantizerOptions
{
Dither = KnownDitherings.BayerDither8x8,
Dither = KnownDitherings.Bayer8x8,
DitherScale = .25F
};
private static readonly QuantizerOptions Ordered0_5_ScaleDitherOptions = new QuantizerOptions
{
Dither = KnownDitherings.BayerDither8x8,
Dither = KnownDitherings.Bayer8x8,
DitherScale = .5F
};
private static readonly QuantizerOptions Ordered0_75_ScaleDitherOptions = new QuantizerOptions
{
Dither = KnownDitherings.BayerDither8x8,
Dither = KnownDitherings.Bayer8x8,
DitherScale = .75F
};
@ -164,9 +164,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
}
string quantizerName = quantizer.GetType().Name;
string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "noDither";
string ditherType = quantizer.Options.Dither?.DitherType.ToString() ?? string.Empty;
string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}";
string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither";
string testOutputDetails = $"{quantizerName}_{ditherName}";
provider.RunRectangleConstrainedValidatingProcessorTest(
(x, rect) => x.Quantize(quantizer, rect),
@ -186,9 +185,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
}
string quantizerName = quantizer.GetType().Name;
string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "noDither";
string ditherType = quantizer.Options.Dither?.DitherType.ToString() ?? string.Empty;
string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}";
string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither";
string testOutputDetails = $"{quantizerName}_{ditherName}";
provider.RunValidatingProcessorTest(
x => x.Quantize(quantizer),
@ -209,9 +207,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
string quantizerName = quantizer.GetType().Name;
string ditherName = quantizer.Options.Dither.GetType().Name;
string ditherType = quantizer.Options.Dither.DitherType.ToString();
float ditherScale = quantizer.Options.DitherScale;
string testOutputDetails = FormattableString.Invariant($"{quantizerName}_{ditherName}_{ditherType}_{ditherScale}");
string testOutputDetails = FormattableString.Invariant($"{quantizerName}_{ditherName}_{ditherScale}");
provider.RunValidatingProcessorTest(
x => x.Quantize(quantizer),

6
tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs

@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests
foreach (ImageFrame<TPixel> frame in image.Frames)
{
using (IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(this.Configuration))
using (IQuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
using (QuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.GetPixelSpan()[0]);
@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Tests
foreach (ImageFrame<TPixel> frame in image.Frames)
{
using (IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(this.Configuration))
using (IQuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
using (QuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.GetPixelSpan()[0]);
@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests
}
}
private int GetTransparentIndex<TPixel>(IQuantizedFrame<TPixel> quantized)
private int GetTransparentIndex<TPixel>(QuantizedFrame<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
{
// Transparent pixels are much more likely to be found at the end of a palette

10
tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs

@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config);
using IQuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
using QuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.GetPixelSpan().Length);
@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config);
using IQuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
using QuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.GetPixelSpan().Length);
@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config);
using IQuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
using QuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
Assert.Equal(256, result.Palette.Length);
Assert.Equal(256, result.GetPixelSpan().Length);
@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<TPixel> frame = image.Frames.RootFrame;
using IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(config);
using IQuantizedFrame<TPixel> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
using QuantizedFrame<TPixel> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
Assert.Equal(48, result.Palette.Length);
}
@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using (IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config))
using (IQuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
using (QuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{
Assert.Equal(4 * 8, result.Palette.Length);
Assert.Equal(256, result.GetPixelSpan().Length);

2
tests/Images/External

@ -1 +1 @@
Subproject commit e027069e57948c94964d0948c5f6a79ace6c601a
Subproject commit 2d1505d7087d91cd83d0cda409aee213de7841ab
Loading…
Cancel
Save