Browse Source

Use non-generic IQuantizer for image formats

pull/487/head
James Jackson-South 8 years ago
parent
commit
0e8220294b
  1. 10
      src/ImageSharp/Common/Helpers/Guard.cs
  2. 15
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  3. 49
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  4. 10
      src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
  5. 10
      src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
  6. 15
      src/ImageSharp/Formats/Png/PngEncoder.cs
  7. 148
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  8. 56
      src/ImageSharp/Processing/Quantization/Box.cs
  9. 27
      src/ImageSharp/Processing/Quantization/FrameQuantizers/FrameQuantizerBase{TPixel}.cs
  10. 35
      src/ImageSharp/Processing/Quantization/FrameQuantizers/IFrameQuantizer{TPixel}.cs
  11. 32
      src/ImageSharp/Processing/Quantization/FrameQuantizers/OctreeFrameQuantizer{TPixel}.cs
  12. 34
      src/ImageSharp/Processing/Quantization/FrameQuantizers/PaletteFrameQuantizer{TPixel}.cs
  13. 74
      src/ImageSharp/Processing/Quantization/FrameQuantizers/WuFrameQuantizer{TPixel}.cs
  14. 33
      src/ImageSharp/Processing/Quantization/IQuantizer.cs
  15. 42
      src/ImageSharp/Processing/Quantization/IQuantizer{TPixel}.cs
  16. 75
      src/ImageSharp/Processing/Quantization/OctreeQuantizer.cs
  17. 68
      src/ImageSharp/Processing/Quantization/PaletteQuantizer.cs
  18. 53
      src/ImageSharp/Processing/Quantization/Processors/QuantizeProcessor.cs
  19. 16
      src/ImageSharp/Processing/Quantization/QuantizationMode.cs
  20. 31
      src/ImageSharp/Processing/Quantization/QuantizeExtensions.cs
  21. 75
      src/ImageSharp/Processing/Quantization/WuQuantizer.cs
  22. 53
      tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs
  23. 10
      tests/ImageSharp.Benchmarks/Image/EncodePng.cs
  24. 64
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  25. 2
      tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs
  26. 18
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs

10
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}.");
}
}

15
src/ImageSharp/Formats/Gif/GifEncoder.cs

@ -24,20 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <summary>
/// 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.
/// </summary>
public int PaletteSize { get; set; } = 0;
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 128;
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// Defaults to the <see cref="OctreeQuantizer"/>
/// </summary>
public IQuantizer Quantizer { get; set; }
public IQuantizer Quantizer { get; set; } = new OctreeQuantizer();
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
@ -47,4 +38,4 @@ namespace SixLabors.ImageSharp.Formats.Gif
encoder.Encode(image, stream);
}
}
}
}

49
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -25,40 +25,30 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
private readonly byte[] buffer = new byte[16];
/// <summary>
/// The number of bits requires to store the image palette.
/// </summary>
private int bitDepth;
/// <summary>
/// Whether the current image has multiple frames.
/// </summary>
private bool hasFrames;
/// <summary>
/// Gets the TextEncoding
/// </summary>
private Encoding textEncoding;
private readonly Encoding textEncoding;
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// </summary>
private IQuantizer quantizer;
private readonly IQuantizer quantizer;
/// <summary>
/// Gets or sets the threshold.
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
private byte threshold;
private readonly bool ignoreMetadata;
/// <summary>
/// Gets or sets the size of the color palette to use.
/// The number of bits requires to store the image palette.
/// </summary>
private int paletteSize;
private int bitDepth;
/// <summary>
/// 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.
/// </summary>
private bool ignoreMetadata;
private bool hasFrames;
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> 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<TPixel>();
// 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<TPixel>)this.quantizer;
// Quantize the image returning a palette.
QuantizedFrame<TPixel> quantized = pixelQuantizer.Quantize(image.Frames.RootFrame, size);
QuantizedFrame<TPixel> quantized = this.quantizer.CreateFrameQuantizer<TPixel>().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<TPixel>().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

10
src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs

@ -21,16 +21,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
Encoding TextEncoding { get; }
/// <summary>
/// Gets the size of the color palette to use. For gifs the value ranges from 1 to 256. Leave as zero for default size.
/// </summary>
int PaletteSize { get; }
/// <summary>
/// Gets the transparency threshold.
/// </summary>
byte Threshold { get; }
/// <summary>
/// Gets the quantizer for reducing the color count.
/// </summary>

10
src/ImageSharp/Formats/Png/IPngEncoderOptions.cs

@ -10,16 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
internal interface IPngEncoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being encoded.
/// </summary>
bool IgnoreMetadata { get; }
/// <summary>
/// Gets the size of the color palette to use. Set to zero to leav png encoding to use pixel data.
/// </summary>
int PaletteSize { get; }
/// <summary>
/// Gets the png color type
/// </summary>

15
src/ImageSharp/Formats/Png/PngEncoder.cs

@ -13,16 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions
{
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded.
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <summary>
/// Gets or sets the size of the color palette to use. Set to zero to leave png encoding to use pixel data.
/// </summary>
public int PaletteSize { get; set; } = 0;
/// <summary>
/// Gets or sets the png color type
/// </summary>
@ -44,8 +34,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Gets or sets quantizer for reducing the color count.
/// Defaults to the <see cref="WuQuantizer"/>
/// </summary>
public IQuantizer Quantizer { get; set; }
public IQuantizer Quantizer { get; set; } = new WuQuantizer();
/// <summary>
/// Gets or sets the transparency threshold.
@ -73,4 +64,4 @@ namespace SixLabors.ImageSharp.Formats.Png
}
}
}
}
}

148
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -40,6 +40,36 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
private readonly Crc32 crc = new Crc32();
/// <summary>
/// The png color type.
/// </summary>
private readonly PngColorType pngColorType;
/// <summary>
/// The quantizer for reducing the color count.
/// </summary>
private readonly IQuantizer quantizer;
/// <summary>
/// Gets or sets the CompressionLevel value
/// </summary>
private readonly int compressionLevel;
/// <summary>
/// Gets or sets the Gamma value
/// </summary>
private readonly float gamma;
/// <summary>
/// Gets or sets the Threshold value
/// </summary>
private readonly byte threshold;
/// <summary>
/// Gets or sets a value indicating whether to Write Gamma
/// </summary>
private readonly bool writeGamma;
/// <summary>
/// Contains the raw pixel data from an indexed image.
/// </summary>
@ -101,50 +131,10 @@ namespace SixLabors.ImageSharp.Formats.Png
private IManagedByteBuffer average;
/// <summary>
/// The buffer for the paeth filter
/// The buffer for the Paeth filter
/// </summary>
private IManagedByteBuffer paeth;
/// <summary>
/// The png color type.
/// </summary>
private PngColorType pngColorType;
/// <summary>
/// The quantizer for reducing the color count.
/// </summary>
private IQuantizer quantizer;
/// <summary>
/// Gets or sets a value indicating whether to ignore metadata
/// </summary>
private bool ignoreMetadata;
/// <summary>
/// Gets or sets the Quality value
/// </summary>
private int paletteSize;
/// <summary>
/// Gets or sets the CompressionLevel value
/// </summary>
private int compressionLevel;
/// <summary>
/// Gets or sets the Gamma value
/// </summary>
private float gamma;
/// <summary>
/// Gets or sets the Threshold value
/// </summary>
private byte threshold;
/// <summary>
/// Gets or sets a value indicating whether to Write Gamma
/// </summary>
private bool writeGamma;
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoderCore"/> class.
/// </summary>
@ -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<TPixel> 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<TPixel>().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);
}
/// <summary>
/// Collects the indexed pixel data.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image to encode.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="header">The <see cref="PngHeader"/>.</param>
private void CollectIndexedBytes<TPixel>(ImageFrame<TPixel> image, Stream stream, PngHeader header)
where TPixel : struct, IPixel<TPixel>
{
// Quantize the image and get the pixels.
QuantizedFrame<TPixel> quantized = this.WritePaletteChunk(stream, header, image);
this.palettePixelData = quantized.Pixels;
}
/// <summary>
/// Collects a row of grayscale pixels.
/// </summary>
@ -496,24 +468,10 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="header">The <see cref="PngHeader"/>.</param>
/// <param name="image">The image to encode.</param>
/// <returns>The <see cref="QuantizedFrame{TPixel}"/></returns>
private QuantizedFrame<TPixel> WritePaletteChunk<TPixel>(Stream stream, PngHeader header, ImageFrame<TPixel> image)
/// <param name="quantized">The quantized frame.</param>
private void WritePaletteChunk<TPixel>(Stream stream, PngHeader header, QuantizedFrame<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
{
if (this.paletteSize > 256)
{
return null;
}
if (this.quantizer == null)
{
this.quantizer = new WuQuantizer<TPixel>();
}
// Quantize the image returning a palette. This boxing is icky.
QuantizedFrame<TPixel> quantized = ((IQuantizer<TPixel>)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;
}
/// <summary>

56
src/ImageSharp/Processing/Quantization/Box.cs

@ -1,56 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Quantization
{
/// <summary>
/// Represents a box color cube.
/// </summary>
internal struct Box
{
/// <summary>
/// Gets or sets the min red value, exclusive.
/// </summary>
public int R0 { get; set; }
/// <summary>
/// Gets or sets the max red value, inclusive.
/// </summary>
public int R1 { get; set; }
/// <summary>
/// Gets or sets the min green value, exclusive.
/// </summary>
public int G0 { get; set; }
/// <summary>
/// Gets or sets the max green value, inclusive.
/// </summary>
public int G1 { get; set; }
/// <summary>
/// Gets or sets the min blue value, exclusive.
/// </summary>
public int B0 { get; set; }
/// <summary>
/// Gets or sets the max blue value, inclusive.
/// </summary>
public int B1 { get; set; }
/// <summary>
/// Gets or sets the min alpha value, exclusive.
/// </summary>
public int A0 { get; set; }
/// <summary>
/// Gets or sets the max alpha value, inclusive.
/// </summary>
public int A1 { get; set; }
/// <summary>
/// Gets or sets the volume.
/// </summary>
public int Volume { get; set; }
}
}

27
src/ImageSharp/Processing/Quantization/QuantizerBase{TPixel}.cs → 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
{
/// <summary>
/// Encapsulates methods to calculate the color palette of an image.
/// The base class for all <see cref="IFrameQuantizer{TPixel}"/> implementations
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public abstract class QuantizerBase<TPixel> : IQuantizer<TPixel>
public abstract class FrameQuantizerBase<TPixel> : IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
@ -24,29 +23,35 @@ namespace SixLabors.ImageSharp.Processing.Quantization
private readonly bool singlePass;
/// <summary>
/// Initializes a new instance of the <see cref="QuantizerBase{TPixel}"/> class.
/// Initializes a new instance of the <see cref="FrameQuantizerBase{TPixel}"/> class.
/// </summary>
/// <param name="quantizer">The quantizer</param>
/// <param name="singlePass">
/// 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
/// </param>
/// <remarks>
/// 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 <see cref="FirstPass(ImageFrame{TPixel}, int, int)"/> methods.
/// If two passes are required, the code will also call <see cref="SecondPass(ImageFrame{TPixel}, byte[], int, int)"/>
/// and then 'QuantizeImage'.
/// </remarks>
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;
}
/// <inheritdoc />
public bool Dither { get; set; } = true;
public bool Dither { get; }
/// <inheritdoc />
public IErrorDiffuser DitherType { get; set; } = DiffuseMode.FloydSteinberg;
public IErrorDiffuser DitherType { get; }
/// <inheritdoc/>
public virtual QuantizedFrame<TPixel> Quantize(ImageFrame<TPixel> image, int maxColors)
public virtual QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> image)
{
Guard.NotNull(image, nameof(image));

35
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
{
/// <summary>
/// Provides methods to allow the execution of the quantization process on an image frame.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Gets a value indicating whether to apply dithering to the output image.
/// </summary>
bool Dither { get; }
/// <summary>
/// Gets the dithering algorithm to apply to the output image.
/// </summary>
IErrorDiffuser DitherType { get; }
/// <summary>
/// Quantize an image frame and return the resulting output pixels.
/// </summary>
/// <param name="image">The image to quantize.</param>
/// <returns>
/// A <see cref="QuantizedFrame{TPixel}"/> representing a quantized version of the image pixels.
/// </returns>
QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> image);
}
}

32
src/ImageSharp/Processing/Quantization/OctreeQuantizer{TPixel}.cs → 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
{
/// <summary>
/// Encapsulates methods to calculate the color palette if an image using an Octree pattern.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public sealed class OctreeQuantizer<TPixel> : QuantizerBase<TPixel>
internal sealed class OctreeFrameQuantizer<TPixel> : FrameQuantizerBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
@ -24,14 +24,14 @@ namespace SixLabors.ImageSharp.Processing.Quantization
private readonly Dictionary<TPixel, byte> colorMap = new Dictionary<TPixel, byte>();
/// <summary>
/// Stores the tree
/// Maximum allowed color depth
/// </summary>
private Octree octree;
private readonly byte colors;
/// <summary>
/// Maximum allowed color depth
/// Stores the tree
/// </summary>
private byte colors;
private readonly Octree octree;
/// <summary>
/// The reduced image palette
@ -44,26 +44,18 @@ namespace SixLabors.ImageSharp.Processing.Quantization
private byte transparentIndex;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer{TPixel}"/> class.
/// Initializes a new instance of the <see cref="OctreeFrameQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="quantizer">The octree quantizer</param>
/// <remarks>
/// 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
/// </remarks>
public OctreeQuantizer()
: base(false)
public OctreeFrameQuantizer(OctreeQuantizer quantizer)
: base(quantizer, false)
{
}
/// <inheritdoc/>
public override QuantizedFrame<TPixel> Quantize(ImageFrame<TPixel> 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);
}
/// <inheritdoc/>
@ -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);

34
src/ImageSharp/Processing/Quantization/PaletteQuantizer{TPixel}.cs → 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
{
/// <summary>
/// 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.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public sealed class PaletteQuantizer<TPixel> : QuantizerBase<TPixel>
internal sealed class PaletteFrameQuantizer<TPixel> : FrameQuantizerBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
@ -26,33 +25,16 @@ namespace SixLabors.ImageSharp.Processing.Quantization
/// <summary>
/// List of all colors in the palette
/// </summary>
private TPixel[] colors;
private readonly TPixel[] colors;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> class.
/// Initializes a new instance of the <see cref="PaletteFrameQuantizer{TPixel}"/> class.
/// </summary>
public PaletteQuantizer()
: this(NamedColors<TPixel>.WebSafePalette)
/// <param name="quantizer">The palette quantizer</param>
public PaletteFrameQuantizer(PaletteQuantizer quantizer)
: base(quantizer, true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="palette">The palette to select substitute colors from.</param>
public PaletteQuantizer(TPixel[] palette = null)
: base(true)
{
Guard.NotNull(palette, nameof(palette));
this.colors = palette;
}
/// <inheritdoc/>
public override QuantizedFrame<TPixel> Quantize(ImageFrame<TPixel> image, int maxColors)
{
Array.Resize(ref this.colors, maxColors.Clamp(1, 255));
this.colorMap.Clear();
return base.Quantize(image, maxColors);
this.colors = quantizer.GetPalette<TPixel>();
}
/// <inheritdoc/>

74
src/ImageSharp/Processing/Quantization/WuQuantizer{TPixel}.cs → 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
{
/// <summary>
/// An implementation of Wu's color quantizer with alpha channel.
@ -32,10 +32,10 @@ namespace SixLabors.ImageSharp.Processing.Quantization
/// </para>
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public class WuQuantizer<TPixel> : QuantizerBase<TPixel>
internal sealed class WuFrameQuantizer<TPixel> : FrameQuantizerBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
// TODO: The WuQuantizer<TPixel> code is rising several questions:
// TODO: The WuFrameQuantizer<TPixel> 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;
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer{TPixel}"/> class.
/// Initializes a new instance of the <see cref="WuFrameQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="quantizer">The wu quantizer</param>
/// <remarks>
/// 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.
/// </remarks>
public WuQuantizer()
: base(false)
public WuFrameQuantizer(WuQuantizer quantizer)
: base(quantizer, false)
{
this.colors = quantizer.MaxColors;
}
/// <inheritdoc/>
public override QuantizedFrame<TPixel> Quantize(ImageFrame<TPixel> image, int maxColors)
public override QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> 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<float>(TableLength);
this.tag = memoryManager.AllocateClean<byte>(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)];
}
/// <summary>
/// Represents a box color cube.
/// </summary>
private struct Box
{
/// <summary>
/// Gets or sets the min red value, exclusive.
/// </summary>
public int R0;
/// <summary>
/// Gets or sets the max red value, inclusive.
/// </summary>
public int R1;
/// <summary>
/// Gets or sets the min green value, exclusive.
/// </summary>
public int G0;
/// <summary>
/// Gets or sets the max green value, inclusive.
/// </summary>
public int G1;
/// <summary>
/// Gets or sets the min blue value, exclusive.
/// </summary>
public int B0;
/// <summary>
/// Gets or sets the max blue value, inclusive.
/// </summary>
public int B1;
/// <summary>
/// Gets or sets the min alpha value, exclusive.
/// </summary>
public int A0;
/// <summary>
/// Gets or sets the max alpha value, inclusive.
/// </summary>
public int A1;
/// <summary>
/// Gets or sets the volume.
/// </summary>
public int Volume;
}
}
}

33
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
{
/// <summary>
/// Provides methods for allowing quantization of images pixels with configurable dithering.
/// </summary>
public interface IQuantizer
{
/// <summary>
/// Gets a value indicating whether to apply dithering to the output image.
/// </summary>
bool Dither { get; }
/// <summary>
/// Gets the dithering algorithm to apply to the output image.
/// </summary>
IErrorDiffuser DitherType { get; }
/// <summary>
/// Creates the generic frame quantizer
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="IFrameQuantizer{TPixel}"/></returns>
IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel>;
}
}

42
src/ImageSharp/Processing/Quantization/IQuantizer{TPixel}.cs

@ -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
{
/// <summary>
/// Provides methods for for allowing quantization of images pixels with configurable dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IQuantizer<TPixel> : IQuantizer
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Quantize an image and return the resulting output pixels.
/// </summary>
/// <param name="image">The image to quantize.</param>
/// <param name="maxColors">The maximum number of colors to return.</param>
/// <returns>
/// A <see cref="T:QuantizedImage"/> representing a quantized version of the image pixels.
/// </returns>
QuantizedFrame<TPixel> Quantize(ImageFrame<TPixel> image, int maxColors);
}
/// <summary>
/// Provides methods for allowing quantization of images pixels with configurable dithering.
/// </summary>
public interface IQuantizer
{
/// <summary>
/// Gets or sets a value indicating whether to apply dithering to the output image.
/// </summary>
bool Dither { get; set; }
/// <summary>
/// Gets or sets the dithering algorithm to apply to the output image.
/// </summary>
IErrorDiffuser DitherType { get; set; }
}
}

75
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
{
/// <summary>
/// Allows the quantization of images pixels using Octrees.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
public class OctreeQuantizer : IQuantizer
{
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
public OctreeQuantizer()
: this(255)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
public OctreeQuantizer(bool dither)
: this(dither, DiffuseMode.FloydSteinberg, 255)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
/// <param name="maxColors">The maximum number of colors to hold in the color palette</param>
public OctreeQuantizer(int maxColors)
: this(true, DiffuseMode.FloydSteinberg, maxColors)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
/// <param name="ditherType">The dithering algorithm to apply to the output image</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette</param>
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;
}
/// <inheritdoc />
public bool Dither { get; }
/// <inheritdoc />
public IErrorDiffuser DitherType { get; }
/// <summary>
/// Gets the maximum number of colors to hold in the color palette.
/// </summary>
public int MaxColors { get; }
/// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel>
=> new OctreeFrameQuantizer<TPixel>(this);
}
}

68
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
{
/// <summary>
/// Allows the quantization of images pixels using web safe colors defined in the CSS Color Module Level 4.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// Override this class to provide your own palette.
/// </summary>
public class PaletteQuantizer : IQuantizer
{
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary>
public PaletteQuantizer()
: this(true, DiffuseMode.FloydSteinberg)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
public PaletteQuantizer(bool dither)
: this(dither, DiffuseMode.FloydSteinberg)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
/// <param name="ditherType">The dithering algorithm to apply to the output image</param>
public PaletteQuantizer(bool dither, IErrorDiffuser ditherType)
{
Guard.NotNull(ditherType, nameof(ditherType));
this.Dither = dither;
this.DitherType = ditherType;
}
/// <inheritdoc />
public bool Dither { get; }
/// <inheritdoc />
public IErrorDiffuser DitherType { get; }
/// <summary>
/// Gets the palette to use to quantize the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="T:TPixel[]"/></returns>
public virtual TPixel[] GetPalette<TPixel>()
where TPixel : struct, IPixel<TPixel>
=> NamedColors<TPixel>.WebSafePalette;
/// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel>
=> new PaletteFrameQuantizer<TPixel>(this);
}
}

53
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
{
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class QuantizeProcessor<TPixel> : ImageProcessor<TPixel>
@ -21,51 +21,38 @@ namespace SixLabors.ImageSharp.Processing.Quantization.Processors
/// Initializes a new instance of the <see cref="QuantizeProcessor{TPixel}"/> class.
/// </summary>
/// <param name="quantizer">The quantizer used to reduce the color palette</param>
/// <param name="maxColors">The maximum number of colors to reduce the palette to</param>
public QuantizeProcessor(IQuantizer<TPixel> quantizer, int maxColors)
public QuantizeProcessor(IQuantizer quantizer)
{
Guard.NotNull(quantizer, nameof(quantizer));
Guard.MustBeGreaterThan(maxColors, 0, nameof(maxColors));
this.Quantizer = quantizer;
this.MaxColors = maxColors;
}
/// <summary>
/// Gets the quantizer
/// </summary>
public IQuantizer<TPixel> Quantizer { get; }
/// <summary>
/// Gets the maximum number of palette colors
/// </summary>
public int MaxColors { get; }
public IQuantizer Quantizer { get; }
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
QuantizedFrame<TPixel> quantized = this.Quantizer.Quantize(source, this.MaxColors);
IFrameQuantizer<TPixel> executor = this.Quantizer.CreateFrameQuantizer<TPixel>();
QuantizedFrame<TPixel> quantized = executor.QuantizeFrame(source);
int paletteCount = quantized.Palette.Length - 1;
using (Buffer2D<TPixel> pixels = source.MemoryManager.Allocate2D<TPixel>(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<TPixel> 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<TPixel>.SwapContents(source.PixelBuffer, pixels);
Span<TPixel> 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;
}
}
}
}

16
src/ImageSharp/Processing/Quantization/QuantizationMode.cs

@ -4,26 +4,26 @@
namespace SixLabors.ImageSharp.Processing.Quantization
{
/// <summary>
/// Provides enumeration over how an image should be quantized.
/// Contains reusable static instances of known quantizing algorithms
/// </summary>
public enum QuantizationMode
public static class QuantizationMode
{
/// <summary>
/// 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.
/// </summary>
Octree,
public static IQuantizer Octree { get; } = new OctreeQuantizer();
/// <summary>
/// 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.
/// </summary>
Wu,
public static IQuantizer Wu { get; } = new WuQuantizer();
/// <summary>
/// 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.
/// </summary>
Palette
public static IQuantizer Palette { get; } = new PaletteQuantizer();
}
}

31
src/ImageSharp/Processing/Quantization/QuantizeExtensions.cs

@ -12,34 +12,14 @@ namespace SixLabors.ImageSharp.Processing.Quantization
public static class QuantizeExtensions
{
/// <summary>
/// Applies quantization to the image.
/// Applies quantization to the image using the <see cref="OctreeQuantizer"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="mode">The quantization mode to apply to perform the operation.</param>
/// <param name="maxColors">The maximum number of colors to return. Defaults to 256.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Quantize<TPixel>(this IImageProcessingContext<TPixel> source, QuantizationMode mode = QuantizationMode.Octree, int maxColors = 256)
public static IImageProcessingContext<TPixel> Quantize<TPixel>(this IImageProcessingContext<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
IQuantizer<TPixel> quantizer;
switch (mode)
{
case QuantizationMode.Wu:
quantizer = new WuQuantizer<TPixel>();
break;
case QuantizationMode.Palette:
quantizer = new PaletteQuantizer<TPixel>();
break;
default:
quantizer = new OctreeQuantizer<TPixel>();
break;
}
return Quantize(source, quantizer, maxColors);
}
=> Quantize(source, QuantizationMode.Octree);
/// <summary>
/// Applies quantization to the image.
@ -47,10 +27,9 @@ namespace SixLabors.ImageSharp.Processing.Quantization
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="quantizer">The quantizer to apply to perform the operation.</param>
/// <param name="maxColors">The maximum number of colors to return.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Quantize<TPixel>(this IImageProcessingContext<TPixel> source, IQuantizer<TPixel> quantizer, int maxColors)
public static IImageProcessingContext<TPixel> Quantize<TPixel>(this IImageProcessingContext<TPixel> source, IQuantizer quantizer)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new QuantizeProcessor<TPixel>(quantizer, maxColors));
=> source.ApplyProcessor(new QuantizeProcessor<TPixel>(quantizer));
}
}

75
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
{
/// <summary>
/// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer.
/// <see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/>
/// </summary>
public class WuQuantizer : IQuantizer
{
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// </summary>
public WuQuantizer()
: this(255)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
public WuQuantizer(bool dither)
: this(dither, DiffuseMode.FloydSteinberg, 255)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// </summary>
/// <param name="maxColors">The maximum number of colors to hold in the color palette</param>
public WuQuantizer(int maxColors)
: this(true, DiffuseMode.FloydSteinberg, maxColors)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
/// <param name="ditherType">The dithering algorithm to apply to the output image</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette</param>
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;
}
/// <inheritdoc />
public bool Dither { get; }
/// <inheritdoc />
public IErrorDiffuser DitherType { get; }
/// <summary>
/// Gets the maximum number of colors to hold in the color palette.
/// </summary>
public int MaxColors { get; }
/// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel>
=> new WuFrameQuantizer<TPixel>(this);
}
}

53
tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs

@ -3,20 +3,15 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
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;
/// <summary>
/// Benchmarks saving png files using different quantizers. System.Drawing cannot save indexed png files so we cannot compare.
/// </summary>
@ -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<Rgba32>(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<Rgba32>(), 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<Rgba32> { 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<Rgba32>(), 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<Rgba32> { 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<Rgba32>(), PaletteSize = 256 };
var options = new PngEncoder { Quantizer = new WuQuantizer(false) };
this.bmpCore.SaveAsPng(memoryStream, options);
}

10
tests/ImageSharp.Benchmarks/Image/EncodePng.cs

@ -70,12 +70,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Image
{
using (var memoryStream = new MemoryStream())
{
QuantizerBase<Rgba32> quantizer = this.UseOctreeQuantizer
? (QuantizerBase<Rgba32>)
new OctreeQuantizer<Rgba32>()
: new PaletteQuantizer<Rgba32>();
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);
}
}

64
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
/// <summary>
/// All types except Palette
/// </summary>
public static readonly TheoryData<PngColorType> PngColorTypes = new TheoryData<PngColorType>()
{
PngColorType.RgbWithAlpha,
PngColorType.Rgb,
PngColorType.Grayscale,
PngColorType.GrayscaleWithAlpha,
};
public static readonly TheoryData<PngColorType> PngColorTypes = new TheoryData<PngColorType>
{
PngColorType.RgbWithAlpha,
PngColorType.Rgb,
PngColorType.Grayscale,
PngColorType.GrayscaleWithAlpha,
};
/// <summary>
/// All types except Palette
/// </summary>
public static readonly TheoryData<int> CompressionLevels = new TheoryData<int>()
{
1, 2, 3, 4, 5, 6, 7, 8, 9
};
public static readonly TheoryData<int> CompressionLevels = new TheoryData<int>
{
1, 2, 3, 4, 5, 6, 7, 8, 9
};
public static readonly TheoryData<int> PaletteSizes = new TheoryData<int>()
public static readonly TheoryData<int> PaletteSizes = new TheoryData<int>
{
30, 55, 100, 201, 255
};
30, 55, 100, 201, 255
};
public static readonly TheoryData<int> PaletteLargeOnly = new TheoryData<int>()
{
80, 100, 120, 230
};
public static readonly TheoryData<int> PaletteLargeOnly = new TheoryData<int>
{
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<TPixel>(TestImageProvider<TPixel> 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<TPixel>(TestImageProvider<TPixel> provider, int paletteSize)
@ -92,7 +94,7 @@ namespace SixLabors.ImageSharp.Tests
TestImageProvider<TPixel> 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<TPixel>(actualOutputFile, referenceDecoder))
using (var referenceImage = Image.Load<TPixel>(referenceOutputFile, referenceDecoder))
{
@ -136,7 +138,7 @@ namespace SixLabors.ImageSharp.Tests
}
}
}
[Theory]
[WithBlankImages(1, 1, PixelTypes.Rgba32)]
public void WritesFileMarker<TPixel>(TestImageProvider<TPixel> 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

2
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]

18
tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs

@ -10,9 +10,9 @@
[Fact]
public void QuantizersDitherByDefault()
{
var palette = new PaletteQuantizer<Rgba32>();
var octree = new OctreeQuantizer<Rgba32>();
var wu = new WuQuantizer<Rgba32>();
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<TPixel> quantizer = new PaletteQuantizer<TPixel> { Dither = dither };
var quantizer = new PaletteQuantizer(dither);
foreach (ImageFrame<TPixel> frame in image.Frames)
{
QuantizedFrame<TPixel> quantized = quantizer.Quantize(frame, 256);
QuantizedFrame<TPixel> quantized = quantizer.CreateFrameQuantizer<TPixel>().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<TPixel> quantizer = new OctreeQuantizer<TPixel> { Dither = dither };
var quantizer = new OctreeQuantizer(dither);
foreach (ImageFrame<TPixel> frame in image.Frames)
{
QuantizedFrame<TPixel> quantized = quantizer.Quantize(frame, 256);
QuantizedFrame<TPixel> quantized = quantizer.CreateFrameQuantizer<TPixel>().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<TPixel> quantizer = new WuQuantizer<TPixel>() { Dither = dither };
var quantizer = new WuQuantizer(dither);
foreach (ImageFrame<TPixel> frame in image.Frames)
{
QuantizedFrame<TPixel> quantized = quantizer.Quantize(frame, 256);
QuantizedFrame<TPixel> quantized = quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(frame);
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.Pixels[0]);

Loading…
Cancel
Save