Browse Source

Add generic palette quantizer, refactor + werner palette

af/merge-core
James Jackson-South 7 years ago
parent
commit
635ce2bede
  1. 5
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  2. 409
      src/ImageSharp/PixelFormats/ColorConstants.cs
  3. 26
      src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs
  4. 13
      src/ImageSharp/Processing/KnownQuantizers.cs
  5. 13
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs
  6. 5
      src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs
  7. 50
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  8. 92
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
  9. 21
      src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs
  10. 47
      src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs
  11. 48
      src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs
  12. 13
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs
  13. 2
      tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs
  14. 2
      tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs
  15. 4
      tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs
  16. 3
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  17. 2
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  18. 11
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs

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

@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void EncodeGlobal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, int transparencyIndex, Stream stream) private void EncodeGlobal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, int transparencyIndex, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var palleteQuantizer = new PaletteQuantizer(this.quantizer.Diffuser); var palleteQuantizer = new PaletteQuantizer<TPixel>(quantized.Palette, this.quantizer.Diffuser);
for (int i = 0; i < image.Frames.Count; i++) for (int i = 0; i < image.Frames.Count; i++)
{ {
@ -149,8 +149,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
} }
else else
{ {
using (QuantizedFrame<TPixel> paletteQuantized using (QuantizedFrame<TPixel> paletteQuantized = palleteQuantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(frame))
= palleteQuantizer.CreateFrameQuantizer(() => quantized.Palette).QuantizeFrame(frame))
{ {
this.WriteImageData(paletteQuantized, stream); this.WriteImageData(paletteQuantized, stream);
} }

409
src/ImageSharp/PixelFormats/ColorConstants.cs

@ -11,157 +11,268 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <summary> /// <summary>
/// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4.
/// </summary> /// </summary>
public static readonly Rgba32[] WebSafeColors = GetWebSafeColors(); public static readonly Rgba32[] WebSafeColors =
{
Rgba32.AliceBlue,
Rgba32.AntiqueWhite,
Rgba32.Aqua,
Rgba32.Aquamarine,
Rgba32.Azure,
Rgba32.Beige,
Rgba32.Bisque,
Rgba32.Black,
Rgba32.BlanchedAlmond,
Rgba32.Blue,
Rgba32.BlueViolet,
Rgba32.Brown,
Rgba32.BurlyWood,
Rgba32.CadetBlue,
Rgba32.Chartreuse,
Rgba32.Chocolate,
Rgba32.Coral,
Rgba32.CornflowerBlue,
Rgba32.Cornsilk,
Rgba32.Crimson,
Rgba32.Cyan,
Rgba32.DarkBlue,
Rgba32.DarkCyan,
Rgba32.DarkGoldenrod,
Rgba32.DarkGray,
Rgba32.DarkGreen,
Rgba32.DarkKhaki,
Rgba32.DarkMagenta,
Rgba32.DarkOliveGreen,
Rgba32.DarkOrange,
Rgba32.DarkOrchid,
Rgba32.DarkRed,
Rgba32.DarkSalmon,
Rgba32.DarkSeaGreen,
Rgba32.DarkSlateBlue,
Rgba32.DarkSlateGray,
Rgba32.DarkTurquoise,
Rgba32.DarkViolet,
Rgba32.DeepPink,
Rgba32.DeepSkyBlue,
Rgba32.DimGray,
Rgba32.DodgerBlue,
Rgba32.Firebrick,
Rgba32.FloralWhite,
Rgba32.ForestGreen,
Rgba32.Fuchsia,
Rgba32.Gainsboro,
Rgba32.GhostWhite,
Rgba32.Gold,
Rgba32.Goldenrod,
Rgba32.Gray,
Rgba32.Green,
Rgba32.GreenYellow,
Rgba32.Honeydew,
Rgba32.HotPink,
Rgba32.IndianRed,
Rgba32.Indigo,
Rgba32.Ivory,
Rgba32.Khaki,
Rgba32.Lavender,
Rgba32.LavenderBlush,
Rgba32.LawnGreen,
Rgba32.LemonChiffon,
Rgba32.LightBlue,
Rgba32.LightCoral,
Rgba32.LightCyan,
Rgba32.LightGoldenrodYellow,
Rgba32.LightGray,
Rgba32.LightGreen,
Rgba32.LightPink,
Rgba32.LightSalmon,
Rgba32.LightSeaGreen,
Rgba32.LightSkyBlue,
Rgba32.LightSlateGray,
Rgba32.LightSteelBlue,
Rgba32.LightYellow,
Rgba32.Lime,
Rgba32.LimeGreen,
Rgba32.Linen,
Rgba32.Magenta,
Rgba32.Maroon,
Rgba32.MediumAquamarine,
Rgba32.MediumBlue,
Rgba32.MediumOrchid,
Rgba32.MediumPurple,
Rgba32.MediumSeaGreen,
Rgba32.MediumSlateBlue,
Rgba32.MediumSpringGreen,
Rgba32.MediumTurquoise,
Rgba32.MediumVioletRed,
Rgba32.MidnightBlue,
Rgba32.MintCream,
Rgba32.MistyRose,
Rgba32.Moccasin,
Rgba32.NavajoWhite,
Rgba32.Navy,
Rgba32.OldLace,
Rgba32.Olive,
Rgba32.OliveDrab,
Rgba32.Orange,
Rgba32.OrangeRed,
Rgba32.Orchid,
Rgba32.PaleGoldenrod,
Rgba32.PaleGreen,
Rgba32.PaleTurquoise,
Rgba32.PaleVioletRed,
Rgba32.PapayaWhip,
Rgba32.PeachPuff,
Rgba32.Peru,
Rgba32.Pink,
Rgba32.Plum,
Rgba32.PowderBlue,
Rgba32.Purple,
Rgba32.RebeccaPurple,
Rgba32.Red,
Rgba32.RosyBrown,
Rgba32.RoyalBlue,
Rgba32.SaddleBrown,
Rgba32.Salmon,
Rgba32.SandyBrown,
Rgba32.SeaGreen,
Rgba32.SeaShell,
Rgba32.Sienna,
Rgba32.Silver,
Rgba32.SkyBlue,
Rgba32.SlateBlue,
Rgba32.SlateGray,
Rgba32.Snow,
Rgba32.SpringGreen,
Rgba32.SteelBlue,
Rgba32.Tan,
Rgba32.Teal,
Rgba32.Thistle,
Rgba32.Tomato,
Rgba32.Transparent,
Rgba32.Turquoise,
Rgba32.Violet,
Rgba32.Wheat,
Rgba32.White,
Rgba32.WhiteSmoke,
Rgba32.Yellow,
Rgba32.YellowGreen
};
/// <summary> /// <summary>
/// Returns an array of web safe colors. /// 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> /// </summary>
/// <returns>The <see cref="T:Color[]"/></returns> public static readonly Rgba32[] WernerColors =
private static Rgba32[] GetWebSafeColors() {
=> new Rgba32[] Rgba32.FromHex("#f1e9cd"),
{ Rgba32.FromHex("#f2e7cf"),
Rgba32.AliceBlue, Rgba32.FromHex("#ece6d0"),
Rgba32.AntiqueWhite, Rgba32.FromHex("#f2eacc"),
Rgba32.Aqua, Rgba32.FromHex("#f3e9ca"),
Rgba32.Aquamarine, Rgba32.FromHex("#f2ebcd"),
Rgba32.Azure, Rgba32.FromHex("#e6e1c9"),
Rgba32.Beige, Rgba32.FromHex("#e2ddc6"),
Rgba32.Bisque, Rgba32.FromHex("#cbc8b7"),
Rgba32.Black, Rgba32.FromHex("#bfbbb0"),
Rgba32.BlanchedAlmond, Rgba32.FromHex("#bebeb3"),
Rgba32.Blue, Rgba32.FromHex("#b7b5ac"),
Rgba32.BlueViolet, Rgba32.FromHex("#bab191"),
Rgba32.Brown, Rgba32.FromHex("#9c9d9a"),
Rgba32.BurlyWood, Rgba32.FromHex("#8a8d84"),
Rgba32.CadetBlue, Rgba32.FromHex("#5b5c61"),
Rgba32.Chartreuse, Rgba32.FromHex("#555152"),
Rgba32.Chocolate, Rgba32.FromHex("#413f44"),
Rgba32.Coral, Rgba32.FromHex("#454445"),
Rgba32.CornflowerBlue, Rgba32.FromHex("#423937"),
Rgba32.Cornsilk, Rgba32.FromHex("#433635"),
Rgba32.Crimson, Rgba32.FromHex("#252024"),
Rgba32.Cyan, Rgba32.FromHex("#241f20"),
Rgba32.DarkBlue, Rgba32.FromHex("#281f3f"),
Rgba32.DarkCyan, Rgba32.FromHex("#1c1949"),
Rgba32.DarkGoldenrod, Rgba32.FromHex("#4f638d"),
Rgba32.DarkGray, Rgba32.FromHex("#383867"),
Rgba32.DarkGreen, Rgba32.FromHex("#5c6b8f"),
Rgba32.DarkKhaki, Rgba32.FromHex("#657abb"),
Rgba32.DarkMagenta, Rgba32.FromHex("#6f88af"),
Rgba32.DarkOliveGreen, Rgba32.FromHex("#7994b5"),
Rgba32.DarkOrange, Rgba32.FromHex("#6fb5a8"),
Rgba32.DarkOrchid, Rgba32.FromHex("#719ba2"),
Rgba32.DarkRed, Rgba32.FromHex("#8aa1a6"),
Rgba32.DarkSalmon, Rgba32.FromHex("#d0d5d3"),
Rgba32.DarkSeaGreen, Rgba32.FromHex("#8590ae"),
Rgba32.DarkSlateBlue, Rgba32.FromHex("#3a2f52"),
Rgba32.DarkSlateGray, Rgba32.FromHex("#39334a"),
Rgba32.DarkTurquoise, Rgba32.FromHex("#6c6d94"),
Rgba32.DarkViolet, Rgba32.FromHex("#584c77"),
Rgba32.DeepPink, Rgba32.FromHex("#533552"),
Rgba32.DeepSkyBlue, Rgba32.FromHex("#463759"),
Rgba32.DimGray, Rgba32.FromHex("#bfbac0"),
Rgba32.DodgerBlue, Rgba32.FromHex("#77747f"),
Rgba32.Firebrick, Rgba32.FromHex("#4a475c"),
Rgba32.FloralWhite, Rgba32.FromHex("#b8bfaf"),
Rgba32.ForestGreen, Rgba32.FromHex("#b2b599"),
Rgba32.Fuchsia, Rgba32.FromHex("#979c84"),
Rgba32.Gainsboro, Rgba32.FromHex("#5d6161"),
Rgba32.GhostWhite, Rgba32.FromHex("#61ac86"),
Rgba32.Gold, Rgba32.FromHex("#a4b6a7"),
Rgba32.Goldenrod, Rgba32.FromHex("#adba98"),
Rgba32.Gray, Rgba32.FromHex("#93b778"),
Rgba32.Green, Rgba32.FromHex("#7d8c55"),
Rgba32.GreenYellow, Rgba32.FromHex("#33431e"),
Rgba32.Honeydew, Rgba32.FromHex("#7c8635"),
Rgba32.HotPink, Rgba32.FromHex("#8e9849"),
Rgba32.IndianRed, Rgba32.FromHex("#c2c190"),
Rgba32.Indigo, Rgba32.FromHex("#67765b"),
Rgba32.Ivory, Rgba32.FromHex("#ab924b"),
Rgba32.Khaki, Rgba32.FromHex("#c8c76f"),
Rgba32.Lavender, Rgba32.FromHex("#ccc050"),
Rgba32.LavenderBlush, Rgba32.FromHex("#ebdd99"),
Rgba32.LawnGreen, Rgba32.FromHex("#ab9649"),
Rgba32.LemonChiffon, Rgba32.FromHex("#dbc364"),
Rgba32.LightBlue, Rgba32.FromHex("#e6d058"),
Rgba32.LightCoral, Rgba32.FromHex("#ead665"),
Rgba32.LightCyan, Rgba32.FromHex("#d09b2c"),
Rgba32.LightGoldenrodYellow, Rgba32.FromHex("#a36629"),
Rgba32.LightGray, Rgba32.FromHex("#a77d35"),
Rgba32.LightGreen, Rgba32.FromHex("#f0d696"),
Rgba32.LightPink, Rgba32.FromHex("#d7c485"),
Rgba32.LightSalmon, Rgba32.FromHex("#f1d28c"),
Rgba32.LightSeaGreen, Rgba32.FromHex("#efcc83"),
Rgba32.LightSkyBlue, Rgba32.FromHex("#f3daa7"),
Rgba32.LightSlateGray, Rgba32.FromHex("#dfa837"),
Rgba32.LightSteelBlue, Rgba32.FromHex("#ebbc71"),
Rgba32.LightYellow, Rgba32.FromHex("#d17c3f"),
Rgba32.Lime, Rgba32.FromHex("#92462f"),
Rgba32.LimeGreen, Rgba32.FromHex("#be7249"),
Rgba32.Linen, Rgba32.FromHex("#bb603c"),
Rgba32.Magenta, Rgba32.FromHex("#c76b4a"),
Rgba32.Maroon, Rgba32.FromHex("#a75536"),
Rgba32.MediumAquamarine, Rgba32.FromHex("#b63e36"),
Rgba32.MediumBlue, Rgba32.FromHex("#b5493a"),
Rgba32.MediumOrchid, Rgba32.FromHex("#cd6d57"),
Rgba32.MediumPurple, Rgba32.FromHex("#711518"),
Rgba32.MediumSeaGreen, Rgba32.FromHex("#e9c49d"),
Rgba32.MediumSlateBlue, Rgba32.FromHex("#eedac3"),
Rgba32.MediumSpringGreen, Rgba32.FromHex("#eecfbf"),
Rgba32.MediumTurquoise, Rgba32.FromHex("#ce536b"),
Rgba32.MediumVioletRed, Rgba32.FromHex("#b74a70"),
Rgba32.MidnightBlue, Rgba32.FromHex("#b7757c"),
Rgba32.MintCream, Rgba32.FromHex("#612741"),
Rgba32.MistyRose, Rgba32.FromHex("#7a4848"),
Rgba32.Moccasin, Rgba32.FromHex("#3f3033"),
Rgba32.NavajoWhite, Rgba32.FromHex("#8d746f"),
Rgba32.Navy, Rgba32.FromHex("#4d3635"),
Rgba32.OldLace, Rgba32.FromHex("#6e3b31"),
Rgba32.Olive, Rgba32.FromHex("#864735"),
Rgba32.OliveDrab, Rgba32.FromHex("#553d3a"),
Rgba32.Orange, Rgba32.FromHex("#613936"),
Rgba32.OrangeRed, Rgba32.FromHex("#7a4b3a"),
Rgba32.Orchid, Rgba32.FromHex("#946943"),
Rgba32.PaleGoldenrod, Rgba32.FromHex("#c39e6d"),
Rgba32.PaleGreen, Rgba32.FromHex("#513e32"),
Rgba32.PaleTurquoise, Rgba32.FromHex("#8b7859"),
Rgba32.PaleVioletRed, Rgba32.FromHex("#9b856b"),
Rgba32.PapayaWhip, Rgba32.FromHex("#766051"),
Rgba32.PeachPuff, Rgba32.FromHex("#453b32")
Rgba32.Peru, };
Rgba32.Pink,
Rgba32.Plum,
Rgba32.PowderBlue,
Rgba32.Purple,
Rgba32.RebeccaPurple,
Rgba32.Red,
Rgba32.RosyBrown,
Rgba32.RoyalBlue,
Rgba32.SaddleBrown,
Rgba32.Salmon,
Rgba32.SandyBrown,
Rgba32.SeaGreen,
Rgba32.SeaShell,
Rgba32.Sienna,
Rgba32.Silver,
Rgba32.SkyBlue,
Rgba32.SlateBlue,
Rgba32.SlateGray,
Rgba32.Snow,
Rgba32.SpringGreen,
Rgba32.SteelBlue,
Rgba32.Tan,
Rgba32.Teal,
Rgba32.Thistle,
Rgba32.Tomato,
Rgba32.Transparent,
Rgba32.Turquoise,
Rgba32.Violet,
Rgba32.Wheat,
Rgba32.White,
Rgba32.WhiteSmoke,
Rgba32.Yellow,
Rgba32.YellowGreen
};
} }
} }

26
src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs

@ -14,9 +14,10 @@ namespace SixLabors.ImageSharp.PixelFormats
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Thread-safe backing field for <see cref="WebSafePalette"/>. /// Thread-safe backing field for the constant palettes.
/// </summary> /// </summary>
private static readonly Lazy<TPixel[]> WebSafePaletteLazy = new Lazy<TPixel[]>(GetWebSafePalette, true); private static readonly Lazy<TPixel[]> WebSafePaletteLazy = new Lazy<TPixel[]>(GetWebSafePalette, true);
private static readonly Lazy<TPixel[]> WernerPaletteLazy = new Lazy<TPixel[]>(GetWernerPalette, true);
/// <summary> /// <summary>
/// Represents a <see paramref="TPixel"/> matching the W3C definition that has an hex value of #F0F8FF. /// Represents a <see paramref="TPixel"/> matching the W3C definition that has an hex value of #F0F8FF.
@ -729,18 +730,27 @@ namespace SixLabors.ImageSharp.PixelFormats
public static readonly TPixel YellowGreen = ColorBuilder<TPixel>.FromRGBA(154, 205, 50, 255); public static readonly TPixel YellowGreen = ColorBuilder<TPixel>.FromRGBA(154, 205, 50, 255);
/// <summary> /// <summary>
/// Gets a <see cref="T:TPixel[]"/> matching the W3C definition of web safe colors. /// Gets a <see cref="T:TPixel[]"/> collection of web safe, colors as defined in the CSS Color Module Level 4.
/// </summary> /// </summary>
public static TPixel[] WebSafePalette => WebSafePaletteLazy.Value; public static TPixel[] WebSafePalette => WebSafePaletteLazy.Value;
private static TPixel[] GetWebSafePalette() /// <summary>
/// Gets a <see cref="T:TPixel[]"/> 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 TPixel[] WernerPalette => WernerPaletteLazy.Value;
private static TPixel[] GetWebSafePalette() => GetPalette(ColorConstants.WebSafeColors);
private static TPixel[] GetWernerPalette() => GetPalette(ColorConstants.WernerColors);
private static TPixel[] GetPalette(Rgba32[] palette)
{ {
Rgba32[] constants = ColorConstants.WebSafeColors; var converted = new TPixel[palette.Length + 1];
var safe = new TPixel[constants.Length + 1];
Span<byte> constantsBytes = MemoryMarshal.Cast<Rgba32, byte>(constants.AsSpan()); Span<byte> constantsBytes = MemoryMarshal.Cast<Rgba32, byte>(palette.AsSpan());
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(constantsBytes, safe, constants.Length); PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(constantsBytes, converted, palette.Length);
return safe; return converted;
} }
} }
} }

13
src/ImageSharp/Processing/KnownQuantizers.cs

@ -23,9 +23,16 @@ namespace SixLabors.ImageSharp.Processing
public static IQuantizer Wu { get; } = new WuQuantizer(); public static IQuantizer Wu { get; } = new WuQuantizer();
/// <summary> /// <summary>
/// Gets the palette based, Using the collection of web-safe colors. /// Gets the palette based quantizer consisting of web safe colors as defined in the CSS Color Module Level 4.
/// The quantizer supports multiple alpha values. /// The quantizer supports a single alpha value.
/// </summary>
public static IQuantizer WebSafe { get; } = new WebSafePaletteQuantizer();
/// <summary>
/// Gets the palette based quantizer consisting 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"/>
/// The quantizer supports a single alpha value.
/// </summary> /// </summary>
public static IQuantizer Palette { get; } = new PaletteQuantizer(); public static IQuantizer Werner { get; } = new WernerPaletteQuantizer();
} }
} }

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

@ -15,11 +15,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
public class OctreeQuantizer : IQuantizer public class OctreeQuantizer : IQuantizer
{ {
/// <summary>
/// The default maximum number of colors to use when quantizing the image.
/// </summary>
public const int DefaultMaxColors = 256;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class. /// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary> /// </summary>
@ -42,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param> /// <param name="dither">Whether to apply dithering to the output image</param>
public OctreeQuantizer(bool dither) public OctreeQuantizer(bool dither)
: this(GetDiffuser(dither), DefaultMaxColors) : this(GetDiffuser(dither), QuantizerConstants.MaxColors)
{ {
} }
@ -51,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param> /// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
public OctreeQuantizer(IErrorDiffuser diffuser) public OctreeQuantizer(IErrorDiffuser diffuser)
: this(diffuser, DefaultMaxColors) : this(diffuser, QuantizerConstants.MaxColors)
{ {
} }
@ -63,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public OctreeQuantizer(IErrorDiffuser diffuser, int maxColors) public OctreeQuantizer(IErrorDiffuser diffuser, int maxColors)
{ {
this.Diffuser = diffuser; this.Diffuser = diffuser;
this.MaxColors = maxColors.Clamp(1, DefaultMaxColors); this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -83,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(int maxColors) public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(int maxColors)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
maxColors = maxColors.Clamp(1, DefaultMaxColors); maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors);
return new OctreeFrameQuantizer<TPixel>(this, maxColors); return new OctreeFrameQuantizer<TPixel>(this, maxColors);
} }

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

@ -33,12 +33,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
/// <param name="quantizer">The palette quantizer.</param> /// <param name="quantizer">The palette quantizer.</param>
/// <param name="colors">An array of all colors in the palette.</param> /// <param name="colors">An array of all colors in the palette.</param>
public PaletteFrameQuantizer(PaletteQuantizer quantizer, TPixel[] colors) public PaletteFrameQuantizer(IQuantizer quantizer, TPixel[] colors)
: base(quantizer, true) : base(quantizer, true)
{ {
// TODO: Why is this value constrained? Gif has limitations but theoretically
// we might want to reduce the palette of an image to greater than that limitation.
Guard.MustBeBetweenOrEqualTo(colors.Length, 1, 256, nameof(colors));
this.palette = colors; this.palette = colors;
this.paletteVector = new Vector4[this.palette.Length]; this.paletteVector = new Vector4[this.palette.Length];
PixelOperations<TPixel>.Instance.ToScaledVector4(this.palette, this.paletteVector, this.palette.Length); PixelOperations<TPixel>.Instance.ToScaledVector4(this.palette, this.paletteVector, this.palette.Length);

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

@ -8,18 +8,18 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
/// <summary> /// <summary>
/// Allows the quantization of images pixels using web safe colors defined in the CSS Color Module Level 4. /// Allows the quantization of images pixels using color palettes.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/> Override this class to provide your own palette. /// Override this class to provide your own palette.
/// <para> /// <para>
/// By default the quantizer uses <see cref="KnownDiffusers.FloydSteinberg"/> dithering and the <see cref="NamedColors{TPixel}.WebSafePalette"/> /// By default the quantizer uses <see cref="KnownDiffusers.FloydSteinberg"/> dithering.
/// </para> /// </para>
/// </summary> /// </summary>
public class PaletteQuantizer : IQuantizer public abstract class PaletteQuantizer : IQuantizer
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class. /// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary> /// </summary>
public PaletteQuantizer() protected PaletteQuantizer()
: this(true) : this(true)
{ {
} }
@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class. /// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary> /// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param> /// <param name="dither">Whether to apply dithering to the output image</param>
public PaletteQuantizer(bool dither) protected PaletteQuantizer(bool dither)
: this(GetDiffuser(dither)) : this(GetDiffuser(dither))
{ {
} }
@ -37,41 +37,39 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class. /// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary> /// </summary>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param> /// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
public PaletteQuantizer(IErrorDiffuser diffuser) => this.Diffuser = diffuser; protected PaletteQuantizer(IErrorDiffuser diffuser) => this.Diffuser = diffuser;
/// <inheritdoc /> /// <inheritdoc />
public IErrorDiffuser Diffuser { get; } public IErrorDiffuser Diffuser { get; }
/// <inheritdoc /> /// <inheritdoc />
public virtual IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>() public abstract IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>;
=> this.CreateFrameQuantizer(() => NamedColors<TPixel>.WebSafePalette);
/// <inheritdoc/> /// <inheritdoc/>
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(int maxColors) public abstract IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(int maxColors)
where TPixel : struct, IPixel<TPixel>;
/// <summary>
/// Creates the generic frame quantizer.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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>(TPixel[] palette, int maxColors)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
TPixel[] websafe = NamedColors<TPixel>.WebSafePalette; int max = Math.Min(QuantizerConstants.MaxColors, Math.Min(maxColors, palette.Length));
int max = Math.Min(maxColors, websafe.Length);
if (max != websafe.Length) if (max != palette.Length)
{ {
return this.CreateFrameQuantizer(() => NamedColors<TPixel>.WebSafePalette.AsSpan(0, max).ToArray()); return new PaletteFrameQuantizer<TPixel>(this, palette.AsSpan(0, max).ToArray());
} }
return this.CreateFrameQuantizer(() => websafe); return new PaletteFrameQuantizer<TPixel>(this, palette);
} }
/// <summary>
/// Gets the palette to use to quantize the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="paletteFunction">The method to return the palette.</param>
/// <returns>The <see cref="IFrameQuantizer{TPixel}"/></returns>
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Func<TPixel[]> paletteFunction)
where TPixel : struct, IPixel<TPixel>
=> new PaletteFrameQuantizer<TPixel>(this, paletteFunction.Invoke());
private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null;
} }
} }

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

@ -0,0 +1,92 @@
// 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>
{
private readonly TPixel[] palette;
/// <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; }
/// <inheritdoc/>
public IFrameQuantizer<TPixel1> CreateFrameQuantizer<TPixel1>()
where TPixel1 : struct, IPixel<TPixel1>
{
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/>
public IFrameQuantizer<TPixel1> CreateFrameQuantizer<TPixel1>(int maxColors)
where TPixel1 : struct, IPixel<TPixel1>
{
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;
}
}

21
src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// Contains color quantization specific constants.
/// </summary>
internal static class QuantizerConstants
{
/// <summary>
/// The minimum number of colors to use when quantizing an image.
/// </summary>
public const int MinColors = 1;
/// <summary>
/// The maximum number of colors to use when quantizing an image.
/// </summary>
public const int MaxColors = 256;
}
}

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

@ -0,0 +1,47 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// A palette quantizer consisting of web safe colors as defined in the CSS Color Module Level 4.
/// </summary>
public class WebSafePaletteQuantizer : PaletteQuantizer
{
/// <summary>
/// Initializes a new instance of the <see cref="WebSafePaletteQuantizer" /> class.
/// </summary>
public WebSafePaletteQuantizer()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WebSafePaletteQuantizer" /> class.
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
public WebSafePaletteQuantizer(bool dither)
: base(dither)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WebSafePaletteQuantizer" /> class.
/// </summary>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
public WebSafePaletteQuantizer(IErrorDiffuser diffuser)
: base(diffuser)
{
}
/// <inheritdoc />
public override IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>()
=> this.CreateFrameQuantizer<TPixel>(NamedColors<TPixel>.WebSafePalette.Length);
/// <inheritdoc/>
public override IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(int maxColors)
=> this.CreateFrameQuantizer(NamedColors<TPixel>.WebSafePalette, maxColors);
}
}

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

@ -0,0 +1,48 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// A palette quantizer consisting 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 class WernerPaletteQuantizer : PaletteQuantizer
{
/// <summary>
/// Initializes a new instance of the <see cref="WernerPaletteQuantizer" /> class.
/// </summary>
public WernerPaletteQuantizer()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WernerPaletteQuantizer" /> class.
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
public WernerPaletteQuantizer(bool dither)
: base(dither)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WernerPaletteQuantizer" /> class.
/// </summary>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
public WernerPaletteQuantizer(IErrorDiffuser diffuser)
: base(diffuser)
{
}
/// <inheritdoc />
public override IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>()
=> this.CreateFrameQuantizer<TPixel>(NamedColors<TPixel>.WernerPalette.Length);
/// <inheritdoc/>
public override IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(int maxColors)
=> this.CreateFrameQuantizer(NamedColors<TPixel>.WernerPalette, maxColors);
}
}

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

@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
public class WuQuantizer : IQuantizer public class WuQuantizer : IQuantizer
{ {
/// <summary>
/// The default maximum number of colors to use when quantizing the image.
/// </summary>
public const int DefaultMaxColors = 256;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class. /// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// </summary> /// </summary>
@ -41,7 +36,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param> /// <param name="dither">Whether to apply dithering to the output image</param>
public WuQuantizer(bool dither) public WuQuantizer(bool dither)
: this(GetDiffuser(dither), DefaultMaxColors) : this(GetDiffuser(dither), QuantizerConstants.MaxColors)
{ {
} }
@ -50,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param> /// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
public WuQuantizer(IErrorDiffuser diffuser) public WuQuantizer(IErrorDiffuser diffuser)
: this(diffuser, DefaultMaxColors) : this(diffuser, QuantizerConstants.MaxColors)
{ {
} }
@ -62,7 +57,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public WuQuantizer(IErrorDiffuser diffuser, int maxColors) public WuQuantizer(IErrorDiffuser diffuser, int maxColors)
{ {
this.Diffuser = diffuser; this.Diffuser = diffuser;
this.MaxColors = maxColors.Clamp(1, DefaultMaxColors); this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -82,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(int maxColors) public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(int maxColors)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
maxColors = maxColors.Clamp(1, DefaultMaxColors); maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors);
return new WuFrameQuantizer<TPixel>(this, maxColors); return new WuFrameQuantizer<TPixel>(this, maxColors);
} }

2
tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs

@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void GifCore() public void GifCore()
{ {
// Try to get as close to System.Drawing's output as possible // Try to get as close to System.Drawing's output as possible
var options = new GifEncoder { Quantizer = new PaletteQuantizer(false) }; var options = new GifEncoder { Quantizer = new WebSafePaletteQuantizer(false) };
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
this.bmpCore.SaveAsGif(memoryStream, options); this.bmpCore.SaveAsGif(memoryStream, options);

2
tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
this.ForEachImageSharpImage((img, ms) => this.ForEachImageSharpImage((img, ms) =>
{ {
// Try to get as close to System.Drawing's output as possible // Try to get as close to System.Drawing's output as possible
var options = new GifEncoder { Quantizer = new PaletteQuantizer(false) }; var options = new GifEncoder { Quantizer = new WebSafePaletteQuantizer(false) };
img.Save(ms, options); return null; img.Save(ms, options); return null;
}); });
} }

4
tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
var options = new PngEncoder { Quantizer = KnownQuantizers.Palette }; var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
} }
@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
var options = new PngEncoder { Quantizer = new PaletteQuantizer(false) }; var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(false) };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
} }

3
tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs

@ -67,7 +67,8 @@ namespace SixLabors.ImageSharp.Tests
new TheoryData<string> new TheoryData<string>
{ {
nameof(KnownQuantizers.Octree), nameof(KnownQuantizers.Octree),
nameof(KnownQuantizers.Palette), nameof(KnownQuantizers.WebSafe),
nameof(KnownQuantizers.Werner),
nameof(KnownQuantizers.Wu) nameof(KnownQuantizers.Wu)
}; };

2
tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
{ {
// Use the palette quantizer without dithering to ensure results // Use the palette quantizer without dithering to ensure results
// are consistant // are consistant
Quantizer = new PaletteQuantizer(false) Quantizer = new WebSafePaletteQuantizer(false)
}; };
// Always save as we need to compare the encoded output. // Always save as we need to compare the encoded output.

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

@ -13,15 +13,18 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void QuantizersDitherByDefault() public void QuantizersDitherByDefault()
{ {
var palette = new PaletteQuantizer(); var werner = new WernerPaletteQuantizer();
var websafe = new WebSafePaletteQuantizer();
var octree = new OctreeQuantizer(); var octree = new OctreeQuantizer();
var wu = new WuQuantizer(); var wu = new WuQuantizer();
Assert.NotNull(palette.Diffuser); Assert.NotNull(werner.Diffuser);
Assert.NotNull(websafe.Diffuser);
Assert.NotNull(octree.Diffuser); Assert.NotNull(octree.Diffuser);
Assert.NotNull(wu.Diffuser); Assert.NotNull(wu.Diffuser);
Assert.True(palette.CreateFrameQuantizer<Rgba32>().Dither); Assert.True(werner.CreateFrameQuantizer<Rgba32>().Dither);
Assert.True(websafe.CreateFrameQuantizer<Rgba32>().Dither);
Assert.True(octree.CreateFrameQuantizer<Rgba32>().Dither); Assert.True(octree.CreateFrameQuantizer<Rgba32>().Dither);
Assert.True(wu.CreateFrameQuantizer<Rgba32>().Dither); Assert.True(wu.CreateFrameQuantizer<Rgba32>().Dither);
} }
@ -36,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests
{ {
Assert.True(image[0, 0].Equals(default(TPixel))); Assert.True(image[0, 0].Equals(default(TPixel)));
var quantizer = new PaletteQuantizer(dither); var quantizer = new WebSafePaletteQuantizer(dither);
foreach (ImageFrame<TPixel> frame in image.Frames) foreach (ImageFrame<TPixel> frame in image.Frames)
{ {

Loading…
Cancel
Save