diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 6caaa1df0..8a26fb51c 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -384,8 +384,7 @@ namespace SixLabors.ImageSharp.Formats.Png } else { - int stride = this.currentScanline.Length(); - quantized.GetPixelBufferSpan().Slice(row * stride, stride).CopyTo(this.currentScanline.GetSpan()); + quantized.GetPixelRowSpan(row).CopyTo(this.currentScanline.GetSpan()); } break; diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 1f554536c..e0dd4eae1 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -19,7 +18,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering { private readonly DitherProcessor ditherProcessor; private readonly IDither dither; - private IMemoryOwner paletteMemory; + private IMemoryOwner paletteOwner; private bool isDisposed; /// @@ -35,12 +34,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering this.dither = definition.Dither; ReadOnlySpan sourcePalette = definition.Palette.Span; - this.paletteMemory = this.Configuration.MemoryAllocator.Allocate(sourcePalette.Length); - Color.ToPixel(this.Configuration, sourcePalette, this.paletteMemory.Memory.Span); + this.paletteOwner = this.Configuration.MemoryAllocator.Allocate(sourcePalette.Length); + Color.ToPixel(this.Configuration, sourcePalette, this.paletteOwner.Memory.Span); this.ditherProcessor = new DitherProcessor( this.Configuration, - this.paletteMemory.Memory, + this.paletteOwner.Memory, definition.DitherScale); } @@ -59,14 +58,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering return; } + this.isDisposed = true; if (disposing) { - this.paletteMemory?.Dispose(); + this.paletteOwner.Dispose(); } - this.paletteMemory = null; - - this.isDisposed = true; + this.paletteOwner = null; base.Dispose(disposing); } diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs similarity index 77% rename from src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs rename to src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs index 26da6a397..4d75042ea 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs @@ -11,10 +11,28 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// - /// Contains extension methods for frame quantizers. + /// Contains utility methods for instances. /// - public static class FrameQuantizerExtensions + public static class FrameQuantizerUtilities { + /// + /// Helper method for throwing an exception when a frame quantizer palette has + /// been requested but not built yet. + /// + /// The pixel format. + /// The frame quantizer palette. + /// + /// The palette has not been built via + /// + public static void CheckPaletteState(in ReadOnlyMemory palette) + where TPixel : unmanaged, IPixel + { + if (palette.Equals(default)) + { + throw new InvalidOperationException("Frame Quantizer palette has not been built."); + } + } + /// /// Quantizes an image frame and return the resulting output pixels. /// @@ -37,8 +55,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization var interest = Rectangle.Intersect(source.Bounds(), bounds); // Collect the palette. Required before the second pass runs. - ReadOnlyMemory palette = quantizer.BuildPalette(source, interest); - var destination = new IndexedImageFrame(quantizer.Configuration, interest.Width, interest.Height, palette); + quantizer.BuildPalette(source, interest); + + var destination = new IndexedImageFrame( + quantizer.Configuration, + interest.Width, + interest.Height, + quantizer.Palette); if (quantizer.Options.Dither is null) { @@ -67,7 +90,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (dither is null) { - var operation = new RowIntervalOperation(ref quantizer, source, destination, bounds); + var operation = new RowIntervalOperation( + ref quantizer, + source, + destination, + bounds); + ParallelRowIterator.IterateRowIntervals( quantizer.Configuration, bounds, diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs index 64caad3e2..cc87715eb 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -23,13 +23,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// QuantizerOptions Options { get; } + /// + /// Gets the quantized color palette. + /// + /// + /// The palette has not been built via . + /// + ReadOnlyMemory Palette { 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); + void BuildPalette(ImageFrame source, Rectangle bounds); /// /// Quantizes an image frame and return the resulting output pixels. diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 6c31fca7f..433ac4567 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -23,6 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private readonly int maxColors; private readonly Octree octree; private IMemoryOwner paletteOwner; + private ReadOnlyMemory palette; private EuclideanPixelMap pixelMap; private readonly bool isDithering; private bool isDisposed; @@ -44,6 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization 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.palette = default; this.pixelMap = default; this.isDithering = !(this.Options.Dither is null); this.isDisposed = false; @@ -56,13 +58,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public QuantizerOptions Options { get; } /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + public ReadOnlyMemory Palette + { + get + { + FrameQuantizerUtilities.CheckPaletteState(in this.palette); + return this.palette; + } + } /// [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) + public void BuildPalette(ImageFrame source, Rectangle bounds) { using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width); Span bufferSpan = buffer.GetSpan(); @@ -90,9 +97,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors)); this.pixelMap = new EuclideanPixelMap(this.Configuration, result); - return result; + this.palette = result; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + /// [MethodImpl(InliningOptions.ShortMethod)] public readonly byte GetQuantizedColor(TPixel color, out TPixel match) diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index d37116855..ade73e2d0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -43,15 +43,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public QuantizerOptions Options { get; } + /// + public ReadOnlyMemory Palette => this.pixelMap.Palette; + /// [MethodImpl(InliningOptions.ShortMethod)] public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + => FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) - => this.pixelMap.Palette; + public void BuildPalette(ImageFrame source, Rectangle bounds) + { + } /// [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 60f3a0a2a..67a46375d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -69,6 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private IMemoryOwner momentsOwner; private IMemoryOwner tagsOwner; private IMemoryOwner paletteOwner; + private ReadOnlyMemory palette; private int maxColors; private readonly Box[] colorCube; private EuclideanPixelMap pixelMap; @@ -93,6 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization 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.palette = default; this.colorCube = new Box[this.maxColors]; this.isDisposed = false; this.pixelMap = default; @@ -106,12 +108,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public QuantizerOptions Options { get; } /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + public ReadOnlyMemory Palette + { + get + { + FrameQuantizerUtilities.CheckPaletteState(in this.palette); + return this.palette; + } + } /// - public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) + public void BuildPalette(ImageFrame source, Rectangle bounds) { this.Build3DHistogram(source, bounds); this.Get3DMoments(this.memoryAllocator); @@ -134,9 +141,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, this.maxColors); this.pixelMap = new EuclideanPixelMap(this.Configuration, result); - return result; + this.palette = result; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + /// public readonly byte GetQuantizedColor(TPixel color, out TPixel match) {