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>
{
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;
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;
// Quantize the image returning a palette.
QuantizedFrame<TPixel> quantized;
IndexedImageFrame<TPixel> quantized;
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
{
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
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>
{
// The palette quantizer can reuse the same pixel map across multiple frames
@ -150,17 +150,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (!pixelMapSet)
{
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 QuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
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>
{
ImageFrame<TPixel> previousFrame = null;
@ -214,10 +214,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <returns>
/// The <see cref="int"/>.
/// </returns>
private int GetTransparentIndex<TPixel>(QuantizedFrame<TPixel> quantized)
private int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel> quantized)
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 length = quantized.Palette.Length;
@ -447,19 +447,20 @@ 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>(QuantizedFrame<TPixel> image, Stream stream)
private void WriteColorTable<TPixel>(IndexedImageFrame<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
// The maximum number of colors for the bit depth
int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3;
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(
this.configuration,
image.Palette.Span,
colorTable.GetSpan(),
pixelCount);
stream.Write(colorTable.Array, 0, colorTableLength);
}
@ -467,13 +468,13 @@ 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="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>
private void WriteImageData<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
private void WriteImageData<TPixel>(IndexedImageFrame<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
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;
PngMetadata pngMetadata = metadata.GetPngMetadata();
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);
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, QuantizedFrame<TPixel> quantized, int row)
private void CollectPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan, IndexedImageFrame<TPixel> quantized, int row)
where TPixel : unmanaged, IPixel<TPixel>
{
switch (this.options.ColorType)
@ -385,7 +385,7 @@ namespace SixLabors.ImageSharp.Formats.Png
else
{
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;
@ -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, QuantizedFrame<TPixel> quantized, int row)
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, IndexedImageFrame<TPixel> quantized, int row)
where TPixel : unmanaged, IPixel<TPixel>
{
this.CollectPixelBytes(rowSpan, quantized, row);
@ -546,17 +546,17 @@ 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, QuantizedFrame<TPixel> quantized)
private void WritePaletteChunk<TPixel>(Stream stream, IndexedImageFrame<TPixel> quantized)
where TPixel : unmanaged, IPixel<TPixel>
{
if (quantized == null)
if (quantized is null)
{
return;
}
// Grab the palette and write it to the stream.
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;
bool anyAlpha = false;
@ -565,7 +565,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan());
ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan());
ReadOnlySpan<byte> quantizedSpan = quantized.GetPixelSpan();
ReadOnlySpan<byte> quantizedSpan = quantized.GetPixelBufferSpan();
Rgba32 rgba = default;
@ -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, QuantizedFrame<TPixel> quantized, Stream stream)
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, IndexedImageFrame<TPixel> quantized, Stream stream)
where TPixel : unmanaged, 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, QuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
private void EncodePixels<TPixel>(ImageFrame<TPixel> pixels, IndexedImageFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, 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>(QuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
private void EncodeAdam7IndexedPixels<TPixel>(IndexedImageFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, 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 QuantizedFrame<TPixel> CreateQuantizedFrame<TPixel>(
public static IndexedImageFrame<TPixel> CreateQuantizedFrame<TPixel>(
PngEncoderOptions options,
Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public static byte CalculateBitDepth<TPixel>(
PngEncoderOptions options,
Image<TPixel> image,
QuantizedFrame<TPixel> quantizedFrame)
IndexedImageFrame<TPixel> quantizedFrame)
where TPixel : unmanaged, IPixel<TPixel>
{
byte bitDepth;

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

@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Memory
/// <inheritdoc />
public override Span<T> GetSpan()
{
if (this.Data == null)
if (this.Data is null)
{
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>(
ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination,
IndexedImageFrame<TPixel> destination,
Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<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++)
{
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++)
{

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

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
void ApplyQuantizationDither<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination,
IndexedImageFrame<TPixel> destination,
Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<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.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -107,19 +106,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
public void ApplyQuantizationDither<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination,
IndexedImageFrame<TPixel> destination,
Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var ditherOperation = new QuantizeDitherRowIntervalOperation<TFrameQuantizer, TPixel>(
var ditherOperation = new QuantizeDitherRowOperation<TFrameQuantizer, TPixel>(
ref quantizer,
in Unsafe.AsRef(this),
source,
destination,
bounds);
ParallelRowIterator.IterateRowIntervals(
ParallelRowIterator.IterateRows(
quantizer.Configuration,
bounds,
in ditherOperation);
@ -134,13 +133,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var ditherOperation = new PaletteDitherRowIntervalOperation<TPaletteDitherImageProcessor, TPixel>(
var ditherOperation = new PaletteDitherRowOperation<TPaletteDitherImageProcessor, TPixel>(
in processor,
in Unsafe.AsRef(this),
source,
bounds);
ParallelRowIterator.IterateRowIntervals(
ParallelRowIterator.IterateRows(
processor.Configuration,
bounds,
in ditherOperation);
@ -195,23 +194,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
public override int GetHashCode()
=> 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 TPixel : unmanaged, IPixel<TPixel>
{
private readonly TFrameQuantizer quantizer;
private readonly OrderedDither dither;
private readonly ImageFrame<TPixel> source;
private readonly QuantizedFrame<TPixel> destination;
private readonly IndexedImageFrame<TPixel> destination;
private readonly Rectangle bounds;
private readonly int bitDepth;
[MethodImpl(InliningOptions.ShortMethod)]
public QuantizeDitherRowIntervalOperation(
public QuantizeDitherRowOperation(
ref TFrameQuantizer quantizer,
in OrderedDither dither,
ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination,
IndexedImageFrame<TPixel> destination,
Rectangle bounds)
{
this.quantizer = quantizer;
@ -223,27 +222,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
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++)
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));
ref byte destinationRowRef = ref MemoryMarshal.GetReference(this.destination.GetPixelRowSpan(y - offsetY));
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 _);
}
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 TPixel : unmanaged, IPixel<TPixel>
{
@ -255,7 +251,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
private readonly int bitDepth;
[MethodImpl(InliningOptions.ShortMethod)]
public PaletteDitherRowIntervalOperation(
public PaletteDitherRowOperation(
in TPaletteDitherImageProcessor processor,
in OrderedDither dither,
ImageFrame<TPixel> source,
@ -270,18 +266,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
}
[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));
for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{
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);
}
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.Configuration,
Rectangle.Intersect(this.SourceRectangle, source.Bounds()),
this.paletteMemory.Memory,
definition.DitherScale);
}
@ -82,12 +81,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
[MethodImpl(InliningOptions.ShortMethod)]
public DitherProcessor(
Configuration configuration,
Rectangle bounds,
ReadOnlyMemory<TPixel> palette,
float ditherScale)
{
this.Configuration = configuration;
this.pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette, palette.Span.Length);
this.pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette);
this.Palette = palette;
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 ConcurrentDictionary<TPixel, int> distanceCache;
private readonly ReadOnlyMemory<TPixel> palette;
private readonly int length;
/// <summary>
/// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> struct.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="palette">The color palette to map from.</param>
/// <param name="length">The length of the color palette.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory<TPixel> palette, int length)
public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory<TPixel> palette)
{
this.palette = palette;
this.length = length;
ReadOnlySpan<TPixel> paletteSpan = this.palette.Span.Slice(0, this.length);
this.vectorCache = new Vector4[length];
this.Palette = palette;
this.vectorCache = new Vector4[palette.Length];
// Use the same rules across all target frameworks.
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>
/// 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>
/// <returns>The <seealso cref="ReadOnlySpan{TPixel}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<TPixel> GetPaletteSpan() => this.palette.Span.Slice(0, this.length);
public ReadOnlyMemory<TPixel> Palette
{
[MethodImpl(InliningOptions.ShortMethod)]
get;
}
/// <summary>
/// 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)]
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
if (!this.distanceCache.TryGetValue(color, out int index))
@ -78,8 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
float leastDistance = float.MaxValue;
var vector = color.ToVector4();
ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference<Vector4>(this.vectorCache);
for (int i = 0; i < this.length; i++)
for (int i = 0; i < this.Palette.Length; i++)
{
Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i);
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="bounds">The bounds within the frame to quantize.</param>
/// <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>
public static QuantizedFrame<TPixel> QuantizeFrame<TFrameQuantizer, TPixel>(
public static IndexedImageFrame<TPixel> QuantizeFrame<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source,
Rectangle bounds)
@ -37,10 +37,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
var interest = Rectangle.Intersect(source.Bounds(), bounds);
// Collect the palette. Required before the second pass runs.
ReadOnlySpan<TPixel> palette = quantizer.BuildPalette(source, interest);
MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator;
var destination = new QuantizedFrame<TPixel>(memoryAllocator, interest.Width, interest.Height, palette);
ReadOnlyMemory<TPixel> palette = quantizer.BuildPalette(source, interest);
var destination = new IndexedImageFrame<TPixel>(quantizer.Configuration, interest.Width, interest.Height, palette);
if (quantizer.Options.Dither is null)
{
@ -60,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private static void SecondPass<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination,
IndexedImageFrame<TPixel> destination,
Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
@ -87,14 +85,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
private readonly TFrameQuantizer quantizer;
private readonly ImageFrame<TPixel> source;
private readonly QuantizedFrame<TPixel> destination;
private readonly IndexedImageFrame<TPixel> destination;
private readonly Rectangle bounds;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination,
IndexedImageFrame<TPixel> destination,
Rectangle bounds)
{
this.quantizer = quantizer;
@ -112,7 +110,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
for (int y = rows.Min; y < rows.Max; 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++)
{

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

@ -23,23 +23,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
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>
/// Quantizes an image frame and return the resulting output pixels.
/// </summary>
/// <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.
/// A <see cref="IndexedImageFrame{TPixel}"/> representing a quantized version of the source frame pixels.
/// </returns>
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="ReadOnlySpan{TPixel}"/> palette.</returns>
ReadOnlySpan<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds);
IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds);
/// <summary>
/// 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>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly int colors;
private readonly int maxColors;
private readonly Octree octree;
private IMemoryOwner<TPixel> palette;
private IMemoryOwner<TPixel> paletteOwner;
private EuclideanPixelMap<TPixel> pixelMap;
private readonly bool isDithering;
private bool isDisposed;
@ -41,9 +41,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Configuration = configuration;
this.Options = options;
this.colors = this.Options.MaxColors;
this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8));
this.palette = configuration.MemoryAllocator.Allocate<TPixel>(this.colors, AllocationOptions.Clean);
this.maxColors = this.Options.MaxColors;
this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.maxColors).Clamp(1, 8));
this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean);
this.pixelMap = default;
this.isDithering = !(this.Options.Dither is null);
this.isDisposed = false;
@ -57,12 +57,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/>
[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);
/// <inheritdoc/>
[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);
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;
this.octree.Palletize(paletteSpan, this.colors, ref paletteIndex);
this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex);
// Length of reduced palette + transparency.
paletteSpan = paletteSpan.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors));
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, this.palette.Memory, paletteSpan.Length);
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors));
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
return paletteSpan;
return result;
}
/// <inheritdoc/>
@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
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);
match = Unsafe.Add(ref paletteRef, index);
return index;
@ -117,8 +117,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (!this.isDisposed)
{
this.isDisposed = true;
this.palette.Dispose();
this.palette = null;
this.paletteOwner.Dispose();
this.paletteOwner = null;
}
}

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

@ -45,13 +45,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/>
[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);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public readonly ReadOnlySpan<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
=> this.pixelMap.GetPaletteSpan();
public readonly ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
=> this.pixelMap.Palette;
/// <inheritdoc/>
[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());
var pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette, length);
var pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette);
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;
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);
ParallelRowIterator.IterateRowIntervals(
@ -52,13 +52,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
private readonly Rectangle bounds;
private readonly ImageFrame<TPixel> source;
private readonly QuantizedFrame<TPixel> quantized;
private readonly IndexedImageFrame<TPixel> quantized;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Rectangle bounds,
ImageFrame<TPixel> source,
QuantizedFrame<TPixel> quantized)
IndexedImageFrame<TPixel> quantized)
{
this.bounds = bounds;
this.source = source;
@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(InliningOptions.ShortMethod)]
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;
int offsetY = this.bounds.Top;
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>
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
private IMemoryOwner<Moment> moments;
private IMemoryOwner<byte> tag;
private IMemoryOwner<TPixel> palette;
private int colors;
private IMemoryOwner<Moment> momentsOwner;
private IMemoryOwner<byte> tagsOwner;
private IMemoryOwner<TPixel> paletteOwner;
private int maxColors;
private readonly Box[] colorCube;
private EuclideanPixelMap<TPixel> pixelMap;
private readonly bool isDithering;
@ -88,12 +88,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Configuration = configuration;
this.Options = options;
this.colors = this.Options.MaxColors;
this.maxColors = this.Options.MaxColors;
this.memoryAllocator = this.Configuration.MemoryAllocator;
this.moments = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean);
this.tag = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean);
this.palette = this.memoryAllocator.Allocate<TPixel>(this.colors, AllocationOptions.Clean);
this.colorCube = new Box[this.colors];
this.momentsOwner = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean);
this.tagsOwner = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean);
this.paletteOwner = this.memoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean);
this.colorCube = new Box[this.maxColors];
this.isDisposed = false;
this.pixelMap = default;
this.isDithering = this.isDithering = !(this.Options.Dither is null);
@ -107,19 +107,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/>
[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);
/// <inheritdoc/>
public ReadOnlySpan<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
public ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
{
this.Build3DHistogram(source, bounds);
this.Get3DMoments(this.memoryAllocator);
this.BuildCube();
ReadOnlySpan<Moment> momentsSpan = this.moments.GetSpan();
Span<TPixel> paletteSpan = this.palette.GetSpan();
for (int k = 0; k < this.colors; k++)
ReadOnlySpan<Moment> momentsSpan = this.momentsOwner.GetSpan();
Span<TPixel> paletteSpan = this.paletteOwner.GetSpan();
for (int k = 0; k < this.maxColors; 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);
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, this.palette.Memory, paletteSpan.Length);
return paletteSpan;
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, this.maxColors);
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
return result;
}
/// <inheritdoc/>
@ -153,9 +153,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
int b = rgba.B >> (8 - IndexBits);
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)];
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);
return index;
}
@ -166,12 +166,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (!this.isDisposed)
{
this.isDisposed = true;
this.moments?.Dispose();
this.tag?.Dispose();
this.palette?.Dispose();
this.moments = null;
this.tag = null;
this.palette = null;
this.momentsOwner?.Dispose();
this.tagsOwner?.Dispose();
this.paletteOwner?.Dispose();
this.momentsOwner = null;
this.tagsOwner = 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>
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
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> area = allocator.Allocate<Moment>(IndexAlphaCount);
Span<Moment> momentSpan = this.moments.GetSpan();
Span<Moment> momentSpan = this.momentsOwner.GetSpan();
Span<Moment> volumeSpan = volume.GetSpan();
Span<Moment> areaSpan = area.GetSpan();
int baseIndex = GetPaletteIndex(1, 0, 0, 0);
@ -426,7 +426,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The <see cref="float"/>.</returns>
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 variance =
@ -467,7 +467,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The <see cref="float"/>.</returns>
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);
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>
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);
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>
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++)
{
@ -620,7 +620,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
private void BuildCube()
{
Span<double> vv = stackalloc double[this.colors];
Span<double> vv = stackalloc double[this.maxColors];
ref Box cube = ref this.colorCube[0];
cube.RMin = cube.GMin = cube.BMin = cube.AMin = 0;
@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
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 currentCube = ref this.colorCube[i];
@ -658,7 +658,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (temp <= 0D)
{
this.colors = i + 1;
this.maxColors = i + 1;
break;
}
}

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

@ -71,10 +71,10 @@ namespace SixLabors.ImageSharp.Tests
foreach (ImageFrame<TPixel> frame in image.Frames)
{
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);
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)
{
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);
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>
{
// Transparent pixels are much more likely to be found at the end of a palette
int index = -1;
Rgba32 trans = default;
ReadOnlySpan<TPixel> paletteSpan = quantized.Palette.Span;
for (int i = paletteSpan.Length - 1; i >= 0; i--)
{
paletteSpan[i].ToRgba32(ref trans);
Span<Rgba32> colorSpan = stackalloc Rgba32[QuantizerConstants.MaxColors].Slice(0, paletteSpan.Length);
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;
}

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

@ -21,13 +21,13 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
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.GetPixelSpan().Length);
Assert.Equal(1, result.GetPixelBufferSpan().Length);
Assert.Equal(Color.Black, (Color)result.Palette.Span[0]);
Assert.Equal(0, result.GetPixelSpan()[0]);
Assert.Equal(0, result.GetPixelBufferSpan()[0]);
}
[Fact]
@ -40,13 +40,13 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
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.GetPixelSpan().Length);
Assert.Equal(1, result.GetPixelBufferSpan().Length);
Assert.Equal(default, result.Palette.Span[0]);
Assert.Equal(0, result.GetPixelSpan()[0]);
Assert.Equal(0, result.GetPixelBufferSpan()[0]);
}
[Fact]
@ -85,19 +85,19 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
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.GetPixelSpan().Length);
Assert.Equal(256, result.GetPixelBufferSpan().Length);
var actualImage = new Image<Rgba32>(1, 256);
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++)
{
Span<Rgba32> row = actualImage.GetPixelRowSpan(y);
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelSpan();
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelBufferSpan();
int yy = y * actualImage.Width;
for (int x = 0; x < actualImage.Width; x++)
@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<TPixel> frame = image.Frames.RootFrame;
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);
}
@ -152,17 +152,17 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
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(256, result.GetPixelSpan().Length);
Assert.Equal(256, result.GetPixelBufferSpan().Length);
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++)
{
Span<Rgba32> row = actualImage.GetPixelRowSpan(y);
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelSpan();
ReadOnlySpan<byte> quantizedPixelSpan = result.GetPixelBufferSpan();
int yy = y * actualImage.Width;
for (int x = 0; x < actualImage.Width; x++)

Loading…
Cancel
Save