diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs index b0546bf9a..0db5cb7c1 100644 --- a/src/ImageSharp/Common/Helpers/Guard.cs +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -100,7 +100,7 @@ namespace SixLabors.ImageSharp { if (value.CompareTo(max) >= 0) { - throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than {max}."); + throw new ArgumentOutOfRangeException(parameterName, $"Value {value} must be less than {max}."); } } @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp { if (value.CompareTo(max) > 0) { - throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than or equal to {max}."); + throw new ArgumentOutOfRangeException(parameterName, $"Value {value} must be less than or equal to {max}."); } } @@ -142,7 +142,7 @@ namespace SixLabors.ImageSharp { throw new ArgumentOutOfRangeException( parameterName, - $"Value must be greater than {min}."); + $"Value {value} must be greater than {min}."); } } @@ -162,7 +162,7 @@ namespace SixLabors.ImageSharp { if (value.CompareTo(min) < 0) { - throw new ArgumentOutOfRangeException(parameterName, $"Value must be greater than or equal to {min}."); + throw new ArgumentOutOfRangeException(parameterName, $"Value {value} must be greater than or equal to {min}."); } } @@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp { if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) { - throw new ArgumentOutOfRangeException(parameterName, $"Value must be greater than or equal to {min} and less than or equal to {max}."); + throw new ArgumentOutOfRangeException(parameterName, $"Value {value} must be greater than or equal to {min} and less than or equal to {max}."); } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index ad3e85f92..fb072bcb7 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -24,20 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; - /// - /// Gets or sets the size of the color palette to use. For gifs the value ranges from 1 to 256. Leave as zero for default size. - /// - public int PaletteSize { get; set; } = 0; - - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 128; - /// /// Gets or sets the quantizer for reducing the color count. + /// Defaults to the /// - public IQuantizer Quantizer { get; set; } + public IQuantizer Quantizer { get; set; } = new OctreeQuantizer(); /// public void Encode(Image image, Stream stream) @@ -47,4 +38,4 @@ namespace SixLabors.ImageSharp.Formats.Gif encoder.Encode(image, stream); } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index bdb228f52..57bb3d09a 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -25,40 +25,30 @@ namespace SixLabors.ImageSharp.Formats.Gif /// private readonly byte[] buffer = new byte[16]; - /// - /// The number of bits requires to store the image palette. - /// - private int bitDepth; - - /// - /// Whether the current image has multiple frames. - /// - private bool hasFrames; - /// /// Gets the TextEncoding /// - private Encoding textEncoding; + private readonly Encoding textEncoding; /// /// Gets or sets the quantizer for reducing the color count. /// - private IQuantizer quantizer; + private readonly IQuantizer quantizer; /// - /// Gets or sets the threshold. + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// - private byte threshold; + private readonly bool ignoreMetadata; /// - /// Gets or sets the size of the color palette to use. + /// The number of bits requires to store the image palette. /// - private int paletteSize; + private int bitDepth; /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// Whether the current image has multiple frames. /// - private bool ignoreMetadata; + private bool hasFrames; /// /// Initializes a new instance of the class. @@ -69,10 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { this.memoryManager = memoryManager; this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; - this.quantizer = options.Quantizer; - this.threshold = options.Threshold; - this.paletteSize = options.PaletteSize; this.ignoreMetadata = options.IgnoreMetadata; } @@ -88,24 +75,16 @@ namespace SixLabors.ImageSharp.Formats.Gif Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - this.quantizer = this.quantizer ?? new OctreeQuantizer(); - // Do not use IDisposable pattern here as we want to preserve the stream. var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); - // Ensure that pallete size can be set but has a fallback. - int size = this.paletteSize; - size = size > 0 ? size.Clamp(1, 256) : 256; - - // Get the number of bits. - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(size); - this.hasFrames = image.Frames.Count > 1; - var pixelQuantizer = (IQuantizer)this.quantizer; - // Quantize the image returning a palette. - QuantizedFrame quantized = pixelQuantizer.Quantize(image.Frames.RootFrame, size); + QuantizedFrame quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(image.Frames.RootFrame); + + // Get the number of bits. + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); int index = this.GetTransparentIndex(quantized); @@ -128,7 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { if (quantized == null) { - quantized = pixelQuantizer.Quantize(frame, size); + quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(frame); } this.WriteGraphicalControlExtension(frame.MetaData, writer, this.GetTransparentIndex(quantized)); @@ -136,7 +115,7 @@ namespace SixLabors.ImageSharp.Formats.Gif this.WriteColorTable(quantized, writer); this.WriteImageData(quantized, writer); - quantized = null; // so next frame can regenerate it + quantized = null; // So next frame can regenerate it } // TODO: Write extension etc diff --git a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs index a709b2b9d..1f1875789 100644 --- a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs +++ b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs @@ -21,16 +21,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Encoding TextEncoding { get; } - /// - /// Gets the size of the color palette to use. For gifs the value ranges from 1 to 256. Leave as zero for default size. - /// - int PaletteSize { get; } - - /// - /// Gets the transparency threshold. - /// - byte Threshold { get; } - /// /// Gets the quantizer for reducing the color count. /// diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs index 28020f260..1bfa4b063 100644 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -10,16 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Png /// internal interface IPngEncoderOptions { - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. - /// - bool IgnoreMetadata { get; } - - /// - /// Gets the size of the color palette to use. Set to zero to leav png encoding to use pixel data. - /// - int PaletteSize { get; } - /// /// Gets the png color type /// diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 2cff18410..993dc6586 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -13,16 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Png /// public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions { - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. - /// - public bool IgnoreMetadata { get; set; } - - /// - /// Gets or sets the size of the color palette to use. Set to zero to leave png encoding to use pixel data. - /// - public int PaletteSize { get; set; } = 0; - /// /// Gets or sets the png color type /// @@ -44,8 +34,9 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Gets or sets quantizer for reducing the color count. + /// Defaults to the /// - public IQuantizer Quantizer { get; set; } + public IQuantizer Quantizer { get; set; } = new WuQuantizer(); /// /// Gets or sets the transparency threshold. @@ -73,4 +64,4 @@ namespace SixLabors.ImageSharp.Formats.Png } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 29c9d2a14..273516499 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -40,6 +40,36 @@ namespace SixLabors.ImageSharp.Formats.Png /// private readonly Crc32 crc = new Crc32(); + /// + /// The png color type. + /// + private readonly PngColorType pngColorType; + + /// + /// The quantizer for reducing the color count. + /// + private readonly IQuantizer quantizer; + + /// + /// Gets or sets the CompressionLevel value + /// + private readonly int compressionLevel; + + /// + /// Gets or sets the Gamma value + /// + private readonly float gamma; + + /// + /// Gets or sets the Threshold value + /// + private readonly byte threshold; + + /// + /// Gets or sets a value indicating whether to Write Gamma + /// + private readonly bool writeGamma; + /// /// Contains the raw pixel data from an indexed image. /// @@ -101,50 +131,10 @@ namespace SixLabors.ImageSharp.Formats.Png private IManagedByteBuffer average; /// - /// The buffer for the paeth filter + /// The buffer for the Paeth filter /// private IManagedByteBuffer paeth; - /// - /// The png color type. - /// - private PngColorType pngColorType; - - /// - /// The quantizer for reducing the color count. - /// - private IQuantizer quantizer; - - /// - /// Gets or sets a value indicating whether to ignore metadata - /// - private bool ignoreMetadata; - - /// - /// Gets or sets the Quality value - /// - private int paletteSize; - - /// - /// Gets or sets the CompressionLevel value - /// - private int compressionLevel; - - /// - /// Gets or sets the Gamma value - /// - private float gamma; - - /// - /// Gets or sets the Threshold value - /// - private byte threshold; - - /// - /// Gets or sets a value indicating whether to Write Gamma - /// - private bool writeGamma; - /// /// Initializes a new instance of the class. /// @@ -153,8 +143,6 @@ namespace SixLabors.ImageSharp.Formats.Png public PngEncoderCore(MemoryManager memoryManager, IPngEncoderOptions options) { this.memoryManager = memoryManager; - this.ignoreMetadata = options.IgnoreMetadata; - this.paletteSize = options.PaletteSize > 0 ? options.PaletteSize.Clamp(1, int.MaxValue) : int.MaxValue; this.pngColorType = options.PngColorType; this.compressionLevel = options.CompressionLevel; this.gamma = options.Gamma; @@ -190,28 +178,27 @@ namespace SixLabors.ImageSharp.Formats.Png stream.Write(this.chunkDataBuffer, 0, 8); - // Set correct color type if the color count is 256 or less. - if (this.paletteSize <= 256) - { - this.pngColorType = PngColorType.Palette; - } - - if (this.pngColorType == PngColorType.Palette && this.paletteSize > 256) + QuantizedFrame quantized = null; + if (this.pngColorType == PngColorType.Palette) { - this.paletteSize = 256; - } + // Create quantized frame returning the palette and set the bit depth. + quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(image.Frames.RootFrame); + this.palettePixelData = quantized.Pixels; + byte bits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); - // Set correct bit depth. - this.bitDepth = this.paletteSize <= 256 - ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.paletteSize).Clamp(1, 8) - : (byte)8; + // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk + if (bits == 3) + { + bits = 4; + } + else if (bits >= 5 || bits <= 7) + { + bits = 8; + } - // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk - if (this.bitDepth == 3) - { - this.bitDepth = 4; + this.bitDepth = bits; } - else if (this.bitDepth >= 5 || this.bitDepth <= 7) + else { this.bitDepth = 8; } @@ -232,9 +219,9 @@ namespace SixLabors.ImageSharp.Formats.Png this.WriteHeaderChunk(stream, header); // Collect the indexed pixel data - if (this.pngColorType == PngColorType.Palette) + if (quantized != null) { - this.CollectIndexedBytes(image.Frames.RootFrame, stream, header); + this.WritePaletteChunk(stream, header, quantized); } this.WritePhysicalChunk(stream, image); @@ -296,21 +283,6 @@ namespace SixLabors.ImageSharp.Formats.Png stream.Write(buffer, 0, 4); } - /// - /// Collects the indexed pixel data. - /// - /// The pixel format. - /// The image to encode. - /// The containing image data. - /// The . - private void CollectIndexedBytes(ImageFrame image, Stream stream, PngHeader header) - where TPixel : struct, IPixel - { - // Quantize the image and get the pixels. - QuantizedFrame quantized = this.WritePaletteChunk(stream, header, image); - this.palettePixelData = quantized.Pixels; - } - /// /// Collects a row of grayscale pixels. /// @@ -496,24 +468,10 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The containing image data. /// The . - /// The image to encode. - /// The - private QuantizedFrame WritePaletteChunk(Stream stream, PngHeader header, ImageFrame image) + /// The quantized frame. + private void WritePaletteChunk(Stream stream, PngHeader header, QuantizedFrame quantized) where TPixel : struct, IPixel { - if (this.paletteSize > 256) - { - return null; - } - - if (this.quantizer == null) - { - this.quantizer = new WuQuantizer(); - } - - // Quantize the image returning a palette. This boxing is icky. - QuantizedFrame quantized = ((IQuantizer)this.quantizer).Quantize(image, this.paletteSize); - // Grab the palette and write it to the stream. TPixel[] palette = quantized.Palette; byte pixelCount = palette.Length.ToByte(); @@ -560,8 +518,6 @@ namespace SixLabors.ImageSharp.Formats.Png this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable.Array, 0, pixelCount); } } - - return quantized; } /// diff --git a/src/ImageSharp/Processing/Quantization/Box.cs b/src/ImageSharp/Processing/Quantization/Box.cs deleted file mode 100644 index e6e1166f8..000000000 --- a/src/ImageSharp/Processing/Quantization/Box.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Quantization -{ - /// - /// Represents a box color cube. - /// - internal struct Box - { - /// - /// Gets or sets the min red value, exclusive. - /// - public int R0 { get; set; } - - /// - /// Gets or sets the max red value, inclusive. - /// - public int R1 { get; set; } - - /// - /// Gets or sets the min green value, exclusive. - /// - public int G0 { get; set; } - - /// - /// Gets or sets the max green value, inclusive. - /// - public int G1 { get; set; } - - /// - /// Gets or sets the min blue value, exclusive. - /// - public int B0 { get; set; } - - /// - /// Gets or sets the max blue value, inclusive. - /// - public int B1 { get; set; } - - /// - /// Gets or sets the min alpha value, exclusive. - /// - public int A0 { get; set; } - - /// - /// Gets or sets the max alpha value, inclusive. - /// - public int A1 { get; set; } - - /// - /// Gets or sets the volume. - /// - public int Volume { get; set; } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Quantization/QuantizerBase{TPixel}.cs b/src/ImageSharp/Processing/Quantization/FrameQuantizers/FrameQuantizerBase{TPixel}.cs similarity index 82% rename from src/ImageSharp/Processing/Quantization/QuantizerBase{TPixel}.cs rename to src/ImageSharp/Processing/Quantization/FrameQuantizers/FrameQuantizerBase{TPixel}.cs index 96763195d..5f0510627 100644 --- a/src/ImageSharp/Processing/Quantization/QuantizerBase{TPixel}.cs +++ b/src/ImageSharp/Processing/Quantization/FrameQuantizers/FrameQuantizerBase{TPixel}.cs @@ -6,16 +6,15 @@ using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Dithering; using SixLabors.ImageSharp.Processing.Dithering.ErrorDiffusion; -namespace SixLabors.ImageSharp.Processing.Quantization +namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers { /// - /// Encapsulates methods to calculate the color palette of an image. + /// The base class for all implementations /// /// The pixel format. - public abstract class QuantizerBase : IQuantizer + public abstract class FrameQuantizerBase : IFrameQuantizer where TPixel : struct, IPixel { /// @@ -24,29 +23,35 @@ namespace SixLabors.ImageSharp.Processing.Quantization private readonly bool singlePass; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// The quantizer /// - /// If true, the quantization only needs to loop through the source pixels once + /// If true, the quantization process only needs to loop through the source pixels once /// /// /// If you construct this class with a true value for singlePass, then the code will, when quantizing your image, - /// only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage' + /// only call the methods. + /// If two passes are required, the code will also call /// and then 'QuantizeImage'. /// - protected QuantizerBase(bool singlePass) + protected FrameQuantizerBase(IQuantizer quantizer, bool singlePass) { + Guard.NotNull(quantizer, nameof(quantizer)); + + this.Dither = quantizer.Dither; + this.DitherType = quantizer.DitherType; this.singlePass = singlePass; } /// - public bool Dither { get; set; } = true; + public bool Dither { get; } /// - public IErrorDiffuser DitherType { get; set; } = DiffuseMode.FloydSteinberg; + public IErrorDiffuser DitherType { get; } /// - public virtual QuantizedFrame Quantize(ImageFrame image, int maxColors) + public virtual QuantizedFrame QuantizeFrame(ImageFrame image) { Guard.NotNull(image, nameof(image)); diff --git a/src/ImageSharp/Processing/Quantization/FrameQuantizers/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Quantization/FrameQuantizers/IFrameQuantizer{TPixel}.cs new file mode 100644 index 000000000..0972a636a --- /dev/null +++ b/src/ImageSharp/Processing/Quantization/FrameQuantizers/IFrameQuantizer{TPixel}.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Dithering.ErrorDiffusion; + +namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers +{ + /// + /// Provides methods to allow the execution of the quantization process on an image frame. + /// + /// The pixel format. + public interface IFrameQuantizer + where TPixel : struct, IPixel + { + /// + /// Gets a value indicating whether to apply dithering to the output image. + /// + bool Dither { get; } + + /// + /// Gets the dithering algorithm to apply to the output image. + /// + IErrorDiffuser DitherType { get; } + + /// + /// Quantize an image frame and return the resulting output pixels. + /// + /// The image to quantize. + /// + /// A representing a quantized version of the image pixels. + /// + QuantizedFrame QuantizeFrame(ImageFrame image); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Quantization/FrameQuantizers/OctreeFrameQuantizer{TPixel}.cs similarity index 96% rename from src/ImageSharp/Processing/Quantization/OctreeQuantizer{TPixel}.cs rename to src/ImageSharp/Processing/Quantization/FrameQuantizers/OctreeFrameQuantizer{TPixel}.cs index 5d8104937..56a6c7240 100644 --- a/src/ImageSharp/Processing/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Quantization/FrameQuantizers/OctreeFrameQuantizer{TPixel}.cs @@ -8,14 +8,14 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Quantization +namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers { /// /// Encapsulates methods to calculate the color palette if an image using an Octree pattern. /// /// /// The pixel format. - public sealed class OctreeQuantizer : QuantizerBase + internal sealed class OctreeFrameQuantizer : FrameQuantizerBase where TPixel : struct, IPixel { /// @@ -24,14 +24,14 @@ namespace SixLabors.ImageSharp.Processing.Quantization private readonly Dictionary colorMap = new Dictionary(); /// - /// Stores the tree + /// Maximum allowed color depth /// - private Octree octree; + private readonly byte colors; /// - /// Maximum allowed color depth + /// Stores the tree /// - private byte colors; + private readonly Octree octree; /// /// The reduced image palette @@ -44,26 +44,18 @@ namespace SixLabors.ImageSharp.Processing.Quantization private byte transparentIndex; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// The octree quantizer /// /// 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 OctreeQuantizer() - : base(false) + public OctreeFrameQuantizer(OctreeQuantizer quantizer) + : base(quantizer, false) { - } - - /// - public override QuantizedFrame Quantize(ImageFrame image, int maxColors) - { - this.colors = (byte)maxColors.Clamp(1, 255); + this.colors = (byte)quantizer.MaxColors; this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors)); - this.palette = null; - this.colorMap.Clear(); - - return base.Quantize(image, this.colors); } /// @@ -322,7 +314,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization } // Now palletize the nodes - TPixel[] palette = new TPixel[colorCount + 1]; + var palette = new TPixel[colorCount + 1]; int paletteIndex = 0; this.root.ConstructPalette(palette, ref paletteIndex); diff --git a/src/ImageSharp/Processing/Quantization/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Quantization/FrameQuantizers/PaletteFrameQuantizer{TPixel}.cs similarity index 76% rename from src/ImageSharp/Processing/Quantization/PaletteQuantizer{TPixel}.cs rename to src/ImageSharp/Processing/Quantization/FrameQuantizers/PaletteFrameQuantizer{TPixel}.cs index 8955e14dd..141c1afa0 100644 --- a/src/ImageSharp/Processing/Quantization/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Quantization/FrameQuantizers/PaletteFrameQuantizer{TPixel}.cs @@ -7,15 +7,14 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Quantization +namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers { /// /// Encapsulates methods to create a quantized image based upon the given palette. - /// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4. /// /// /// The pixel format. - public sealed class PaletteQuantizer : QuantizerBase + internal sealed class PaletteFrameQuantizer : FrameQuantizerBase where TPixel : struct, IPixel { /// @@ -26,33 +25,16 @@ namespace SixLabors.ImageSharp.Processing.Quantization /// /// List of all colors in the palette /// - private TPixel[] colors; + private readonly TPixel[] colors; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public PaletteQuantizer() - : this(NamedColors.WebSafePalette) + /// The palette quantizer + public PaletteFrameQuantizer(PaletteQuantizer quantizer) + : base(quantizer, true) { - } - - /// - /// Initializes a new instance of the class. - /// - /// The palette to select substitute colors from. - public PaletteQuantizer(TPixel[] palette = null) - : base(true) - { - Guard.NotNull(palette, nameof(palette)); - this.colors = palette; - } - - /// - public override QuantizedFrame Quantize(ImageFrame image, int maxColors) - { - Array.Resize(ref this.colors, maxColors.Clamp(1, 255)); - this.colorMap.Clear(); - return base.Quantize(image, maxColors); + this.colors = quantizer.GetPalette(); } /// diff --git a/src/ImageSharp/Processing/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Quantization/FrameQuantizers/WuFrameQuantizer{TPixel}.cs similarity index 94% rename from src/ImageSharp/Processing/Quantization/WuQuantizer{TPixel}.cs rename to src/ImageSharp/Processing/Quantization/FrameQuantizers/WuFrameQuantizer{TPixel}.cs index 0c2371cf3..6adb38d2e 100644 --- a/src/ImageSharp/Processing/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Quantization/FrameQuantizers/WuFrameQuantizer{TPixel}.cs @@ -10,7 +10,7 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Quantization +namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers { /// /// An implementation of Wu's color quantizer with alpha channel. @@ -32,10 +32,10 @@ namespace SixLabors.ImageSharp.Processing.Quantization /// /// /// The pixel format. - public class WuQuantizer : QuantizerBase + internal sealed class WuFrameQuantizer : FrameQuantizerBase where TPixel : struct, IPixel { - // TODO: The WuQuantizer code is rising several questions: + // TODO: The WuFrameQuantizer code is rising several questions: // - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes ) // - Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case? // (T, R, G, B, A, M2) could be grouped together! @@ -124,26 +124,23 @@ namespace SixLabors.ImageSharp.Processing.Quantization private Box[] colorCube; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// The wu quantizer /// /// 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 WuQuantizer() - : base(false) + public WuFrameQuantizer(WuQuantizer quantizer) + : base(quantizer, false) { + this.colors = quantizer.MaxColors; } /// - public override QuantizedFrame Quantize(ImageFrame image, int maxColors) + public override QuantizedFrame QuantizeFrame(ImageFrame image) { Guard.NotNull(image, nameof(image)); - - this.colors = maxColors.Clamp(1, 255); - this.palette = null; - this.colorMap.Clear(); - MemoryManager memoryManager = image.MemoryManager; try @@ -156,7 +153,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization this.m2 = memoryManager.AllocateClean(TableLength); this.tag = memoryManager.AllocateClean(TableLength); - return base.Quantize(image, this.colors); + return base.QuantizeFrame(image); } finally { @@ -873,5 +870,56 @@ namespace SixLabors.ImageSharp.Processing.Quantization return tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; } + + /// + /// Represents a box color cube. + /// + private struct Box + { + /// + /// Gets or sets the min red value, exclusive. + /// + public int R0; + + /// + /// Gets or sets the max red value, inclusive. + /// + public int R1; + + /// + /// Gets or sets the min green value, exclusive. + /// + public int G0; + + /// + /// Gets or sets the max green value, inclusive. + /// + public int G1; + + /// + /// Gets or sets the min blue value, exclusive. + /// + public int B0; + + /// + /// Gets or sets the max blue value, inclusive. + /// + public int B1; + + /// + /// Gets or sets the min alpha value, exclusive. + /// + public int A0; + + /// + /// Gets or sets the max alpha value, inclusive. + /// + public int A1; + + /// + /// Gets or sets the volume. + /// + public int Volume; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Quantization/IQuantizer.cs b/src/ImageSharp/Processing/Quantization/IQuantizer.cs new file mode 100644 index 000000000..2eb872a4f --- /dev/null +++ b/src/ImageSharp/Processing/Quantization/IQuantizer.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Dithering.ErrorDiffusion; +using SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers; + +namespace SixLabors.ImageSharp.Processing.Quantization +{ + /// + /// Provides methods for allowing quantization of images pixels with configurable dithering. + /// + public interface IQuantizer + { + /// + /// Gets a value indicating whether to apply dithering to the output image. + /// + bool Dither { get; } + + /// + /// Gets the dithering algorithm to apply to the output image. + /// + IErrorDiffuser DitherType { get; } + + /// + /// Creates the generic frame quantizer + /// + /// The pixel format. + /// The + IFrameQuantizer CreateFrameQuantizer() + where TPixel : struct, IPixel; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Quantization/IQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Quantization/IQuantizer{TPixel}.cs deleted file mode 100644 index 841b84496..000000000 --- a/src/ImageSharp/Processing/Quantization/IQuantizer{TPixel}.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Dithering.ErrorDiffusion; - -namespace SixLabors.ImageSharp.Processing.Quantization -{ - /// - /// Provides methods for for allowing quantization of images pixels with configurable dithering. - /// - /// The pixel format. - public interface IQuantizer : IQuantizer - where TPixel : struct, IPixel - { - /// - /// Quantize an image and return the resulting output pixels. - /// - /// The image to quantize. - /// The maximum number of colors to return. - /// - /// A representing a quantized version of the image pixels. - /// - QuantizedFrame Quantize(ImageFrame image, int maxColors); - } - - /// - /// Provides methods for allowing quantization of images pixels with configurable dithering. - /// - public interface IQuantizer - { - /// - /// Gets or sets a value indicating whether to apply dithering to the output image. - /// - bool Dither { get; set; } - - /// - /// Gets or sets the dithering algorithm to apply to the output image. - /// - IErrorDiffuser DitherType { get; set; } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Quantization/OctreeQuantizer.cs new file mode 100644 index 000000000..acc5943c3 --- /dev/null +++ b/src/ImageSharp/Processing/Quantization/OctreeQuantizer.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Dithering; +using SixLabors.ImageSharp.Processing.Dithering.ErrorDiffusion; +using SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers; + +namespace SixLabors.ImageSharp.Processing.Quantization +{ + /// + /// Allows the quantization of images pixels using Octrees. + /// + /// + public class OctreeQuantizer : IQuantizer + { + /// + /// Initializes a new instance of the class. + /// + public OctreeQuantizer() + : this(255) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether to apply dithering to the output image + public OctreeQuantizer(bool dither) + : this(dither, DiffuseMode.FloydSteinberg, 255) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum number of colors to hold in the color palette + public OctreeQuantizer(int maxColors) + : this(true, DiffuseMode.FloydSteinberg, maxColors) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether to apply dithering to the output image + /// The dithering algorithm to apply to the output image + /// The maximum number of colors to hold in the color palette + public OctreeQuantizer(bool dither, IErrorDiffuser ditherType, int maxColors) + { + Guard.NotNull(ditherType, nameof(ditherType)); + Guard.MustBeBetweenOrEqualTo(maxColors, 1, 255, nameof(maxColors)); + + this.Dither = dither; + this.DitherType = ditherType; + this.MaxColors = maxColors; + } + + /// + public bool Dither { get; } + + /// + public IErrorDiffuser DitherType { get; } + + /// + /// Gets the maximum number of colors to hold in the color palette. + /// + public int MaxColors { get; } + + /// + public IFrameQuantizer CreateFrameQuantizer() + where TPixel : struct, IPixel + => new OctreeFrameQuantizer(this); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Quantization/PaletteQuantizer.cs new file mode 100644 index 000000000..ccdfae9a1 --- /dev/null +++ b/src/ImageSharp/Processing/Quantization/PaletteQuantizer.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Dithering; +using SixLabors.ImageSharp.Processing.Dithering.ErrorDiffusion; +using SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers; + +namespace SixLabors.ImageSharp.Processing.Quantization +{ + /// + /// Allows the quantization of images pixels using web safe colors defined in the CSS Color Module Level 4. + /// + /// Override this class to provide your own palette. + /// + public class PaletteQuantizer : IQuantizer + { + /// + /// Initializes a new instance of the class. + /// + public PaletteQuantizer() + : this(true, DiffuseMode.FloydSteinberg) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether to apply dithering to the output image + public PaletteQuantizer(bool dither) + : this(dither, DiffuseMode.FloydSteinberg) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether to apply dithering to the output image + /// The dithering algorithm to apply to the output image + public PaletteQuantizer(bool dither, IErrorDiffuser ditherType) + { + Guard.NotNull(ditherType, nameof(ditherType)); + + this.Dither = dither; + this.DitherType = ditherType; + } + + /// + public bool Dither { get; } + + /// + public IErrorDiffuser DitherType { get; } + + /// + /// Gets the palette to use to quantize the image. + /// + /// The pixel format. + /// The + public virtual TPixel[] GetPalette() + where TPixel : struct, IPixel + => NamedColors.WebSafePalette; + + /// + public IFrameQuantizer CreateFrameQuantizer() + where TPixel : struct, IPixel + => new PaletteFrameQuantizer(this); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Quantization/Processors/QuantizeProcessor.cs b/src/ImageSharp/Processing/Quantization/Processors/QuantizeProcessor.cs index e6b109993..951e47127 100644 --- a/src/ImageSharp/Processing/Quantization/Processors/QuantizeProcessor.cs +++ b/src/ImageSharp/Processing/Quantization/Processors/QuantizeProcessor.cs @@ -2,16 +2,16 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Quantization.Processors { /// - /// Enables the quantization of images to remove the number of colors used in the image palette. + /// Enables the quantization of images to reduce the number of colors used in the image palette. /// /// The pixel format. internal class QuantizeProcessor : ImageProcessor @@ -21,51 +21,38 @@ namespace SixLabors.ImageSharp.Processing.Quantization.Processors /// Initializes a new instance of the class. /// /// The quantizer used to reduce the color palette - /// The maximum number of colors to reduce the palette to - public QuantizeProcessor(IQuantizer quantizer, int maxColors) + public QuantizeProcessor(IQuantizer quantizer) { Guard.NotNull(quantizer, nameof(quantizer)); - Guard.MustBeGreaterThan(maxColors, 0, nameof(maxColors)); - this.Quantizer = quantizer; - this.MaxColors = maxColors; } /// /// Gets the quantizer /// - public IQuantizer Quantizer { get; } - - /// - /// Gets the maximum number of palette colors - /// - public int MaxColors { get; } + public IQuantizer Quantizer { get; } /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - QuantizedFrame quantized = this.Quantizer.Quantize(source, this.MaxColors); + IFrameQuantizer executor = this.Quantizer.CreateFrameQuantizer(); + QuantizedFrame quantized = executor.QuantizeFrame(source); int paletteCount = quantized.Palette.Length - 1; - using (Buffer2D pixels = source.MemoryManager.Allocate2D(quantized.Width, quantized.Height)) + // Not parallel to remove "quantized" closure allocation. + // We can operate directly on the source here as we've already read it to get the + // quantized result + for (int y = 0; y < source.Height; y++) { - Parallel.For( - 0, - pixels.Height, - configuration.ParallelOptions, - y => - { - Span row = pixels.GetRowSpan(y); - int yy = y * pixels.Width; - for (int x = 0; x < pixels.Width; x++) - { - int i = x + yy; - TPixel color = quantized.Palette[Math.Min(paletteCount, quantized.Pixels[i])]; - row[x] = color; - } - }); - - Buffer2D.SwapContents(source.PixelBuffer, pixels); + Span row = source.GetPixelRowSpan(y); + int yy = y * source.Width; + + for (int x = 0; x < source.Width; x++) + { + int i = x + yy; + TPixel color = quantized.Palette[Math.Min(paletteCount, quantized.Pixels[i])]; + row[x] = color; + } } } } diff --git a/src/ImageSharp/Processing/Quantization/QuantizationMode.cs b/src/ImageSharp/Processing/Quantization/QuantizationMode.cs index 69857b384..45a085c01 100644 --- a/src/ImageSharp/Processing/Quantization/QuantizationMode.cs +++ b/src/ImageSharp/Processing/Quantization/QuantizationMode.cs @@ -4,26 +4,26 @@ namespace SixLabors.ImageSharp.Processing.Quantization { /// - /// Provides enumeration over how an image should be quantized. + /// Contains reusable static instances of known quantizing algorithms /// - public enum QuantizationMode + public static class QuantizationMode { /// - /// An adaptive Octree quantizer. Fast with good quality. + /// Gets the adaptive Octree quantizer. Fast with good quality. /// The quantizer only supports a single alpha value. /// - Octree, + public static IQuantizer Octree { get; } = new OctreeQuantizer(); /// - /// Xiaolin Wu's Color Quantizer which generates high quality output. + /// Gets the Xiaolin Wu's Color Quantizer which generates high quality output. /// The quantizer supports multiple alpha values. /// - Wu, + public static IQuantizer Wu { get; } = new WuQuantizer(); /// - /// Palette based, Uses the collection of web-safe colors by default. + /// Gets the palette based, Using the collection of web-safe colors. /// The quantizer supports multiple alpha values. /// - Palette + public static IQuantizer Palette { get; } = new PaletteQuantizer(); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Quantization/QuantizeExtensions.cs b/src/ImageSharp/Processing/Quantization/QuantizeExtensions.cs index 2b367ffe9..0583c176b 100644 --- a/src/ImageSharp/Processing/Quantization/QuantizeExtensions.cs +++ b/src/ImageSharp/Processing/Quantization/QuantizeExtensions.cs @@ -12,34 +12,14 @@ namespace SixLabors.ImageSharp.Processing.Quantization public static class QuantizeExtensions { /// - /// Applies quantization to the image. + /// Applies quantization to the image using the . /// /// The pixel format. /// The image this method extends. - /// The quantization mode to apply to perform the operation. - /// The maximum number of colors to return. Defaults to 256. /// The . - public static IImageProcessingContext Quantize(this IImageProcessingContext source, QuantizationMode mode = QuantizationMode.Octree, int maxColors = 256) + public static IImageProcessingContext Quantize(this IImageProcessingContext source) where TPixel : struct, IPixel - { - IQuantizer quantizer; - switch (mode) - { - case QuantizationMode.Wu: - quantizer = new WuQuantizer(); - break; - - case QuantizationMode.Palette: - quantizer = new PaletteQuantizer(); - break; - - default: - quantizer = new OctreeQuantizer(); - break; - } - - return Quantize(source, quantizer, maxColors); - } + => Quantize(source, QuantizationMode.Octree); /// /// Applies quantization to the image. @@ -47,10 +27,9 @@ namespace SixLabors.ImageSharp.Processing.Quantization /// The pixel format. /// The image this method extends. /// The quantizer to apply to perform the operation. - /// The maximum number of colors to return. /// The . - public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer, int maxColors) + public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer) where TPixel : struct, IPixel - => source.ApplyProcessor(new QuantizeProcessor(quantizer, maxColors)); + => source.ApplyProcessor(new QuantizeProcessor(quantizer)); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Quantization/WuQuantizer.cs new file mode 100644 index 000000000..0d306107e --- /dev/null +++ b/src/ImageSharp/Processing/Quantization/WuQuantizer.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Dithering; +using SixLabors.ImageSharp.Processing.Dithering.ErrorDiffusion; +using SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers; + +namespace SixLabors.ImageSharp.Processing.Quantization +{ + /// + /// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer. + /// + /// + public class WuQuantizer : IQuantizer + { + /// + /// Initializes a new instance of the class. + /// + public WuQuantizer() + : this(255) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether to apply dithering to the output image + public WuQuantizer(bool dither) + : this(dither, DiffuseMode.FloydSteinberg, 255) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum number of colors to hold in the color palette + public WuQuantizer(int maxColors) + : this(true, DiffuseMode.FloydSteinberg, maxColors) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Whether to apply dithering to the output image + /// The dithering algorithm to apply to the output image + /// The maximum number of colors to hold in the color palette + public WuQuantizer(bool dither, IErrorDiffuser ditherType, int maxColors) + { + Guard.NotNull(ditherType, nameof(ditherType)); + Guard.MustBeBetweenOrEqualTo(maxColors, 1, 255, nameof(maxColors)); + + this.Dither = dither; + this.DitherType = ditherType; + this.MaxColors = maxColors; + } + + /// + public bool Dither { get; } + + /// + public IErrorDiffuser DitherType { get; } + + /// + /// Gets the maximum number of colors to hold in the color palette. + /// + public int MaxColors { get; } + + /// + public IFrameQuantizer CreateFrameQuantizer() + where TPixel : struct, IPixel + => new WuFrameQuantizer(this); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs index a4cc06e25..bed826c10 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs @@ -3,20 +3,15 @@ // Licensed under the Apache License, Version 2.0. // +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Quantization; +using CoreImage = SixLabors.ImageSharp.Image; namespace SixLabors.ImageSharp.Benchmarks.Image { - using System.IO; - - using BenchmarkDotNet.Attributes; - - using SixLabors.ImageSharp; - using SixLabors.ImageSharp.Formats.Png; - using SixLabors.ImageSharp.Processing.Quantization; - - using CoreImage = ImageSharp.Image; - /// /// Benchmarks saving png files using different quantizers. System.Drawing cannot save indexed png files so we cannot compare. /// @@ -35,8 +30,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Image if (this.bmpStream == null) { string path = this.LargeImage - ? "../ImageSharp.Tests/TestImages/Formats/Jpg/baseline/jpeg420exif.jpg" - : "../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp"; + ? "../ImageSharp.Tests/TestImages/Formats/Jpg/baseline/jpeg420exif.jpg" + : "../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp"; + this.bmpStream = File.OpenRead(path); this.bmpCore = CoreImage.Load(this.bmpStream); this.bmpStream.Position = 0; @@ -53,20 +49,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Image [Benchmark(Baseline = true, Description = "ImageSharp Octree Png")] public void PngCoreOctree() { - using (MemoryStream memoryStream = new MemoryStream()) + using (var memoryStream = new MemoryStream()) { - PngEncoder encoder = new PngEncoder() { Quantizer = new OctreeQuantizer(), PaletteSize = 256 }; + var encoder = new PngEncoder { Quantizer = new OctreeQuantizer() }; this.bmpCore.SaveAsPng(memoryStream, encoder); } } [Benchmark(Description = "ImageSharp Octree NoDither Png")] - public void PngCoreOctreeNoDIther() + public void PngCoreOctreeNoDither() { - using (MemoryStream memoryStream = new MemoryStream()) + using (var memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder() { Quantizer = new OctreeQuantizer { Dither = false }, PaletteSize = 256 }; + var options = new PngEncoder { Quantizer = new OctreeQuantizer(false) }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -75,9 +71,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Image [Benchmark(Description = "ImageSharp Palette Png")] public void PngCorePalette() { - using (MemoryStream memoryStream = new MemoryStream()) + using (var memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder() { Quantizer = new PaletteQuantizer(), PaletteSize = 256 }; + var options = new PngEncoder { Quantizer = new PaletteQuantizer() }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -86,9 +82,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Image [Benchmark(Description = "ImageSharp Palette NoDither Png")] public void PngCorePaletteNoDither() { - using (MemoryStream memoryStream = new MemoryStream()) + using (var memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder() { Quantizer = new PaletteQuantizer { Dither = false }, PaletteSize = 256 }; + var options = new PngEncoder { Quantizer = new PaletteQuantizer(false) }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -97,9 +93,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Image [Benchmark(Description = "ImageSharp Wu Png")] public void PngCoreWu() { - using (MemoryStream memoryStream = new MemoryStream()) + using (var memoryStream = new MemoryStream()) + { + var options = new PngEncoder { Quantizer = new WuQuantizer() }; + + this.bmpCore.SaveAsPng(memoryStream, options); + } + } + + [Benchmark(Description = "ImageSharp Wu NoDither Png")] + public void PngCoreWuNoDither() + { + using (var memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder() { Quantizer = new WuQuantizer(), PaletteSize = 256 }; + var options = new PngEncoder { Quantizer = new WuQuantizer(false) }; this.bmpCore.SaveAsPng(memoryStream, options); } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index ffd810f28..4fc84ba61 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs @@ -70,12 +70,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Image { using (var memoryStream = new MemoryStream()) { - QuantizerBase quantizer = this.UseOctreeQuantizer - ? (QuantizerBase) - new OctreeQuantizer() - : new PaletteQuantizer(); + IQuantizer quantizer = this.UseOctreeQuantizer + ? + (IQuantizer)new OctreeQuantizer() + : new PaletteQuantizer(); - var options = new PngEncoder() { Quantizer = quantizer }; + var options = new PngEncoder { Quantizer = quantizer }; this.bmpCore.SaveAsPng(memoryStream, options); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 7f983e1e4..9a7b9413e 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -7,6 +7,8 @@ using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Quantization; + using Xunit; // ReSharper disable InconsistentNaming @@ -22,31 +24,31 @@ namespace SixLabors.ImageSharp.Tests /// /// All types except Palette /// - public static readonly TheoryData PngColorTypes = new TheoryData() - { - PngColorType.RgbWithAlpha, - PngColorType.Rgb, - PngColorType.Grayscale, - PngColorType.GrayscaleWithAlpha, - }; + public static readonly TheoryData PngColorTypes = new TheoryData + { + PngColorType.RgbWithAlpha, + PngColorType.Rgb, + PngColorType.Grayscale, + PngColorType.GrayscaleWithAlpha, + }; /// /// All types except Palette /// - public static readonly TheoryData CompressionLevels = new TheoryData() - { - 1, 2, 3, 4, 5, 6, 7, 8, 9 - }; + public static readonly TheoryData CompressionLevels = new TheoryData + { + 1, 2, 3, 4, 5, 6, 7, 8, 9 + }; - public static readonly TheoryData PaletteSizes = new TheoryData() + public static readonly TheoryData PaletteSizes = new TheoryData { - 30, 55, 100, 201, 255 - }; + 30, 55, 100, 201, 255 + }; - public static readonly TheoryData PaletteLargeOnly = new TheoryData() - { - 80, 100, 120, 230 - }; + public static readonly TheoryData PaletteLargeOnly = new TheoryData + { + 80, 100, 120, 230 + }; [Theory] [WithFile(TestImages.Png.Palette8Bpp, nameof(PngColorTypes), PixelTypes.Rgba32)] @@ -60,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests { TestPngEncoderCore(provider, pngColorType, appendPngColorType: true); } - + [Theory] [WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] public void IsNotBoundToSinglePixelType(TestImageProvider provider, PngColorType pngColorType) @@ -76,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests { TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, compressionLevel, appendCompressionLevel: true); } - + [Theory] [WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)] public void PaletteColorType_WuQuantizer(TestImageProvider provider, int paletteSize) @@ -92,7 +94,7 @@ namespace SixLabors.ImageSharp.Tests TestImageProvider provider, PngColorType pngColorType, int compressionLevel = 6, - int paletteSize = 0, + int paletteSize = 255, bool appendPngColorType = false, bool appendPixelType = false, bool appendCompressionLevel = false, @@ -107,11 +109,11 @@ namespace SixLabors.ImageSharp.Tests } var encoder = new PngEncoder - { - PngColorType = pngColorType, - CompressionLevel = compressionLevel, - PaletteSize = paletteSize - }; + { + PngColorType = pngColorType, + CompressionLevel = compressionLevel, + Quantizer = new WuQuantizer(paletteSize) + }; string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : ""; string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : ""; @@ -121,10 +123,10 @@ namespace SixLabors.ImageSharp.Tests // Does DebugSave & load reference CompareToReferenceInput(): string actualOutputFile = ((ITestImageProvider)provider).Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); - + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); string referenceOutputFile = ((ITestImageProvider)provider).Utility.GetReferenceOutputFileName("png", debugInfo, appendPixelType); - + using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) using (var referenceImage = Image.Load(referenceOutputFile, referenceDecoder)) { @@ -136,7 +138,7 @@ namespace SixLabors.ImageSharp.Tests } } } - + [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void WritesFileMarker(TestImageProvider provider) @@ -146,8 +148,8 @@ namespace SixLabors.ImageSharp.Tests using (var ms = new MemoryStream()) { image.Save(ms, new PngEncoder()); - - byte[] data = ms.ToArray().Take(8).ToArray(); + + byte[] data = ms.ToArray().Take(8).ToArray(); byte[] expected = { 0x89, // Set the high bit. 0x50, // P diff --git a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs index 1f2137070..987805ca1 100644 --- a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests this.collection.AddFrame(new Rgba32[0]); }); - Assert.StartsWith("Value must be greater than or equal to 100.", ex.Message); + Assert.StartsWith("Value 0 must be greater than or equal to 100.", ex.Message); } [Fact] diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index 8ca04ac23..15d2bf51f 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -10,9 +10,9 @@ [Fact] public void QuantizersDitherByDefault() { - var palette = new PaletteQuantizer(); - var octree = new OctreeQuantizer(); - var wu = new WuQuantizer(); + var palette = new PaletteQuantizer(); + var octree = new OctreeQuantizer(); + var wu = new WuQuantizer(); Assert.True(palette.Dither); Assert.True(octree.Dither); @@ -29,11 +29,11 @@ { Assert.True(image[0, 0].Equals(default(TPixel))); - IQuantizer quantizer = new PaletteQuantizer { Dither = dither }; + var quantizer = new PaletteQuantizer(dither); foreach (ImageFrame frame in image.Frames) { - QuantizedFrame quantized = quantizer.Quantize(frame, 256); + QuantizedFrame quantized = quantizer.CreateFrameQuantizer().QuantizeFrame(frame); int index = this.GetTransparentIndex(quantized); Assert.Equal(index, quantized.Pixels[0]); @@ -51,11 +51,11 @@ { Assert.True(image[0, 0].Equals(default(TPixel))); - IQuantizer quantizer = new OctreeQuantizer { Dither = dither }; + var quantizer = new OctreeQuantizer(dither); foreach (ImageFrame frame in image.Frames) { - QuantizedFrame quantized = quantizer.Quantize(frame, 256); + QuantizedFrame quantized = quantizer.CreateFrameQuantizer().QuantizeFrame(frame); int index = this.GetTransparentIndex(quantized); Assert.Equal(index, quantized.Pixels[0]); @@ -73,11 +73,11 @@ { Assert.True(image[0, 0].Equals(default(TPixel))); - IQuantizer quantizer = new WuQuantizer() { Dither = dither }; + var quantizer = new WuQuantizer(dither); foreach (ImageFrame frame in image.Frames) { - QuantizedFrame quantized = quantizer.Quantize(frame, 256); + QuantizedFrame quantized = quantizer.CreateFrameQuantizer().QuantizeFrame(frame); int index = this.GetTransparentIndex(quantized); Assert.Equal(index, quantized.Pixels[0]);