Browse Source

Clean up quantized frame API

pull/1574/head
James Jackson-South 6 years ago
parent
commit
a73a63becc
  1. 2
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  2. 25
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  3. 22
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  4. 4
      src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs
  5. 2
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs
  6. 4
      src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
  7. 2
      src/ImageSharp/Processing/Processors/Dithering/IDither.cs
  8. 57
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  9. 4
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  10. 29
      src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
  11. 18
      src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs
  12. 20
      src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs
  13. 115
      src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs
  14. 30
      src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs
  15. 6
      src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs
  16. 2
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  17. 8
      src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs
  18. 94
      src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
  19. 68
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
  20. 20
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
  21. 30
      tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs

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

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

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

@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global; bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global;
// Quantize the image returning a palette. // Quantize the image returning a palette.
QuantizedFrame<TPixel> quantized; IndexedImageFrame<TPixel> quantized;
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration)) using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
{ {
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
stream.WriteByte(GifConstants.EndIntroducer); stream.WriteByte(GifConstants.EndIntroducer);
} }
private void EncodeGlobal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, int transparencyIndex, Stream stream) private void EncodeGlobal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, int transparencyIndex, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// The palette quantizer can reuse the same pixel map across multiple frames // The palette quantizer can reuse the same pixel map across multiple frames
@ -150,17 +150,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (!pixelMapSet) if (!pixelMapSet)
{ {
pixelMapSet = true; pixelMapSet = true;
pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette, quantized.Palette.Span.Length); pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette);
} }
using var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap); using var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap);
using QuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
this.WriteImageData(paletteQuantized, stream); this.WriteImageData(paletteQuantized, stream);
} }
} }
} }
private void EncodeLocal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, Stream stream) private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
ImageFrame<TPixel> previousFrame = null; ImageFrame<TPixel> previousFrame = null;
@ -214,10 +214,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <returns> /// <returns>
/// The <see cref="int"/>. /// The <see cref="int"/>.
/// </returns> /// </returns>
private int GetTransparentIndex<TPixel>(QuantizedFrame<TPixel> quantized) private int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel> quantized)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// Transparent pixels are much more likely to be found at the end of a palette // Transparent pixels are much more likely to be found at the end of a palette.
int index = -1; int index = -1;
int length = quantized.Palette.Length; int length = quantized.Palette.Length;
@ -447,19 +447,20 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode.</param> /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode.</param>
/// <param name="stream">The stream to write to.</param> /// <param name="stream">The stream to write to.</param>
private void WriteColorTable<TPixel>(QuantizedFrame<TPixel> image, Stream stream) private void WriteColorTable<TPixel>(IndexedImageFrame<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// The maximum number of colors for the bit depth // The maximum number of colors for the bit depth
int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3; int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3;
int pixelCount = image.Palette.Length; int pixelCount = image.Palette.Length;
using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean);
PixelOperations<TPixel>.Instance.ToRgb24Bytes( PixelOperations<TPixel>.Instance.ToRgb24Bytes(
this.configuration, this.configuration,
image.Palette.Span, image.Palette.Span,
colorTable.GetSpan(), colorTable.GetSpan(),
pixelCount); pixelCount);
stream.Write(colorTable.Array, 0, colorTableLength); stream.Write(colorTable.Array, 0, colorTableLength);
} }
@ -467,13 +468,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Writes the image pixel data to the stream. /// Writes the image pixel data to the stream.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="QuantizedFrame{TPixel}"/> containing indexed pixels.</param> /// <param name="image">The <see cref="IndexedImageFrame{TPixel}"/> containing indexed pixels.</param>
/// <param name="stream">The stream to write to.</param> /// <param name="stream">The stream to write to.</param>
private void WriteImageData<TPixel>(QuantizedFrame<TPixel> image, Stream stream) private void WriteImageData<TPixel>(IndexedImageFrame<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth); using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth);
encoder.Encode(image.GetPixelSpan(), stream); encoder.Encode(image.GetPixelBufferSpan(), stream);
} }
} }
} }

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

@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Png
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
PngMetadata pngMetadata = metadata.GetPngMetadata(); PngMetadata pngMetadata = metadata.GetPngMetadata();
PngEncoderOptionsHelpers.AdjustOptions<TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel); PngEncoderOptionsHelpers.AdjustOptions<TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
QuantizedFrame<TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); IndexedImageFrame<TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized); this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized);
stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length); 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="rowSpan">The row span.</param>
/// <param name="quantized">The quantized pixels. Can be null.</param> /// <param name="quantized">The quantized pixels. Can be null.</param>
/// <param name="row">The row.</param> /// <param name="row">The row.</param>
private void CollectPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan, QuantizedFrame<TPixel> quantized, int row) private void CollectPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan, IndexedImageFrame<TPixel> quantized, int row)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
switch (this.options.ColorType) switch (this.options.ColorType)
@ -385,7 +385,7 @@ namespace SixLabors.ImageSharp.Formats.Png
else else
{ {
int stride = this.currentScanline.Length(); int stride = this.currentScanline.Length();
quantized.GetPixelSpan().Slice(row * stride, stride).CopyTo(this.currentScanline.GetSpan()); quantized.GetPixelBufferSpan().Slice(row * stride, stride).CopyTo(this.currentScanline.GetSpan());
} }
break; break;
@ -440,7 +440,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="quantized">The quantized pixels. Can be null.</param> /// <param name="quantized">The quantized pixels. Can be null.</param>
/// <param name="row">The row.</param> /// <param name="row">The row.</param>
/// <returns>The <see cref="IManagedByteBuffer"/></returns> /// <returns>The <see cref="IManagedByteBuffer"/></returns>
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, QuantizedFrame<TPixel> quantized, int row) private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, IndexedImageFrame<TPixel> quantized, int row)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.CollectPixelBytes(rowSpan, quantized, row); this.CollectPixelBytes(rowSpan, quantized, row);
@ -546,17 +546,17 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="quantized">The quantized frame.</param> /// <param name="quantized">The quantized frame.</param>
private void WritePaletteChunk<TPixel>(Stream stream, QuantizedFrame<TPixel> quantized) private void WritePaletteChunk<TPixel>(Stream stream, IndexedImageFrame<TPixel> quantized)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
if (quantized == null) if (quantized is null)
{ {
return; return;
} }
// Grab the palette and write it to the stream. // Grab the palette and write it to the stream.
ReadOnlySpan<TPixel> palette = quantized.Palette.Span; ReadOnlySpan<TPixel> palette = quantized.Palette.Span;
int paletteLength = Math.Min(palette.Length, 256); int paletteLength = Math.Min(palette.Length, QuantizerConstants.MaxColors);
int colorTableLength = paletteLength * 3; int colorTableLength = paletteLength * 3;
bool anyAlpha = false; bool anyAlpha = false;
@ -565,7 +565,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan()); ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan());
ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan());
ReadOnlySpan<byte> quantizedSpan = quantized.GetPixelSpan(); ReadOnlySpan<byte> quantizedSpan = quantized.GetPixelBufferSpan();
Rgba32 rgba = default; Rgba32 rgba = default;
@ -783,7 +783,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="pixels">The image.</param> /// <param name="pixels">The image.</param>
/// <param name="quantized">The quantized pixel data. Can be null.</param> /// <param name="quantized">The quantized pixel data. Can be null.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, QuantizedFrame<TPixel> quantized, Stream stream) private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, IndexedImageFrame<TPixel> quantized, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
byte[] buffer; byte[] buffer;
@ -881,7 +881,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="pixels">The pixels.</param> /// <param name="pixels">The pixels.</param>
/// <param name="quantized">The quantized pixels span.</param> /// <param name="quantized">The quantized pixels span.</param>
/// <param name="deflateStream">The deflate stream.</param> /// <param name="deflateStream">The deflate stream.</param>
private void EncodePixels<TPixel>(ImageFrame<TPixel> pixels, QuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream) private void EncodePixels<TPixel>(ImageFrame<TPixel> pixels, IndexedImageFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int bytesPerScanline = this.CalculateScanlineLength(this.width); int bytesPerScanline = this.CalculateScanlineLength(this.width);
@ -960,7 +960,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="quantized">The quantized.</param> /// <param name="quantized">The quantized.</param>
/// <param name="deflateStream">The deflate stream.</param> /// <param name="deflateStream">The deflate stream.</param>
private void EncodeAdam7IndexedPixels<TPixel>(QuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream) private void EncodeAdam7IndexedPixels<TPixel>(IndexedImageFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = quantized.Width; 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> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <param name="image">The image.</param> /// <param name="image">The image.</param>
public static QuantizedFrame<TPixel> CreateQuantizedFrame<TPixel>( public static IndexedImageFrame<TPixel> CreateQuantizedFrame<TPixel>(
PngEncoderOptions options, PngEncoderOptions options,
Image<TPixel> image) Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public static byte CalculateBitDepth<TPixel>( public static byte CalculateBitDepth<TPixel>(
PngEncoderOptions options, PngEncoderOptions options,
Image<TPixel> image, Image<TPixel> image,
QuantizedFrame<TPixel> quantizedFrame) IndexedImageFrame<TPixel> quantizedFrame)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
byte bitDepth; byte bitDepth;

2
src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs

@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Memory
/// <inheritdoc /> /// <inheritdoc />
public override Span<T> GetSpan() public override Span<T> GetSpan()
{ {
if (this.Data == null) if (this.Data is null)
{ {
throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer<T>"); throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer<T>");
} }

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

@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
public void ApplyQuantizationDither<TFrameQuantizer, TPixel>( public void ApplyQuantizationDither<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination, IndexedImageFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
for (int y = bounds.Top; y < bounds.Bottom; y++) for (int y = bounds.Top; y < bounds.Bottom; y++)
{ {
ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y));
ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y - offsetY)); ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY));
for (int x = bounds.Left; x < bounds.Right; x++) for (int x = bounds.Left; x < bounds.Right; x++)
{ {

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

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
void ApplyQuantizationDither<TFrameQuantizer, TPixel>( void ApplyQuantizationDither<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination, IndexedImageFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>;

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

@ -5,7 +5,6 @@ using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -107,19 +106,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
public void ApplyQuantizationDither<TFrameQuantizer, TPixel>( public void ApplyQuantizationDither<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination, IndexedImageFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var ditherOperation = new QuantizeDitherRowIntervalOperation<TFrameQuantizer, TPixel>( var ditherOperation = new QuantizeDitherRowOperation<TFrameQuantizer, TPixel>(
ref quantizer, ref quantizer,
in Unsafe.AsRef(this), in Unsafe.AsRef(this),
source, source,
destination, destination,
bounds); bounds);
ParallelRowIterator.IterateRowIntervals( ParallelRowIterator.IterateRows(
quantizer.Configuration, quantizer.Configuration,
bounds, bounds,
in ditherOperation); in ditherOperation);
@ -134,13 +133,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel> where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var ditherOperation = new PaletteDitherRowIntervalOperation<TPaletteDitherImageProcessor, TPixel>( var ditherOperation = new PaletteDitherRowOperation<TPaletteDitherImageProcessor, TPixel>(
in processor, in processor,
in Unsafe.AsRef(this), in Unsafe.AsRef(this),
source, source,
bounds); bounds);
ParallelRowIterator.IterateRowIntervals( ParallelRowIterator.IterateRows(
processor.Configuration, processor.Configuration,
bounds, bounds,
in ditherOperation); in ditherOperation);
@ -195,23 +194,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
public override int GetHashCode() public override int GetHashCode()
=> HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); => HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY);
private readonly struct QuantizeDitherRowIntervalOperation<TFrameQuantizer, TPixel> : IRowIntervalOperation private readonly struct QuantizeDitherRowOperation<TFrameQuantizer, TPixel> : IRowOperation
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly TFrameQuantizer quantizer; private readonly TFrameQuantizer quantizer;
private readonly OrderedDither dither; private readonly OrderedDither dither;
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly QuantizedFrame<TPixel> destination; private readonly IndexedImageFrame<TPixel> destination;
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly int bitDepth; private readonly int bitDepth;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public QuantizeDitherRowIntervalOperation( public QuantizeDitherRowOperation(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
in OrderedDither dither, in OrderedDither dither,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination, IndexedImageFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
{ {
this.quantizer = quantizer; this.quantizer = quantizer;
@ -223,27 +222,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows) public void Invoke(int y)
{ {
int offsetY = this.bounds.Top; int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left; int offsetX = this.bounds.Left;
float scale = this.quantizer.Options.DitherScale; float scale = this.quantizer.Options.DitherScale;
for (int y = rows.Min; y < rows.Max; y++) ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y));
ref byte destinationRowRef = ref MemoryMarshal.GetReference(this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY));
for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{ {
ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); TPixel dithered = this.dither.Dither(Unsafe.Add(ref sourceRowRef, x), x, y, this.bitDepth, scale);
ref byte destinationRowRef = ref MemoryMarshal.GetReference(this.destination.GetPixelRowSpan(y - offsetY)); Unsafe.Add(ref destinationRowRef, x - offsetX) = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, out TPixel _);
for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{
TPixel dithered = this.dither.Dither(Unsafe.Add(ref sourceRowRef, x), x, y, this.bitDepth, scale);
Unsafe.Add(ref destinationRowRef, x - offsetX) = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, out TPixel _);
}
} }
} }
} }
private readonly struct PaletteDitherRowIntervalOperation<TPaletteDitherImageProcessor, TPixel> : IRowIntervalOperation private readonly struct PaletteDitherRowOperation<TPaletteDitherImageProcessor, TPixel> : IRowOperation
where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel> where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -255,7 +251,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
private readonly int bitDepth; private readonly int bitDepth;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public PaletteDitherRowIntervalOperation( public PaletteDitherRowOperation(
in TPaletteDitherImageProcessor processor, in TPaletteDitherImageProcessor processor,
in OrderedDither dither, in OrderedDither dither,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
@ -270,18 +266,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows) public void Invoke(int y)
{ {
for (int y = rows.Min; y < rows.Max; y++) ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y));
for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{ {
ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x);
TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale);
for (int x = this.bounds.Left; x < this.bounds.Right; x++) sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered);
{
ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x);
TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale);
sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered);
}
} }
} }
} }

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

@ -40,7 +40,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
this.ditherProcessor = new DitherProcessor( this.ditherProcessor = new DitherProcessor(
this.Configuration, this.Configuration,
Rectangle.Intersect(this.SourceRectangle, source.Bounds()),
this.paletteMemory.Memory, this.paletteMemory.Memory,
definition.DitherScale); definition.DitherScale);
} }
@ -82,12 +81,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public DitherProcessor( public DitherProcessor(
Configuration configuration, Configuration configuration,
Rectangle bounds,
ReadOnlyMemory<TPixel> palette, ReadOnlyMemory<TPixel> palette,
float ditherScale) float ditherScale)
{ {
this.Configuration = configuration; this.Configuration = configuration;
this.pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette, palette.Span.Length); this.pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette);
this.Palette = palette; this.Palette = palette;
this.DitherScale = ditherScale; this.DitherScale = ditherScale;
} }

29
src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs

@ -19,34 +19,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
private readonly Vector4[] vectorCache; private readonly Vector4[] vectorCache;
private readonly ConcurrentDictionary<TPixel, int> distanceCache; private readonly ConcurrentDictionary<TPixel, int> distanceCache;
private readonly ReadOnlyMemory<TPixel> palette;
private readonly int length;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> struct. /// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> struct.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="palette">The color palette to map from.</param> /// <param name="palette">The color palette to map from.</param>
/// <param name="length">The length of the color palette.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory<TPixel> palette, int length) public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory<TPixel> palette)
{ {
this.palette = palette; this.Palette = palette;
this.length = length; this.vectorCache = new Vector4[palette.Length];
ReadOnlySpan<TPixel> paletteSpan = this.palette.Span.Slice(0, this.length);
this.vectorCache = new Vector4[length];
// Use the same rules across all target frameworks. // Use the same rules across all target frameworks.
this.distanceCache = new ConcurrentDictionary<TPixel, int>(Environment.ProcessorCount, 31); this.distanceCache = new ConcurrentDictionary<TPixel, int>(Environment.ProcessorCount, 31);
PixelOperations<TPixel>.Instance.ToVector4(configuration, paletteSpan, this.vectorCache); PixelOperations<TPixel>.Instance.ToVector4(configuration, this.Palette.Span, this.vectorCache);
} }
/// <summary> /// <summary>
/// Returns the palette span. /// Gets the color palette of this <see cref="EuclideanPixelMap{TPixel}"/>.
/// The palette memory is owned by the palette source that created it.
/// </summary> /// </summary>
/// <returns>The <seealso cref="ReadOnlySpan{TPixel}"/>.</returns> public ReadOnlyMemory<TPixel> Palette
[MethodImpl(InliningOptions.ShortMethod)] {
public ReadOnlySpan<TPixel> GetPaletteSpan() => this.palette.Span.Slice(0, this.length); [MethodImpl(InliningOptions.ShortMethod)]
get;
}
/// <summary> /// <summary>
/// Returns the closest color in the palette and the index of that pixel. /// Returns the closest color in the palette and the index of that pixel.
@ -58,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public int GetClosestColor(TPixel color, out TPixel match) public int GetClosestColor(TPixel color, out TPixel match)
{ {
ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.GetPaletteSpan()); ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span);
// Check if the color is in the lookup table // Check if the color is in the lookup table
if (!this.distanceCache.TryGetValue(color, out int index)) if (!this.distanceCache.TryGetValue(color, out int index))
@ -78,8 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
float leastDistance = float.MaxValue; float leastDistance = float.MaxValue;
var vector = color.ToVector4(); var vector = color.ToVector4();
ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference<Vector4>(this.vectorCache); ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference<Vector4>(this.vectorCache);
for (int i = 0; i < this.Palette.Length; i++)
for (int i = 0; i < this.length; i++)
{ {
Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i); Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i);
float distance = Vector4.DistanceSquared(vector, candidate); float distance = Vector4.DistanceSquared(vector, candidate);

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

@ -24,9 +24,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="source">The source image frame to quantize.</param> /// <param name="source">The source image frame to quantize.</param>
/// <param name="bounds">The bounds within the frame to quantize.</param> /// <param name="bounds">The bounds within the frame to quantize.</param>
/// <returns> /// <returns>
/// A <see cref="QuantizedFrame{TPixel}"/> representing a quantized version of the source frame pixels. /// A <see cref="IndexedImageFrame{TPixel}"/> representing a quantized version of the source frame pixels.
/// </returns> /// </returns>
public static QuantizedFrame<TPixel> QuantizeFrame<TFrameQuantizer, TPixel>( public static IndexedImageFrame<TPixel> QuantizeFrame<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Rectangle bounds) Rectangle bounds)
@ -37,10 +37,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
var interest = Rectangle.Intersect(source.Bounds(), bounds); var interest = Rectangle.Intersect(source.Bounds(), bounds);
// Collect the palette. Required before the second pass runs. // Collect the palette. Required before the second pass runs.
ReadOnlySpan<TPixel> palette = quantizer.BuildPalette(source, interest); ReadOnlyMemory<TPixel> palette = quantizer.BuildPalette(source, interest);
MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator; var destination = new IndexedImageFrame<TPixel>(quantizer.Configuration, interest.Width, interest.Height, palette);
var destination = new QuantizedFrame<TPixel>(memoryAllocator, interest.Width, interest.Height, palette);
if (quantizer.Options.Dither is null) if (quantizer.Options.Dither is null)
{ {
@ -60,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private static void SecondPass<TFrameQuantizer, TPixel>( private static void SecondPass<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination, IndexedImageFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -87,14 +85,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
private readonly TFrameQuantizer quantizer; private readonly TFrameQuantizer quantizer;
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly QuantizedFrame<TPixel> destination; private readonly IndexedImageFrame<TPixel> destination;
private readonly Rectangle bounds; private readonly Rectangle bounds;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation( public RowIntervalOperation(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination, IndexedImageFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
{ {
this.quantizer = quantizer; this.quantizer = quantizer;
@ -112,7 +110,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y); Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
Span<byte> destinationRow = this.destination.GetPixelRowSpan(y - offsetY); Span<byte> destinationRow = this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY);
for (int x = this.bounds.Left; x < this.bounds.Right; x++) for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{ {

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

@ -23,23 +23,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
QuantizerOptions Options { get; } QuantizerOptions Options { get; }
/// <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> /// <summary>
/// Quantizes an image frame and return the resulting output pixels. /// Quantizes an image frame and return the resulting output pixels.
/// </summary> /// </summary>
/// <param name="source">The source image frame to quantize.</param> /// <param name="source">The source image frame to quantize.</param>
/// <param name="bounds">The bounds within the frame to quantize.</param> /// <param name="bounds">The bounds within the frame to quantize.</param>
/// <returns> /// <returns>
/// A <see cref="QuantizedFrame{TPixel}"/> representing a quantized version of the source frame pixels. /// A <see cref="IndexedImageFrame{TPixel}"/> representing a quantized version of the source frame pixels.
/// </returns> /// </returns>
QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds); IndexedImageFrame<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="ReadOnlySpan{TPixel}"/> palette.</returns>
ReadOnlySpan<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds);
/// <summary> /// <summary>
/// Returns the index and color from the quantized palette corresponding to the given color. /// Returns the index and color from the quantized palette corresponding to the given color.

115
src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs

@ -0,0 +1,115 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// A pixel-specific image frame where each pixel buffer value represents an index in a color palette.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public sealed class IndexedImageFrame<TPixel> : IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
private IMemoryOwner<byte> pixelsOwner;
private IMemoryOwner<TPixel> paletteOwner;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="IndexedImageFrame{TPixel}"/> class.
/// </summary>
/// <param name="configuration">
/// The configuration which allows altering default behaviour or extending the library.
/// </param>
/// <param name="width">The frame width.</param>
/// <param name="height">The frame height.</param>
/// <param name="palette">The color palette.</param>
internal IndexedImageFrame(Configuration configuration, int width, int height, ReadOnlyMemory<TPixel> palette)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
this.Configuration = configuration;
this.Width = width;
this.Height = height;
this.pixelsOwner = configuration.MemoryAllocator.AllocateManagedByteBuffer(width * height);
// Copy the palette over. We want the lifetime of this frame to be independant of any palette source.
this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(palette.Length);
palette.Span.CopyTo(this.paletteOwner.GetSpan());
this.Palette = this.paletteOwner.Memory.Slice(0, palette.Length);
}
/// <summary>
/// Gets the configuration which allows altering default behaviour or extending the library.
/// </summary>
public Configuration Configuration { get; }
/// <summary>
/// Gets the width of this <see cref="IndexedImageFrame{TPixel}"/>.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the height of this <see cref="IndexedImageFrame{TPixel}"/>.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the color palette of this <see cref="IndexedImageFrame{TPixel}"/>.
/// </summary>
public ReadOnlyMemory<TPixel> Palette { get; }
/// <summary>
/// Gets the pixels of this <see cref="IndexedImageFrame{TPixel}"/>.
/// </summary>
/// <returns>The <see cref="ReadOnlySpan{T}"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<byte> GetPixelBufferSpan() => this.pixelsOwner.GetSpan(); // TODO: Buffer2D<byte>
/// <summary>
/// Gets the representation of the pixels as a <see cref="ReadOnlySpan{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
/// </summary>
/// <param name="rowIndex">The row index in the pixel buffer.</param>
/// <returns>The pixel row as a <see cref="ReadOnlySpan{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<byte> GetPixelRowSpan(int rowIndex)
=> this.GetWritablePixelRowSpanUnsafe(rowIndex);
/// <summary>
/// <para>
/// 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.
/// </para>
/// <para>
/// Note: Values written to this span are not sanitized against the palette length.
/// Care should be taken during assignment to prevent out-of-bounds errors.
/// </para>
/// </summary>
/// <param name="rowIndex">The row index in the pixel buffer.</param>
/// <returns>The pixel row as a <see cref="Span{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public Span<byte> GetWritablePixelRowSpanUnsafe(int rowIndex)
=> this.pixelsOwner.GetSpan().Slice(rowIndex * this.Width, this.Width);
/// <inheritdoc/>
public void Dispose()
{
if (!this.isDisposed)
{
this.isDisposed = true;
this.pixelsOwner.Dispose();
this.paletteOwner.Dispose();
this.pixelsOwner = null;
this.paletteOwner = null;
}
}
}
}

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

@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public struct OctreeFrameQuantizer<TPixel> : IFrameQuantizer<TPixel> public struct OctreeFrameQuantizer<TPixel> : IFrameQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly int colors; private readonly int maxColors;
private readonly Octree octree; private readonly Octree octree;
private IMemoryOwner<TPixel> palette; private IMemoryOwner<TPixel> paletteOwner;
private EuclideanPixelMap<TPixel> pixelMap; private EuclideanPixelMap<TPixel> pixelMap;
private readonly bool isDithering; private readonly bool isDithering;
private bool isDisposed; private bool isDisposed;
@ -41,9 +41,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Configuration = configuration; this.Configuration = configuration;
this.Options = options; this.Options = options;
this.colors = this.Options.MaxColors; this.maxColors = this.Options.MaxColors;
this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8)); this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.maxColors).Clamp(1, 8));
this.palette = configuration.MemoryAllocator.Allocate<TPixel>(this.colors, AllocationOptions.Clean); this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean);
this.pixelMap = default; this.pixelMap = default;
this.isDithering = !(this.Options.Dither is null); this.isDithering = !(this.Options.Dither is null);
this.isDisposed = false; this.isDisposed = false;
@ -57,12 +57,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public readonly QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds) public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds);
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds) public ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
{ {
using IMemoryOwner<Rgba32> buffer = this.Configuration.MemoryAllocator.Allocate<Rgba32>(bounds.Width); using IMemoryOwner<Rgba32> buffer = this.Configuration.MemoryAllocator.Allocate<Rgba32>(bounds.Width);
Span<Rgba32> bufferSpan = buffer.GetSpan(); Span<Rgba32> bufferSpan = buffer.GetSpan();
@ -82,15 +82,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
} }
Span<TPixel> paletteSpan = this.palette.GetSpan(); Span<TPixel> paletteSpan = this.paletteOwner.GetSpan();
int paletteIndex = 0; int paletteIndex = 0;
this.octree.Palletize(paletteSpan, this.colors, ref paletteIndex); this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex);
// Length of reduced palette + transparency. // Length of reduced palette + transparency.
paletteSpan = paletteSpan.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors)); ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors));
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, this.palette.Memory, paletteSpan.Length); this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
return paletteSpan; return result;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return (byte)this.pixelMap.GetClosestColor(color, out match); return (byte)this.pixelMap.GetClosestColor(color, out match);
} }
ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.GetPaletteSpan()); ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span);
var index = (byte)this.octree.GetPaletteIndex(color); var index = (byte)this.octree.GetPaletteIndex(color);
match = Unsafe.Add(ref paletteRef, index); match = Unsafe.Add(ref paletteRef, index);
return index; return index;
@ -117,8 +117,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (!this.isDisposed) if (!this.isDisposed)
{ {
this.isDisposed = true; this.isDisposed = true;
this.palette.Dispose(); this.paletteOwner.Dispose();
this.palette = null; this.paletteOwner = null;
} }
} }

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

@ -45,13 +45,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public readonly QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds) public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds);
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public readonly ReadOnlySpan<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds) public readonly ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
=> this.pixelMap.GetPaletteSpan(); => this.pixelMap.Palette;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]

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

@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan());
var pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette, length); var pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette);
return new PaletteFrameQuantizer<TPixel>(configuration, options, pixelMap); return new PaletteFrameQuantizer<TPixel>(configuration, options, pixelMap);
} }
} }

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

@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(configuration); using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(configuration);
using QuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(source, interest); using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(source, interest);
var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized);
ParallelRowIterator.IterateRowIntervals( ParallelRowIterator.IterateRowIntervals(
@ -52,13 +52,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly QuantizedFrame<TPixel> quantized; private readonly IndexedImageFrame<TPixel> quantized;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation( public RowIntervalOperation(
Rectangle bounds, Rectangle bounds,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
QuantizedFrame<TPixel> quantized) IndexedImageFrame<TPixel> quantized)
{ {
this.bounds = bounds; this.bounds = bounds;
this.source = source; this.source = source;
@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows) public void Invoke(in RowInterval rows)
{ {
ReadOnlySpan<byte> quantizedPixelSpan = this.quantized.GetPixelSpan(); ReadOnlySpan<byte> quantizedPixelSpan = this.quantized.GetPixelBufferSpan();
ReadOnlySpan<TPixel> paletteSpan = this.quantized.Palette.Span; ReadOnlySpan<TPixel> paletteSpan = this.quantized.Palette.Span;
int offsetY = this.bounds.Top; int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left; int offsetX = this.bounds.Left;

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

@ -1,94 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// 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> : IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
private IMemoryOwner<TPixel> palette;
private IMemoryOwner<byte> pixels;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="QuantizedFrame{TPixel}"/> class.
/// </summary>
/// <param name="memoryAllocator">Used to allocated memory for image processing operations.</param>
/// <param name="width">The image width.</param>
/// <param name="height">The image height.</param>
/// <param name="palette">The color palette.</param>
internal QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, ReadOnlySpan<TPixel> palette)
{
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
this.Width = width;
this.Height = height;
this.pixels = memoryAllocator.AllocateManagedByteBuffer(width * height, AllocationOptions.Clean);
this.palette = memoryAllocator.Allocate<TPixel>(palette.Length);
palette.CopyTo(this.palette.GetSpan());
}
/// <summary>
/// Gets the width of this <see cref="QuantizedFrame{TPixel}"/>.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the height of this <see cref="QuantizedFrame{TPixel}"/>.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the color palette of this <see cref="QuantizedFrame{TPixel}"/>.
/// </summary>
public ReadOnlyMemory<TPixel> Palette
{
[MethodImpl(InliningOptions.ShortMethod)]
get { return this.palette.Memory; }
}
/// <summary>
/// Gets the pixels of this <see cref="QuantizedFrame{TPixel}"/>.
/// </summary>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public Span<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="Span{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public Span<byte> GetPixelRowSpan(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.palette?.Dispose();
this.pixels = null;
this.palette = null;
}
}
}

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

@ -66,10 +66,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
private IMemoryOwner<Moment> moments; private IMemoryOwner<Moment> momentsOwner;
private IMemoryOwner<byte> tag; private IMemoryOwner<byte> tagsOwner;
private IMemoryOwner<TPixel> palette; private IMemoryOwner<TPixel> paletteOwner;
private int colors; private int maxColors;
private readonly Box[] colorCube; private readonly Box[] colorCube;
private EuclideanPixelMap<TPixel> pixelMap; private EuclideanPixelMap<TPixel> pixelMap;
private readonly bool isDithering; private readonly bool isDithering;
@ -88,12 +88,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Configuration = configuration; this.Configuration = configuration;
this.Options = options; this.Options = options;
this.colors = this.Options.MaxColors; this.maxColors = this.Options.MaxColors;
this.memoryAllocator = this.Configuration.MemoryAllocator; this.memoryAllocator = this.Configuration.MemoryAllocator;
this.moments = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean); this.momentsOwner = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean);
this.tag = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean); this.tagsOwner = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean);
this.palette = this.memoryAllocator.Allocate<TPixel>(this.colors, AllocationOptions.Clean); this.paletteOwner = this.memoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean);
this.colorCube = new Box[this.colors]; this.colorCube = new Box[this.maxColors];
this.isDisposed = false; this.isDisposed = false;
this.pixelMap = default; this.pixelMap = default;
this.isDithering = this.isDithering = !(this.Options.Dither is null); this.isDithering = this.isDithering = !(this.Options.Dither is null);
@ -107,19 +107,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public readonly QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds) public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds);
/// <inheritdoc/> /// <inheritdoc/>
public ReadOnlySpan<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds) public ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
{ {
this.Build3DHistogram(source, bounds); this.Build3DHistogram(source, bounds);
this.Get3DMoments(this.memoryAllocator); this.Get3DMoments(this.memoryAllocator);
this.BuildCube(); this.BuildCube();
ReadOnlySpan<Moment> momentsSpan = this.moments.GetSpan(); ReadOnlySpan<Moment> momentsSpan = this.momentsOwner.GetSpan();
Span<TPixel> paletteSpan = this.palette.GetSpan(); Span<TPixel> paletteSpan = this.paletteOwner.GetSpan();
for (int k = 0; k < this.colors; k++) for (int k = 0; k < this.maxColors; k++)
{ {
this.Mark(ref this.colorCube[k], (byte)k); this.Mark(ref this.colorCube[k], (byte)k);
@ -132,9 +132,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
} }
paletteSpan = paletteSpan.Slice(0, this.colors); ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, this.maxColors);
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, this.palette.Memory, paletteSpan.Length); this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
return paletteSpan; return result;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -153,9 +153,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
int b = rgba.B >> (8 - IndexBits); int b = rgba.B >> (8 - IndexBits);
int a = rgba.A >> (8 - IndexAlphaBits); int a = rgba.A >> (8 - IndexAlphaBits);
ReadOnlySpan<byte> tagSpan = this.tag.GetSpan(); ReadOnlySpan<byte> tagSpan = this.tagsOwner.GetSpan();
byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)];
ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.GetPaletteSpan()); ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span);
match = Unsafe.Add(ref paletteRef, index); match = Unsafe.Add(ref paletteRef, index);
return index; return index;
} }
@ -166,12 +166,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (!this.isDisposed) if (!this.isDisposed)
{ {
this.isDisposed = true; this.isDisposed = true;
this.moments?.Dispose(); this.momentsOwner?.Dispose();
this.tag?.Dispose(); this.tagsOwner?.Dispose();
this.palette?.Dispose(); this.paletteOwner?.Dispose();
this.moments = null; this.momentsOwner = null;
this.tag = null; this.tagsOwner = null;
this.palette = null; this.paletteOwner = null;
} }
} }
@ -350,7 +350,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="bounds">The bounds within the source image to quantize.</param> /// <param name="bounds">The bounds within the source image to quantize.</param>
private void Build3DHistogram(ImageFrame<TPixel> source, Rectangle bounds) private void Build3DHistogram(ImageFrame<TPixel> source, Rectangle bounds)
{ {
Span<Moment> momentSpan = this.moments.GetSpan(); Span<Moment> momentSpan = this.momentsOwner.GetSpan();
// Build up the 3-D color histogram // Build up the 3-D color histogram
using IMemoryOwner<Rgba32> buffer = this.memoryAllocator.Allocate<Rgba32>(bounds.Width); using IMemoryOwner<Rgba32> buffer = this.memoryAllocator.Allocate<Rgba32>(bounds.Width);
@ -384,7 +384,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
using IMemoryOwner<Moment> volume = allocator.Allocate<Moment>(IndexCount * IndexAlphaCount); using IMemoryOwner<Moment> volume = allocator.Allocate<Moment>(IndexCount * IndexAlphaCount);
using IMemoryOwner<Moment> area = allocator.Allocate<Moment>(IndexAlphaCount); using IMemoryOwner<Moment> area = allocator.Allocate<Moment>(IndexAlphaCount);
Span<Moment> momentSpan = this.moments.GetSpan(); Span<Moment> momentSpan = this.momentsOwner.GetSpan();
Span<Moment> volumeSpan = volume.GetSpan(); Span<Moment> volumeSpan = volume.GetSpan();
Span<Moment> areaSpan = area.GetSpan(); Span<Moment> areaSpan = area.GetSpan();
int baseIndex = GetPaletteIndex(1, 0, 0, 0); int baseIndex = GetPaletteIndex(1, 0, 0, 0);
@ -426,7 +426,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The <see cref="float"/>.</returns> /// <returns>The <see cref="float"/>.</returns>
private double Variance(ref Box cube) private double Variance(ref Box cube)
{ {
ReadOnlySpan<Moment> momentSpan = this.moments.GetSpan(); ReadOnlySpan<Moment> momentSpan = this.momentsOwner.GetSpan();
Moment volume = Volume(ref cube, momentSpan); Moment volume = Volume(ref cube, momentSpan);
Moment variance = Moment variance =
@ -467,7 +467,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The <see cref="float"/>.</returns> /// <returns>The <see cref="float"/>.</returns>
private float Maximize(ref Box cube, int direction, int first, int last, out int cut, Moment whole) private float Maximize(ref Box cube, int direction, int first, int last, out int cut, Moment whole)
{ {
ReadOnlySpan<Moment> momentSpan = this.moments.GetSpan(); ReadOnlySpan<Moment> momentSpan = this.momentsOwner.GetSpan();
Moment bottom = Bottom(ref cube, direction, momentSpan); Moment bottom = Bottom(ref cube, direction, momentSpan);
float max = 0F; float max = 0F;
@ -513,7 +513,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>Returns a value indicating whether the box has been split.</returns> /// <returns>Returns a value indicating whether the box has been split.</returns>
private bool Cut(ref Box set1, ref Box set2) private bool Cut(ref Box set1, ref Box set2)
{ {
ReadOnlySpan<Moment> momentSpan = this.moments.GetSpan(); ReadOnlySpan<Moment> momentSpan = this.momentsOwner.GetSpan();
Moment whole = Volume(ref set1, momentSpan); Moment whole = Volume(ref set1, momentSpan);
float maxR = this.Maximize(ref set1, 3, set1.RMin + 1, set1.RMax, out int cutR, whole); float maxR = this.Maximize(ref set1, 3, set1.RMin + 1, set1.RMax, out int cutR, whole);
@ -598,7 +598,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="label">A label.</param> /// <param name="label">A label.</param>
private void Mark(ref Box cube, byte label) private void Mark(ref Box cube, byte label)
{ {
Span<byte> tagSpan = this.tag.GetSpan(); Span<byte> tagSpan = this.tagsOwner.GetSpan();
for (int r = cube.RMin + 1; r <= cube.RMax; r++) for (int r = cube.RMin + 1; r <= cube.RMax; r++)
{ {
@ -620,7 +620,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
private void BuildCube() private void BuildCube()
{ {
Span<double> vv = stackalloc double[this.colors]; Span<double> vv = stackalloc double[this.maxColors];
ref Box cube = ref this.colorCube[0]; ref Box cube = ref this.colorCube[0];
cube.RMin = cube.GMin = cube.BMin = cube.AMin = 0; cube.RMin = cube.GMin = cube.BMin = cube.AMin = 0;
@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
int next = 0; int next = 0;
for (int i = 1; i < this.colors; i++) for (int i = 1; i < this.maxColors; i++)
{ {
ref Box nextCube = ref this.colorCube[next]; ref Box nextCube = ref this.colorCube[next];
ref Box currentCube = ref this.colorCube[i]; ref Box currentCube = ref this.colorCube[i];
@ -658,7 +658,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (temp <= 0D) if (temp <= 0D)
{ {
this.colors = i + 1; this.maxColors = i + 1;
break; break;
} }
} }

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

@ -71,10 +71,10 @@ namespace SixLabors.ImageSharp.Tests
foreach (ImageFrame<TPixel> frame in image.Frames) foreach (ImageFrame<TPixel> frame in image.Frames)
{ {
using (IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(this.Configuration)) using (IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(this.Configuration))
using (QuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) using (IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{ {
int index = this.GetTransparentIndex(quantized); int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.GetPixelSpan()[0]); Assert.Equal(index, quantized.GetPixelBufferSpan()[0]);
} }
} }
} }
@ -101,27 +101,27 @@ namespace SixLabors.ImageSharp.Tests
foreach (ImageFrame<TPixel> frame in image.Frames) foreach (ImageFrame<TPixel> frame in image.Frames)
{ {
using (IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(this.Configuration)) using (IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(this.Configuration))
using (QuantizedFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) using (IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{ {
int index = this.GetTransparentIndex(quantized); int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.GetPixelSpan()[0]); Assert.Equal(index, quantized.GetPixelBufferSpan()[0]);
} }
} }
} }
} }
private int GetTransparentIndex<TPixel>(QuantizedFrame<TPixel> quantized) private int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel> quantized)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// Transparent pixels are much more likely to be found at the end of a palette // Transparent pixels are much more likely to be found at the end of a palette
int index = -1; int index = -1;
Rgba32 trans = default;
ReadOnlySpan<TPixel> paletteSpan = quantized.Palette.Span; ReadOnlySpan<TPixel> paletteSpan = quantized.Palette.Span;
for (int i = paletteSpan.Length - 1; i >= 0; i--) Span<Rgba32> colorSpan = stackalloc Rgba32[QuantizerConstants.MaxColors].Slice(0, paletteSpan.Length);
{
paletteSpan[i].ToRgba32(ref trans);
if (trans.Equals(default)) PixelOperations<TPixel>.Instance.ToRgba32(quantized.Configuration, paletteSpan, colorSpan);
for (int i = colorSpan.Length - 1; i >= 0; i--)
{
if (colorSpan[i].Equals(default))
{ {
index = i; index = i;
} }

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

@ -21,13 +21,13 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame; ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config); using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config);
using QuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.GetPixelSpan().Length); Assert.Equal(1, result.GetPixelBufferSpan().Length);
Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); Assert.Equal(Color.Black, (Color)result.Palette.Span[0]);
Assert.Equal(0, result.GetPixelSpan()[0]); Assert.Equal(0, result.GetPixelBufferSpan()[0]);
} }
[Fact] [Fact]
@ -40,13 +40,13 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame; ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config); using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config);
using QuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.GetPixelSpan().Length); Assert.Equal(1, result.GetPixelBufferSpan().Length);
Assert.Equal(default, result.Palette.Span[0]); Assert.Equal(default, result.Palette.Span[0]);
Assert.Equal(0, result.GetPixelSpan()[0]); Assert.Equal(0, result.GetPixelBufferSpan()[0]);
} }
[Fact] [Fact]
@ -85,19 +85,19 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame; ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config); using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config);
using QuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
Assert.Equal(256, result.Palette.Length); Assert.Equal(256, result.Palette.Length);
Assert.Equal(256, result.GetPixelSpan().Length); Assert.Equal(256, result.GetPixelBufferSpan().Length);
var actualImage = new Image<Rgba32>(1, 256); var actualImage = new Image<Rgba32>(1, 256);
ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span; ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span;
int paletteCount = result.Palette.Length - 1; int paletteCount = paletteSpan.Length - 1;
for (int y = 0; y < actualImage.Height; y++) for (int y = 0; y < actualImage.Height; y++)
{ {
Span<Rgba32> row = actualImage.GetPixelRowSpan(y); Span<Rgba32> row = actualImage.GetPixelRowSpan(y);
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelSpan(); ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelBufferSpan();
int yy = y * actualImage.Width; int yy = y * actualImage.Width;
for (int x = 0; x < actualImage.Width; x++) for (int x = 0; x < actualImage.Width; x++)
@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<TPixel> frame = image.Frames.RootFrame; ImageFrame<TPixel> frame = image.Frames.RootFrame;
using IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(config); using IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(config);
using QuantizedFrame<TPixel> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<TPixel> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
Assert.Equal(48, result.Palette.Length); Assert.Equal(48, result.Palette.Length);
} }
@ -152,17 +152,17 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame; ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using (IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config)) using (IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config))
using (QuantizedFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) using (IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{ {
Assert.Equal(4 * 8, result.Palette.Length); Assert.Equal(4 * 8, result.Palette.Length);
Assert.Equal(256, result.GetPixelSpan().Length); Assert.Equal(256, result.GetPixelBufferSpan().Length);
ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span; ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span;
int paletteCount = result.Palette.Length - 1; int paletteCount = paletteSpan.Length - 1;
for (int y = 0; y < actualImage.Height; y++) for (int y = 0; y < actualImage.Height; y++)
{ {
Span<Rgba32> row = actualImage.GetPixelRowSpan(y); Span<Rgba32> row = actualImage.GetPixelRowSpan(y);
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelSpan(); ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelBufferSpan();
int yy = y * actualImage.Width; int yy = y * actualImage.Width;
for (int x = 0; x < actualImage.Width; x++) for (int x = 0; x < actualImage.Width; x++)

Loading…
Cancel
Save