From a73a63becc3ba40ab76de99c988d46c0b5bc19a3 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 5 Mar 2020 15:16:20 +1100 Subject: [PATCH] Clean up quantized frame API --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 25 ++-- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 22 ++-- .../Formats/Png/PngEncoderOptionsHelpers.cs | 4 +- .../ArrayPoolMemoryAllocator.Buffer{T}.cs | 2 +- .../Processors/Dithering/ErrorDither.cs | 4 +- .../Processors/Dithering/IDither.cs | 2 +- .../Processors/Dithering/OrderedDither.cs | 57 ++++----- .../PaletteDitherProcessor{TPixel}.cs | 4 +- .../Quantization/EuclideanPixelMap{TPixel}.cs | 29 ++--- .../Quantization/FrameQuantizerExtensions.cs | 18 ++- .../Quantization/IFrameQuantizer{TPixel}.cs | 20 +-- .../Quantization/IndexedImageFrame{TPixel}.cs | 115 ++++++++++++++++++ .../OctreeFrameQuantizer{TPixel}.cs | 30 ++--- .../PaletteFrameQuantizer{TPixel}.cs | 6 +- .../Quantization/PaletteQuantizer.cs | 2 +- .../Quantization/QuantizeProcessor{TPixel}.cs | 8 +- .../Quantization/QuantizedFrame{TPixel}.cs | 94 -------------- .../Quantization/WuFrameQuantizer{TPixel}.cs | 68 +++++------ .../Quantization/QuantizedImageTests.cs | 20 +-- .../Quantization/WuQuantizerTests.cs | 30 ++--- 21 files changed, 285 insertions(+), 277 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs delete mode 100644 src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 3d5854ce5..7d2799503 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -337,7 +337,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : unmanaged, IPixel { using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration); - using QuantizedFrame quantized = frameQuantizer.QuantizeFrame(image, image.Bounds()); + using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(image, image.Bounds()); ReadOnlySpan quantizedColors = quantized.Palette.Span; var color = default(Rgba32); diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index dc74353e3..29e2e8fe2 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/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 quantized; + IndexedImageFrame quantized; using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(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(Image image, QuantizedFrame quantized, int transparencyIndex, Stream stream) + private void EncodeGlobal(Image image, IndexedImageFrame quantized, int transparencyIndex, Stream stream) where TPixel : unmanaged, IPixel { // 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(this.configuration, quantized.Palette, quantized.Palette.Span.Length); + pixelMap = new EuclideanPixelMap(this.configuration, quantized.Palette); } using var paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Options, pixelMap); - using QuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using IndexedImageFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); this.WriteImageData(paletteQuantized, stream); } } } - private void EncodeLocal(Image image, QuantizedFrame quantized, Stream stream) + private void EncodeLocal(Image image, IndexedImageFrame quantized, Stream stream) where TPixel : unmanaged, IPixel { ImageFrame previousFrame = null; @@ -214,10 +214,10 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The . /// - private int GetTransparentIndex(QuantizedFrame quantized) + private int GetTransparentIndex(IndexedImageFrame quantized) where TPixel : unmanaged, IPixel { - // 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 /// The pixel format. /// The to encode. /// The stream to write to. - private void WriteColorTable(QuantizedFrame image, Stream stream) + private void WriteColorTable(IndexedImageFrame image, Stream stream) where TPixel : unmanaged, IPixel { // 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.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. /// /// The pixel format. - /// The containing indexed pixels. + /// The containing indexed pixels. /// The stream to write to. - private void WriteImageData(QuantizedFrame image, Stream stream) + private void WriteImageData(IndexedImageFrame image, Stream stream) where TPixel : unmanaged, IPixel { using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth); - encoder.Encode(image.GetPixelSpan(), stream); + encoder.Encode(image.GetPixelBufferSpan(), stream); } } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index ce624f768..6caaa1df0 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/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(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel); - QuantizedFrame quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); + IndexedImageFrame 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 /// The row span. /// The quantized pixels. Can be null. /// The row. - private void CollectPixelBytes(ReadOnlySpan rowSpan, QuantizedFrame quantized, int row) + private void CollectPixelBytes(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) where TPixel : unmanaged, IPixel { 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 /// The quantized pixels. Can be null. /// The row. /// The - private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, QuantizedFrame quantized, int row) + private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) where TPixel : unmanaged, IPixel { this.CollectPixelBytes(rowSpan, quantized, row); @@ -546,17 +546,17 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The containing image data. /// The quantized frame. - private void WritePaletteChunk(Stream stream, QuantizedFrame quantized) + private void WritePaletteChunk(Stream stream, IndexedImageFrame quantized) where TPixel : unmanaged, IPixel { - if (quantized == null) + if (quantized is null) { return; } // Grab the palette and write it to the stream. ReadOnlySpan 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 quantizedSpan = quantized.GetPixelSpan(); + ReadOnlySpan quantizedSpan = quantized.GetPixelBufferSpan(); Rgba32 rgba = default; @@ -783,7 +783,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The image. /// The quantized pixel data. Can be null. /// The stream. - private void WriteDataChunks(ImageFrame pixels, QuantizedFrame quantized, Stream stream) + private void WriteDataChunks(ImageFrame pixels, IndexedImageFrame quantized, Stream stream) where TPixel : unmanaged, IPixel { byte[] buffer; @@ -881,7 +881,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixels. /// The quantized pixels span. /// The deflate stream. - private void EncodePixels(ImageFrame pixels, QuantizedFrame quantized, ZlibDeflateStream deflateStream) + private void EncodePixels(ImageFrame pixels, IndexedImageFrame quantized, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { int bytesPerScanline = this.CalculateScanlineLength(this.width); @@ -960,7 +960,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The type of the pixel. /// The quantized. /// The deflate stream. - private void EncodeAdam7IndexedPixels(QuantizedFrame quantized, ZlibDeflateStream deflateStream) + private void EncodeAdam7IndexedPixels(IndexedImageFrame quantized, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { int width = quantized.Width; diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs index 20b8c41c9..3f490ca6f 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The type of the pixel. /// The options. /// The image. - public static QuantizedFrame CreateQuantizedFrame( + public static IndexedImageFrame CreateQuantizedFrame( PngEncoderOptions options, Image image) where TPixel : unmanaged, IPixel @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Png public static byte CalculateBitDepth( PngEncoderOptions options, Image image, - QuantizedFrame quantizedFrame) + IndexedImageFrame quantizedFrame) where TPixel : unmanaged, IPixel { byte bitDepth; diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs index 7a8b4f8bd..16ca4de67 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Memory /// public override Span GetSpan() { - if (this.Data == null) + if (this.Data is null) { throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer"); } diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 9d0c563da..7d30bada6 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering public void ApplyQuantizationDither( ref TFrameQuantizer quantizer, ImageFrame source, - QuantizedFrame destination, + IndexedImageFrame destination, Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer where TPixel : unmanaged, IPixel @@ -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++) { diff --git a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs index f7057b8f3..8f9d82537 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering void ApplyQuantizationDither( ref TFrameQuantizer quantizer, ImageFrame source, - QuantizedFrame destination, + IndexedImageFrame destination, Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer where TPixel : unmanaged, IPixel; diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 3e25e2a02..6862cff00 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/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( ref TFrameQuantizer quantizer, ImageFrame source, - QuantizedFrame destination, + IndexedImageFrame destination, Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer where TPixel : unmanaged, IPixel { - var ditherOperation = new QuantizeDitherRowIntervalOperation( + var ditherOperation = new QuantizeDitherRowOperation( 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 where TPixel : unmanaged, IPixel { - var ditherOperation = new PaletteDitherRowIntervalOperation( + var ditherOperation = new PaletteDitherRowOperation( 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 : IRowIntervalOperation + private readonly struct QuantizeDitherRowOperation : IRowOperation where TFrameQuantizer : struct, IFrameQuantizer where TPixel : unmanaged, IPixel { private readonly TFrameQuantizer quantizer; private readonly OrderedDither dither; private readonly ImageFrame source; - private readonly QuantizedFrame destination; + private readonly IndexedImageFrame destination; private readonly Rectangle bounds; private readonly int bitDepth; [MethodImpl(InliningOptions.ShortMethod)] - public QuantizeDitherRowIntervalOperation( + public QuantizeDitherRowOperation( ref TFrameQuantizer quantizer, in OrderedDither dither, ImageFrame source, - QuantizedFrame destination, + IndexedImageFrame 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 : IRowIntervalOperation + private readonly struct PaletteDitherRowOperation : IRowOperation where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor where TPixel : unmanaged, IPixel { @@ -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 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); } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 6b5ffabf4..1f554536c 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/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 palette, float ditherScale) { this.Configuration = configuration; - this.pixelMap = new EuclideanPixelMap(configuration, palette, palette.Span.Length); + this.pixelMap = new EuclideanPixelMap(configuration, palette); this.Palette = palette; this.DitherScale = ditherScale; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 84a204bba..775e0aa23 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/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 distanceCache; - private readonly ReadOnlyMemory palette; - private readonly int length; /// /// Initializes a new instance of the struct. /// /// The configuration. /// The color palette to map from. - /// The length of the color palette. [MethodImpl(InliningOptions.ShortMethod)] - public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette, int length) + public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) { - this.palette = palette; - this.length = length; - ReadOnlySpan 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(Environment.ProcessorCount, 31); - PixelOperations.Instance.ToVector4(configuration, paletteSpan, this.vectorCache); + PixelOperations.Instance.ToVector4(configuration, this.Palette.Span, this.vectorCache); } /// - /// Returns the palette span. + /// Gets the color palette of this . + /// The palette memory is owned by the palette source that created it. /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan GetPaletteSpan() => this.palette.Span.Slice(0, this.length); + public ReadOnlyMemory Palette + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } /// /// 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(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); diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs index 88973c44b..26da6a397 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs @@ -24,9 +24,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The source image frame to quantize. /// The bounds within the frame to quantize. /// - /// A representing a quantized version of the source frame pixels. + /// A representing a quantized version of the source frame pixels. /// - public static QuantizedFrame QuantizeFrame( + public static IndexedImageFrame QuantizeFrame( ref TFrameQuantizer quantizer, ImageFrame 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 palette = quantizer.BuildPalette(source, interest); - MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator; - - var destination = new QuantizedFrame(memoryAllocator, interest.Width, interest.Height, palette); + ReadOnlyMemory palette = quantizer.BuildPalette(source, interest); + var destination = new IndexedImageFrame(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( ref TFrameQuantizer quantizer, ImageFrame source, - QuantizedFrame destination, + IndexedImageFrame destination, Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer where TPixel : unmanaged, IPixel @@ -87,14 +85,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { private readonly TFrameQuantizer quantizer; private readonly ImageFrame source; - private readonly QuantizedFrame destination; + private readonly IndexedImageFrame destination; private readonly Rectangle bounds; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperation( ref TFrameQuantizer quantizer, ImageFrame source, - QuantizedFrame destination, + IndexedImageFrame 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 sourceRow = this.source.GetPixelRowSpan(y); - Span destinationRow = this.destination.GetPixelRowSpan(y - offsetY); + Span destinationRow = this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY); for (int x = this.bounds.Left; x < this.bounds.Right; x++) { diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs index d49852cf1..64caad3e2 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -23,23 +23,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// QuantizerOptions Options { get; } + /// + /// Builds the quantized palette from the given image frame and bounds. + /// + /// The source image frame. + /// The region of interest bounds. + /// The palette. + ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds); + /// /// Quantizes an image frame and return the resulting output pixels. /// /// The source image frame to quantize. /// The bounds within the frame to quantize. /// - /// A representing a quantized version of the source frame pixels. + /// A representing a quantized version of the source frame pixels. /// - QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds); - - /// - /// Builds the quantized palette from the given image frame and bounds. - /// - /// The source image frame. - /// The region of interest bounds. - /// The palette. - ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds); + IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds); /// /// Returns the index and color from the quantized palette corresponding to the given color. diff --git a/src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs new file mode 100644 index 000000000..42aadb5fd --- /dev/null +++ b/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 +{ + /// + /// A pixel-specific image frame where each pixel buffer value represents an index in a color palette. + /// + /// The pixel format. + public sealed class IndexedImageFrame : IDisposable + where TPixel : unmanaged, IPixel + { + private IMemoryOwner pixelsOwner; + private IMemoryOwner paletteOwner; + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The configuration which allows altering default behaviour or extending the library. + /// + /// The frame width. + /// The frame height. + /// The color palette. + internal IndexedImageFrame(Configuration configuration, int width, int height, ReadOnlyMemory 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(palette.Length); + palette.Span.CopyTo(this.paletteOwner.GetSpan()); + this.Palette = this.paletteOwner.Memory.Slice(0, palette.Length); + } + + /// + /// Gets the configuration which allows altering default behaviour or extending the library. + /// + public Configuration Configuration { get; } + + /// + /// Gets the width of this . + /// + public int Width { get; } + + /// + /// Gets the height of this . + /// + public int Height { get; } + + /// + /// Gets the color palette of this . + /// + public ReadOnlyMemory Palette { get; } + + /// + /// Gets the pixels of this . + /// + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public ReadOnlySpan GetPixelBufferSpan() => this.pixelsOwner.GetSpan(); // TODO: Buffer2D + + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// The row index in the pixel buffer. + /// The pixel row as a . + [MethodImpl(InliningOptions.ShortMethod)] + public ReadOnlySpan GetPixelRowSpan(int rowIndex) + => this.GetWritablePixelRowSpanUnsafe(rowIndex); + + /// + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// + /// 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. + /// + /// + /// The row index in the pixel buffer. + /// The pixel row as a . + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetWritablePixelRowSpanUnsafe(int rowIndex) + => this.pixelsOwner.GetSpan().Slice(rowIndex * this.Width, this.Width); + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.isDisposed = true; + this.pixelsOwner.Dispose(); + this.paletteOwner.Dispose(); + this.pixelsOwner = null; + this.paletteOwner = null; + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index cc6a3a485..6c31fca7f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public struct OctreeFrameQuantizer : IFrameQuantizer where TPixel : unmanaged, IPixel { - private readonly int colors; + private readonly int maxColors; private readonly Octree octree; - private IMemoryOwner palette; + private IMemoryOwner paletteOwner; private EuclideanPixelMap 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(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(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 /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds) + public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) { using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width); Span bufferSpan = buffer.GetSpan(); @@ -82,15 +82,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } } - Span paletteSpan = this.palette.GetSpan(); + Span 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(this.Configuration, this.palette.Memory, paletteSpan.Length); + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors)); + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); - return paletteSpan; + return result; } /// @@ -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; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index a9a938562..d37116855 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -45,13 +45,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds) - => this.pixelMap.GetPaletteSpan(); + public readonly ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) + => this.pixelMap.Palette; /// [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index e856c389c..c14ea6153 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/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(configuration, palette, length); + var pixelMap = new EuclideanPixelMap(configuration, palette); return new PaletteFrameQuantizer(configuration, options, pixelMap); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index cbef19300..4583b7cff 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Configuration configuration = this.Configuration; using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(configuration); - using QuantizedFrame quantized = frameQuantizer.QuantizeFrame(source, interest); + using IndexedImageFrame 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 source; - private readonly QuantizedFrame quantized; + private readonly IndexedImageFrame quantized; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperation( Rectangle bounds, ImageFrame source, - QuantizedFrame quantized) + IndexedImageFrame 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 quantizedPixelSpan = this.quantized.GetPixelSpan(); + ReadOnlySpan quantizedPixelSpan = this.quantized.GetPixelBufferSpan(); ReadOnlySpan paletteSpan = this.quantized.Palette.Span; int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs deleted file mode 100644 index d5facbe63..000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ /dev/null @@ -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 -{ - /// - /// Represents a quantized image frame where the pixels indexed by a color palette. - /// - /// The pixel format. - public sealed class QuantizedFrame : IDisposable - where TPixel : unmanaged, IPixel - { - private IMemoryOwner palette; - private IMemoryOwner pixels; - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// Used to allocated memory for image processing operations. - /// The image width. - /// The image height. - /// The color palette. - internal QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, ReadOnlySpan 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(palette.Length); - palette.CopyTo(this.palette.GetSpan()); - } - - /// - /// Gets the width of this . - /// - public int Width { get; } - - /// - /// Gets the height of this . - /// - public int Height { get; } - - /// - /// Gets the color palette of this . - /// - public ReadOnlyMemory Palette - { - [MethodImpl(InliningOptions.ShortMethod)] - get { return this.palette.Memory; } - } - - /// - /// Gets the pixels of this . - /// - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public Span GetPixelSpan() => this.pixels.GetSpan(); - - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the the first pixel on that row. - /// - /// The row. - /// The pixel row as a . - [MethodImpl(InliningOptions.ShortMethod)] - public Span GetPixelRowSpan(int rowIndex) - => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width); - - /// - public void Dispose() - { - if (!this.isDisposed) - { - return; - } - - this.isDisposed = true; - this.pixels?.Dispose(); - this.palette?.Dispose(); - this.pixels = null; - this.palette = null; - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index f50282f9a..60f3a0a2a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -66,10 +66,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; - private IMemoryOwner moments; - private IMemoryOwner tag; - private IMemoryOwner palette; - private int colors; + private IMemoryOwner momentsOwner; + private IMemoryOwner tagsOwner; + private IMemoryOwner paletteOwner; + private int maxColors; private readonly Box[] colorCube; private EuclideanPixelMap 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(TableLength, AllocationOptions.Clean); - this.tag = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.palette = this.memoryAllocator.Allocate(this.colors, AllocationOptions.Clean); - this.colorCube = new Box[this.colors]; + this.momentsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.tagsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.paletteOwner = this.memoryAllocator.Allocate(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 /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// - public ReadOnlySpan BuildPalette(ImageFrame source, Rectangle bounds) + public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) { this.Build3DHistogram(source, bounds); this.Get3DMoments(this.memoryAllocator); this.BuildCube(); - ReadOnlySpan momentsSpan = this.moments.GetSpan(); - Span paletteSpan = this.palette.GetSpan(); - for (int k = 0; k < this.colors; k++) + ReadOnlySpan momentsSpan = this.momentsOwner.GetSpan(); + Span 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(this.Configuration, this.palette.Memory, paletteSpan.Length); - return paletteSpan; + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, this.maxColors); + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + return result; } /// @@ -153,9 +153,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization int b = rgba.B >> (8 - IndexBits); int a = rgba.A >> (8 - IndexAlphaBits); - ReadOnlySpan tagSpan = this.tag.GetSpan(); + ReadOnlySpan 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 /// The bounds within the source image to quantize. private void Build3DHistogram(ImageFrame source, Rectangle bounds) { - Span momentSpan = this.moments.GetSpan(); + Span momentSpan = this.momentsOwner.GetSpan(); // Build up the 3-D color histogram using IMemoryOwner buffer = this.memoryAllocator.Allocate(bounds.Width); @@ -384,7 +384,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization using IMemoryOwner volume = allocator.Allocate(IndexCount * IndexAlphaCount); using IMemoryOwner area = allocator.Allocate(IndexAlphaCount); - Span momentSpan = this.moments.GetSpan(); + Span momentSpan = this.momentsOwner.GetSpan(); Span volumeSpan = volume.GetSpan(); Span areaSpan = area.GetSpan(); int baseIndex = GetPaletteIndex(1, 0, 0, 0); @@ -426,7 +426,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The . private double Variance(ref Box cube) { - ReadOnlySpan momentSpan = this.moments.GetSpan(); + ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); Moment volume = Volume(ref cube, momentSpan); Moment variance = @@ -467,7 +467,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The . private float Maximize(ref Box cube, int direction, int first, int last, out int cut, Moment whole) { - ReadOnlySpan momentSpan = this.moments.GetSpan(); + ReadOnlySpan 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 a value indicating whether the box has been split. private bool Cut(ref Box set1, ref Box set2) { - ReadOnlySpan momentSpan = this.moments.GetSpan(); + ReadOnlySpan 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 /// A label. private void Mark(ref Box cube, byte label) { - Span tagSpan = this.tag.GetSpan(); + Span tagSpan = this.tagsOwner.GetSpan(); for (int r = cube.RMin + 1; r <= cube.RMax; r++) { @@ -620,7 +620,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private void BuildCube() { - Span vv = stackalloc double[this.colors]; + Span 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; } } diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index cd93ab0cf..7e4eced8f 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -71,10 +71,10 @@ namespace SixLabors.ImageSharp.Tests foreach (ImageFrame frame in image.Frames) { using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(this.Configuration)) - using (QuantizedFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + using (IndexedImageFrame 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 frame in image.Frames) { using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(this.Configuration)) - using (QuantizedFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + using (IndexedImageFrame 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(QuantizedFrame quantized) + private int GetTransparentIndex(IndexedImageFrame quantized) where TPixel : unmanaged, IPixel { // Transparent pixels are much more likely to be found at the end of a palette int index = -1; - Rgba32 trans = default; ReadOnlySpan paletteSpan = quantized.Palette.Span; - for (int i = paletteSpan.Length - 1; i >= 0; i--) - { - paletteSpan[i].ToRgba32(ref trans); + Span colorSpan = stackalloc Rgba32[QuantizerConstants.MaxColors].Slice(0, paletteSpan.Length); - if (trans.Equals(default)) + PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, colorSpan); + for (int i = colorSpan.Length - 1; i >= 0; i--) + { + if (colorSpan[i].Equals(default)) { index = i; } diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index f3bcd0b95..2a0a02d94 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -21,13 +21,13 @@ namespace SixLabors.ImageSharp.Tests.Quantization ImageFrame frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); - using QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using IndexedImageFrame 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 frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); - using QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using IndexedImageFrame 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 frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); - using QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using IndexedImageFrame 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(1, 256); ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = result.Palette.Length - 1; + int paletteCount = paletteSpan.Length - 1; for (int y = 0; y < actualImage.Height; y++) { Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); + ReadOnlySpan 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 frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); - using QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); Assert.Equal(48, result.Palette.Length); } @@ -152,17 +152,17 @@ namespace SixLabors.ImageSharp.Tests.Quantization ImageFrame frame = image.Frames.RootFrame; using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) - using (QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + using (IndexedImageFrame 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 paletteSpan = result.Palette.Span; - int paletteCount = result.Palette.Length - 1; + int paletteCount = paletteSpan.Length - 1; for (int y = 0; y < actualImage.Height; y++) { Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); + ReadOnlySpan quantizedPixelSpan = result.GetPixelBufferSpan(); int yy = y * actualImage.Width; for (int x = 0; x < actualImage.Width; x++)