From 1ac9de53ffce22dc593572e77496e0761b412317 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 16 Oct 2022 20:46:33 +1000 Subject: [PATCH] Convert TiffEncoder, Use sampling strategy for local palette building --- src/ImageSharp/Advanced/AotCompilerTools.cs | 15 +++-- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 5 +- src/ImageSharp/Formats/IEncoderOptions.cs | 4 +- src/ImageSharp/Formats/ImageEncoder.cs | 2 +- src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs | 4 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 6 +- .../Formats/Tiff/ITiffEncoderOptions.cs | 46 -------------- src/ImageSharp/Formats/Tiff/TiffEncoder.cs | 61 ++++++++++--------- .../Formats/Tiff/TiffEncoderCore.cs | 21 +++++-- .../Tiff/Writers/TiffColorWriterFactory.cs | 3 +- .../Tiff/Writers/TiffPaletteWriter{TPixel}.cs | 34 ++++++----- .../DefaultPixelSamplingStrategy.cs | 50 +++++++++++++++ .../ExtensivePixelSamplingStrategy.cs | 7 +++ .../Quantization/IPixelSamplingStrategy.cs | 13 +++- .../Quantization/QuantizerUtilities.cs | 32 +++++++++- .../Formats/Gif/GifEncoderTests.cs | 2 +- .../Formats/Tiff/TiffEncoderHeaderTests.cs | 23 +++---- 18 files changed, 200 insertions(+), 130 deletions(-) delete mode 100644 src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 9c285e21de..0cf28c6bb2 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -57,6 +57,9 @@ internal static class AotCompilerTools /// If you are getting the above error, you need to call this method, which will pre-seed the AoT compiler with the /// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!! /// + /// + /// This method is used for AOT code generation only. Do not call it at runtime. + /// [Preserve] private static void SeedPixelFormats() { @@ -487,8 +490,10 @@ internal static class AotCompilerTools private static void AotCompilePixelSamplingStrategys() where TPixel : unmanaged, IPixel { - default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default); - default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default); + default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default(Image)); + default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame)); + default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(Image)); + default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame)); } /// @@ -513,13 +518,13 @@ internal static class AotCompilerTools where TPixel : unmanaged, IPixel where TDither : struct, IDither { - var octree = default(OctreeQuantizer); + OctreeQuantizer octree = default; default(TDither).ApplyQuantizationDither, TPixel>(ref octree, default, default, default); - var palette = default(PaletteQuantizer); + PaletteQuantizer palette = default; default(TDither).ApplyQuantizationDither, TPixel>(ref palette, default, default, default); - var wu = default(WuQuantizer); + WuQuantizer wu = default; default(TDither).ApplyQuantizationDither, TPixel>(ref wu, default, default, default); default(TDither).ApplyPaletteDither.DitherProcessor, TPixel>(default, default, default); } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 98d8b6233f..a4e1f8eef9 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -106,7 +106,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals this.memoryAllocator = memoryAllocator; this.bitsPerPixel = encoder.BitsPerPixel; this.quantizer = encoder.Quantizer; - this.pixelSamplingStrategy = encoder.GlobalPixelSamplingStrategy; + this.pixelSamplingStrategy = encoder.PixelSamplingStrategy; this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3; } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index c0eb160eb3..14d20cf909 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -70,7 +70,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals this.skipMetadata = encoder.SkipMetadata; this.quantizer = encoder.Quantizer; this.colorTableMode = encoder.ColorTableMode; - this.pixelSamplingStrategy = encoder.GlobalPixelSamplingStrategy; + this.pixelSamplingStrategy = encoder.PixelSamplingStrategy; } /// @@ -103,7 +103,8 @@ internal sealed class GifEncoderCore : IImageEncoderInternals } else { - quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds()); + frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image.Frames.RootFrame); + quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); } } diff --git a/src/ImageSharp/Formats/IEncoderOptions.cs b/src/ImageSharp/Formats/IEncoderOptions.cs index 5e627f02b3..1f8337f0a2 100644 --- a/src/ImageSharp/Formats/IEncoderOptions.cs +++ b/src/ImageSharp/Formats/IEncoderOptions.cs @@ -27,7 +27,7 @@ public interface IQuantizingEncoderOptions : IEncoderOptions IQuantizer Quantizer { get; init; } /// - /// Gets the used for quantization when building a global color palette. + /// Gets the used for quantization when building color palettes. /// - IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; init; } + IPixelSamplingStrategy PixelSamplingStrategy { get; init; } } diff --git a/src/ImageSharp/Formats/ImageEncoder.cs b/src/ImageSharp/Formats/ImageEncoder.cs index ffd87a31bf..074e803958 100644 --- a/src/ImageSharp/Formats/ImageEncoder.cs +++ b/src/ImageSharp/Formats/ImageEncoder.cs @@ -33,5 +33,5 @@ public abstract class QuantizingImageEncoder : ImageEncoder, IQuantizingEncoderO public IQuantizer Quantizer { get; init; } = KnownQuantizers.Octree; /// - public IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy(); + public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy(); } diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs index 8595498ce3..7c7860c58e 100644 --- a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs @@ -65,7 +65,7 @@ internal sealed class PbmEncoderCore : IImageEncoderInternals Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - this.DeduceOptions(image); + this.SanitizeAndSetEncoderOptions(image); byte signature = this.DeduceSignature(); this.WriteHeader(stream, signature, image.Size()); @@ -75,7 +75,7 @@ internal sealed class PbmEncoderCore : IImageEncoderInternals stream.Flush(); } - private void DeduceOptions(Image image) + private void SanitizeAndSetEncoderOptions(Image image) where TPixel : unmanaged, IPixel { this.configuration = image.GetConfiguration(); diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 4267580dec..70dd73b938 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -149,7 +149,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable ImageMetadata metadata = image.Metadata; PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); - this.DeduceOptions(this.encoder, pngMetadata, out this.use16Bit, out this.bytesPerPixel); + this.SanitizeAndSetEncoderOptions(this.encoder, pngMetadata, out this.use16Bit, out this.bytesPerPixel); Image clonedImage = null; bool clearTransparency = this.encoder.TransparentColorMode == PngTransparentColorMode.Clear; if (clearTransparency) @@ -1228,7 +1228,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// The PNG metadata. /// if set to true [use16 bit]. /// The bytes per pixel. - private void DeduceOptions( + private void SanitizeAndSetEncoderOptions( PngEncoder encoder, PngMetadata pngMetadata, out bool use16Bit, @@ -1298,7 +1298,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable // Create quantized frame returning the palette and set the bit depth. using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(image.GetConfiguration()); - frameQuantizer.BuildPalette(encoder.GlobalPixelSamplingStrategy, image); + frameQuantizer.BuildPalette(encoder.PixelSamplingStrategy, image); return frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); } diff --git a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs deleted file mode 100644 index 1e74e630ce..0000000000 --- a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.Processing.Processors.Quantization; - -namespace SixLabors.ImageSharp.Formats.Tiff; - -/// -/// Encapsulates the options for the . -/// -internal interface ITiffEncoderOptions -{ - /// - /// Gets the number of bits per pixel. - /// - TiffBitsPerPixel? BitsPerPixel { get; } - - /// - /// Gets the compression type to use. - /// - TiffCompression? Compression { get; } - - /// - /// Gets the compression level 1-9 for the deflate compression mode. - /// Defaults to . - /// - DeflateCompressionLevel? CompressionLevel { get; } - - /// - /// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor. - /// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used. - /// - TiffPhotometricInterpretation? PhotometricInterpretation { get; } - - /// - /// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression. - /// - TiffPredictor? HorizontalPredictor { get; } - - /// - /// Gets the quantizer for creating a color palette image. - /// - IQuantizer Quantizer { get; } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs index 3b5d347722..e7bb08cdc3 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs @@ -4,47 +4,52 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Tiff.Constants; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Tiff; /// /// Encoder for writing the data image to a stream in TIFF format. /// -public class TiffEncoder : IImageEncoder, ITiffEncoderOptions +public class TiffEncoder : QuantizingImageEncoder { - /// - public TiffBitsPerPixel? BitsPerPixel { get; set; } - - /// - public TiffCompression? Compression { get; set; } - - /// - public DeflateCompressionLevel? CompressionLevel { get; set; } - - /// - public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; } - - /// - public TiffPredictor? HorizontalPredictor { get; set; } - - /// - public IQuantizer Quantizer { get; set; } - - /// - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel + /// + /// Gets the number of bits per pixel. + /// + public TiffBitsPerPixel? BitsPerPixel { get; init; } + + /// + /// Gets the compression type to use. + /// + public TiffCompression? Compression { get; init; } + + /// + /// Gets the compression level 1-9 for the deflate compression mode. + /// Defaults to . + /// + public DeflateCompressionLevel? CompressionLevel { get; init; } + + /// + /// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor. + /// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used. + /// + public TiffPhotometricInterpretation? PhotometricInterpretation { get; init; } + + /// + /// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression. + /// + public TiffPredictor? HorizontalPredictor { get; init; } + + /// + public override void Encode(Image image, Stream stream) { - var encode = new TiffEncoderCore(this, image.GetMemoryAllocator()); + TiffEncoderCore encode = new(this, image.GetMemoryAllocator()); encode.Encode(image, stream); } /// - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) { - var encoder = new TiffEncoderCore(this, image.GetMemoryAllocator()); + TiffEncoderCore encoder = new(this, image.GetMemoryAllocator()); return encoder.EncodeAsync(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 9b50b958c8..849eb69aeb 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -10,7 +10,6 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Tiff; @@ -40,10 +39,15 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals private Configuration configuration; /// - /// The quantizer for creating color palette image. + /// The quantizer for creating color palette images. /// private readonly IQuantizer quantizer; + /// + /// The pixel sampling strategy for quantization. + /// + private readonly IPixelSamplingStrategy pixelSamplingStrategy; + /// /// Sets the deflate compression level. /// @@ -76,11 +80,12 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals /// /// The options for the encoder. /// The memory allocator. - public TiffEncoderCore(ITiffEncoderOptions options, MemoryAllocator memoryAllocator) + public TiffEncoderCore(TiffEncoder options, MemoryAllocator memoryAllocator) { this.memoryAllocator = memoryAllocator; this.PhotometricInterpretation = options.PhotometricInterpretation; - this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; + this.quantizer = options.Quantizer; + this.pixelSamplingStrategy = options.PixelSamplingStrategy; this.BitsPerPixel = options.BitsPerPixel; this.HorizontalPredictor = options.HorizontalPredictor; this.CompressionType = options.Compression; @@ -215,6 +220,7 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals this.PhotometricInterpretation, frame, this.quantizer, + this.pixelSamplingStrategy, this.memoryAllocator, this.configuration, entriesCollector, @@ -331,7 +337,12 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals return nextIfdMarker; } - private void SanitizeAndSetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, int inputBitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor) + private void SanitizeAndSetEncoderOptions( + TiffBitsPerPixel? bitsPerPixel, + int inputBitsPerPixel, + TiffPhotometricInterpretation? photometricInterpretation, + TiffCompression compression, + TiffPredictor predictor) { // BitsPerPixel should be the primary source of truth for the encoder options. if (bitsPerPixel.HasValue) diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs index bb13137cef..a52d49a353 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs @@ -14,6 +14,7 @@ internal static class TiffColorWriterFactory TiffPhotometricInterpretation? photometricInterpretation, ImageFrame image, IQuantizer quantizer, + IPixelSamplingStrategy pixelSamplingStrategy, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector, @@ -23,7 +24,7 @@ internal static class TiffColorWriterFactory switch (photometricInterpretation) { case TiffPhotometricInterpretation.PaletteColor: - return new TiffPaletteWriter(image, quantizer, memoryAllocator, configuration, entriesCollector, bitsPerPixel); + return new TiffPaletteWriter(image, quantizer, pixelSamplingStrategy, memoryAllocator, configuration, entriesCollector, bitsPerPixel); case TiffPhotometricInterpretation.BlackIsZero: case TiffPhotometricInterpretation.WhiteIsZero: if (bitsPerPixel == 1) diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs index f8810d65ac..87118d6f76 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs @@ -17,19 +17,21 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter private readonly int maxColors; private readonly int colorPaletteSize; private readonly int colorPaletteBytes; - private readonly IndexedImageFrame quantizedImage; + private readonly IndexedImageFrame quantizedFrame; private IMemoryOwner indexedPixelsBuffer; public TiffPaletteWriter( - ImageFrame image, + ImageFrame frame, IQuantizer quantizer, + IPixelSamplingStrategy pixelSamplingStrategy, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector, int bitsPerPixel) - : base(image, memoryAllocator, configuration, entriesCollector) + : base(frame, memoryAllocator, configuration, entriesCollector) { DebugGuard.NotNull(quantizer, nameof(quantizer)); + DebugGuard.NotNull(quantizer, nameof(pixelSamplingStrategy)); DebugGuard.NotNull(configuration, nameof(configuration)); DebugGuard.NotNull(entriesCollector, nameof(entriesCollector)); DebugGuard.MustBeBetweenOrEqualTo(bitsPerPixel, 4, 8, nameof(bitsPerPixel)); @@ -38,11 +40,15 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter this.maxColors = this.BitsPerPixel == 4 ? 16 : 256; this.colorPaletteSize = this.maxColors * 3; this.colorPaletteBytes = this.colorPaletteSize * 2; - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration, new QuantizerOptions() - { - MaxColors = this.maxColors - }); - this.quantizedImage = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer( + this.Configuration, + new QuantizerOptions() + { + MaxColors = this.maxColors + }); + + frameQuantizer.BuildPalette(pixelSamplingStrategy, frame); + this.quantizedFrame = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); this.AddColorMapTag(); } @@ -66,7 +72,7 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter int lastRow = y + height; for (int row = y; row < lastRow; row++) { - ReadOnlySpan indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row); + ReadOnlySpan indexedPixelRow = this.quantizedFrame.DangerousGetRowSpan(row); int idxPixels = 0; for (int x = 0; x < halfWidth; x++) { @@ -93,7 +99,7 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter int indexedPixelsRowIdx = 0; for (int row = y; row < lastRow; row++) { - ReadOnlySpan indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row); + ReadOnlySpan indexedPixelRow = this.quantizedFrame.DangerousGetRowSpan(row); indexedPixelRow.CopyTo(indexedPixels.Slice(indexedPixelsRowIdx * width, width)); indexedPixelsRowIdx++; } @@ -105,7 +111,7 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter /// protected override void Dispose(bool disposing) { - this.quantizedImage?.Dispose(); + this.quantizedFrame?.Dispose(); this.indexedPixelsBuffer?.Dispose(); } @@ -114,7 +120,7 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.Allocate(this.colorPaletteBytes); Span colorPalette = colorPaletteBuffer.GetSpan(); - ReadOnlySpan quantizedColors = this.quantizedImage.Palette.Span; + ReadOnlySpan quantizedColors = this.quantizedFrame.Palette.Span; int quantizedColorBytes = quantizedColors.Length * 3 * 2; // In the ColorMap, black is represented by 0, 0, 0 and white is represented by 65535, 65535, 65535. @@ -126,7 +132,7 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter // In a TIFF ColorMap, all the Red values come first, followed by the Green values, // then the Blue values. Convert the quantized palette to this format. - var palette = new ushort[this.colorPaletteSize]; + ushort[] palette = new ushort[this.colorPaletteSize]; int paletteIdx = 0; for (int i = 0; i < quantizedColors.Length; i++) { @@ -147,7 +153,7 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter palette[paletteIdx++] = quantizedColorRgb48[i].B; } - var colorMap = new ExifShortArray(ExifTagValue.ColorMap) + ExifShortArray colorMap = new(ExifTagValue.ColorMap) { Value = palette }; diff --git a/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs index de36d1592a..5c387949e5 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs @@ -101,4 +101,54 @@ public class DefaultPixelSamplingStrategy : IPixelSamplingStrategy } } } + + /// + public IEnumerable> EnumeratePixelRegions(ImageFrame frame) + where TPixel : unmanaged, IPixel + { + long maximumPixels = Math.Min(this.MaximumPixels, (long)frame.Width * frame.Height); + long maxNumberOfRows = maximumPixels / frame.Width; + long totalNumberOfRows = frame.Height; + + if (totalNumberOfRows <= maxNumberOfRows) + { + yield return frame.PixelBuffer.GetRegion(); + } + else + { + double r = maxNumberOfRows / (double)totalNumberOfRows; + + // Use a rough approximation to make sure we don't leave out large contiguous regions: + if (maxNumberOfRows > 200) + { + r = Math.Round(r, 2); + } + else + { + r = Math.Round(r, 1); + } + + r = Math.Max(this.MinimumScanRatio, r); // always visit the minimum defined portion of the image. + + Rational ratio = new(r); + + int denom = (int)ratio.Denominator; + int num = (int)ratio.Numerator; + + for (int pos = 0; pos < totalNumberOfRows; pos++) + { + int subPos = pos % denom; + if (subPos < num) + { + yield return GetRow(pos); + } + } + + Buffer2DRegion GetRow(int pos) + { + int y = pos % frame.Height; + return frame.PixelBuffer.GetRegion(0, y, frame.Width, 1); + } + } + } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs index 580227c2d7..150f785b38 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs @@ -20,4 +20,11 @@ public class ExtensivePixelSamplingStrategy : IPixelSamplingStrategy yield return frame.PixelBuffer.GetRegion(); } } + + /// + public IEnumerable> EnumeratePixelRegions(ImageFrame frame) + where TPixel : unmanaged, IPixel + { + yield return frame.PixelBuffer.GetRegion(); + } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs index ab118b55d4..55d56679ed 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs @@ -7,16 +7,25 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization; /// -/// Provides an abstraction to enumerate pixel regions within a multi-framed . +/// Provides an abstraction to enumerate pixel regions for sampling within . /// public interface IPixelSamplingStrategy { /// - /// Enumerates pixel regions within the image as . + /// Enumerates pixel regions for all frames within the image as . /// /// The image. /// The pixel type. /// An enumeration of pixel regions. IEnumerable> EnumeratePixelRegions(Image image) where TPixel : unmanaged, IPixel; + + /// + /// Enumerates pixel regions within a single image frame as . + /// + /// The image frame. + /// The pixel type. + /// An enumeration of pixel regions. + IEnumerable> EnumeratePixelRegions(ImageFrame frame) + where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs index 63094287c7..167cf91282 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -99,13 +99,39 @@ public static class QuantizerUtilities return destination; } - internal static void BuildPalette( + /// + /// Adds colors to the quantized palette from the given pixel regions. + /// + /// The pixel format. + /// The pixel specific quantizer. + /// The pixel sampling strategy. + /// The source image to sample from. + public static void BuildPalette( + this IQuantizer quantizer, + IPixelSamplingStrategy pixelSamplingStrategy, + Image source) + where TPixel : unmanaged, IPixel + { + foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) + { + quantizer.AddPaletteColors(region); + } + } + + /// + /// Adds colors to the quantized palette from the given pixel regions. + /// + /// The pixel format. + /// The pixel specific quantizer. + /// The pixel sampling strategy. + /// The source image frame to sample from. + public static void BuildPalette( this IQuantizer quantizer, IPixelSamplingStrategy pixelSamplingStrategy, - Image image) + ImageFrame source) where TPixel : unmanaged, IPixel { - foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(image)) + foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(source)) { quantizer.AddPaletteColors(region); } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 83b8a88695..18eb7708ca 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -144,7 +144,7 @@ public class GifEncoderTests GifEncoder encoder = new() { ColorTableMode = GifColorTableMode.Global, - GlobalPixelSamplingStrategy = new DefaultPixelSamplingStrategy(maxPixels, scanRatio) + PixelSamplingStrategy = new DefaultPixelSamplingStrategy(maxPixels, scanRatio) }; string testOutputFile = provider.Utility.SaveTestOutputFile( diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs index 95ed17f1c7..7907597854 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs @@ -3,24 +3,21 @@ using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Writers; -using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests.Formats.Tiff; [Trait("Format", "Tiff")] public class TiffEncoderHeaderTests { - private static readonly MemoryAllocator MemoryAllocator = MemoryAllocator.Create(); - private static readonly Configuration Configuration = Configuration.Default; - private static readonly ITiffEncoderOptions Options = new TiffEncoder(); + private static readonly TiffEncoder Encoder = new(); [Fact] public void WriteHeader_WritesValidHeader() { - using var stream = new MemoryStream(); - var encoder = new TiffEncoderCore(Options, MemoryAllocator); + using MemoryStream stream = new(); + TiffEncoderCore encoder = new(Encoder, Configuration.Default.MemoryAllocator); - using (var writer = new TiffStreamWriter(stream)) + using (TiffStreamWriter writer = new(stream)) { long firstIfdMarker = TiffEncoderCore.WriteHeader(writer); } @@ -31,13 +28,11 @@ public class TiffEncoderHeaderTests [Fact] public void WriteHeader_ReturnsFirstIfdMarker() { - using var stream = new MemoryStream(); - var encoder = new TiffEncoderCore(Options, MemoryAllocator); + using MemoryStream stream = new(); + TiffEncoderCore encoder = new(Encoder, Configuration.Default.MemoryAllocator); - using (var writer = new TiffStreamWriter(stream)) - { - long firstIfdMarker = TiffEncoderCore.WriteHeader(writer); - Assert.Equal(4, firstIfdMarker); - } + using TiffStreamWriter writer = new(stream); + long firstIfdMarker = TiffEncoderCore.WriteHeader(writer); + Assert.Equal(4, firstIfdMarker); } }