From ad8d7757b410eeadbcd5bcec6fb9d05712e24069 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 20 Feb 2020 10:48:42 +1100 Subject: [PATCH] Refactor to inline based on feedback. --- src/ImageSharp/Advanced/AotCompilerTools.cs | 12 +- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 20 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 14 +- .../Formats/Png/PngEncoderOptionsHelpers.cs | 4 +- .../Extensions/Dithering/DitherExtensions.cs | 8 +- src/ImageSharp/Processing/KnownDitherings.cs | 26 +- .../Processors/Dithering/AtkinsonDither.cs | 34 -- .../Processors/Dithering/BayerDither2x2.cs | 19 -- .../Processors/Dithering/BayerDither4x4.cs | 19 -- .../Processors/Dithering/BayerDither8x8.cs | 19 -- .../Processors/Dithering/BurksDither.cs | 33 -- .../Processors/Dithering/DitherType.cs | 21 -- .../Dithering/ErroDither.KnownTypes.cs | 188 +++++++++++ .../Processors/Dithering/ErrorDither.cs | 84 ++++- .../Dithering/FloydSteinbergDither.cs | 33 -- .../Processors/Dithering/IDither.cs | 50 +-- .../Dithering/JarvisJudiceNinkeDither.cs | 34 -- .../Dithering/OrderedDither.KnownTypes.cs | 31 ++ .../Processors/Dithering/OrderedDither.cs | 172 +++++++++- .../Processors/Dithering/OrderedDither3x3.cs | 19 -- .../PaletteDitherProcessor{TPixel}.cs | 90 +---- .../Processors/Dithering/PixelPair.cs | 48 --- .../Processors/Dithering/Sierra2Dither.cs | 33 -- .../Processors/Dithering/Sierra3Dither.cs | 34 -- .../Processors/Dithering/SierraLiteDither.cs | 33 -- .../Dithering/StevensonArceDither.cs | 34 -- .../Processors/Dithering/StuckiDither.cs | 34 -- .../EuclideanPixelMap{TPixel}.cs | 51 ++- .../Quantization/FrameQuantizerExtensions.cs | 136 ++++++++ .../Quantization/FrameQuantizer{TPixel}.cs | 308 ------------------ .../Quantization/IFrameQuantizer{TPixel}.cs | 38 ++- .../Quantization/IPixelMap{TPixel}.cs | 30 ++ .../Quantization/IQuantizedFrame{TPixel}.cs | 38 --- .../OctreeFrameQuantizer{TPixel}.cs | 65 ++-- .../PaletteFrameQuantizer{TPixel}.cs | 44 ++- .../Quantization/QuantizeProcessor.cs | 4 +- .../Quantization/QuantizeProcessor{TPixel}.cs | 6 +- .../Quantization/QuantizedFrameExtensions.cs | 29 -- .../Quantization/QuantizedFrame{TPixel}.cs | 21 +- .../Quantization/WuFrameQuantizer{TPixel}.cs | 121 ++++--- .../ImageSharp.Benchmarks/Codecs/EncodeGif.cs | 2 +- .../Codecs/EncodeGifMultiple.cs | 2 +- .../ImageSharp.Benchmarks/Samplers/Diffuse.cs | 18 +- .../Processing/Dithering/DitherTest.cs | 2 +- .../Binarization/BinaryDitherTests.cs | 10 +- .../Processors/Dithering/DitherTests.cs | 46 +-- .../Processors/Quantization/QuantizerTests.cs | 23 +- .../Quantization/QuantizedImageTests.cs | 6 +- .../Quantization/WuQuantizerTests.cs | 10 +- tests/Images/External | 2 +- 51 files changed, 990 insertions(+), 1170 deletions(-) delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/AtkinsonDither.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/BurksDither.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/DitherType.cs create mode 100644 src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDither.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDither.cs create mode 100644 src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/Sierra2Dither.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/Sierra3Dither.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/SierraLiteDither.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/StevensonArceDither.cs delete mode 100644 src/ImageSharp/Processing/Processors/Dithering/StuckiDither.cs rename src/ImageSharp/Processing/Processors/{Dithering => Quantization}/EuclideanPixelMap{TPixel}.cs (55%) create mode 100644 src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs delete mode 100644 src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs create mode 100644 src/ImageSharp/Processing/Processors/Quantization/IPixelMap{TPixel}.cs delete mode 100644 src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs delete mode 100644 src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 435fdc4fc..c8c8568e4 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -113,7 +114,8 @@ namespace SixLabors.ImageSharp.Advanced { using (var test = new OctreeFrameQuantizer(Configuration.Default, new OctreeQuantizer().Options)) { - test.AotGetPalette(); + var frame = new ImageFrame(Configuration.Default, 1, 1); + test.QuantizeFrame(frame, frame.Bounds()); } } @@ -128,7 +130,6 @@ namespace SixLabors.ImageSharp.Advanced { var frame = new ImageFrame(Configuration.Default, 1, 1); test.QuantizeFrame(frame, frame.Bounds()); - test.AotGetPalette(); } } @@ -143,7 +144,6 @@ namespace SixLabors.ImageSharp.Advanced { var frame = new ImageFrame(Configuration.Default, 1, 1); test.QuantizeFrame(frame, frame.Bounds()); - test.AotGetPalette(); } } @@ -154,11 +154,13 @@ namespace SixLabors.ImageSharp.Advanced private static void AotCompileDithering() where TPixel : struct, IPixel { - var test = new FloydSteinbergDither(); + ErrorDither errorDither = ErrorDither.FloydSteinberg; + OrderedDither orderedDither = OrderedDither.Bayer2x2; TPixel pixel = default; using (var image = new ImageFrame(Configuration.Default, 1, 1)) { - test.Dither(image, default, pixel, pixel, 0, 0, 0, 0); + errorDither.Dither(image, image.Bounds(), pixel, pixel, 0, 0, 0); + orderedDither.Dither(pixel, 0, 0, 0, 0); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 2d6b06111..1b3e0228a 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 : struct, IPixel { using IFrameQuantizer quantizer = this.quantizer.CreateFrameQuantizer(this.configuration); - using IQuantizedFrame quantized = quantizer.QuantizeFrame(image, image.Bounds()); + using QuantizedFrame quantized = quantizer.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 0307f7d94..3a0fa5169 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -6,8 +6,6 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -81,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Gif bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global; // Quantize the image returning a palette. - IQuantizedFrame quantized; + QuantizedFrame quantized; using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration)) { quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); @@ -127,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Gif stream.WriteByte(GifConstants.EndIntroducer); } - private void EncodeGlobal(Image image, IQuantizedFrame quantized, int transparencyIndex, Stream stream) + private void EncodeGlobal(Image image, QuantizedFrame quantized, int transparencyIndex, Stream stream) where TPixel : struct, IPixel { for (int i = 0; i < image.Frames.Count; i++) @@ -144,8 +142,8 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - using (IFrameQuantizer paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Options, quantized.Palette)) - using (IQuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds())) + using (var paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Options, quantized.Palette)) + using (QuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds())) { this.WriteImageData(paletteQuantized, stream); } @@ -153,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } } - private void EncodeLocal(Image image, IQuantizedFrame quantized, Stream stream) + private void EncodeLocal(Image image, QuantizedFrame quantized, Stream stream) where TPixel : struct, IPixel { ImageFrame previousFrame = null; @@ -210,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The . /// - private int GetTransparentIndex(IQuantizedFrame quantized) + private int GetTransparentIndex(QuantizedFrame quantized) where TPixel : struct, IPixel { // Transparent pixels are much more likely to be found at the end of a palette @@ -439,7 +437,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The pixel format. /// The to encode. /// The stream to write to. - private void WriteColorTable(IQuantizedFrame image, Stream stream) + private void WriteColorTable(QuantizedFrame image, Stream stream) where TPixel : struct, IPixel { // The maximum number of colors for the bit depth @@ -461,9 +459,9 @@ 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(IQuantizedFrame image, Stream stream) + private void WriteImageData(QuantizedFrame image, Stream stream) where TPixel : struct, IPixel { using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth)) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 69a80e024..5f14d483b 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); - IQuantizedFrame quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); + QuantizedFrame 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, IQuantizedFrame quantized, int row) + private void CollectPixelBytes(ReadOnlySpan rowSpan, QuantizedFrame quantized, int row) where TPixel : struct, IPixel { switch (this.options.ColorType) @@ -440,7 +440,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The quantized pixels. Can be null. /// The row. /// The - private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IQuantizedFrame quantized, int row) + private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, QuantizedFrame quantized, int row) where TPixel : struct, IPixel { this.CollectPixelBytes(rowSpan, quantized, row); @@ -546,7 +546,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The containing image data. /// The quantized frame. - private void WritePaletteChunk(Stream stream, IQuantizedFrame quantized) + private void WritePaletteChunk(Stream stream, QuantizedFrame quantized) where TPixel : struct, IPixel { if (quantized == null) @@ -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, IQuantizedFrame quantized, Stream stream) + private void WriteDataChunks(ImageFrame pixels, QuantizedFrame quantized, Stream stream) where TPixel : struct, 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, IQuantizedFrame quantized, ZlibDeflateStream deflateStream) + private void EncodePixels(ImageFrame pixels, QuantizedFrame quantized, ZlibDeflateStream deflateStream) where TPixel : struct, 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(IQuantizedFrame quantized, ZlibDeflateStream deflateStream) + private void EncodeAdam7IndexedPixels(QuantizedFrame quantized, ZlibDeflateStream deflateStream) where TPixel : struct, IPixel { int width = quantized.Width; diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs index c29ec578c..172b6208a 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 IQuantizedFrame CreateQuantizedFrame( + public static QuantizedFrame CreateQuantizedFrame( PngEncoderOptions options, Image image) where TPixel : struct, IPixel @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Png public static byte CalculateBitDepth( PngEncoderOptions options, Image image, - IQuantizedFrame quantizedFrame) + QuantizedFrame quantizedFrame) where TPixel : struct, IPixel { byte bitDepth; diff --git a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs index abdfb969c..e765ea9b0 100644 --- a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs @@ -13,12 +13,12 @@ namespace SixLabors.ImageSharp.Processing public static class DitherExtensions { /// - /// Dithers the image reducing it to a web-safe palette using . + /// Dithers the image reducing it to a web-safe palette using . /// /// The image this method extends. /// The to allow chaining of operations. public static IImageProcessingContext Dither(this IImageProcessingContext source) => - Dither(source, KnownDitherings.BayerDither4x4); + Dither(source, KnownDitherings.Bayer8x8); /// /// Dithers the image reducing it to a web-safe palette using ordered dithering. @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Processing source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale, palette)); /// - /// Dithers the image reducing it to a web-safe palette using . + /// Dithers the image reducing it to a web-safe palette using . /// /// The image this method extends. /// @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing /// /// The to allow chaining of operations. public static IImageProcessingContext Dither(this IImageProcessingContext source, Rectangle rectangle) => - Dither(source, KnownDitherings.BayerDither4x4, rectangle); + Dither(source, KnownDitherings.Bayer4x4, rectangle); /// /// Dithers the image reducing it to a web-safe palette using ordered dithering. diff --git a/src/ImageSharp/Processing/KnownDitherings.cs b/src/ImageSharp/Processing/KnownDitherings.cs index 43387c55e..bb968d2ef 100644 --- a/src/ImageSharp/Processing/KnownDitherings.cs +++ b/src/ImageSharp/Processing/KnownDitherings.cs @@ -13,66 +13,66 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets the order ditherer using the 2x2 Bayer dithering matrix /// - public static IDither BayerDither2x2 { get; } = new BayerDither2x2(); + public static IDither Bayer2x2 { get; } = OrderedDither.Bayer2x2; /// /// Gets the order ditherer using the 3x3 dithering matrix /// - public static IDither OrderedDither3x3 { get; } = new OrderedDither3x3(); + public static IDither Ordered3x3 { get; } = OrderedDither.Ordered3x3; /// /// Gets the order ditherer using the 4x4 Bayer dithering matrix /// - public static IDither BayerDither4x4 { get; } = new BayerDither4x4(); + public static IDither Bayer4x4 { get; } = OrderedDither.Bayer4x4; /// /// Gets the order ditherer using the 8x8 Bayer dithering matrix /// - public static IDither BayerDither8x8 { get; } = new BayerDither8x8(); + public static IDither Bayer8x8 { get; } = OrderedDither.Bayer8x8; /// /// Gets the error Dither that implements the Atkinson algorithm. /// - public static IDither Atkinson { get; } = new AtkinsonDither(); + public static IDither Atkinson { get; } = ErrorDither.Atkinson; /// /// Gets the error Dither that implements the Burks algorithm. /// - public static IDither Burks { get; } = new BurksDither(); + public static IDither Burks { get; } = ErrorDither.Burkes; /// /// Gets the error Dither that implements the Floyd-Steinberg algorithm. /// - public static IDither FloydSteinberg { get; } = new FloydSteinbergDither(); + public static IDither FloydSteinberg { get; } = ErrorDither.FloydSteinberg; /// /// Gets the error Dither that implements the Jarvis-Judice-Ninke algorithm. /// - public static IDither JarvisJudiceNinke { get; } = new JarvisJudiceNinkeDither(); + public static IDither JarvisJudiceNinke { get; } = ErrorDither.JarvisJudiceNinke; /// /// Gets the error Dither that implements the Sierra-2 algorithm. /// - public static IDither Sierra2 { get; } = new Sierra2Dither(); + public static IDither Sierra2 { get; } = ErrorDither.Sierra2; /// /// Gets the error Dither that implements the Sierra-3 algorithm. /// - public static IDither Sierra3 { get; } = new Sierra3Dither(); + public static IDither Sierra3 { get; } = ErrorDither.Sierra3; /// /// Gets the error Dither that implements the Sierra-Lite algorithm. /// - public static IDither SierraLite { get; } = new SierraLiteDither(); + public static IDither SierraLite { get; } = ErrorDither.SierraLite; /// /// Gets the error Dither that implements the Stevenson-Arce algorithm. /// - public static IDither StevensonArce { get; } = new StevensonArceDither(); + public static IDither StevensonArce { get; } = ErrorDither.StevensonArce; /// /// Gets the error Dither that implements the Stucki algorithm. /// - public static IDither Stucki { get; } = new StuckiDither(); + public static IDither Stucki { get; } = ErrorDither.Stucki; } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDither.cs b/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDither.cs deleted file mode 100644 index 635777bf3..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDither.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. - /// - /// - public sealed class AtkinsonDither : ErrorDither - { - private const float Divisor = 8F; - private const int Offset = 1; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix AtkinsonMatrix = - new float[,] - { - { 0, 0, 1 / Divisor, 1 / Divisor }, - { 1 / Divisor, 1 / Divisor, 1 / Divisor, 0 }, - { 0, 1 / Divisor, 0, 0 } - }; - - /// - /// Initializes a new instance of the class. - /// - public AtkinsonDither() - : base(AtkinsonMatrix, Offset) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs b/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs deleted file mode 100644 index b7fdfbfe5..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies order dithering using the 2x2 Bayer dithering matrix. - /// - public sealed class BayerDither2x2 : OrderedDither - { - /// - /// Initializes a new instance of the class. - /// - public BayerDither2x2() - : base(2) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs b/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs deleted file mode 100644 index 4f6d5dd07..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies order dithering using the 4x4 Bayer dithering matrix. - /// - public sealed class BayerDither4x4 : OrderedDither - { - /// - /// Initializes a new instance of the class. - /// - public BayerDither4x4() - : base(4) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs b/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs deleted file mode 100644 index 8d0c23aa3..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies order dithering using the 8x8 Bayer dithering matrix. - /// - public sealed class BayerDither8x8 : OrderedDither - { - /// - /// Initializes a new instance of the class. - /// - public BayerDither8x8() - : base(8) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/BurksDither.cs b/src/ImageSharp/Processing/Processors/Dithering/BurksDither.cs deleted file mode 100644 index f7ac30e68..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BurksDither.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Burks image dithering algorithm. - /// - /// - public sealed class BurksDither : ErrorDither - { - private const float Divisor = 32F; - private const int Offset = 2; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix BurksMatrix = - new float[,] - { - { 0, 0, 0, 8 / Divisor, 4 / Divisor }, - { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor } - }; - - /// - /// Initializes a new instance of the class. - /// - public BurksDither() - : base(BurksMatrix, Offset) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/DitherType.cs b/src/ImageSharp/Processing/Processors/Dithering/DitherType.cs deleted file mode 100644 index 0dac15787..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/DitherType.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Enumerates the possible dithering algorithm transform behaviors. - /// - public enum DitherType - { - /// - /// Error diffusion. Spreads the difference between source and quanized color values as distributed error. - /// - ErrorDiffusion, - - /// - /// Ordered dithering. Applies thresholding matrices agains the source to determine the quantized color. - /// - OrderedDither - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs new file mode 100644 index 000000000..d39237a2c --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs @@ -0,0 +1,188 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// An error diffusion dithering implementation. + /// + public readonly partial struct ErrorDither + { + /// + /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. + /// + public static ErrorDither Atkinson = CreateAtkinson(); + + /// + /// Applies error diffusion based dithering using the Burks image dithering algorithm. + /// + public static ErrorDither Burkes = CreateBurks(); + + /// + /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. + /// + public static ErrorDither FloydSteinberg = CreateFloydSteinberg(); + + /// + /// Applies error diffusion based dithering using the Jarvis, Judice, Ninke image dithering algorithm. + /// + public static ErrorDither JarvisJudiceNinke = CreateJarvisJudiceNinke(); + + /// + /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. + /// + public static ErrorDither Sierra2 = CreateSierra2(); + + /// + /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. + /// + public static ErrorDither Sierra3 = CreateSierra3(); + + /// + /// Applies error diffusion based dithering using the Sierra Lite image dithering algorithm. + /// + public static ErrorDither SierraLite = CreateSierraLite(); + + /// + /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. + /// + public static ErrorDither StevensonArce = CreateStevensonArce(); + + /// + /// Applies error diffusion based dithering using the Stucki image dithering algorithm. + /// + public static ErrorDither Stucki = CreateStucki(); + + private static ErrorDither CreateAtkinson() + { + const float Divisor = 8F; + const int Offset = 1; + + var matrix = new float[,] + { + { 0, 0, 1 / Divisor, 1 / Divisor }, + { 1 / Divisor, 1 / Divisor, 1 / Divisor, 0 }, + { 0, 1 / Divisor, 0, 0 } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateBurks() + { + const float Divisor = 32F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 8 / Divisor, 4 / Divisor }, + { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateFloydSteinberg() + { + const float Divisor = 16F; + const int Offset = 1; + + var matrix = new float[,] + { + { 0, 0, 7 / Divisor }, + { 3 / Divisor, 5 / Divisor, 1 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateJarvisJudiceNinke() + { + const float Divisor = 48F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 7 / Divisor, 5 / Divisor }, + { 3 / Divisor, 5 / Divisor, 7 / Divisor, 5 / Divisor, 3 / Divisor }, + { 1 / Divisor, 3 / Divisor, 5 / Divisor, 3 / Divisor, 1 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateSierra2() + { + const float Divisor = 16F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 4 / Divisor, 3 / Divisor }, + { 1 / Divisor, 2 / Divisor, 3 / Divisor, 2 / Divisor, 1 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateSierra3() + { + const float Divisor = 32F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 5 / Divisor, 3 / Divisor }, + { 2 / Divisor, 4 / Divisor, 5 / Divisor, 4 / Divisor, 2 / Divisor }, + { 0, 2 / Divisor, 3 / Divisor, 2 / Divisor, 0 } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateSierraLite() + { + const float Divisor = 4F; + const int Offset = 1; + + var matrix = new float[,] + { + { 0, 0, 2 / Divisor }, + { 1 / Divisor, 1 / Divisor, 0 } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateStevensonArce() + { + const float Divisor = 200F; + const int Offset = 3; + + var matrix = new float[,] + { + { 0, 0, 0, 0, 0, 32 / Divisor, 0 }, + { 12 / Divisor, 0, 26 / Divisor, 0, 30 / Divisor, 0, 16 / Divisor }, + { 0, 12 / Divisor, 0, 26 / Divisor, 0, 12 / Divisor, 0 }, + { 5 / Divisor, 0, 12 / Divisor, 0, 12 / Divisor, 0, 5 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateStucki() + { + const float Divisor = 42F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 8 / Divisor, 4 / Divisor }, + { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor }, + { 1 / Divisor, 2 / Divisor, 4 / Divisor, 2 / Divisor, 1 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 92db4638b..6a4254032 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -3,42 +3,100 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { /// - /// The base class of all error diffusion dithering implementations. + /// An error diffusion dithering implementation. + /// /// - public abstract class ErrorDither : IDither + public readonly partial struct ErrorDither : IDither, IEquatable { private readonly int offset; private readonly DenseMatrix matrix; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The diffusion matrix. /// The starting offset within the matrix. - protected ErrorDither(in DenseMatrix matrix, int offset) + [MethodImpl(InliningOptions.ShortMethod)] + public ErrorDither(in DenseMatrix matrix, int offset) { this.matrix = matrix; this.offset = offset; } /// - public DitherType DitherType { get; } = DitherType.ErrorDiffusion; + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyQuantizationDither( + ref TFrameQuantizer quantizer, + ReadOnlyMemory palette, + ImageFrame source, + Memory output, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : struct, IPixel + { + Span outputSpan = output.Span; + ReadOnlySpan paletteSpan = palette.Span; + int width = bounds.Width; + int offsetY = bounds.Top; + int offsetX = bounds.Left; + float scale = quantizer.Options.DitherScale; + + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + Span row = source.GetPixelRowSpan(y); + int rowStart = (y - offsetY) * width; + + for (int x = bounds.Left; x < bounds.Right; x++) + { + TPixel sourcePixel = row[x]; + outputSpan[rowStart + x - offsetX] = quantizer.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed); + this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); + } + } + } /// - public TPixel Dither( + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyPaletteDither( + Configuration configuration, + ReadOnlyMemory palette, + ImageFrame source, + Rectangle bounds, + float scale) + where TPixel : struct, IPixel + { + var pixelMap = new EuclideanPixelMap(palette); + + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + Span row = source.GetPixelRowSpan(y); + for (int x = bounds.Left; x < bounds.Right; x++) + { + TPixel sourcePixel = row[x]; + pixelMap.GetClosestColor(sourcePixel, out TPixel transformed); + this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); + row[x] = transformed; + } + } + } + + // Internal for AOT + [MethodImpl(InliningOptions.ShortMethod)] + internal TPixel Dither( ImageFrame image, Rectangle bounds, TPixel source, TPixel transformed, int x, int y, - int bitDepth, float scale) where TPixel : struct, IPixel { @@ -88,5 +146,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering return transformed; } + + /// + public override bool Equals(object obj) + => obj is ErrorDither dither && this.Equals(dither); + + /// + public bool Equals(ErrorDither other) + => this.offset == other.offset && this.matrix.Equals(other.matrix); + + /// + public override int GetHashCode() + => HashCode.Combine(this.offset, this.matrix); } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDither.cs b/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDither.cs deleted file mode 100644 index 4dc8b5441..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDither.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. - /// - /// - public sealed class FloydSteinbergDither : ErrorDither - { - private const float Divisor = 16F; - private const int Offset = 1; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix FloydSteinbergMatrix = - new float[,] - { - { 0, 0, 7 / Divisor }, - { 3 / Divisor, 5 / Divisor, 1 / Divisor } - }; - - /// - /// Initializes a new instance of the class. - /// - public FloydSteinbergDither() - : base(FloydSteinbergMatrix, Offset) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs index dc48b7e6d..fc8ee32f7 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { @@ -11,34 +13,40 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering public interface IDither { /// - /// Gets the which determines whether the - /// transformed color should be calculated and supplied to the algorithm. + /// Transforms the quantized image frame applying a dither matrix. + /// This method should be treated as destructive, altering the input pixels. /// - public DitherType DitherType { get; } + /// The type of frame quantizer. + /// The pixel format. + /// The frame quantizer. + /// The quantized palette. + /// The source image. + /// The output target + /// The region of interest bounds. + void ApplyQuantizationDither( + ref TFrameQuantizer quantizer, + ReadOnlyMemory palette, + ImageFrame source, + Memory output, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : struct, IPixel; /// - /// Transforms the image applying a dither matrix. - /// When is this - /// this method is destructive and will alter the input pixels. + /// Transforms the image frame applying a dither matrix. + /// This method should be treated as destructive, altering the input pixels. /// - /// The image. + /// The pixel format. + /// The configuration. + /// The quantized palette. + /// The source image. /// The region of interest bounds. - /// The source pixel - /// The transformed pixel - /// The column index. - /// The row index. - /// The bit depth of the target palette. /// The dithering scale used to adjust the amount of dither. Range 0..1. - /// The pixel format. - /// The dithered result for the source pixel. - TPixel Dither( - ImageFrame image, + void ApplyPaletteDither( + Configuration configuration, + ReadOnlyMemory palette, + ImageFrame source, Rectangle bounds, - TPixel source, - TPixel transformed, - int x, - int y, - int bitDepth, float scale) where TPixel : struct, IPixel; } diff --git a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDither.cs b/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDither.cs deleted file mode 100644 index 43431c01d..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDither.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the JarvisJudiceNinke image dithering algorithm. - /// - /// - public sealed class JarvisJudiceNinkeDither : ErrorDither - { - private const float Divisor = 48F; - private const int Offset = 2; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix JarvisJudiceNinkeMatrix = - new float[,] - { - { 0, 0, 0, 7 / Divisor, 5 / Divisor }, - { 3 / Divisor, 5 / Divisor, 7 / Divisor, 5 / Divisor, 3 / Divisor }, - { 1 / Divisor, 3 / Divisor, 5 / Divisor, 3 / Divisor, 1 / Divisor } - }; - - /// - /// Initializes a new instance of the class. - /// - public JarvisJudiceNinkeDither() - : base(JarvisJudiceNinkeMatrix, Offset) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs new file mode 100644 index 000000000..d3e710782 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// An ordered dithering matrix with equal sides of arbitrary length + /// + public readonly partial struct OrderedDither : IDither + { + /// + /// Applies order dithering using the 2x2 Bayer dithering matrix. + /// + public static OrderedDither Bayer2x2 = new OrderedDither(2); + + /// + /// Applies order dithering using the 4x4 Bayer dithering matrix. + /// + public static OrderedDither Bayer4x4 = new OrderedDither(4); + + /// + /// Applies order dithering using the 8x8 Bayer dithering matrix. + /// + public static OrderedDither Bayer8x8 = new OrderedDither(8); + + /// + /// Applies order dithering using the 3x3 ordered dithering matrix. + /// + public static OrderedDither Ordered3x3 = new OrderedDither(3); + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 2e66ae86f..daf3d4732 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -1,24 +1,29 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { /// /// An ordered dithering matrix with equal sides of arbitrary length /// - public class OrderedDither : IDither + public readonly partial struct OrderedDither : IDither, IEquatable { private readonly DenseMatrix thresholdMatrix; private readonly int modulusX; private readonly int modulusY; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The length of the matrix sides + [MethodImpl(InliningOptions.ShortMethod)] public OrderedDither(uint length) { DenseMatrix ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length); @@ -43,15 +48,58 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } /// - public DitherType DitherType { get; } = DitherType.OrderedDither; + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyQuantizationDither( + ref TFrameQuantizer quantizer, + ReadOnlyMemory palette, + ImageFrame source, + Memory output, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : struct, IPixel + { + var ditherOperation = new QuantizeDitherRowIntervalOperation( + ref quantizer, + in Unsafe.AsRef(this), + source, + output, + bounds, + palette, + ImageMaths.GetBitsNeededForColorDepth(palette.Span.Length)); + + ParallelRowIterator.IterateRows( + quantizer.Configuration, + bounds, + in ditherOperation); + } /// [MethodImpl(InliningOptions.ShortMethod)] - public TPixel Dither( - ImageFrame image, + public void ApplyPaletteDither( + Configuration configuration, + ReadOnlyMemory palette, + ImageFrame source, Rectangle bounds, + float scale) + where TPixel : struct, IPixel + { + var ditherOperation = new PaletteDitherRowIntervalOperation( + in Unsafe.AsRef(this), + source, + bounds, + palette, + scale, + ImageMaths.GetBitsNeededForColorDepth(palette.Span.Length)); + + ParallelRowIterator.IterateRows( + configuration, + bounds, + in ditherOperation); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal TPixel Dither( TPixel source, - TPixel transformed, int x, int y, int bitDepth, @@ -79,5 +127,117 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering return result; } + + /// + public override bool Equals(object obj) + => obj is OrderedDither dither && this.Equals(dither); + + /// + public bool Equals(OrderedDither other) + => this.thresholdMatrix.Equals(other.thresholdMatrix) && this.modulusX == other.modulusX && this.modulusY == other.modulusY; + + /// + public override int GetHashCode() + => HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); + + private readonly struct QuantizeDitherRowIntervalOperation : IRowIntervalOperation + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : struct, IPixel + { + private readonly TFrameQuantizer quantizer; + private readonly OrderedDither dither; + private readonly ImageFrame source; + private readonly Memory output; + private readonly Rectangle bounds; + private readonly ReadOnlyMemory palette; + private readonly int bitDepth; + + [MethodImpl(InliningOptions.ShortMethod)] + public QuantizeDitherRowIntervalOperation( + ref TFrameQuantizer quantizer, + in OrderedDither dither, + ImageFrame source, + Memory output, + Rectangle bounds, + ReadOnlyMemory palette, + int bitDepth) + { + this.quantizer = quantizer; + this.dither = dither; + this.source = source; + this.output = output; + this.bounds = bounds; + this.palette = palette; + this.bitDepth = bitDepth; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ReadOnlySpan paletteSpan = this.palette.Span; + Span outputSpan = this.output.Span; + int width = this.bounds.Width; + 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++) + { + Span row = this.source.GetPixelRowSpan(y); + int rowStart = (y - offsetY) * width; + + // TODO: This can be a bulk operation. + for (int x = this.bounds.Left; x < this.bounds.Right; x++) + { + TPixel dithered = this.dither.Dither(row[x], x, y, this.bitDepth, scale); + outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _); + } + } + } + } + + private readonly struct PaletteDitherRowIntervalOperation : IRowIntervalOperation + where TPixel : struct, IPixel + { + private readonly OrderedDither dither; + private readonly ImageFrame source; + private readonly Rectangle bounds; + private readonly EuclideanPixelMap pixelMap; + private readonly float scale; + private readonly int bitDepth; + + [MethodImpl(InliningOptions.ShortMethod)] + public PaletteDitherRowIntervalOperation( + in OrderedDither dither, + ImageFrame source, + Rectangle bounds, + ReadOnlyMemory palette, + float scale, + int bitDepth) + { + this.dither = dither; + this.source = source; + this.bounds = bounds; + this.pixelMap = new EuclideanPixelMap(palette); + this.scale = scale; + this.bitDepth = bitDepth; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span row = this.source.GetPixelRowSpan(y); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) + { + TPixel dithered = this.dither.Dither(row[x], x, y, this.bitDepth, this.scale); + this.pixelMap.GetClosestColor(dithered, out TPixel transformed); + row[x] = transformed; + } + } + } + } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs deleted file mode 100644 index 93bce0578..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies order dithering using the 3x3 dithering matrix. - /// - public sealed class OrderedDither3x3 : OrderedDither - { - /// - /// Initializes a new instance of the class. - /// - public OrderedDither3x3() - : base(3) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index 315ce22e0..118352ec3 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -3,9 +3,6 @@ using System; using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Dithering @@ -18,12 +15,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TPixel : struct, IPixel { private readonly int paletteLength; - private readonly int bitDepth; private readonly IDither dither; private readonly float ditherScale; private readonly ReadOnlyMemory sourcePalette; private IMemoryOwner palette; - private EuclideanPixelMap pixelMap; private bool isDisposed; /// @@ -37,7 +32,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering : base(configuration, source, sourceRectangle) { this.paletteLength = definition.Palette.Span.Length; - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.paletteLength); this.dither = definition.Dither; this.ditherScale = definition.DitherScale; this.sourcePalette = definition.Palette; @@ -48,51 +42,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - // Error diffusion. The difference between the source and transformed color - // is spread to neighboring pixels. - if (this.dither.DitherType == DitherType.ErrorDiffusion) - { - for (int y = interest.Top; y < interest.Bottom; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = interest.Left; x < interest.Right; x++) - { - TPixel sourcePixel = row[x]; - this.pixelMap.GetClosestColor(sourcePixel, out TPixel transformed); - this.dither.Dither(source, interest, sourcePixel, transformed, x, y, this.bitDepth, this.ditherScale); - row[x] = transformed; - } - } - - return; - } - - // Ordered dithering. We are only operating on a single pixel so we can work in parallel. - var ditherOperation = new DitherRowIntervalOperation( - source, - interest, - this.pixelMap, - this.dither, - this.ditherScale, - this.bitDepth); - - ParallelRowIterator.IterateRows( + this.dither.ApplyPaletteDither( this.Configuration, + this.palette.Memory, + source, interest, - in ditherOperation); + this.ditherScale); } /// protected override void BeforeFrameApply(ImageFrame source) { // Lazy init palettes: - if (this.pixelMap is null) + if (this.palette is null) { this.palette = this.Configuration.MemoryAllocator.Allocate(this.paletteLength); ReadOnlySpan sourcePalette = this.sourcePalette.Span; Color.ToPixel(this.Configuration, sourcePalette, this.palette.Memory.Span); - this.pixelMap = new EuclideanPixelMap(this.palette.Memory); } base.BeforeFrameApply(source); @@ -116,51 +82,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering this.isDisposed = true; base.Dispose(disposing); } - - private readonly struct DitherRowIntervalOperation : IRowIntervalOperation - { - private readonly ImageFrame source; - private readonly Rectangle bounds; - private readonly EuclideanPixelMap pixelMap; - private readonly IDither dither; - private readonly float scale; - private readonly int bitDepth; - - [MethodImpl(InliningOptions.ShortMethod)] - public DitherRowIntervalOperation( - ImageFrame source, - Rectangle bounds, - EuclideanPixelMap pixelMap, - IDither dither, - float scale, - int bitDepth) - { - this.source = source; - this.bounds = bounds; - this.pixelMap = pixelMap; - this.dither = dither; - this.scale = scale; - this.bitDepth = bitDepth; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - IDither dither = this.dither; - TPixel transformed = default; - - for (int y = rows.Min; y < rows.Max; y++) - { - Span row = this.source.GetPixelRowSpan(y); - - for (int x = this.bounds.Left; x < this.bounds.Right; x++) - { - TPixel dithered = dither.Dither(this.source, this.bounds, row[x], transformed, x, y, this.bitDepth, this.scale); - this.pixelMap.GetClosestColor(dithered, out transformed); - row[x] = transformed; - } - } - } - } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs b/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs deleted file mode 100644 index 13660d30a..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Represents a composite pair of pixels. Used for caching color distance lookups. - /// - /// The pixel format. - internal readonly struct PixelPair : IEquatable> - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the struct. - /// - /// The first pixel color - /// The second pixel color - public PixelPair(TPixel first, TPixel second) - { - this.First = first; - this.Second = second; - } - - /// - /// Gets the first pixel color - /// - public TPixel First { get; } - - /// - /// Gets the second pixel color - /// - public TPixel Second { get; } - - /// - public bool Equals(PixelPair other) - => this.First.Equals(other.First) && this.Second.Equals(other.Second); - - /// - public override bool Equals(object obj) - => obj is PixelPair other && this.First.Equals(other.First) && this.Second.Equals(other.Second); - - /// - public override int GetHashCode() => HashCode.Combine(this.First, this.Second); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/Sierra2Dither.cs b/src/ImageSharp/Processing/Processors/Dithering/Sierra2Dither.cs deleted file mode 100644 index 36b9577b1..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/Sierra2Dither.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. - /// - /// - public sealed class Sierra2Dither : ErrorDither - { - private const float Divisor = 16F; - private const int Offset = 2; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix Sierra2Matrix = - new float[,] - { - { 0, 0, 0, 4 / Divisor, 3 / Divisor }, - { 1 / Divisor, 2 / Divisor, 3 / Divisor, 2 / Divisor, 1 / Divisor } - }; - - /// - /// Initializes a new instance of the class. - /// - public Sierra2Dither() - : base(Sierra2Matrix, Offset) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/Sierra3Dither.cs b/src/ImageSharp/Processing/Processors/Dithering/Sierra3Dither.cs deleted file mode 100644 index 25baa9b40..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/Sierra3Dither.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. - /// - /// - public sealed class Sierra3Dither : ErrorDither - { - private const float Divisor = 32F; - private const int Offset = 2; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix Sierra3Matrix = - new float[,] - { - { 0, 0, 0, 5 / Divisor, 3 / Divisor }, - { 2 / Divisor, 4 / Divisor, 5 / Divisor, 4 / Divisor, 2 / Divisor }, - { 0, 2 / Divisor, 3 / Divisor, 2 / Divisor, 0 } - }; - - /// - /// Initializes a new instance of the class. - /// - public Sierra3Dither() - : base(Sierra3Matrix, Offset) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDither.cs b/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDither.cs deleted file mode 100644 index 55b1a9048..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDither.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the SierraLite image dithering algorithm. - /// - /// - public sealed class SierraLiteDither : ErrorDither - { - private const float Divisor = 4F; - private const int Offset = 1; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix SierraLiteMatrix = - new float[,] - { - { 0, 0, 2 / Divisor }, - { 1 / Divisor, 1 / Divisor, 0 } - }; - - /// - /// Initializes a new instance of the class. - /// - public SierraLiteDither() - : base(SierraLiteMatrix, Offset) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDither.cs b/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDither.cs deleted file mode 100644 index e4287a53f..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDither.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. - /// - public sealed class StevensonArceDither : ErrorDither - { - private const float Divisor = 200F; - private const int Offset = 3; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix StevensonArceMatrix = - new float[,] - { - { 0, 0, 0, 0, 0, 32 / Divisor, 0 }, - { 12 / Divisor, 0, 26 / Divisor, 0, 30 / Divisor, 0, 16 / Divisor }, - { 0, 12 / Divisor, 0, 26 / Divisor, 0, 12 / Divisor, 0 }, - { 5 / Divisor, 0, 12 / Divisor, 0, 12 / Divisor, 0, 5 / Divisor } - }; - - /// - /// Initializes a new instance of the class. - /// - public StevensonArceDither() - : base(StevensonArceMatrix, Offset) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/StuckiDither.cs b/src/ImageSharp/Processing/Processors/Dithering/StuckiDither.cs deleted file mode 100644 index a50f304a4..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/StuckiDither.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Stucki image dithering algorithm. - /// - /// - public sealed class StuckiDither : ErrorDither - { - private const float Divisor = 42F; - private const int Offset = 2; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix StuckiMatrix = - new float[,] - { - { 0, 0, 0, 8 / Divisor, 4 / Divisor }, - { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor }, - { 1 / Divisor, 2 / Divisor, 4 / Divisor, 2 / Divisor, 1 / Divisor } - }; - - /// - /// Initializes a new instance of the class. - /// - public StuckiDither() - : base(StuckiMatrix, Offset) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs similarity index 55% rename from src/ImageSharp/Processing/Processors/Dithering/EuclideanPixelMap{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index 37924e87d..a5e8d70b0 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -7,23 +7,31 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Dithering +namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Gets the closest color to the supplied color based upon the Eucladean distance. + /// TODO: Expose this somehow. /// /// The pixel format. - internal sealed class EuclideanPixelMap + internal readonly struct EuclideanPixelMap : IPixelMap, IEquatable> where TPixel : struct, IPixel { - private readonly ReadOnlyMemory palette; - private readonly ConcurrentDictionary vectorCache = new ConcurrentDictionary(); - private readonly ConcurrentDictionary distanceCache = new ConcurrentDictionary(); + private readonly ConcurrentDictionary vectorCache; + private readonly ConcurrentDictionary distanceCache; + /// + /// Initializes a new instance of the struct. + /// + /// The color palette to map from. public EuclideanPixelMap(ReadOnlyMemory palette) { - this.palette = palette; - ReadOnlySpan paletteSpan = this.palette.Span; + Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); + + this.Palette = palette; + ReadOnlySpan paletteSpan = this.Palette.Span; + this.vectorCache = new ConcurrentDictionary(); + this.distanceCache = new ConcurrentDictionary(); for (int i = 0; i < paletteSpan.Length; i++) { @@ -31,13 +39,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } } + /// + public ReadOnlyMemory Palette { get; } + + /// + public override bool Equals(object obj) + => obj is EuclideanPixelMap map && this.Equals(map); + + /// + public bool Equals(EuclideanPixelMap other) + => this.Palette.Equals(other.Palette); + + /// [MethodImpl(InliningOptions.ShortMethod)] - public byte GetClosestColor(TPixel color, out TPixel match) + public int GetClosestColor(TPixel color, out TPixel match) { - ReadOnlySpan paletteSpan = this.palette.Span; + ReadOnlySpan paletteSpan = this.Palette.Span; // Check if the color is in the lookup table - if (this.distanceCache.TryGetValue(color, out byte index)) + if (this.distanceCache.TryGetValue(color, out int index)) { match = paletteSpan[index]; return index; @@ -46,8 +66,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering return this.GetClosestColorSlow(color, paletteSpan, out match); } + /// + public override int GetHashCode() + => this.vectorCache.GetHashCode(); + [MethodImpl(InliningOptions.ShortMethod)] - private byte GetClosestColorSlow(TPixel color, ReadOnlySpan palette, out TPixel match) + private int GetClosestColorSlow(TPixel color, ReadOnlySpan palette, out TPixel match) { // Loop through the palette and find the nearest match. int index = 0; @@ -74,10 +98,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } // Now I have the index, pop it into the cache for next time - var result = (byte)index; - this.distanceCache[color] = result; + this.distanceCache[color] = index; match = palette[index]; - return result; + return index; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs new file mode 100644 index 000000000..5b49fe9e8 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs @@ -0,0 +1,136 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Contains extension methods for frame quantizers. + /// + public static class FrameQuantizerExtensions + { + /// + /// Quantizes an image frame and return the resulting output pixels. + /// + /// The type of frame quantizer. + /// The pixel format. + /// The frame + /// The source image frame to quantize. + /// The bounds within the frame to quantize. + /// + /// A representing a quantized version of the source frame pixels. + /// + public static QuantizedFrame QuantizeFrame( + ref TFrameQuantizer quantizer, + ImageFrame source, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : struct, IPixel + { + Guard.NotNull(source, nameof(source)); + var interest = Rectangle.Intersect(source.Bounds(), bounds); + + // Collect the palette. Required before the second pass runs. + ReadOnlyMemory palette = quantizer.BuildPalette(source, interest); + MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator; + + var quantizedFrame = new QuantizedFrame(memoryAllocator, interest.Width, interest.Height, palette); + Memory output = quantizedFrame.GetWritablePixelMemory(); + + if (quantizer.Options.Dither is null) + { + SecondPass(ref quantizer, source, interest, output, palette); + } + else + { + // We clone the image as we don't want to alter the original via error diffusion based dithering. + using (ImageFrame clone = source.Clone()) + { + SecondPass(ref quantizer, clone, interest, output, palette); + } + } + + return quantizedFrame; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void SecondPass( + ref TFrameQuantizer quantizer, + ImageFrame source, + Rectangle bounds, + Memory output, + ReadOnlyMemory palette) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : struct, IPixel + { + IDither dither = quantizer.Options.Dither; + + if (dither is null) + { + var operation = new RowIntervalOperation(quantizer, source, output, bounds, palette); + ParallelRowIterator.IterateRows( + quantizer.Configuration, + bounds, + in operation); + + return; + } + + dither.ApplyQuantizationDither(ref quantizer, palette, source, output, bounds); + } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : struct, IPixel + { + private readonly TFrameQuantizer quantizer; + private readonly ImageFrame source; + private readonly Memory output; + private readonly Rectangle bounds; + private readonly ReadOnlyMemory palette; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + in TFrameQuantizer quantizer, + ImageFrame source, + Memory output, + Rectangle bounds, + ReadOnlyMemory palette) + { + this.quantizer = quantizer; + this.source = source; + this.output = output; + this.bounds = bounds; + this.palette = palette; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ReadOnlySpan paletteSpan = this.palette.Span; + Span outputSpan = this.output.Span; + int width = this.bounds.Width; + int offsetY = this.bounds.Top; + int offsetX = this.bounds.Left; + + for (int y = rows.Min; y < rows.Max; y++) + { + Span row = this.source.GetPixelRowSpan(y); + int rowStart = (y - offsetY) * width; + + // TODO: This can be a bulk operation. + for (int x = this.bounds.Left; x < this.bounds.Right; x++) + { + outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(row[x], paletteSpan, out TPixel _); + } + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs deleted file mode 100644 index 0d3b7de6d..000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// The base class for all implementations - /// - /// The pixel format. - public abstract class FrameQuantizer : IFrameQuantizer - where TPixel : struct, IPixel - { - private readonly bool singlePass; - private EuclideanPixelMap pixelMap; - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The quantizer options defining quantization rules. - /// - /// If , the quantization process only needs to loop through the source pixels once. - /// - /// - /// If you construct this class with a true for , then the code will - /// only call the method. - /// If two passes are required, the code will also call . - /// - protected FrameQuantizer(Configuration configuration, QuantizerOptions options, bool singlePass) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(options, nameof(options)); - - this.Configuration = configuration; - this.Options = options; - this.IsDitheringQuantizer = options.Dither != null; - this.singlePass = singlePass; - } - - /// - public QuantizerOptions Options { get; } - - /// - /// Gets the configuration which allows altering default behaviour or extending the library. - /// - protected Configuration Configuration { get; } - - /// - /// Gets a value indicating whether the frame quantizer utilizes a dithering method. - /// - protected bool IsDitheringQuantizer { get; } - - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - public IQuantizedFrame QuantizeFrame(ImageFrame image, Rectangle bounds) - { - Guard.NotNull(image, nameof(image)); - var interest = Rectangle.Intersect(image.Bounds(), bounds); - - // Call the FirstPass function if not a single pass algorithm. - // For something like an Octree quantizer, this will run through - // all image pixels, build a data structure, and create a palette. - if (!this.singlePass) - { - this.FirstPass(image, interest); - } - - // Collect the palette. Required before the second pass runs. - ReadOnlyMemory palette = this.GenerateQuantizedPalette(); - MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; - this.pixelMap = new EuclideanPixelMap(palette); - - var quantizedFrame = new QuantizedFrame(memoryAllocator, interest.Width, interest.Height, palette); - - Memory output = quantizedFrame.GetWritablePixelMemory(); - if (this.Options.Dither is null) - { - this.SecondPass(image, interest, output, palette); - } - else - { - // We clone the image as we don't want to alter the original via error diffusion based dithering. - using (ImageFrame clone = image.Clone()) - { - this.SecondPass(clone, interest, output, palette); - } - } - - return quantizedFrame; - } - - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose managed and unmanaged objects. - protected virtual void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - this.isDisposed = true; - } - - /// - /// Execute the first pass through the pixels in the image to create the palette. - /// - /// The source data. - /// The bounds within the source image to quantize. - protected virtual void FirstPass(ImageFrame source, Rectangle bounds) - { - } - - /// - /// Execute a second pass through the image to assign the pixels to a palette entry. - /// - /// The source image. - /// The bounds within the source image to quantize. - /// The output pixel array. - /// The output color palette. - protected virtual void SecondPass( - ImageFrame source, - Rectangle bounds, - Memory output, - ReadOnlyMemory palette) - { - ReadOnlySpan paletteSpan = palette.Span; - IDither dither = this.Options.Dither; - - if (dither is null) - { - var operation = new RowIntervalOperation(source, output, bounds, this, palette); - ParallelRowIterator.IterateRows( - this.Configuration, - bounds, - in operation); - - return; - } - - // Error diffusion. - // The difference between the source and transformed color is spread to neighboring pixels. - // TODO: Investigate parallel strategy. - Span outputSpan = output.Span; - - int bitDepth = ImageMaths.GetBitsNeededForColorDepth(paletteSpan.Length); - if (dither.DitherType == DitherType.ErrorDiffusion) - { - float ditherScale = this.Options.DitherScale; - int width = bounds.Width; - int offsetY = bounds.Top; - int offsetX = bounds.Left; - for (int y = bounds.Top; y < bounds.Bottom; y++) - { - Span row = source.GetPixelRowSpan(y); - int rowStart = (y - offsetY) * width; - - for (int x = bounds.Left; x < bounds.Right; x++) - { - TPixel sourcePixel = row[x]; - outputSpan[rowStart + x - offsetX] = this.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed); - dither.Dither(source, bounds, sourcePixel, transformed, x, y, bitDepth, ditherScale); - } - } - - return; - } - - // Ordered dithering. We are only operating on a single pixel so we can work in parallel. - var ditherOperation = new DitherRowIntervalOperation(source, output, bounds, this, palette, bitDepth); - ParallelRowIterator.IterateRows( - this.Configuration, - bounds, - in ditherOperation); - } - - /// - /// Returns the index and color from the quantized palette corresponding to the give to the given color. - /// - /// The color to match. - /// The output color palette. - /// The matched color. - /// The index. - [MethodImpl(InliningOptions.ShortMethod)] - protected virtual byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) - => this.pixelMap.GetClosestColor(color, out match); - - /// - /// Generates the palette for the quantized image. - /// - /// - /// - /// - protected abstract ReadOnlyMemory GenerateQuantizedPalette(); - - private readonly struct RowIntervalOperation : IRowIntervalOperation - { - private readonly ImageFrame source; - private readonly Memory output; - private readonly Rectangle bounds; - private readonly FrameQuantizer quantizer; - private readonly ReadOnlyMemory palette; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( - ImageFrame source, - Memory output, - Rectangle bounds, - FrameQuantizer quantizer, - ReadOnlyMemory palette) - { - this.source = source; - this.output = output; - this.bounds = bounds; - this.quantizer = quantizer; - this.palette = palette; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - ReadOnlySpan paletteSpan = this.palette.Span; - Span outputSpan = this.output.Span; - int width = this.bounds.Width; - int offsetY = this.bounds.Top; - int offsetX = this.bounds.Left; - - for (int y = rows.Min; y < rows.Max; y++) - { - Span row = this.source.GetPixelRowSpan(y); - int rowStart = (y - offsetY) * width; - - for (int x = this.bounds.Left; x < this.bounds.Right; x++) - { - outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(row[x], paletteSpan, out TPixel _); - } - } - } - } - - private readonly struct DitherRowIntervalOperation : IRowIntervalOperation - { - private readonly ImageFrame source; - private readonly Memory output; - private readonly Rectangle bounds; - private readonly FrameQuantizer quantizer; - private readonly ReadOnlyMemory palette; - private readonly int bitDepth; - - [MethodImpl(InliningOptions.ShortMethod)] - public DitherRowIntervalOperation( - ImageFrame source, - Memory output, - Rectangle bounds, - FrameQuantizer quantizer, - ReadOnlyMemory palette, - int bitDepth) - { - this.source = source; - this.output = output; - this.bounds = bounds; - this.quantizer = quantizer; - this.palette = palette; - this.bitDepth = bitDepth; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - ReadOnlySpan paletteSpan = this.palette.Span; - Span outputSpan = this.output.Span; - int width = this.bounds.Width; - int offsetY = this.bounds.Top; - int offsetX = this.bounds.Left; - IDither dither = this.quantizer.Options.Dither; - float scale = this.quantizer.Options.DitherScale; - TPixel transformed = default; - - for (int y = rows.Min; y < rows.Max; y++) - { - Span row = this.source.GetPixelRowSpan(y); - int rowStart = (y - offsetY) * width; - - for (int x = this.bounds.Left; x < this.bounds.Right; x++) - { - TPixel dithered = dither.Dither(this.source, this.bounds, row[x], transformed, x, y, this.bitDepth, scale); - outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _); - } - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs index 591317902..d3091c3b0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -3,7 +3,6 @@ using System; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -14,19 +13,46 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public interface IFrameQuantizer : IDisposable where TPixel : struct, IPixel { + /// + /// Gets the configuration. + /// + Configuration Configuration { get; } + /// /// Gets the quantizer options defining quantization rules. /// QuantizerOptions Options { get; } /// - /// Quantize an image frame and return the resulting output pixels. + /// Quantizes an image frame and return the resulting output pixels. /// - /// The image to quantize. - /// The bounds within the source image to quantize. + /// The source image frame to quantize. + /// The bounds within the frame to quantize. /// - /// A representing a quantized version of the source image pixels. + /// A representing a quantized version of the source frame pixels. /// - IQuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds); + 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. + ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds); + + /// + /// Returns the index and color from the quantized palette corresponding to the give to the given color. + /// + /// The color to match. + /// The output color palette. + /// The matched color. + /// The index. + public byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match); + + // TODO: Enable bulk operations. + // void GetQuantizedColors(ReadOnlySpan colors, ReadOnlySpan palette, Span indices, Span matches); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IPixelMap{TPixel}.cs new file mode 100644 index 000000000..d25f2b07d --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/IPixelMap{TPixel}.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Allows the mapping of input colors to colors within a given palette. + /// TODO: Expose this somehow. + /// + /// The pixel format. + internal interface IPixelMap + where TPixel : struct, IPixel + { + /// + /// Gets the color palette containing colors to match. + /// + ReadOnlyMemory Palette { get; } + + /// + /// Returns the closest color in the palette and the index of that pixel. + /// + /// The color to match. + /// The matched color. + /// The index. + int GetClosestColor(TPixel color, out TPixel match); + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs deleted file mode 100644 index 42016459b..000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// Defines an abstraction to represent a quantized image frame where the pixels indexed by a color palette. - /// - /// The pixel format. - public interface IQuantizedFrame : IDisposable - where TPixel : struct, IPixel - { - /// - /// Gets the width of this . - /// - int Width { get; } - - /// - /// Gets the height of this . - /// - int Height { get; } - - /// - /// Gets the color palette of this . - /// - ReadOnlyMemory Palette { get; } - - /// - /// Gets the pixels of this . - /// - /// The The pixel span. - ReadOnlySpan GetPixelSpan(); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 4fecc5702..2b8ef3f0b 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -17,42 +17,48 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// The pixel format. - internal sealed class OctreeFrameQuantizer : FrameQuantizer + public struct OctreeFrameQuantizer : IFrameQuantizer where TPixel : struct, IPixel { - /// - /// Maximum allowed color depth - /// private readonly int colors; - - /// - /// Stores the tree - /// private readonly Octree octree; + private EuclideanPixelMap pixelMap; + private readonly bool isDithering; /// - /// The reduced image palette - /// - private TPixel[] palette; - - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The configuration which allows altering default behaviour or extending the library. /// The quantizer options defining quantization rules. - /// - /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, - /// the second pass quantizes a color based on the nodes in the tree - /// + [MethodImpl(InliningOptions.ShortMethod)] public OctreeFrameQuantizer(Configuration configuration, QuantizerOptions options) - : base(configuration, options, false) { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); + + this.Configuration = configuration; + this.Options = options; + this.colors = this.Options.MaxColors; this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8)); + this.pixelMap = default; + this.isDithering = !(this.Options.Dither is null); } /// - protected override void FirstPass(ImageFrame source, Rectangle bounds) + public Configuration Configuration { get; } + + /// + public QuantizerOptions Options { get; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerExtensions.QuantizeFrame(ref this, source, bounds); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) { using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width); Span bufferSpan = buffer.GetSpan(); @@ -71,31 +77,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.octree.AddColor(rgba); } } + + TPixel[] palette = this.octree.Palletize(this.colors); + this.pixelMap = new EuclideanPixelMap(palette); + + return palette; } /// [MethodImpl(InliningOptions.ShortMethod)] - protected override byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) + public byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) { // Octree only maps the RGB component of a color // so cannot tell the difference between a fully transparent // pixel and a black one. - if (!this.IsDitheringQuantizer && !color.Equals(default)) + if (!this.isDithering && !color.Equals(default)) { var index = (byte)this.octree.GetPaletteIndex(color); match = palette[index]; return index; } - return base.GetQuantizedColor(color, palette, out match); + return (byte)this.pixelMap.GetClosestColor(color, out match); } - internal ReadOnlyMemory AotGetPalette() => this.GenerateQuantizedPalette(); - /// - [MethodImpl(InliningOptions.ShortMethod)] - protected override ReadOnlyMemory GenerateQuantizedPalette() - => this.palette ?? (this.palette = this.octree.Palletize(this.colors)); + public void Dispose() + { + } /// /// Class which does the actual quantization. diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index 453c1d5dc..b8925b664 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -12,27 +12,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// The pixel format. - internal sealed class PaletteFrameQuantizer : FrameQuantizer + internal struct PaletteFrameQuantizer : IFrameQuantizer where TPixel : struct, IPixel { - /// - /// The reduced image palette. - /// private readonly ReadOnlyMemory palette; + private readonly EuclideanPixelMap pixelMap; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The configuration which allows altering default behaviour or extending the library. /// The quantizer options defining quantization rules. /// A containing all colors in the palette. + [MethodImpl(InliningOptions.ShortMethod)] public PaletteFrameQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory colors) - : base(configuration, options, true) => this.palette = colors; + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); + + this.Configuration = configuration; + this.Options = options; + + this.palette = colors; + this.pixelMap = new EuclideanPixelMap(colors); + } + + /// + public Configuration Configuration { get; } + + /// + public QuantizerOptions Options { get; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerExtensions.QuantizeFrame(ref this, source, bounds); /// [MethodImpl(InliningOptions.ShortMethod)] - protected override ReadOnlyMemory GenerateQuantizedPalette() => this.palette; + public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) + => this.palette; - internal ReadOnlyMemory AotGetPalette() => this.GenerateQuantizedPalette(); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) + => (byte)this.pixelMap.GetClosestColor(color, out match); + + /// + public void Dispose() + { + } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs index 8e1dffeed..93211855d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs @@ -15,9 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The quantizer used to reduce the color palette. public QuantizeProcessor(IQuantizer quantizer) - { - this.Quantizer = quantizer; - } + => this.Quantizer = quantizer; /// /// Gets the quantizer. diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index b42e0f3e2..bfcc26ae2 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 IQuantizedFrame quantized = frameQuantizer.QuantizeFrame(source, interest); + using QuantizedFrame quantized = frameQuantizer.QuantizeFrame(source, interest); var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); ParallelRowIterator.IterateRows( @@ -52,13 +52,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { private readonly Rectangle bounds; private readonly ImageFrame source; - private readonly IQuantizedFrame quantized; + private readonly QuantizedFrame quantized; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperation( Rectangle bounds, ImageFrame source, - IQuantizedFrame quantized) + QuantizedFrame quantized) { this.bounds = bounds; this.source = source; diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs deleted file mode 100644 index fa3d36e10..000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// Contains extension methods for . - /// - public static class QuantizedFrameExtensions - { - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the the first pixel on that row. - /// - /// The . - /// The row. - /// The pixel type. - /// The pixel row as a . - [MethodImpl(InliningOptions.ShortMethod)] - public static ReadOnlySpan GetRowSpan(this IQuantizedFrame frame, int rowIndex) - where TPixel : struct, IPixel - => frame.GetPixelSpan().Slice(rowIndex * frame.Width, frame.Width); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs index 90183473b..fccc799bb 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs @@ -13,10 +13,11 @@ 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 : IQuantizedFrame + public sealed class QuantizedFrame : IDisposable where TPixel : struct, IPixel { private IMemoryOwner pixels; + private bool isDisposed; /// /// Initializes a new instance of the class. @@ -58,16 +59,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization [MethodImpl(InliningOptions.ShortMethod)] public ReadOnlySpan 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 ReadOnlySpan GetRowSpan(int rowIndex) + => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width); + /// public void Dispose() { + if (this.isDisposed) + { + return; + } + + this.isDisposed = true; this.pixels?.Dispose(); this.pixels = null; this.Palette = null; } /// - /// Get the non-readonly memory of pixel data so can fill it. + /// Get the non-readonly memory of pixel data so can fill it. /// internal Memory GetWritablePixelMemory() => this.pixels.Memory; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 0a46cd302..396f120aa 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// The pixel format. - internal sealed class WuFrameQuantizer : FrameQuantizer + internal struct WuFrameQuantizer : IFrameQuantizer where TPixel : struct, IPixel { private readonly MemoryAllocator memoryAllocator; @@ -80,97 +80,82 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private int colors; - /// - /// The reduced image palette - /// - private TPixel[] palette; - /// /// The color cube representing the image palette /// - private Box[] colorCube; + private readonly Box[] colorCube; + + private EuclideanPixelMap pixelMap; + + private readonly bool isDithering; private bool isDisposed; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The configuration which allows altering default behaviour or extending the library. /// The quantizer options defining quantization rules. - /// - /// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram, - /// the second pass quantizes a color based on the position in the histogram. - /// + [MethodImpl(InliningOptions.ShortMethod)] public WuFrameQuantizer(Configuration configuration, QuantizerOptions options) - : base(configuration, options, false) { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); + + this.Configuration = configuration; + this.Options = options; this.memoryAllocator = this.Configuration.MemoryAllocator; this.moments = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); this.tag = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); this.colors = this.Options.MaxColors; + this.colorCube = new Box[this.colors]; + this.isDisposed = false; + this.pixelMap = default; + this.isDithering = this.isDithering = !(this.Options.Dither is null); } /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - this.moments?.Dispose(); - this.tag?.Dispose(); - } + public Configuration Configuration { get; } - this.moments = null; - this.tag = null; - - this.isDisposed = true; - base.Dispose(true); - } + /// + public QuantizerOptions Options { get; } - internal ReadOnlyMemory AotGetPalette() => this.GenerateQuantizedPalette(); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public QuantizedFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerExtensions.QuantizeFrame(ref this, source, bounds); /// - protected override ReadOnlyMemory GenerateQuantizedPalette() + public ReadOnlyMemory BuildPalette(ImageFrame source, Rectangle bounds) { - if (this.palette is null) - { - this.palette = new TPixel[this.colors]; - ReadOnlySpan momentsSpan = this.moments.GetSpan(); + this.Build3DHistogram(source, bounds); + this.Get3DMoments(this.memoryAllocator); + this.BuildCube(); - for (int k = 0; k < this.colors; k++) - { - this.Mark(ref this.colorCube[k], (byte)k); + var palette = new TPixel[this.colors]; + ReadOnlySpan momentsSpan = this.moments.GetSpan(); - Moment moment = Volume(ref this.colorCube[k], momentsSpan); + for (int k = 0; k < this.colors; k++) + { + this.Mark(ref this.colorCube[k], (byte)k); - if (moment.Weight > 0) - { - ref TPixel color = ref this.palette[k]; - color.FromScaledVector4(moment.Normalize()); - } + Moment moment = Volume(ref this.colorCube[k], momentsSpan); + + if (moment.Weight > 0) + { + ref TPixel color = ref palette[k]; + color.FromScaledVector4(moment.Normalize()); } } - return this.palette; - } - - /// - protected override void FirstPass(ImageFrame source, Rectangle bounds) - { - this.Build3DHistogram(source, bounds); - this.Get3DMoments(this.memoryAllocator); - this.BuildCube(); + this.pixelMap = new EuclideanPixelMap(palette); + return palette; } /// - [MethodImpl(InliningOptions.ShortMethod)] - protected override byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) + public byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) { - if (!this.IsDitheringQuantizer) + if (!this.isDithering) { Rgba32 rgba = default; color.ToRgba32(ref rgba); @@ -181,12 +166,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization int a = rgba.A >> (8 - IndexAlphaBits); ReadOnlySpan tagSpan = this.tag.GetSpan(); - var index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; + byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; match = palette[index]; return index; } - return base.GetQuantizedColor(color, palette, out match); + return (byte)this.pixelMap.GetClosestColor(color, out match); + } + + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + this.isDisposed = true; + this.moments?.Dispose(); + this.tag?.Dispose(); + this.moments = null; + this.tag = null; } /// @@ -634,7 +634,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private void BuildCube() { - this.colorCube = new Box[this.colors]; Span vv = stackalloc double[this.colors]; ref Box cube = ref this.colorCube[0]; diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs index 8983d3040..5e91f98eb 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs // Try to get as close to System.Drawing's output as possible var options = new GifEncoder { - Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.BayerDither4x4 }) + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) }; using (var memoryStream = new MemoryStream()) diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs index e21fbfc61..5c7a9e991 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs // Try to get as close to System.Drawing's output as possible var options = new GifEncoder { - Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.BayerDither4x4 }) + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) }; img.Save(ms, options); diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs index f5df7a3c3..096167eb9 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers // | DoDiffuse | Clr | Clr | 124.93 ms | 33.297 ms | 1.8251 ms | - | - | - | 2 KB | // | DoDiffuse | Core | Core | 89.63 ms | 9.895 ms | 0.5424 ms | - | - | - | 1.91 KB | -// #### 15th February 2020 #### +// #### 20th February 2020 #### // // BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 // Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores @@ -73,11 +73,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers // // IterationCount=3 LaunchCount=1 WarmupCount=3 // -// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------- |-------------- |---------:|----------:|---------:|------:|------:|------:|----------:| -// | DoDiffuse | .NET 4.7.2 | 40.32 ms | 16.788 ms | 0.920 ms | - | - | - | 26.46 KB | -// | DoDither | .NET 4.7.2 | 12.86 ms | 3.066 ms | 0.168 ms | - | - | - | 30.75 KB | -// | DoDiffuse | .NET Core 2.1 | 27.09 ms | 3.180 ms | 0.174 ms | - | - | - | 26.04 KB | -// | DoDither | .NET Core 2.1 | 12.89 ms | 34.535 ms | 1.893 ms | - | - | - | 29.26 KB | -// | DoDiffuse | .NET Core 3.1 | 27.39 ms | 2.699 ms | 0.148 ms | - | - | - | 26.02 KB | -// | DoDither | .NET Core 3.1 | 12.50 ms | 5.083 ms | 0.279 ms | - | - | - | 30.96 KB | +// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |---------- |-------------- |----------:|----------:|----------:|------:|------:|------:|----------:| +// | DoDiffuse | .NET 4.7.2 | 30.535 ms | 19.217 ms | 1.0534 ms | - | - | - | 26.25 KB | +// | DoDither | .NET 4.7.2 | 14.174 ms | 1.625 ms | 0.0891 ms | - | - | - | 31.38 KB | +// | DoDiffuse | .NET Core 2.1 | 15.984 ms | 3.686 ms | 0.2020 ms | - | - | - | 25.98 KB | +// | DoDither | .NET Core 2.1 | 8.646 ms | 1.635 ms | 0.0896 ms | - | - | - | 28.99 KB | +// | DoDiffuse | .NET Core 3.1 | 16.235 ms | 9.612 ms | 0.5269 ms | - | - | - | 25.96 KB | +// | DoDither | .NET Core 3.1 | 8.429 ms | 1.270 ms | 0.0696 ms | - | - | - | 31.61 KB | diff --git a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs index f343d9266..5cb44ef03 100644 --- a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public DitherTest() { - this.orderedDither = KnownDitherings.BayerDither4x4; + this.orderedDither = KnownDitherings.Bayer4x4; this.errorDiffuser = KnownDitherings.FloydSteinberg; } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs index d57a63432..9cb7e0409 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs @@ -20,10 +20,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization public static readonly TheoryData OrderedDitherers = new TheoryData { - { "Bayer8x8", KnownDitherings.BayerDither8x8 }, - { "Bayer4x4", KnownDitherings.BayerDither4x4 }, - { "Ordered3x3", KnownDitherings.OrderedDither3x3 }, - { "Bayer2x2", KnownDitherings.BayerDither2x2 } + { "Bayer8x8", KnownDitherings.Bayer8x8 }, + { "Bayer4x4", KnownDitherings.Bayer4x4 }, + { "Ordered3x3", KnownDitherings.Ordered3x3 }, + { "Bayer2x2", KnownDitherings.Bayer2x2 } }; public static readonly TheoryData ErrorDiffusers = new TheoryData @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; - private static IDither DefaultDitherer => KnownDitherings.BayerDither4x4; + private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 2ce655a7e..86f982118 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -17,32 +17,32 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial, TestImages.Png.Bike }; - public static readonly TheoryData ErrorDiffusers - = new TheoryData + public static readonly TheoryData ErrorDiffusers + = new TheoryData { - KnownDitherings.Atkinson, - KnownDitherings.Burks, - KnownDitherings.FloydSteinberg, - KnownDitherings.JarvisJudiceNinke, - KnownDitherings.Sierra2, - KnownDitherings.Sierra3, - KnownDitherings.SierraLite, - KnownDitherings.StevensonArce, - KnownDitherings.Stucki, + { KnownDitherings.Atkinson, nameof(KnownDitherings.Atkinson) }, + { KnownDitherings.Burks, nameof(KnownDitherings.Burks) }, + { KnownDitherings.FloydSteinberg, nameof(KnownDitherings.FloydSteinberg) }, + { KnownDitherings.JarvisJudiceNinke, nameof(KnownDitherings.JarvisJudiceNinke) }, + { KnownDitherings.Sierra2, nameof(KnownDitherings.Sierra2) }, + { KnownDitherings.Sierra3, nameof(KnownDitherings.Sierra3) }, + { KnownDitherings.SierraLite, nameof(KnownDitherings.SierraLite) }, + { KnownDitherings.StevensonArce, nameof(KnownDitherings.StevensonArce) }, + { KnownDitherings.Stucki, nameof(KnownDitherings.Stucki) }, }; - public static readonly TheoryData OrderedDitherers - = new TheoryData + public static readonly TheoryData OrderedDitherers + = new TheoryData { - KnownDitherings.BayerDither8x8, - KnownDitherings.BayerDither4x4, - KnownDitherings.OrderedDither3x3, - KnownDitherings.BayerDither2x2 + { KnownDitherings.Bayer2x2, nameof(KnownDitherings.Bayer2x2) }, + { KnownDitherings.Bayer4x4, nameof(KnownDitherings.Bayer4x4) }, + { KnownDitherings.Bayer8x8, nameof(KnownDitherings.Bayer8x8) }, + { KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) } }; private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); - private static IDither DefaultDitherer => KnownDitherings.BayerDither4x4; + private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; @@ -102,7 +102,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] public void DiffusionFilter_WorksWithAllErrorDiffusers( TestImageProvider provider, - IDither diffuser) + IDither diffuser, + string name) where TPixel : struct, IPixel { if (SkipAllDitherTests) @@ -112,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization provider.RunValidatingProcessorTest( x => x.Dither(diffuser), - testOutputDetails: diffuser.GetType().Name, + testOutputDetails: name, comparer: ValidatorComparer, appendPixelTypeToFileName: false); } @@ -136,7 +137,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] public void DitherFilter_WorksWithAllDitherers( TestImageProvider provider, - IDither ditherer) + IDither ditherer, + string name) where TPixel : struct, IPixel { if (SkipAllDitherTests) @@ -146,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization provider.RunValidatingProcessorTest( x => x.Dither(ditherer), - testOutputDetails: ditherer.GetType().Name, + testOutputDetails: name, comparer: ValidatorComparer, appendPixelTypeToFileName: false); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs index 0d50ddf2f..70a07f74f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization private static readonly QuantizerOptions NoDitherOptions = new QuantizerOptions { Dither = null }; private static readonly QuantizerOptions DiffuserDitherOptions = new QuantizerOptions { Dither = KnownDitherings.FloydSteinberg }; - private static readonly QuantizerOptions OrderedDitherOptions = new QuantizerOptions { Dither = KnownDitherings.BayerDither8x8 }; + private static readonly QuantizerOptions OrderedDitherOptions = new QuantizerOptions { Dither = KnownDitherings.Bayer8x8 }; private static readonly QuantizerOptions Diffuser0_ScaleDitherOptions = new QuantizerOptions { @@ -57,25 +57,25 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization private static readonly QuantizerOptions Ordered0_ScaleDitherOptions = new QuantizerOptions { - Dither = KnownDitherings.BayerDither8x8, + Dither = KnownDitherings.Bayer8x8, DitherScale = 0F }; private static readonly QuantizerOptions Ordered0_25_ScaleDitherOptions = new QuantizerOptions { - Dither = KnownDitherings.BayerDither8x8, + Dither = KnownDitherings.Bayer8x8, DitherScale = .25F }; private static readonly QuantizerOptions Ordered0_5_ScaleDitherOptions = new QuantizerOptions { - Dither = KnownDitherings.BayerDither8x8, + Dither = KnownDitherings.Bayer8x8, DitherScale = .5F }; private static readonly QuantizerOptions Ordered0_75_ScaleDitherOptions = new QuantizerOptions { - Dither = KnownDitherings.BayerDither8x8, + Dither = KnownDitherings.Bayer8x8, DitherScale = .75F }; @@ -164,9 +164,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization } string quantizerName = quantizer.GetType().Name; - string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "noDither"; - string ditherType = quantizer.Options.Dither?.DitherType.ToString() ?? string.Empty; - string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}"; + string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither"; + string testOutputDetails = $"{quantizerName}_{ditherName}"; provider.RunRectangleConstrainedValidatingProcessorTest( (x, rect) => x.Quantize(quantizer, rect), @@ -186,9 +185,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization } string quantizerName = quantizer.GetType().Name; - string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "noDither"; - string ditherType = quantizer.Options.Dither?.DitherType.ToString() ?? string.Empty; - string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}"; + string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither"; + string testOutputDetails = $"{quantizerName}_{ditherName}"; provider.RunValidatingProcessorTest( x => x.Quantize(quantizer), @@ -209,9 +207,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization string quantizerName = quantizer.GetType().Name; string ditherName = quantizer.Options.Dither.GetType().Name; - string ditherType = quantizer.Options.Dither.DitherType.ToString(); float ditherScale = quantizer.Options.DitherScale; - string testOutputDetails = FormattableString.Invariant($"{quantizerName}_{ditherName}_{ditherType}_{ditherScale}"); + string testOutputDetails = FormattableString.Invariant($"{quantizerName}_{ditherName}_{ditherScale}"); provider.RunValidatingProcessorTest( x => x.Quantize(quantizer), diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index 42da64fdb..92e14b6a1 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests foreach (ImageFrame frame in image.Frames) { using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(this.Configuration)) - using (IQuantizedFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + using (QuantizedFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) { int index = this.GetTransparentIndex(quantized); Assert.Equal(index, quantized.GetPixelSpan()[0]); @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Tests foreach (ImageFrame frame in image.Frames) { using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(this.Configuration)) - using (IQuantizedFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + using (QuantizedFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) { int index = this.GetTransparentIndex(quantized); Assert.Equal(index, quantized.GetPixelSpan()[0]); @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests } } - private int GetTransparentIndex(IQuantizedFrame quantized) + private int GetTransparentIndex(QuantizedFrame quantized) where TPixel : struct, IPixel { // Transparent pixels are much more likely to be found at the end of a palette diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index 6d48660f6..d41d133fa 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization ImageFrame frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); - using IQuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.GetPixelSpan().Length); @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization ImageFrame frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); - using IQuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.GetPixelSpan().Length); @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization ImageFrame frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); - using IQuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); Assert.Equal(256, result.Palette.Length); Assert.Equal(256, result.GetPixelSpan().Length); @@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization ImageFrame frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); - using IQuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + using QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); Assert.Equal(48, result.Palette.Length); } @@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization ImageFrame frame = image.Frames.RootFrame; using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) - using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + using (QuantizedFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) { Assert.Equal(4 * 8, result.Palette.Length); Assert.Equal(256, result.GetPixelSpan().Length); diff --git a/tests/Images/External b/tests/Images/External index e027069e5..2d1505d70 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit e027069e57948c94964d0948c5f6a79ace6c601a +Subproject commit 2d1505d7087d91cd83d0cda409aee213de7841ab