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]);