diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 995aee91d5..435fdc4fc6 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; @@ -82,6 +83,7 @@ namespace SixLabors.ImageSharp.Advanced // This is we actually call all the individual methods you need to seed. AotCompileOctreeQuantizer(); AotCompileWuQuantizer(); + AotCompilePaletteQuantizer(); AotCompileDithering(); AotCompilePixelOperations(); @@ -109,7 +111,7 @@ namespace SixLabors.ImageSharp.Advanced private static void AotCompileOctreeQuantizer() where TPixel : struct, IPixel { - using (var test = new OctreeFrameQuantizer(Configuration.Default, new OctreeQuantizer(false))) + using (var test = new OctreeFrameQuantizer(Configuration.Default, new OctreeQuantizer().Options)) { test.AotGetPalette(); } @@ -122,7 +124,22 @@ namespace SixLabors.ImageSharp.Advanced private static void AotCompileWuQuantizer() where TPixel : struct, IPixel { - using (var test = new WuFrameQuantizer(Configuration.Default, new WuQuantizer(false))) + using (var test = new WuFrameQuantizer(Configuration.Default, new WuQuantizer().Options)) + { + var frame = new ImageFrame(Configuration.Default, 1, 1); + test.QuantizeFrame(frame, frame.Bounds()); + test.AotGetPalette(); + } + } + + /// + /// This method pre-seeds the PaletteQuantizer in the AoT compiler for iOS. + /// + /// The pixel format. + private static void AotCompilePaletteQuantizer() + where TPixel : struct, IPixel + { + using (var test = (PaletteFrameQuantizer)new PaletteQuantizer(Array.Empty()).CreateFrameQuantizer(Configuration.Default)) { var frame = new ImageFrame(Configuration.Default, 1, 1); test.QuantizeFrame(frame, frame.Bounds()); @@ -141,7 +158,7 @@ namespace SixLabors.ImageSharp.Advanced TPixel pixel = default; using (var image = new ImageFrame(Configuration.Default, 1, 1)) { - test.Dither(image, default, pixel, pixel, 0, 0, 0); + test.Dither(image, default, pixel, pixel, 0, 0, 0, 0); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index a1c415f76e..2d6b06111d 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Bmp @@ -87,7 +88,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.memoryAllocator = memoryAllocator; this.bitsPerPixel = options.BitsPerPixel; this.writeV4Header = options.SupportTransparency; - this.quantizer = options.Quantizer ?? new OctreeQuantizer(dither: true, maxColors: 256); + this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; } /// @@ -335,7 +336,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp private void Write8BitColor(Stream stream, ImageFrame image, Span colorPalette) where TPixel : struct, IPixel { - using IFrameQuantizer quantizer = this.quantizer.CreateFrameQuantizer(this.configuration, 256); + using IFrameQuantizer quantizer = this.quantizer.CreateFrameQuantizer(this.configuration); using IQuantizedFrame quantized = quantizer.QuantizeFrame(image, image.Bounds()); ReadOnlySpan quantizedColors = quantized.Palette.Span; diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 8577ab4768..0307f7d94b 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -144,13 +144,10 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - using (IFrameQuantizer paletteFrameQuantizer = - new PaletteFrameQuantizer(this.configuration, this.quantizer.Dither, quantized.Palette)) + using (IFrameQuantizer paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Options, quantized.Palette)) + using (IQuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds())) { - using (IQuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds())) - { - this.WriteImageData(paletteQuantized, stream); - } + this.WriteImageData(paletteQuantized, stream); } } } @@ -171,7 +168,14 @@ namespace SixLabors.ImageSharp.Formats.Gif if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength && frameMetadata.ColorTableLength > 0) { - using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration, frameMetadata.ColorTableLength)) + var options = new QuantizerOptions + { + Dither = this.quantizer.Options.Dither, + DitherScale = this.quantizer.Options.DitherScale, + MaxColors = frameMetadata.ColorTableLength + }; + + using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration, options)) { quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs index dc3d9d3ce6..c29ec578c1 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs @@ -72,7 +72,8 @@ namespace SixLabors.ImageSharp.Formats.Png // Use the metadata to determine what quantization depth to use if no quantizer has been set. if (options.Quantizer is null) { - options.Quantizer = new WuQuantizer(ImageMaths.GetColorCountForBitDepth(bits)); + var maxColors = ImageMaths.GetColorCountForBitDepth(bits); + options.Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = maxColors }); } // Create quantized frame returning the palette and set the bit depth. diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 91ca4e95ef..92db4638be 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -38,7 +38,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering TPixel transformed, int x, int y, - int bitDepth) + int bitDepth, + float scale) where TPixel : struct, IPixel { // Equal? Break out as there's no error to pass. @@ -48,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } // Calculate the error - Vector4 error = source.ToVector4() - transformed.ToVector4(); + Vector4 error = (source.ToVector4() - transformed.ToVector4()) * scale; int offset = this.offset; DenseMatrix matrix = this.matrix; diff --git a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs index 0d7841884b..dc48b7e6d2 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs @@ -28,6 +28,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// 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( @@ -37,7 +38,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering TPixel transformed, int x, int y, - int bitDepth) + int bitDepth, + float scale) where TPixel : struct, IPixel; } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index c3277e3266..2e66ae86ff 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -54,20 +54,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering TPixel transformed, int x, int y, - int bitDepth) + int bitDepth, + float scale) where TPixel : struct, IPixel { - // TODO: Should we consider a pixel format with a larger coror range? Rgba32 rgba = default; source.ToRgba32(ref rgba); Rgba32 attempt; - // Srpead assumes an even colorspace distribution and precision. + // Spread assumes an even colorspace distribution and precision. // Calculated as 0-255/component count. 256 / bitDepth // https://bisqwit.iki.fi/story/howto/dither/jy/ // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm int spread = 256 / bitDepth; - float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX]; + float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale; attempt.R = (byte)(rgba.R + factor).Clamp(byte.MinValue, byte.MaxValue); attempt.G = (byte)(rgba.G + factor).Clamp(byte.MinValue, byte.MaxValue); diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs index c7abb308f3..40949bb284 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs @@ -32,10 +32,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } /// - /// Gets the dithering algorithm. + /// Gets the dithering algorithm to apply to the output image. /// public IDither Dither { get; } + /// + /// Gets the dithering scale used to adjust the amount of dither. Range 0..1. + /// + public float DitherScale { get; } + /// /// Gets the palette to select substitute colors from. /// diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index bdcc9e6b89..315ce22e08 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -20,6 +20,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering 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; @@ -38,6 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering this.paletteLength = definition.Palette.Span.Length; this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.paletteLength); this.dither = definition.Dither; + this.ditherScale = definition.DitherScale; this.sourcePalette = definition.Palette; } @@ -58,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering { TPixel sourcePixel = row[x]; this.pixelMap.GetClosestColor(sourcePixel, out TPixel transformed); - this.dither.Dither(source, interest, sourcePixel, transformed, x, y, this.bitDepth); + this.dither.Dither(source, interest, sourcePixel, transformed, x, y, this.bitDepth, this.ditherScale); row[x] = transformed; } } @@ -67,7 +69,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering } // 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.bitDepth); + var ditherOperation = new DitherRowIntervalOperation( + source, + interest, + this.pixelMap, + this.dither, + this.ditherScale, + this.bitDepth); + ParallelRowIterator.IterateRows( this.Configuration, interest, @@ -114,6 +123,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering private readonly Rectangle bounds; private readonly EuclideanPixelMap pixelMap; private readonly IDither dither; + private readonly float scale; private readonly int bitDepth; [MethodImpl(InliningOptions.ShortMethod)] @@ -122,12 +132,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering 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; } @@ -143,7 +155,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering 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); + 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/Quantization/FrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs index 1914ed8915..0d3b7de6d6 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs @@ -17,11 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public abstract class FrameQuantizer : IFrameQuantizer where TPixel : struct, IPixel { - /// - /// Flag used to indicate whether a single pass or two passes are needed for quantization. - /// private readonly bool singlePass; - private EuclideanPixelMap pixelMap; private bool isDisposed; @@ -29,57 +25,39 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// The quantizer. + /// The quantizer options defining quantization rules. /// - /// If true, the quantization process only needs to loop through the source pixels once. + /// 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, IQuantizer quantizer, bool singlePass) + protected FrameQuantizer(Configuration configuration, QuantizerOptions options, bool singlePass) { - Guard.NotNull(quantizer, nameof(quantizer)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); this.Configuration = configuration; - this.Dither = quantizer.Dither; - this.DoDither = this.Dither != null; + this.Options = options; + this.IsDitheringQuantizer = options.Dither != null; this.singlePass = singlePass; } - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The diffuser - /// - /// If true, 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, IDither diffuser, bool singlePass) - { - this.Configuration = configuration; - this.Dither = diffuser; - this.DoDither = this.Dither != null; - this.singlePass = singlePass; - } - - /// - public IDither Dither { get; } - - /// - public bool DoDither { get; } + /// + 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() { @@ -109,7 +87,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization var quantizedFrame = new QuantizedFrame(memoryAllocator, interest.Width, interest.Height, palette); Memory output = quantizedFrame.GetWritablePixelMemory(); - if (this.DoDither) + 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()) @@ -117,10 +99,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.SecondPass(clone, interest, output, palette); } } - else - { - this.SecondPass(image, interest, output, palette); - } return quantizedFrame; } @@ -162,7 +140,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlyMemory palette) { ReadOnlySpan paletteSpan = palette.Span; - if (!this.DoDither) + IDither dither = this.Options.Dither; + + if (dither is null) { var operation = new RowIntervalOperation(source, output, bounds, this, palette); ParallelRowIterator.IterateRows( @@ -179,8 +159,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization Span outputSpan = output.Span; int bitDepth = ImageMaths.GetBitsNeededForColorDepth(paletteSpan.Length); - if (this.Dither.DitherType == DitherType.ErrorDiffusion) + if (dither.DitherType == DitherType.ErrorDiffusion) { + float ditherScale = this.Options.DitherScale; int width = bounds.Width; int offsetY = bounds.Top; int offsetX = bounds.Left; @@ -193,7 +174,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { TPixel sourcePixel = row[x]; outputSpan[rowStart + x - offsetX] = this.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed); - this.Dither.Dither(source, bounds, sourcePixel, transformed, x, y, bitDepth); + dither.Dither(source, bounds, sourcePixel, transformed, x, y, bitDepth, ditherScale); } } @@ -306,7 +287,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization int width = this.bounds.Width; int offsetY = this.bounds.Top; int offsetX = this.bounds.Left; - IDither dither = this.quantizer.Dither; + IDither dither = this.quantizer.Options.Dither; + float scale = this.quantizer.Options.DitherScale; TPixel transformed = default; for (int y = rows.Min; y < rows.Max; y++) @@ -316,7 +298,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization 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); + 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 30d58ab0b1..5913179025 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -15,14 +15,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : struct, IPixel { /// - /// Gets a value indicating whether to apply dithering to the output image. + /// Gets the quantizer options defining quantization rules. /// - bool DoDither { get; } - - /// - /// Gets the algorithm to apply to the output image. - /// - IDither Dither { get; } + QuantizerOptions Options { get; } /// /// Quantize an image frame and return the resulting output pixels. diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs index 7bf58b31f8..2daddf1057 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -12,27 +11,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public interface IQuantizer { /// - /// Gets the dithering algorithm to apply to the output image. + /// Gets the quantizer options defining quantization rules. /// - IDither Dither { get; } + QuantizerOptions Options { get; } /// - /// Creates the generic frame quantizer + /// Creates the generic frame quantizer. /// /// The to configure internal operations. /// The pixel format. - /// The + /// The . IFrameQuantizer CreateFrameQuantizer(Configuration configuration) where TPixel : struct, IPixel; /// - /// Creates the generic frame quantizer + /// Creates the generic frame quantizer. /// /// The pixel format. /// The to configure internal operations. - /// The maximum number of colors to hold in the color palette. - /// The - IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) + /// The options to create the quantizer with. + /// The . + IFrameQuantizer CreateFrameQuantizer(Configuration configuration, QuantizerOptions options) where TPixel : struct, IPixel; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 643507351b..4fecc5702a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -39,30 +39,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// The octree quantizer + /// 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 /// - public OctreeFrameQuantizer(Configuration configuration, OctreeQuantizer quantizer) - : this(configuration, quantizer, quantizer.MaxColors) + public OctreeFrameQuantizer(Configuration configuration, QuantizerOptions options) + : base(configuration, options, false) { - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The octree quantizer. - /// The maximum number of colors to hold in the color palette. - /// - /// 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 - /// - public OctreeFrameQuantizer(Configuration configuration, OctreeQuantizer quantizer, int maxColors) - : base(configuration, quantizer, false) - { - this.colors = maxColors; + this.colors = this.Options.MaxColors; this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8)); } @@ -95,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization // 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.DoDither && !color.Equals(default)) + if (!this.IsDitheringQuantizer && !color.Equals(default)) { var index = (byte)this.octree.GetPaletteIndex(color); match = palette[index]; diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index 06578354c0..a5660c43b4 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -2,97 +2,45 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Allows the quantization of images pixels using Octrees. /// - /// - /// By default the quantizer uses dithering and a color palette of a maximum length of 255 - /// /// public class OctreeQuantizer : IQuantizer { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// using the default . /// public OctreeQuantizer() - : this(true) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum number of colors to hold in the color palette. - public OctreeQuantizer(int maxColors) - : this(GetDiffuser(true), maxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Whether to apply dithering to the output image. - public OctreeQuantizer(bool dither) - : this(GetDiffuser(dither), QuantizerConstants.MaxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Whether to apply dithering to the output image. - /// The maximum number of colors to hold in the color palette. - public OctreeQuantizer(bool dither, int maxColors) - : this(GetDiffuser(dither), maxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The dithering algorithm, if any, to apply to the output image. - public OctreeQuantizer(IDither diffuser) - : this(diffuser, QuantizerConstants.MaxColors) + : this(new QuantizerOptions()) { } /// /// Initializes a new instance of the class. /// - /// The dithering algorithm, if any, to apply to the output image. - /// The maximum number of colors to hold in the color palette. - public OctreeQuantizer(IDither dither, int maxColors) + /// The quantizer options defining quantization rules. + public OctreeQuantizer(QuantizerOptions options) { - this.Dither = dither; - this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + Guard.NotNull(options, nameof(options)); + this.Options = options; } /// - public IDither Dither { get; } - - /// - /// Gets the maximum number of colors to hold in the color palette. - /// - public int MaxColors { get; } + public QuantizerOptions Options { get; } - /// /// public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) where TPixel : struct, IPixel - => new OctreeFrameQuantizer(configuration, this); + => this.CreateFrameQuantizer(configuration, this.Options); - /// - public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, QuantizerOptions options) where TPixel : struct, IPixel - { - maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); - return new OctreeFrameQuantizer(configuration, this, maxColors); - } - - private static IDither GetDiffuser(bool dither) => dither ? KnownDitherings.FloydSteinberg : null; + => new OctreeFrameQuantizer(configuration, options); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index f60e6d79a7..453c1d5dcc 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -25,13 +24,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// The palette quantizer. - /// An array of all colors in the palette. - public PaletteFrameQuantizer(Configuration configuration, IDither diffuser, ReadOnlyMemory colors) - : base(configuration, diffuser, true) => this.palette = colors; + /// The quantizer options defining quantization rules. + /// A containing all colors in the palette. + public PaletteFrameQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory colors) + : base(configuration, options, true) => this.palette = colors; /// [MethodImpl(InliningOptions.ShortMethod)] protected override ReadOnlyMemory GenerateQuantizedPalette() => this.palette; + + internal ReadOnlyMemory AotGetPalette() => this.GenerateQuantizedPalette(); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index fd2e6052ee..c1198c58f7 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -2,80 +2,62 @@ // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Allows the quantization of images pixels using color palettes. - /// Override this class to provide your own palette. - /// - /// By default the quantizer uses dithering. - /// /// public class PaletteQuantizer : IQuantizer { /// /// Initializes a new instance of the class. /// - /// The palette. + /// The color palette. public PaletteQuantizer(ReadOnlyMemory palette) - : this(palette, true) + : this(palette, new QuantizerOptions()) { } /// /// Initializes a new instance of the class. /// - /// The palette. - /// Whether to apply dithering to the output image - public PaletteQuantizer(ReadOnlyMemory palette, bool dither) - : this(palette, GetDiffuser(dither)) + /// The color palette. + /// The quantizer options defining quantization rules. + public PaletteQuantizer(ReadOnlyMemory palette, QuantizerOptions options) { - } + Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); + Guard.NotNull(options, nameof(options)); - /// - /// Initializes a new instance of the class. - /// - /// The palette. - /// The dithering algorithm, if any, to apply to the output image - public PaletteQuantizer(ReadOnlyMemory palette, IDither dither) - { this.Palette = palette; - this.Dither = dither; + this.Options = options; } - /// - public IDither Dither { get; } - /// - /// Gets the palette. + /// Gets the color palette. /// public ReadOnlyMemory Palette { get; } + /// + public QuantizerOptions Options { get; } + /// public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) where TPixel : struct, IPixel - { - var palette = new TPixel[this.Palette.Length]; - Color.ToPixel(configuration, this.Palette.Span, palette.AsSpan()); - return new PaletteFrameQuantizer(configuration, this.Dither, palette); - } + => this.CreateFrameQuantizer(configuration, this.Options); - /// - public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, QuantizerOptions options) where TPixel : struct, IPixel { - maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); - int max = Math.Min(maxColors, this.Palette.Length); + Guard.NotNull(options, nameof(options)); - var palette = new TPixel[max]; - Color.ToPixel(configuration, this.Palette.Span.Slice(0, max), palette.AsSpan()); - return new PaletteFrameQuantizer(configuration, this.Dither, palette); - } + int length = Math.Min(this.Palette.Span.Length, options.MaxColors); + var palette = new TPixel[length]; - private static IDither GetDiffuser(bool dither) => dither ? KnownDitherings.FloydSteinberg : null; + Color.ToPixel(configuration, this.Palette.Span, palette.AsSpan()); + return new PaletteFrameQuantizer(configuration, options, palette); + } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs index d79a91c301..ece3777e0e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs @@ -1,12 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Processing.Processors.Dithering; + namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Contains color quantization specific constants. /// - internal static class QuantizerConstants + public static class QuantizerConstants { /// /// The minimum number of colors to use when quantizing an image. @@ -17,5 +19,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The maximum number of colors to use when quantizing an image. /// public const int MaxColors = 256; + + /// + /// The minumim dithering scale used to adjust the amount of dither. + /// + public const float MinDitherScale = 0; + + /// + /// The max dithering scale used to adjust the amount of dither. + /// + public const float MaxDitherScale = 1F; + + /// + /// Gets the default dithering algorithm to use. + /// + public static IDither DefaultDither { get; } = KnownDitherings.FloydSteinberg; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs new file mode 100644 index 0000000000..5c1daf183b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Defines options for quantization. + /// + public class QuantizerOptions + { + private float ditherScale = QuantizerConstants.MaxDitherScale; + private int maxColors = QuantizerConstants.MaxColors; + + /// + /// Gets or sets the algorithm to apply to the output image. + /// Defaults to ; set to for no dithering. + /// + public IDither Dither { get; set; } = QuantizerConstants.DefaultDither; + + /// + /// Gets or sets the dithering scale used to adjust the amount of dither. Range 0..1. + /// Defaults to . + /// + public float DitherScale + { + get { return this.ditherScale; } + set { this.ditherScale = value.Clamp(QuantizerConstants.MinDitherScale, QuantizerConstants.MaxDitherScale); } + } + + /// + /// Gets or sets the maximum number of colors to hold in the color palette. Range 0..256. + /// Defaults to . + /// + public int MaxColors + { + get { return this.maxColors; } + set { this.maxColors = value.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs index ff965e3930..8aa634b9ff 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Dithering; @@ -14,26 +14,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Initializes a new instance of the class. /// public WebSafePaletteQuantizer() - : this(true) + : this(new QuantizerOptions()) { } /// /// Initializes a new instance of the class. /// - /// Whether to apply dithering to the output image - public WebSafePaletteQuantizer(bool dither) - : base(Color.WebSafePalette, dither) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffusion algorithm, if any, to apply to the output image - public WebSafePaletteQuantizer(IDither diffuser) - : base(Color.WebSafePalette, diffuser) + /// The quantizer options defining quantization rules. + public WebSafePaletteQuantizer(QuantizerOptions options) + : base(Color.WebSafePalette, options) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs index 3b48ddedac..168c837d57 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Processing.Processors.Dithering; - namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// @@ -15,26 +13,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Initializes a new instance of the class. /// public WernerPaletteQuantizer() - : this(true) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Whether to apply dithering to the output image - public WernerPaletteQuantizer(bool dither) - : base(Color.WernerPalette, dither) + : this(new QuantizerOptions()) { } /// /// Initializes a new instance of the class. /// - /// The error diffusion algorithm, if any, to apply to the output image - public WernerPaletteQuantizer(IDither diffuser) - : base(Color.WernerPalette, diffuser) + /// The quantizer options defining quantization rules. + public WernerPaletteQuantizer(QuantizerOptions options) + : base(Color.WernerPalette, options) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 75b922e347..0a46cd302e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -96,33 +96,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// The Wu quantizer + /// 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. /// - public WuFrameQuantizer(Configuration configuration, WuQuantizer quantizer) - : this(configuration, quantizer, quantizer.MaxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The Wu quantizer. - /// The maximum number of colors to hold in the color palette. - /// - /// 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. - /// - public WuFrameQuantizer(Configuration configuration, WuQuantizer quantizer, int maxColors) - : base(configuration, quantizer, false) + public WuFrameQuantizer(Configuration configuration, QuantizerOptions options) + : base(configuration, options, false) { this.memoryAllocator = this.Configuration.MemoryAllocator; this.moments = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); this.tag = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.colors = maxColors; + this.colors = this.Options.MaxColors; } /// @@ -185,9 +170,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization [MethodImpl(InliningOptions.ShortMethod)] protected override byte GetQuantizedColor(TPixel color, ReadOnlySpan palette, out TPixel match) { - if (!this.DoDither) + if (!this.IsDitheringQuantizer) { - // Expected order r->g->b->a Rgba32 rgba = default; color.ToRgba32(ref rgba); diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs index 682b6ec64f..b8c54f467e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs @@ -2,89 +2,44 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer - /// - /// By default the quantizer uses dithering and a color palette of a maximum length of 255 - /// /// public class WuQuantizer : IQuantizer { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// using the default . /// public WuQuantizer() - : this(true) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum number of colors to hold in the color palette - public WuQuantizer(int maxColors) - : this(GetDiffuser(true), maxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Whether to apply dithering to the output image - public WuQuantizer(bool dither) - : this(GetDiffuser(dither), QuantizerConstants.MaxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The dithering algorithm, if any, to apply to the output image - public WuQuantizer(IDither diffuser) - : this(diffuser, QuantizerConstants.MaxColors) + : this(new QuantizerOptions()) { } /// /// Initializes a new instance of the class. /// - /// The dithering algorithm, if any, to apply to the output image - /// The maximum number of colors to hold in the color palette - public WuQuantizer(IDither dither, int maxColors) + /// The quantizer options defining quantization rules. + public WuQuantizer(QuantizerOptions options) { - this.Dither = dither; - this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + Guard.NotNull(options, nameof(options)); + this.Options = options; } /// - public IDither Dither { get; } - - /// - /// Gets the maximum number of colors to hold in the color palette. - /// - public int MaxColors { get; } + public QuantizerOptions Options { get; } /// public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) where TPixel : struct, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - return new WuFrameQuantizer(configuration, this); - } + => this.CreateFrameQuantizer(configuration, this.Options); - /// - public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, QuantizerOptions options) where TPixel : struct, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); - return new WuFrameQuantizer(configuration, this, maxColors); - } - - private static IDither GetDiffuser(bool dither) => dither ? KnownDitherings.FloydSteinberg : null; + => new WuFrameQuantizer(configuration, options); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs index 89eb63d629..8983d30409 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Drawing.Imaging; @@ -6,6 +6,7 @@ using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; @@ -53,11 +54,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void GifCore() { // Try to get as close to System.Drawing's output as possible - var options = new GifEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; + var options = new GifEncoder + { + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.BayerDither4x4 }) + }; + using (var memoryStream = new MemoryStream()) { this.bmpCore.SaveAsGif(memoryStream, options); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs index 4d93d89af2..e21fbfc612 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Drawing.Imaging; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Benchmarks.Codecs @@ -23,7 +24,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs this.ForEachImageSharpImage((img, ms) => { // Try to get as close to System.Drawing's output as possible - var options = new GifEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; + var options = new GifEncoder + { + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.BayerDither4x4 }) + }; + img.Save(ms, options); return null; }); diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs index 639d1594ee..aedf9cd777 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.IO; @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - var options = new PngEncoder { Quantizer = new OctreeQuantizer(false) }; + var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } } @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; + var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } } @@ -95,9 +95,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - var options = new PngEncoder { Quantizer = new WuQuantizer(false) }; + var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 55d31b5a38..10be33a97a 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -197,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp var encoder = new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel8, - Quantizer = new WuQuantizer(256) + Quantizer = new WuQuantizer() }; string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); @@ -223,7 +223,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp var encoder = new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel8, - Quantizer = new OctreeQuantizer(256) + Quantizer = new OctreeQuantizer() }; string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index fe1faa5aed..ea1eb700a7 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { // Use the palette quantizer without dithering to ensure results // are consistent - Quantizer = new WebSafePaletteQuantizer(false) + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; // Always save as we need to compare the encoded output. @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var encoder = new GifEncoder { ColorTableMode = GifColorTableMode.Global, - Quantizer = new OctreeQuantizer(false) + Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; // Always save as we need to compare the encoded output. @@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var encoder = new GifEncoder { ColorTableMode = colorMode, - Quantizer = new OctreeQuantizer(frameMetadata.ColorTableLength) + Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = frameMetadata.ColorTableLength }) }; image.Save(outStream, encoder); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index f5b06eb6c3..2fa1657e66 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -428,7 +428,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png FilterMethod = pngFilterMethod, CompressionLevel = compressionLevel, BitDepth = bitDepth, - Quantizer = new WuQuantizer(paletteSize), + Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }), InterlaceMethod = interlaceMode }; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs index 69a681bb36..bb7921d686 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs @@ -13,22 +13,26 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization [Fact] public void OctreeQuantizerConstructor() { - var quantizer = new OctreeQuantizer(128); - - Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); - - quantizer = new OctreeQuantizer(false); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Null(quantizer.Dither); - - quantizer = new OctreeQuantizer(KnownDitherings.Atkinson); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); - - quantizer = new OctreeQuantizer(KnownDitherings.Atkinson, 128); - Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new OctreeQuantizer(expected); + + Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = null }; + quantizer = new OctreeQuantizer(expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Null(quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; + quantizer = new OctreeQuantizer(expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; + quantizer = new OctreeQuantizer(expected); + Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); } [Fact] @@ -38,23 +42,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.DoDither); - Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(false); + quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.DoDither); - Assert.Null(frameQuantizer.Dither); + Assert.Null(frameQuantizer.Options.Dither); frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(KnownDitherings.Atkinson); + quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.DoDither); - Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); frameQuantizer.Dispose(); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs index a348deb654..3c1fa11ab0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs @@ -10,49 +10,55 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization { public class PaletteQuantizerTests { - private static readonly Color[] Rgb = new Color[] { Color.Red, Color.Green, Color.Blue }; + private static readonly Color[] Palette = new Color[] { Color.Red, Color.Green, Color.Blue }; [Fact] public void PaletteQuantizerConstructor() { - var quantizer = new PaletteQuantizer(Rgb); + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new PaletteQuantizer(Palette, expected); - Assert.Equal(Rgb, quantizer.Palette); - Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); + Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); - quantizer = new PaletteQuantizer(Rgb, false); - Assert.Equal(Rgb, quantizer.Palette); - Assert.Null(quantizer.Dither); + expected = new QuantizerOptions { Dither = null }; + quantizer = new PaletteQuantizer(Palette, expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Null(quantizer.Options.Dither); - quantizer = new PaletteQuantizer(Rgb, KnownDitherings.Atkinson); - Assert.Equal(Rgb, quantizer.Palette); - Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; + quantizer = new PaletteQuantizer(Palette, expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; + quantizer = new PaletteQuantizer(Palette, expected); + Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); } [Fact] public void PaletteQuantizerCanCreateFrameQuantizer() { - var quantizer = new PaletteQuantizer(Rgb); + var quantizer = new PaletteQuantizer(Palette); IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.DoDither); - Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); frameQuantizer.Dispose(); - quantizer = new PaletteQuantizer(Rgb, false); + quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = null }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.DoDither); - Assert.Null(frameQuantizer.Dither); + Assert.Null(frameQuantizer.Options.Dither); frameQuantizer.Dispose(); - quantizer = new PaletteQuantizer(Rgb, KnownDitherings.Atkinson); + quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = KnownDitherings.Atkinson }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.DoDither); - Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); frameQuantizer.Dispose(); } @@ -60,14 +66,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization public void KnownQuantizersWebSafeTests() { IQuantizer quantizer = KnownQuantizers.WebSafe; - Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); } [Fact] public void KnownQuantizersWernerTests() { IQuantizer quantizer = KnownQuantizers.Werner; - Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs index efad57d5b9..d3e8b034be 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs @@ -17,21 +17,128 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization TestImages.Png.Bike }; + 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 Diffuser0_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = 0F + }; + + private static readonly QuantizerOptions Diffuser0_25_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = .25F + }; + + private static readonly QuantizerOptions Diffuser0_5_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = .5F + }; + + private static readonly QuantizerOptions Diffuser0_75_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = .75F + }; + + private static readonly QuantizerOptions Ordered0_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.BayerDither8x8, + DitherScale = 0F + }; + + private static readonly QuantizerOptions Ordered0_25_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.BayerDither8x8, + DitherScale = .25F + }; + + private static readonly QuantizerOptions Ordered0_5_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.BayerDither8x8, + DitherScale = .5F + }; + + private static readonly QuantizerOptions Ordered0_75_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.BayerDither8x8, + DitherScale = .75F + }; + public static readonly TheoryData Quantizers = new TheoryData { + // Known uses error diffusion by default. KnownQuantizers.Octree, KnownQuantizers.WebSafe, KnownQuantizers.Werner, KnownQuantizers.Wu, - new OctreeQuantizer(false), - new WebSafePaletteQuantizer(false), - new WernerPaletteQuantizer(false), - new WuQuantizer(false), - new OctreeQuantizer(KnownDitherings.BayerDither8x8), - new WebSafePaletteQuantizer(KnownDitherings.BayerDither8x8), - new WernerPaletteQuantizer(KnownDitherings.BayerDither8x8), - new WuQuantizer(KnownDitherings.BayerDither8x8) + new OctreeQuantizer(NoDitherOptions), + new WebSafePaletteQuantizer(NoDitherOptions), + new WernerPaletteQuantizer(NoDitherOptions), + new WuQuantizer(NoDitherOptions), + new OctreeQuantizer(OrderedDitherOptions), + new WebSafePaletteQuantizer(OrderedDitherOptions), + new WernerPaletteQuantizer(OrderedDitherOptions), + new WuQuantizer(OrderedDitherOptions) + }; + + public static readonly TheoryData DitherScaleQuantizers + = new TheoryData + { + new OctreeQuantizer(Diffuser0_ScaleDitherOptions), + new WebSafePaletteQuantizer(Diffuser0_ScaleDitherOptions), + new WernerPaletteQuantizer(Diffuser0_ScaleDitherOptions), + new WuQuantizer(Diffuser0_ScaleDitherOptions), + + new OctreeQuantizer(Diffuser0_25_ScaleDitherOptions), + new WebSafePaletteQuantizer(Diffuser0_25_ScaleDitherOptions), + new WernerPaletteQuantizer(Diffuser0_25_ScaleDitherOptions), + new WuQuantizer(Diffuser0_25_ScaleDitherOptions), + + new OctreeQuantizer(Diffuser0_5_ScaleDitherOptions), + new WebSafePaletteQuantizer(Diffuser0_5_ScaleDitherOptions), + new WernerPaletteQuantizer(Diffuser0_5_ScaleDitherOptions), + new WuQuantizer(Diffuser0_5_ScaleDitherOptions), + + new OctreeQuantizer(Diffuser0_75_ScaleDitherOptions), + new WebSafePaletteQuantizer(Diffuser0_75_ScaleDitherOptions), + new WernerPaletteQuantizer(Diffuser0_75_ScaleDitherOptions), + new WuQuantizer(Diffuser0_75_ScaleDitherOptions), + + new OctreeQuantizer(DiffuserDitherOptions), + new WebSafePaletteQuantizer(DiffuserDitherOptions), + new WernerPaletteQuantizer(DiffuserDitherOptions), + new WuQuantizer(DiffuserDitherOptions), + + new OctreeQuantizer(Ordered0_ScaleDitherOptions), + new WebSafePaletteQuantizer(Ordered0_ScaleDitherOptions), + new WernerPaletteQuantizer(Ordered0_ScaleDitherOptions), + new WuQuantizer(Ordered0_ScaleDitherOptions), + + new OctreeQuantizer(Ordered0_25_ScaleDitherOptions), + new WebSafePaletteQuantizer(Ordered0_25_ScaleDitherOptions), + new WernerPaletteQuantizer(Ordered0_25_ScaleDitherOptions), + new WuQuantizer(Ordered0_25_ScaleDitherOptions), + + new OctreeQuantizer(Ordered0_5_ScaleDitherOptions), + new WebSafePaletteQuantizer(Ordered0_5_ScaleDitherOptions), + new WernerPaletteQuantizer(Ordered0_5_ScaleDitherOptions), + new WuQuantizer(Ordered0_5_ScaleDitherOptions), + + new OctreeQuantizer(Ordered0_75_ScaleDitherOptions), + new WebSafePaletteQuantizer(Ordered0_75_ScaleDitherOptions), + new WernerPaletteQuantizer(Ordered0_75_ScaleDitherOptions), + new WuQuantizer(Ordered0_75_ScaleDitherOptions), + + new OctreeQuantizer(OrderedDitherOptions), + new WebSafePaletteQuantizer(OrderedDitherOptions), + new WernerPaletteQuantizer(OrderedDitherOptions), + new WuQuantizer(OrderedDitherOptions), }; private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); @@ -42,8 +149,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization where TPixel : struct, IPixel { string quantizerName = quantizer.GetType().Name; - string ditherName = quantizer.Dither?.GetType()?.Name ?? "noDither"; - string ditherType = quantizer.Dither?.DitherType.ToString() ?? string.Empty; + string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "noDither"; + string ditherType = quantizer.Options.Dither?.DitherType.ToString() ?? string.Empty; string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}"; provider.RunRectangleConstrainedValidatingProcessorTest( @@ -59,8 +166,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization where TPixel : struct, IPixel { string quantizerName = quantizer.GetType().Name; - string ditherName = quantizer.Dither?.GetType()?.Name ?? "noDither"; - string ditherType = quantizer.Dither?.DitherType.ToString() ?? string.Empty; + string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "noDither"; + string ditherType = quantizer.Options.Dither?.DitherType.ToString() ?? string.Empty; string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}"; provider.RunValidatingProcessorTest( @@ -69,5 +176,23 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization testOutputDetails: testOutputDetails, appendPixelTypeToFileName: false); } + + [Theory] + [WithFile(TestImages.Png.David, nameof(DitherScaleQuantizers), PixelTypes.Rgba32)] + public void ApplyQuantizationWithDitheringScale(TestImageProvider provider, IQuantizer quantizer) + where TPixel : struct, IPixel + { + 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 = $"{quantizerName}_{ditherName}_{ditherType}_{ditherScale}"; + + provider.RunValidatingProcessorTest( + x => x.Quantize(quantizer), + comparer: ValidatorComparer, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: false); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs index e352d51f63..eb9d738e9a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs @@ -13,22 +13,26 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization [Fact] public void WuQuantizerConstructor() { - var quantizer = new WuQuantizer(128); - - Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); - - quantizer = new WuQuantizer(false); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Null(quantizer.Dither); - - quantizer = new WuQuantizer(KnownDitherings.Atkinson); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); - - quantizer = new WuQuantizer(KnownDitherings.Atkinson, 128); - Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new WuQuantizer(expected); + + Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = null }; + quantizer = new WuQuantizer(expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Null(quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; + quantizer = new WuQuantizer(expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; + quantizer = new WuQuantizer(expected); + Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); } [Fact] @@ -38,23 +42,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.DoDither); - Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); frameQuantizer.Dispose(); - quantizer = new WuQuantizer(false); + quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.DoDither); - Assert.Null(frameQuantizer.Dither); + Assert.Null(frameQuantizer.Options.Dither); frameQuantizer.Dispose(); - quantizer = new WuQuantizer(KnownDitherings.Atkinson); + quantizer = new WuQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.DoDither); - Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); frameQuantizer.Dispose(); } } diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index 0b11395a87..42da64fdbd 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -22,29 +22,29 @@ namespace SixLabors.ImageSharp.Tests var octree = new OctreeQuantizer(); var wu = new WuQuantizer(); - Assert.NotNull(werner.Dither); - Assert.NotNull(webSafe.Dither); - Assert.NotNull(octree.Dither); - Assert.NotNull(wu.Dither); + Assert.NotNull(werner.Options.Dither); + Assert.NotNull(webSafe.Options.Dither); + Assert.NotNull(octree.Options.Dither); + Assert.NotNull(wu.Options.Dither); using (IFrameQuantizer quantizer = werner.CreateFrameQuantizer(this.Configuration)) { - Assert.True(quantizer.DoDither); + Assert.NotNull(quantizer.Options.Dither); } using (IFrameQuantizer quantizer = webSafe.CreateFrameQuantizer(this.Configuration)) { - Assert.True(quantizer.DoDither); + Assert.NotNull(quantizer.Options.Dither); } using (IFrameQuantizer quantizer = octree.CreateFrameQuantizer(this.Configuration)) { - Assert.True(quantizer.DoDither); + Assert.NotNull(quantizer.Options.Dither); } using (IFrameQuantizer quantizer = wu.CreateFrameQuantizer(this.Configuration)) { - Assert.True(quantizer.DoDither); + Assert.NotNull(quantizer.Options.Dither); } } @@ -58,9 +58,15 @@ namespace SixLabors.ImageSharp.Tests { using (Image image = provider.GetImage()) { - Assert.True(image[0, 0].Equals(default(TPixel))); + Assert.True(image[0, 0].Equals(default)); - var quantizer = new OctreeQuantizer(dither); + var options = new QuantizerOptions(); + if (!dither) + { + options.Dither = null; + } + + var quantizer = new OctreeQuantizer(options); foreach (ImageFrame frame in image.Frames) { @@ -82,9 +88,15 @@ namespace SixLabors.ImageSharp.Tests { using (Image image = provider.GetImage()) { - Assert.True(image[0, 0].Equals(default(TPixel))); + Assert.True(image[0, 0].Equals(default)); + + var options = new QuantizerOptions(); + if (!dither) + { + options.Dither = null; + } - var quantizer = new WuQuantizer(dither); + var quantizer = new WuQuantizer(options); foreach (ImageFrame frame in image.Frames) { diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index f0ee576235..6d48660f62 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization public void SinglePixelOpaque() { Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); using var image = new Image(config, 1, 1, Color.Black); ImageFrame frame = image.Frames.RootFrame; @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization public void SinglePixelTransparent() { Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); using var image = new Image(config, 1, 1, default(Rgba32)); ImageFrame frame = image.Frames.RootFrame; @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization } Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); ImageFrame frame = image.Frames.RootFrame; @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization using (Image image = provider.GetImage()) { Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); ImageFrame frame = image.Frames.RootFrame; using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); @@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization } Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); ImageFrame frame = image.Frames.RootFrame; using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 16c570d63d..fb3e974bb1 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -56,6 +56,7 @@ namespace SixLabors.ImageSharp.Tests public const string LowColorVariance = "Png/low-variance.png"; public const string PngWithMetadata = "Png/PngWithMetaData.png"; public const string InvalidTextData = "Png/InvalidTextData.png"; + public const string David = "Png/david.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; diff --git a/tests/Images/Input/Png/david.png b/tests/Images/Input/Png/david.png new file mode 100644 index 0000000000..c1e3b5cd5a --- /dev/null +++ b/tests/Images/Input/Png/david.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e7e3b46a2f62251950f8c17f94c9d9a434ae643a98c058679644b5a0c5633b6 +size 27218