Browse Source

Made PaletteQuantizer non-generic all the way

af/merge-core
Anton Firszov 7 years ago
parent
commit
5d470807d8
  1. 2
      src/ImageSharp/Color/Color.WebSafePalette.cs
  2. 2
      src/ImageSharp/Color/Color.WernerPalette.cs
  3. 11
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  4. 4
      src/ImageSharp/Processing/DiffuseExtensions.cs
  5. 4
      src/ImageSharp/Processing/DitherExtensions.cs
  6. 2
      src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs
  7. 2
      src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs
  8. 10
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs
  9. 51
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  10. 2
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
  11. 43
      src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs
  12. 6
      src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs
  13. 13
      src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs
  14. 59
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  15. 110
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
  16. 13
      src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs
  17. 13
      src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs
  18. 6
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
  19. 2
      tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs
  20. 28
      tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs

2
src/ImageSharp/Color/Color.WebSafePalette.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Gets a collection of named, web safe colors as defined in the CSS Color Module Level 4.
/// </summary>
public static ReadOnlySpan<Color> WebSafePalette => WebSafePaletteLazy.Value;
public static ReadOnlyMemory<Color> WebSafePalette => WebSafePaletteLazy.Value;
private static Color[] CreateWebSafePalette() => new[]
{

2
src/ImageSharp/Color/Color.WernerPalette.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp
/// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821.
/// The hex codes were collected and defined by Nicholas Rougeux <see href="https://www.c82.net/werner"/>.
/// </summary>
public static ReadOnlySpan<Color> WernerPalette => WernerPaletteLazy.Value;
public static ReadOnlyMemory<Color> WernerPalette => WernerPaletteLazy.Value;
private static Color[] CreateWernerPalette() => new[]
{

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

@ -144,8 +144,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void EncodeGlobal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, int transparencyIndex, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
var palleteQuantizer = new PaletteQuantizer<TPixel>(quantized.Palette, this.quantizer.Diffuser);
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<TPixel> frame = image.Frames[i];
@ -160,10 +158,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
using (IFrameQuantizer<TPixel> palleteFrameQuantizer = palleteQuantizer.CreateFrameQuantizer(image.GetConfiguration()))
using (QuantizedFrame<TPixel> paletteQuantized = palleteFrameQuantizer.QuantizeFrame(frame))
using (IFrameQuantizer<TPixel> palleteFrameQuantizer =
new PaletteFrameQuantizer<TPixel>(this.quantizer.Diffuser, quantized.Palette))
{
this.WriteImageData(paletteQuantized, stream);
using (QuantizedFrame<TPixel> paletteQuantized = palleteFrameQuantizer.QuantizeFrame(frame))
{
this.WriteImageData(paletteQuantized, stream);
}
}
}
}

4
src/ImageSharp/Processing/DiffuseExtensions.cs

@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Processing.Dithering
this IImageProcessingContext source,
IErrorDiffuser diffuser,
float threshold,
ReadOnlySpan<Color> palette) =>
ReadOnlyMemory<Color> palette) =>
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette));
/// <summary>
@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Dithering
this IImageProcessingContext source,
IErrorDiffuser diffuser,
float threshold,
ReadOnlySpan<Color> palette,
ReadOnlyMemory<Color> palette,
Rectangle rectangle) =>
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle);
}

4
src/ImageSharp/Processing/DitherExtensions.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing
public static IImageProcessingContext Dither(
this IImageProcessingContext source,
IOrderedDither dither,
ReadOnlySpan<Color> palette) =>
ReadOnlyMemory<Color> palette) =>
source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette));
/// <summary>
@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing
public static IImageProcessingContext Dither(
this IImageProcessingContext source,
IOrderedDither dither,
ReadOnlySpan<Color> palette,
ReadOnlyMemory<Color> palette,
Rectangle rectangle) =>
source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette), rectangle);
}

2
src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs

@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <param name="diffuser">The error diffuser</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold, ReadOnlySpan<Color> palette)
public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold, ReadOnlyMemory<Color> palette)
: base(palette)
{
Guard.NotNull(diffuser, nameof(diffuser));

2
src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
public OrderedDitherPaletteProcessor(IOrderedDither dither, ReadOnlySpan<Color> palette)
public OrderedDitherPaletteProcessor(IOrderedDither dither, ReadOnlyMemory<Color> palette)
: base(palette) => this.Dither = dither ?? throw new ArgumentNullException(nameof(dither));
/// <summary>

10
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs

@ -12,23 +12,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
public abstract class PaletteDitherProcessor : IImageProcessor
{
private readonly Color[] palette;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteDitherProcessor"/> class.
/// </summary>
/// <param name="palette">The palette to select substitute colors from.</param>
protected PaletteDitherProcessor(ReadOnlySpan<Color> palette)
protected PaletteDitherProcessor(ReadOnlyMemory<Color> palette)
{
// This shouldn't be a perf issue:
// these arrays are small, and created with low frequency.
this.palette = palette.ToArray();
this.Palette = palette;
}
/// <summary>
/// Gets the palette to select substitute colors from.
/// </summary>
public ReadOnlySpan<Color> Palette => this.palette;
public ReadOnlyMemory<Color> Palette { get; }
/// <inheritdoc />
public abstract IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>()

51
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs

@ -20,13 +20,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
private readonly Dictionary<TPixel, PixelPair<TPixel>> cache = new Dictionary<TPixel, PixelPair<TPixel>>();
private TPixel[] palette;
/// <summary>
/// The vector representation of the image palette.
/// </summary>
private Vector4[] paletteVector;
private TPixel[] palette;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteDitherProcessor{TPixel}"/> class.
/// </summary>
@ -37,6 +37,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
protected PaletteDitherProcessor Definition { get; }
protected override void BeforeFrameApply(
ImageFrame<TPixel> source,
Rectangle sourceRectangle,
Configuration configuration)
{
base.BeforeFrameApply(source, sourceRectangle, configuration);
// Lazy init palette:
if (this.palette is null)
{
ReadOnlySpan<Color> sourcePalette = this.Definition.Palette.Span;
this.palette = new TPixel[sourcePalette.Length];
Color.ToPixel<TPixel>(configuration, sourcePalette, this.palette);
}
// Lazy init paletteVector:
if (this.paletteVector is null)
{
this.paletteVector = new Vector4[this.palette.Length];
PixelOperations<TPixel>.Instance.ToVector4(
configuration,
(ReadOnlySpan<TPixel>)this.palette,
(Span<Vector4>)this.paletteVector,
PixelConversionModifiers.Scale);
}
}
/// <summary>
/// Returns the two closest colors from the palette calculated via Euclidean distance in the Rgba space.
/// </summary>
@ -88,25 +115,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
return pair;
}
protected override void BeforeFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
base.BeforeFrameApply(source, sourceRectangle, configuration);
// Lazy init palette:
if (this.palette is null)
{
ReadOnlySpan<Color> sourcePalette = this.Definition.Palette;
this.palette = new TPixel[sourcePalette.Length];
Color.ToPixel<TPixel>(configuration, sourcePalette, this.palette);
}
// Lazy init paletteVector:
if (this.paletteVector is null)
{
this.paletteVector = new Vector4[this.palette.Length];
PixelOperations<TPixel>.Instance.ToVector4(configuration, (ReadOnlySpan<TPixel>)this.palette, (Span<Vector4>)this.paletteVector, PixelConversionModifiers.Scale);
}
}
}
}

2
src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs

@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
}
/// <summary>
/// Gets the options effecting blending and composition
/// Gets the options effecting blending and composition.
/// </summary>
public GraphicsOptions GraphicsOptions { get; }

43
src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerBase{TPixel}.cs → src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The base class for all <see cref="IFrameQuantizer{TPixel}"/> implementations
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public abstract class FrameQuantizerBase<TPixel> : IFrameQuantizer<TPixel>
public abstract class FrameQuantizer<TPixel> : IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private Vector4[] paletteVector;
/// <summary>
/// Initializes a new instance of the <see cref="FrameQuantizerBase{TPixel}"/> class.
/// Initializes a new instance of the <see cref="FrameQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="quantizer">The quantizer</param>
/// <param name="singlePass">
@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// only call the <see cref="SecondPass(ImageFrame{TPixel}, Span{byte}, ReadOnlySpan{TPixel}, int, int)"/> method.
/// If two passes are required, the code will also call <see cref="FirstPass(ImageFrame{TPixel}, int, int)"/>.
/// </remarks>
protected FrameQuantizerBase(IQuantizer quantizer, bool singlePass)
protected FrameQuantizer(IQuantizer quantizer, bool singlePass)
{
Guard.NotNull(quantizer, nameof(quantizer));
@ -52,6 +52,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Dither = this.Diffuser != null;
this.singlePass = singlePass;
}
/// <summary>
/// Initializes a new instance of the <see cref="FrameQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The diffuser</param>
/// <param name="singlePass">
/// If true, the quantization process only needs to loop through the source pixels once
/// </param>
/// <remarks>
/// If you construct this class with a <value>true</value> for <paramref name="singlePass"/>, then the code will
/// only call the <see cref="SecondPass(ImageFrame{TPixel}, Span{byte}, ReadOnlySpan{TPixel}, int, int)"/> method.
/// If two passes are required, the code will also call <see cref="FirstPass(ImageFrame{TPixel}, int, int)"/>.
/// </remarks>
protected FrameQuantizer(IErrorDiffuser diffuser, bool singlePass)
{
this.Diffuser = diffuser;
this.Dither = this.Diffuser != null;
this.singlePass = singlePass;
}
/// <inheritdoc />
public bool Dither { get; }
@ -77,22 +96,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
// Collect the palette. Required before the second pass runs.
TPixel[] palette = this.GetPalette();
ReadOnlyMemory<TPixel> palette = this.GetPalette();
this.paletteVector = new Vector4[palette.Length];
PixelOperations<TPixel>.Instance.ToVector4(image.Configuration, (ReadOnlySpan<TPixel>)palette, (Span<Vector4>)this.paletteVector, PixelConversionModifiers.Scale);
var quantizedFrame = new QuantizedFrame<TPixel>(image.MemoryAllocator, width, height, palette);
PixelOperations<TPixel>.Instance.ToVector4(image.Configuration, palette.Span, (Span<Vector4>)this.paletteVector, PixelConversionModifiers.Scale);
// TODO: Pass ReadOnlyMemory<T> instead of array!
var quantizedFrame = new QuantizedFrame<TPixel>(
image.MemoryAllocator,
width,
height,
palette.Span.ToArray());
if (this.Dither)
{
// We clone the image as we don't want to alter the original via dithering.
using (ImageFrame<TPixel> clone = image.Clone())
{
this.SecondPass(clone, quantizedFrame.GetPixelSpan(), palette, width, height);
this.SecondPass(clone, quantizedFrame.GetPixelSpan(), palette.Span, width, height);
}
}
else
{
this.SecondPass(image, quantizedFrame.GetPixelSpan(), palette, width, height);
this.SecondPass(image, quantizedFrame.GetPixelSpan(), palette.Span, width, height);
}
return quantizedFrame;
@ -134,7 +159,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>
/// <see cref="T:TPixel[]"/>
/// </returns>
protected abstract TPixel[] GetPalette();
protected abstract ReadOnlyMemory<TPixel> GetPalette();
/// <summary>
/// Returns the index of the first instance of the transparent color in the palette.

6
src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal sealed class OctreeFrameQuantizer<TPixel> : FrameQuantizerBase<TPixel>
internal sealed class OctreeFrameQuantizer<TPixel> : FrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
@ -136,10 +136,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
}
internal TPixel[] AotGetPalette() => this.GetPalette();
internal ReadOnlyMemory<TPixel> AotGetPalette() => this.GetPalette();
/// <inheritdoc/>
protected override TPixel[] GetPalette() => this.octree.Palletize(this.colors);
protected override ReadOnlyMemory<TPixel> GetPalette() => this.octree.Palletize(this.colors);
/// <summary>
/// Process the pixel in the second pass of the algorithm.

13
src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs

@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
@ -14,21 +15,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal sealed class PaletteFrameQuantizer<TPixel> : FrameQuantizerBase<TPixel>
internal sealed class PaletteFrameQuantizer<TPixel> : FrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The reduced image palette.
/// </summary>
private readonly TPixel[] palette;
private readonly ReadOnlyMemory<TPixel> palette;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteFrameQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="quantizer">The palette quantizer.</param>
/// <param name="diffuser">The palette quantizer.</param>
/// <param name="colors">An array of all colors in the palette.</param>
public PaletteFrameQuantizer(IQuantizer quantizer, TPixel[] colors)
: base(quantizer, true) => this.palette = colors;
public PaletteFrameQuantizer(IErrorDiffuser diffuser, ReadOnlyMemory<TPixel> colors)
: base(diffuser, true) => this.palette = colors;
/// <inheritdoc/>
protected override void SecondPass(
@ -85,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override TPixel[] GetPalette() => this.palette;
protected override ReadOnlyMemory<TPixel> GetPalette() => this.palette;
/// <summary>
/// Process the pixel in the second pass of the algorithm

59
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
@ -14,61 +15,65 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// By default the quantizer uses <see cref="KnownDiffusers.FloydSteinberg"/> dithering.
/// </para>
/// </summary>
public abstract class PaletteQuantizer : IQuantizer
public class PaletteQuantizer : IQuantizer
{
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary>
protected PaletteQuantizer()
: this(true)
/// <param name="palette">The palette.</param>
public PaletteQuantizer(ReadOnlyMemory<Color> palette)
: this(palette, true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary>
/// <param name="palette">The palette.</param>
/// <param name="dither">Whether to apply dithering to the output image</param>
protected PaletteQuantizer(bool dither)
: this(GetDiffuser(dither))
public PaletteQuantizer(ReadOnlyMemory<Color> palette, bool dither)
: this(palette, GetDiffuser(dither))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary>
/// <param name="palette">The palette.</param>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
protected PaletteQuantizer(IErrorDiffuser diffuser) => this.Diffuser = diffuser;
public PaletteQuantizer(ReadOnlyMemory<Color> palette, IErrorDiffuser diffuser)
{
this.Palette = palette;
this.Diffuser = diffuser;
}
/// <inheritdoc />
public IErrorDiffuser Diffuser { get; }
/// <inheritdoc />
public abstract IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration)
where TPixel : struct, IPixel<TPixel>;
/// <inheritdoc/>
public abstract IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, int maxColors)
where TPixel : struct, IPixel<TPixel>;
/// <summary>
/// Creates the generic frame quantizer.
/// Gets the palette.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param>
/// <param name="palette">The color palette.</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
/// <returns>The <see cref="IFrameQuantizer{TPixel}"/></returns>
protected IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, TPixel[] palette, int maxColors)
public ReadOnlyMemory<Color> Palette { get; }
/// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration)
where TPixel : struct, IPixel<TPixel>
{
int max = Math.Min(QuantizerConstants.MaxColors, Math.Min(maxColors, palette.Length));
TPixel[] palette = new TPixel[this.Palette.Length];
Color.ToPixel(configuration, this.Palette.Span, palette.AsSpan());
return new PaletteFrameQuantizer<TPixel>(this.Diffuser, palette);
}
if (max != palette.Length)
{
return new PaletteFrameQuantizer<TPixel>(this, palette.AsSpan(0, max).ToArray());
}
/// <inheritdoc/>
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, int maxColors)
where TPixel : struct, IPixel<TPixel>
{
maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors);
int max = Math.Min(maxColors, this.Palette.Length);
return new PaletteFrameQuantizer<TPixel>(this, palette);
TPixel[] palette = new TPixel[max];
Color.ToPixel(configuration, this.Palette.Span.Slice(0, max), palette.AsSpan());
return new PaletteFrameQuantizer<TPixel>(this.Diffuser, palette);
}
private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null;

110
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs

@ -1,110 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// A generic palette quantizer.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public class PaletteQuantizer<TPixel> : IQuantizer
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="palette">The color palette to use.</param>
public PaletteQuantizer(TPixel[] palette)
: this(palette, true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="palette">The color palette to use.</param>
/// <param name="dither">Whether to apply dithering to the output image</param>
public PaletteQuantizer(TPixel[] palette, bool dither)
: this(palette, GetDiffuser(dither))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="palette">The color palette to use.</param>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
public PaletteQuantizer(TPixel[] palette, IErrorDiffuser diffuser)
{
Guard.MustBeBetweenOrEqualTo(palette.Length, QuantizerConstants.MinColors, QuantizerConstants.MaxColors, nameof(palette));
this.Palette = palette;
this.Diffuser = diffuser;
}
/// <inheritdoc/>
public IErrorDiffuser Diffuser { get; }
/// <summary>
/// Gets the palette.
/// </summary>
public TPixel[] Palette { get; }
/// <summary>
/// Creates the generic frame quantizer.
/// </summary>
/// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param>
/// <returns>The <see cref="IFrameQuantizer{TPixel}"/>.</returns>
public IFrameQuantizer<TPixel> CreateFrameQuantizer(Configuration configuration)
=> ((IQuantizer)this).CreateFrameQuantizer<TPixel>(configuration);
/// <summary>
/// Creates the generic frame quantizer.
/// </summary>
/// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
/// <returns>The <see cref="IFrameQuantizer{TPixel}"/>.</returns>
public IFrameQuantizer<TPixel> CreateFrameQuantizer(Configuration configuration, int maxColors)
=> ((IQuantizer)this).CreateFrameQuantizer<TPixel>(configuration, maxColors);
/// <inheritdoc/>
IFrameQuantizer<TPixel1> IQuantizer.CreateFrameQuantizer<TPixel1>(Configuration configuration)
{
if (!typeof(TPixel).Equals(typeof(TPixel1)))
{
throw new InvalidOperationException("Generic method type must be the same as class type.");
}
TPixel[] paletteRef = this.Palette;
return new PaletteFrameQuantizer<TPixel1>(this, Unsafe.As<TPixel[], TPixel1[]>(ref paletteRef));
}
/// <inheritdoc/>
IFrameQuantizer<TPixel1> IQuantizer.CreateFrameQuantizer<TPixel1>(Configuration configuration, int maxColors)
{
if (!typeof(TPixel).Equals(typeof(TPixel1)))
{
throw new InvalidOperationException("Generic method type must be the same as class type.");
}
TPixel[] paletteRef = this.Palette;
TPixel1[] castPalette = Unsafe.As<TPixel[], TPixel1[]>(ref paletteRef);
maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors);
int max = Math.Min(maxColors, castPalette.Length);
if (max != castPalette.Length)
{
return new PaletteFrameQuantizer<TPixel1>(this, castPalette.AsSpan(0, max).ToArray());
}
return new PaletteFrameQuantizer<TPixel1>(this, castPalette);
}
private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null;
}
}

13
src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs

@ -15,6 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="WebSafePaletteQuantizer" /> class.
/// </summary>
public WebSafePaletteQuantizer()
: this(true)
{
}
@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
public WebSafePaletteQuantizer(bool dither)
: base(dither)
: base(Color.WebSafePalette, dither)
{
}
@ -32,16 +33,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
public WebSafePaletteQuantizer(IErrorDiffuser diffuser)
: base(diffuser)
: base(Color.WebSafePalette, diffuser)
{
}
/// <inheritdoc />
public override IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration)
=> this.CreateFrameQuantizer<TPixel>(configuration, NamedColors<TPixel>.WebSafePalette.Length);
/// <inheritdoc/>
public override IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, int maxColors)
=> this.CreateFrameQuantizer(configuration, NamedColors<TPixel>.WebSafePalette, maxColors);
}
}

13
src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs

@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="WernerPaletteQuantizer" /> class.
/// </summary>
public WernerPaletteQuantizer()
: this(true)
{
}
@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
public WernerPaletteQuantizer(bool dither)
: base(dither)
: base(Color.WernerPalette, dither)
{
}
@ -33,16 +34,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
public WernerPaletteQuantizer(IErrorDiffuser diffuser)
: base(diffuser)
: base(Color.WernerPalette, diffuser)
{
}
/// <inheritdoc />
public override IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration)
=> this.CreateFrameQuantizer<TPixel>(configuration, NamedColors<TPixel>.WernerPalette.Length);
/// <inheritdoc/>
public override IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, int maxColors)
=> this.CreateFrameQuantizer(configuration, NamedColors<TPixel>.WernerPalette, maxColors);
}
}

6
src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </para>
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal sealed class WuFrameQuantizer<TPixel> : FrameQuantizerBase<TPixel>
internal sealed class WuFrameQuantizer<TPixel> : FrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
// The following two variables determine the amount of bits to preserve when calculating the histogram.
@ -171,10 +171,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.tag?.Dispose();
}
internal TPixel[] AotGetPalette() => this.GetPalette();
internal ReadOnlyMemory<TPixel> AotGetPalette() => this.GetPalette();
/// <inheritdoc/>
protected override TPixel[] GetPalette()
protected override ReadOnlyMemory<TPixel> GetPalette()
{
if (this.palette is null)
{

2
tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs

@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
this.operations.Dither(this.orderedDither);
OrderedDitherPaletteProcessor p = this.Verify<OrderedDitherPaletteProcessor>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(Color.WebSafePalette.ToArray(), p.Palette);
Assert.Equal(Color.WebSafePalette, p.Palette);
}
[Fact]

28
tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs

@ -11,21 +11,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
{
public class PaletteQuantizerTests
{
private static readonly Rgba32[] Rgb = new Rgba32[] { Rgba32.Red, Rgba32.Green, Rgba32.Blue };
private static readonly Color[] Rgb = new Color[] { Rgba32.Red, Rgba32.Green, Rgba32.Blue };
[Fact]
public void PaletteQuantizerConstructor()
{
var quantizer = new PaletteQuantizer<Rgba32>(Rgb);
var quantizer = new PaletteQuantizer(Rgb);
Assert.Equal(Rgb, quantizer.Palette);
Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser);
quantizer = new PaletteQuantizer<Rgba32>(Rgb, false);
quantizer = new PaletteQuantizer(Rgb, false);
Assert.Equal(Rgb, quantizer.Palette);
Assert.Null(quantizer.Diffuser);
quantizer = new PaletteQuantizer<Rgba32>(Rgb, KnownDiffusers.Atkinson);
quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson);
Assert.Equal(Rgb, quantizer.Palette);
Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser);
}
@ -33,35 +33,27 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
[Fact]
public void PaletteQuantizerCanCreateFrameQuantizer()
{
var quantizer = new PaletteQuantizer<Rgba32>(Rgb);
IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default);
var quantizer = new PaletteQuantizer(Rgb);
IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.Dither);
Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser);
quantizer = new PaletteQuantizer<Rgba32>(Rgb, false);
frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default);
quantizer = new PaletteQuantizer(Rgb, false);
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer);
Assert.False(frameQuantizer.Dither);
Assert.Null(frameQuantizer.Diffuser);
quantizer = new PaletteQuantizer<Rgba32>(Rgb, KnownDiffusers.Atkinson);
frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default);
quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson);
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.Dither);
Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser);
}
[Fact]
public void PaletteQuantizerThrowsOnInvalidGenericMethodCall()
{
var quantizer = new PaletteQuantizer<Rgba32>(Rgb);
Assert.Throws<InvalidOperationException>(() => ((IQuantizer)quantizer).CreateFrameQuantizer<Rgb24>(Configuration.Default));
}
[Fact]
public void KnownQuantizersWebSafeTests()
{

Loading…
Cancel
Save