Browse Source

Interchangeable quantizers 🎈

Former-commit-id: d9c1d3a41b639781c0e44ac921a6de9de59321b2
Former-commit-id: 3b21b6cd0c539bf121d9f88fccf874793de71180
Former-commit-id: 4510bab7605f17c4c85d7793daaa7e24ea2dc55f
af/merge-core
James Jackson-South 10 years ago
parent
commit
36b3f2742d
  1. 3
      README.md
  2. 24
      src/ImageProcessorCore/Formats/Gif/GifEncoder.cs
  3. 5
      src/ImageProcessorCore/Quantizers/IQuantizer.cs
  4. 53
      src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs
  5. 20
      src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
  6. 9
      src/ImageProcessorCore/Quantizers/QuantizedImage.cs
  7. 241
      src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs
  8. 4
      tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs

3
README.md

@ -55,6 +55,9 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor
- [x] bmp (More bmp format saving support required, 24bit just now) - [x] bmp (More bmp format saving support required, 24bit just now)
- [x] png (Need updating for saving indexed support) - [x] png (Need updating for saving indexed support)
- [x] gif - [x] gif
- Quantizers (IQuantizer with alpha channel support)
- [x] Octree
- [x] Wu
- Basic color structs with implicit operators. Vector backed. [#260](https://github.com/JimBobSquarePants/ImageProcessor/issues/260) - Basic color structs with implicit operators. Vector backed. [#260](https://github.com/JimBobSquarePants/ImageProcessor/issues/260)
- [x] Color - Float based, premultiplied alpha, No limit to r, g, b, a values allowing for a fuller color range. - [x] Color - Float based, premultiplied alpha, No limit to r, g, b, a values allowing for a fuller color range.
- [x] BGRA32 - [x] BGRA32

24
src/ImageProcessorCore/Formats/Gif/GifEncoder.cs

@ -23,7 +23,10 @@ namespace ImageProcessorCore.Formats
/// <remarks>For gifs the value ranges from 1 to 256.</remarks> /// <remarks>For gifs the value ranges from 1 to 256.</remarks>
public int Quality { get; set; } public int Quality { get; set; }
public IQuantizer Quantizer { get; set; } /// <summary>
/// The quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; } = new WuQuantizer();
/// <inheritdoc/> /// <inheritdoc/>
public string Extension => "gif"; public string Extension => "gif";
@ -39,7 +42,7 @@ namespace ImageProcessorCore.Formats
extension = extension.StartsWith(".") ? extension.Substring(1) : extension; extension = extension.StartsWith(".") ? extension.Substring(1) : extension;
return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase); return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase);
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Encode(ImageBase imageBase, Stream stream) public void Encode(ImageBase imageBase, Stream stream)
{ {
@ -65,7 +68,7 @@ namespace ImageProcessorCore.Formats
this.WriteGlobalLogicalScreenDescriptor(image, stream, bitDepth); this.WriteGlobalLogicalScreenDescriptor(image, stream, bitDepth);
QuantizedImage quantized = this.WriteColorTable(imageBase, stream, quality, bitDepth); QuantizedImage quantized = this.WriteColorTable(imageBase, stream, quality, bitDepth);
this.WriteGraphicalControlExtension(imageBase, stream); this.WriteGraphicalControlExtension(imageBase, stream, quantized.TransparentIndex);
this.WriteImageDescriptor(quantized, quality, stream); this.WriteImageDescriptor(quantized, quality, stream);
if (image.Frames.Any()) if (image.Frames.Any())
@ -73,7 +76,7 @@ namespace ImageProcessorCore.Formats
this.WriteApplicationExtension(stream, image.RepeatCount, image.Frames.Count); this.WriteApplicationExtension(stream, image.RepeatCount, image.Frames.Count);
foreach (ImageFrame frame in image.Frames) foreach (ImageFrame frame in image.Frames)
{ {
this.WriteGraphicalControlExtension(frame, stream); this.WriteGraphicalControlExtension(frame, stream, quantized.TransparentIndex);
this.WriteFrameImageDescriptor(frame, stream); this.WriteFrameImageDescriptor(frame, stream);
} }
} }
@ -128,8 +131,7 @@ namespace ImageProcessorCore.Formats
private QuantizedImage WriteColorTable(ImageBase image, Stream stream, int quality, int bitDepth) private QuantizedImage WriteColorTable(ImageBase image, Stream stream, int quality, int bitDepth)
{ {
// Quantize the image returning a pallete. // Quantize the image returning a pallete.
IQuantizer quantizer = new OctreeQuantizer(quality.Clamp(1, 255), bitDepth); QuantizedImage quantizedImage = this.Quantizer.Quantize(image, quality.Clamp(1, 255));
QuantizedImage quantizedImage = quantizer.Quantize(image);
// Grab the pallete and write it to the stream. // Grab the pallete and write it to the stream.
Bgra32[] pallete = quantizedImage.Palette; Bgra32[] pallete = quantizedImage.Palette;
@ -160,14 +162,10 @@ namespace ImageProcessorCore.Formats
/// </summary> /// </summary>
/// <param name="image">The <see cref="ImageBase"/> to encode.</param> /// <param name="image">The <see cref="ImageBase"/> to encode.</param>
/// <param name="stream">The stream to write to.</param> /// <param name="stream">The stream to write to.</param>
private void WriteGraphicalControlExtension(ImageBase image, Stream stream) private void WriteGraphicalControlExtension(ImageBase image, Stream stream, int transparencyIndex)
{ {
// Calculate the quality.
int quality = this.Quality > 0 ? this.Quality : image.Quality;
quality = quality > 0 ? quality.Clamp(1, 256) : 256;
// TODO: Check transparency logic. // TODO: Check transparency logic.
bool hasTransparent = quality > 1; bool hasTransparent = transparencyIndex > -1;
DisposalMethod disposalMethod = hasTransparent DisposalMethod disposalMethod = hasTransparent
? DisposalMethod.RestoreToBackground ? DisposalMethod.RestoreToBackground
: DisposalMethod.Unspecified; : DisposalMethod.Unspecified;
@ -176,7 +174,7 @@ namespace ImageProcessorCore.Formats
{ {
DisposalMethod = disposalMethod, DisposalMethod = disposalMethod,
TransparencyFlag = hasTransparent, TransparencyFlag = hasTransparent,
TransparencyIndex = quality - 1, // Quantizer sets last index as transparent. TransparencyIndex = transparencyIndex,
DelayTime = image.FrameDelay DelayTime = image.FrameDelay
}; };

5
src/ImageProcessorCore/Quantizers/IQuantizer.cs

@ -13,10 +13,11 @@ namespace ImageProcessorCore.Quantizers
/// <summary> /// <summary>
/// Quantize an image and return the resulting output pixels. /// Quantize an image and return the resulting output pixels.
/// </summary> /// </summary>
/// <param name="imageBase">The image to quantize.</param> /// <param name="image">The image to quantize.</param>
/// <param name="maxColors">The maximum number of colors to return.</param>
/// <returns> /// <returns>
/// A <see cref="T:QuantizedImage"/> representing a quantized version of the image pixels. /// A <see cref="T:QuantizedImage"/> representing a quantized version of the image pixels.
/// </returns> /// </returns>
QuantizedImage Quantize(ImageBase imageBase); QuantizedImage Quantize(ImageBase image, int maxColors);
} }
} }

53
src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs

@ -17,54 +17,44 @@ namespace ImageProcessorCore.Quantizers
/// <summary> /// <summary>
/// Stores the tree /// Stores the tree
/// </summary> /// </summary>
private readonly Octree octree; private Octree octree;
/// <summary> /// <summary>
/// Maximum allowed color depth /// Maximum allowed color depth
/// </summary> /// </summary>
private readonly int maxColors; private int colors;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class. /// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, /// 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. /// the second pass quantizes a color based on the nodes in the tree
/// <para>
/// Defaults to return a maximum of 255 colors plus transparency with 8 significant bits.
/// </para>
/// </remarks> /// </remarks>
public OctreeQuantizer() public OctreeQuantizer()
: this(255, 8) : base(false)
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class. /// Gets or sets the transparency threshold.
/// </summary> /// </summary>
/// <remarks> public byte Threshold { get; set; } = 128;
/// 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 /// <inheritdoc/>
/// </remarks> public override QuantizedImage Quantize(ImageBase image, int maxColors)
/// <param name="maxColors">The maximum number of colors to return</param>
/// <param name="maxColorBits">The number of significant bits</param>
public OctreeQuantizer(int maxColors, int maxColorBits)
: base(false)
{ {
Guard.MustBeBetweenOrEqualTo(maxColors, 1, 255, nameof(maxColors)); this.colors = maxColors.Clamp(1, 255);
Guard.MustBeBetweenOrEqualTo(maxColorBits, 1, 8, nameof(maxColorBits));
// Construct the Octree if (this.octree == null)
this.octree = new Octree(maxColorBits); {
// Construct the Octree
this.octree = new Octree(this.GetBitsNeededForColorDepth(maxColors));
}
this.maxColors = maxColors; return base.Quantize(image, maxColors);
} }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 128;
/// <summary> /// <summary>
/// Process the pixel in the first pass of the algorithm /// Process the pixel in the first pass of the algorithm
/// </summary> /// </summary>
@ -93,7 +83,7 @@ namespace ImageProcessorCore.Quantizers
protected override byte QuantizePixel(Bgra32 pixel) protected override byte QuantizePixel(Bgra32 pixel)
{ {
// The color at [maxColors] is set to transparent // The color at [maxColors] is set to transparent
byte paletteIndex = (byte)this.maxColors; byte paletteIndex = (byte)this.colors;
// Get the palette index if it's transparency meets criterea. // Get the palette index if it's transparency meets criterea.
if (pixel.A > this.Threshold) if (pixel.A > this.Threshold)
@ -113,9 +103,10 @@ namespace ImageProcessorCore.Quantizers
protected override List<Bgra32> GetPalette() protected override List<Bgra32> GetPalette()
{ {
// First off convert the Octree to maxColors colors // First off convert the Octree to maxColors colors
List<Bgra32> palette = this.octree.Palletize(Math.Max(this.maxColors - 1, 1)); List<Bgra32> palette = this.octree.Palletize(Math.Max(this.colors, 1));
palette.Add(Bgra32.Empty); palette.Add(Bgra32.Empty);
this.TransparentIndex = this.colors;
return palette; return palette;
} }
@ -124,13 +115,13 @@ namespace ImageProcessorCore.Quantizers
/// Returns how many bits are required to store the specified number of colors. /// Returns how many bits are required to store the specified number of colors.
/// Performs a Log2() on the value. /// Performs a Log2() on the value.
/// </summary> /// </summary>
/// <param name="colors">The number of colors.</param> /// <param name="colorCount">The number of colors.</param>
/// <returns> /// <returns>
/// The <see cref="int"/> /// The <see cref="int"/>
/// </returns> /// </returns>
private int GetBitsNeededForColorDepth(int colors) private int GetBitsNeededForColorDepth(int colorCount)
{ {
return (int)Math.Ceiling(Math.Log(colors, 2)); return (int)Math.Ceiling(Math.Log(colorCount, 2));
} }
/// <summary> /// <summary>

20
src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs

@ -5,6 +5,7 @@
namespace ImageProcessorCore.Quantizers namespace ImageProcessorCore.Quantizers
{ {
using System;
using System.Collections.Generic; using System.Collections.Generic;
/// <summary> /// <summary>
@ -33,19 +34,26 @@ namespace ImageProcessorCore.Quantizers
this.singlePass = singlePass; this.singlePass = singlePass;
} }
/// <summary>
/// Gets or sets the transparency index.
/// </summary>
public int TransparentIndex { get; protected set; }
/// <inheritdoc/> /// <inheritdoc/>
public QuantizedImage Quantize(ImageBase imageBase) public virtual QuantizedImage Quantize(ImageBase image, int maxColors)
{ {
Guard.NotNull(image, nameof(image));
// Get the size of the source image // Get the size of the source image
int height = imageBase.Height; int height = image.Height;
int width = imageBase.Width; int width = image.Width;
// Call the FirstPass function if not a single pass algorithm. // Call the FirstPass function if not a single pass algorithm.
// For something like an Octree quantizer, this will run through // For something like an Octree quantizer, this will run through
// all image pixels, build a data structure, and create a palette. // all image pixels, build a data structure, and create a palette.
if (!this.singlePass) if (!this.singlePass)
{ {
this.FirstPass(imageBase, width, height); this.FirstPass(image, width, height);
} }
byte[] quantizedPixels = new byte[width * height]; byte[] quantizedPixels = new byte[width * height];
@ -53,9 +61,9 @@ namespace ImageProcessorCore.Quantizers
// Get the pallete // Get the pallete
List<Bgra32> palette = this.GetPalette(); List<Bgra32> palette = this.GetPalette();
this.SecondPass(imageBase, quantizedPixels, width, height); this.SecondPass(image, quantizedPixels, width, height);
return new QuantizedImage(width, height, palette.ToArray(), quantizedPixels); return new QuantizedImage(width, height, palette.ToArray(), quantizedPixels, this.TransparentIndex);
} }
/// <summary> /// <summary>

9
src/ImageProcessorCore/Quantizers/QuantizedImage.cs

@ -20,7 +20,8 @@ namespace ImageProcessorCore.Quantizers
/// <param name="height">The image height.</param> /// <param name="height">The image height.</param>
/// <param name="palette">The color palette.</param> /// <param name="palette">The color palette.</param>
/// <param name="pixels">The quantized pixels.</param> /// <param name="pixels">The quantized pixels.</param>
public QuantizedImage(int width, int height, Bgra32[] palette, byte[] pixels) /// <param name="transparentIndex">The transparency index.</param>
public QuantizedImage(int width, int height, Bgra32[] palette, byte[] pixels, int transparentIndex = -1)
{ {
Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height)); Guard.MustBeGreaterThan(height, 0, nameof(height));
@ -37,6 +38,7 @@ namespace ImageProcessorCore.Quantizers
this.Height = height; this.Height = height;
this.Palette = palette; this.Palette = palette;
this.Pixels = pixels; this.Pixels = pixels;
this.TransparentIndex = transparentIndex;
} }
/// <summary> /// <summary>
@ -59,6 +61,11 @@ namespace ImageProcessorCore.Quantizers
/// </summary> /// </summary>
public byte[] Pixels { get; } public byte[] Pixels { get; }
/// <summary>
/// Gets the transparent index
/// </summary>
public int TransparentIndex { get; }
/// <summary> /// <summary>
/// Converts this quantized image to a normal image. /// Converts this quantized image to a normal image.
/// </summary> /// </summary>

241
src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs

@ -61,11 +61,6 @@ namespace ImageProcessorCore.Quantizers
/// </summary> /// </summary>
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
/// <summary>
/// Maximum allowed color depth
/// </summary>
private readonly int maxColors;
/// <summary> /// <summary>
/// Moment of <c>P(c)</c>. /// Moment of <c>P(c)</c>.
/// </summary> /// </summary>
@ -105,19 +100,7 @@ namespace ImageProcessorCore.Quantizers
/// Initializes a new instance of the <see cref="WuQuantizer"/> class. /// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// </summary> /// </summary>
public WuQuantizer() public WuQuantizer()
: this(256)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// </summary>
/// <param name="maxColors">The maximum number of colors to return</param>
public WuQuantizer(int maxColors)
{ {
Guard.MustBeBetweenOrEqualTo(maxColors, 1, 256, nameof(maxColors));
this.maxColors = maxColors;
this.vwt = new long[TableLength]; this.vwt = new long[TableLength];
this.vmr = new long[TableLength]; this.vmr = new long[TableLength];
this.vmg = new long[TableLength]; this.vmg = new long[TableLength];
@ -128,11 +111,11 @@ namespace ImageProcessorCore.Quantizers
} }
/// <inheritdoc/> /// <inheritdoc/>
public QuantizedImage Quantize(ImageBase image) public QuantizedImage Quantize(ImageBase image, int maxColors)
{ {
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
int colorCount = this.maxColors; int colorCount = maxColors.Clamp(1, 256);
this.Clear(); this.Clear();
@ -153,7 +136,7 @@ namespace ImageProcessorCore.Quantizers
/// <param name="b">The blue value.</param> /// <param name="b">The blue value.</param>
/// <param name="a">The alpha value.</param> /// <param name="a">The alpha value.</param>
/// <returns>The index.</returns> /// <returns>The index.</returns>
private static int Ind(int r, int g, int b, int a) private static int GetPalleteIndex(int r, int g, int b, int a)
{ {
return (r << ((IndexBits * 2) + IndexAlphaBits)) return (r << ((IndexBits * 2) + IndexAlphaBits))
+ (r << (IndexBits + IndexAlphaBits + 1)) + (r << (IndexBits + IndexAlphaBits + 1))
@ -173,22 +156,22 @@ namespace ImageProcessorCore.Quantizers
/// <returns>The result.</returns> /// <returns>The result.</returns>
private static double Volume(Box cube, long[] moment) private static double Volume(Box cube, long[] moment)
{ {
return moment[Ind(cube.R1, cube.G1, cube.B1, cube.A1)] return moment[GetPalleteIndex(cube.R1, cube.G1, cube.B1, cube.A1)]
- moment[Ind(cube.R1, cube.G1, cube.B1, cube.A0)] - moment[GetPalleteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
- moment[Ind(cube.R1, cube.G1, cube.B0, cube.A1)] - moment[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A1)]
+ moment[Ind(cube.R1, cube.G1, cube.B0, cube.A0)] + moment[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A0)]
- moment[Ind(cube.R1, cube.G0, cube.B1, cube.A1)] - moment[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A1)]
+ moment[Ind(cube.R1, cube.G0, cube.B1, cube.A0)] + moment[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A0)]
+ moment[Ind(cube.R1, cube.G0, cube.B0, cube.A1)] + moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A1)]
- moment[Ind(cube.R1, cube.G0, cube.B0, cube.A0)] - moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
- moment[Ind(cube.R0, cube.G1, cube.B1, cube.A1)] - moment[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A1)]
+ moment[Ind(cube.R0, cube.G1, cube.B1, cube.A0)] + moment[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A0)]
+ moment[Ind(cube.R0, cube.G1, cube.B0, cube.A1)] + moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A1)]
- moment[Ind(cube.R0, cube.G1, cube.B0, cube.A0)] - moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
+ moment[Ind(cube.R0, cube.G0, cube.B1, cube.A1)] + moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A1)]
- moment[Ind(cube.R0, cube.G0, cube.B1, cube.A0)] - moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
- moment[Ind(cube.R0, cube.G0, cube.B0, cube.A1)] - moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
} }
/// <summary> /// <summary>
@ -204,47 +187,47 @@ namespace ImageProcessorCore.Quantizers
{ {
// Red // Red
case 0: case 0:
return -moment[Ind(cube.R0, cube.G1, cube.B1, cube.A1)] return -moment[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A1)]
+ moment[Ind(cube.R0, cube.G1, cube.B1, cube.A0)] + moment[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A0)]
+ moment[Ind(cube.R0, cube.G1, cube.B0, cube.A1)] + moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A1)]
- moment[Ind(cube.R0, cube.G1, cube.B0, cube.A0)] - moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
+ moment[Ind(cube.R0, cube.G0, cube.B1, cube.A1)] + moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A1)]
- moment[Ind(cube.R0, cube.G0, cube.B1, cube.A0)] - moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
- moment[Ind(cube.R0, cube.G0, cube.B0, cube.A1)] - moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
// Green // Green
case 1: case 1:
return -moment[Ind(cube.R1, cube.G0, cube.B1, cube.A1)] return -moment[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A1)]
+ moment[Ind(cube.R1, cube.G0, cube.B1, cube.A0)] + moment[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A0)]
+ moment[Ind(cube.R1, cube.G0, cube.B0, cube.A1)] + moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A1)]
- moment[Ind(cube.R1, cube.G0, cube.B0, cube.A0)] - moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
+ moment[Ind(cube.R0, cube.G0, cube.B1, cube.A1)] + moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A1)]
- moment[Ind(cube.R0, cube.G0, cube.B1, cube.A0)] - moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
- moment[Ind(cube.R0, cube.G0, cube.B0, cube.A1)] - moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
// Blue // Blue
case 2: case 2:
return -moment[Ind(cube.R1, cube.G1, cube.B0, cube.A1)] return -moment[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A1)]
+ moment[Ind(cube.R1, cube.G1, cube.B0, cube.A0)] + moment[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A0)]
+ moment[Ind(cube.R1, cube.G0, cube.B0, cube.A1)] + moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A1)]
- moment[Ind(cube.R1, cube.G0, cube.B0, cube.A0)] - moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
+ moment[Ind(cube.R0, cube.G1, cube.B0, cube.A1)] + moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A1)]
- moment[Ind(cube.R0, cube.G1, cube.B0, cube.A0)] - moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
- moment[Ind(cube.R0, cube.G0, cube.B0, cube.A1)] - moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
// Alpha // Alpha
case 3: case 3:
return -moment[Ind(cube.R1, cube.G1, cube.B1, cube.A0)] return -moment[GetPalleteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
+ moment[Ind(cube.R1, cube.G1, cube.B0, cube.A0)] + moment[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A0)]
+ moment[Ind(cube.R1, cube.G0, cube.B1, cube.A0)] + moment[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A0)]
- moment[Ind(cube.R1, cube.G0, cube.B0, cube.A0)] - moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
+ moment[Ind(cube.R0, cube.G1, cube.B1, cube.A0)] + moment[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A0)]
- moment[Ind(cube.R0, cube.G1, cube.B0, cube.A0)] - moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
- moment[Ind(cube.R0, cube.G0, cube.B1, cube.A0)] - moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
+ moment[Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
default: default:
throw new ArgumentOutOfRangeException(nameof(direction)); throw new ArgumentOutOfRangeException(nameof(direction));
@ -265,47 +248,47 @@ namespace ImageProcessorCore.Quantizers
{ {
// Red // Red
case 0: case 0:
return moment[Ind(position, cube.G1, cube.B1, cube.A1)] return moment[GetPalleteIndex(position, cube.G1, cube.B1, cube.A1)]
- moment[Ind(position, cube.G1, cube.B1, cube.A0)] - moment[GetPalleteIndex(position, cube.G1, cube.B1, cube.A0)]
- moment[Ind(position, cube.G1, cube.B0, cube.A1)] - moment[GetPalleteIndex(position, cube.G1, cube.B0, cube.A1)]
+ moment[Ind(position, cube.G1, cube.B0, cube.A0)] + moment[GetPalleteIndex(position, cube.G1, cube.B0, cube.A0)]
- moment[Ind(position, cube.G0, cube.B1, cube.A1)] - moment[GetPalleteIndex(position, cube.G0, cube.B1, cube.A1)]
+ moment[Ind(position, cube.G0, cube.B1, cube.A0)] + moment[GetPalleteIndex(position, cube.G0, cube.B1, cube.A0)]
+ moment[Ind(position, cube.G0, cube.B0, cube.A1)] + moment[GetPalleteIndex(position, cube.G0, cube.B0, cube.A1)]
- moment[Ind(position, cube.G0, cube.B0, cube.A0)]; - moment[GetPalleteIndex(position, cube.G0, cube.B0, cube.A0)];
// Green // Green
case 1: case 1:
return moment[Ind(cube.R1, position, cube.B1, cube.A1)] return moment[GetPalleteIndex(cube.R1, position, cube.B1, cube.A1)]
- moment[Ind(cube.R1, position, cube.B1, cube.A0)] - moment[GetPalleteIndex(cube.R1, position, cube.B1, cube.A0)]
- moment[Ind(cube.R1, position, cube.B0, cube.A1)] - moment[GetPalleteIndex(cube.R1, position, cube.B0, cube.A1)]
+ moment[Ind(cube.R1, position, cube.B0, cube.A0)] + moment[GetPalleteIndex(cube.R1, position, cube.B0, cube.A0)]
- moment[Ind(cube.R0, position, cube.B1, cube.A1)] - moment[GetPalleteIndex(cube.R0, position, cube.B1, cube.A1)]
+ moment[Ind(cube.R0, position, cube.B1, cube.A0)] + moment[GetPalleteIndex(cube.R0, position, cube.B1, cube.A0)]
+ moment[Ind(cube.R0, position, cube.B0, cube.A1)] + moment[GetPalleteIndex(cube.R0, position, cube.B0, cube.A1)]
- moment[Ind(cube.R0, position, cube.B0, cube.A0)]; - moment[GetPalleteIndex(cube.R0, position, cube.B0, cube.A0)];
// Blue // Blue
case 2: case 2:
return moment[Ind(cube.R1, cube.G1, position, cube.A1)] return moment[GetPalleteIndex(cube.R1, cube.G1, position, cube.A1)]
- moment[Ind(cube.R1, cube.G1, position, cube.A0)] - moment[GetPalleteIndex(cube.R1, cube.G1, position, cube.A0)]
- moment[Ind(cube.R1, cube.G0, position, cube.A1)] - moment[GetPalleteIndex(cube.R1, cube.G0, position, cube.A1)]
+ moment[Ind(cube.R1, cube.G0, position, cube.A0)] + moment[GetPalleteIndex(cube.R1, cube.G0, position, cube.A0)]
- moment[Ind(cube.R0, cube.G1, position, cube.A1)] - moment[GetPalleteIndex(cube.R0, cube.G1, position, cube.A1)]
+ moment[Ind(cube.R0, cube.G1, position, cube.A0)] + moment[GetPalleteIndex(cube.R0, cube.G1, position, cube.A0)]
+ moment[Ind(cube.R0, cube.G0, position, cube.A1)] + moment[GetPalleteIndex(cube.R0, cube.G0, position, cube.A1)]
- moment[Ind(cube.R0, cube.G0, position, cube.A0)]; - moment[GetPalleteIndex(cube.R0, cube.G0, position, cube.A0)];
// Alpha // Alpha
case 3: case 3:
return moment[Ind(cube.R1, cube.G1, cube.B1, position)] return moment[GetPalleteIndex(cube.R1, cube.G1, cube.B1, position)]
- moment[Ind(cube.R1, cube.G1, cube.B0, position)] - moment[GetPalleteIndex(cube.R1, cube.G1, cube.B0, position)]
- moment[Ind(cube.R1, cube.G0, cube.B1, position)] - moment[GetPalleteIndex(cube.R1, cube.G0, cube.B1, position)]
+ moment[Ind(cube.R1, cube.G0, cube.B0, position)] + moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, position)]
- moment[Ind(cube.R0, cube.G1, cube.B1, position)] - moment[GetPalleteIndex(cube.R0, cube.G1, cube.B1, position)]
+ moment[Ind(cube.R0, cube.G1, cube.B0, position)] + moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, position)]
+ moment[Ind(cube.R0, cube.G0, cube.B1, position)] + moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, position)]
- moment[Ind(cube.R0, cube.G0, cube.B0, position)]; - moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, position)];
default: default:
throw new ArgumentOutOfRangeException(nameof(direction)); throw new ArgumentOutOfRangeException(nameof(direction));
@ -349,7 +332,7 @@ namespace ImageProcessorCore.Quantizers
int inb = b >> (8 - IndexBits); int inb = b >> (8 - IndexBits);
int ina = a >> (8 - IndexAlphaBits); int ina = a >> (8 - IndexAlphaBits);
int ind = Ind(inr + 1, ing + 1, inb + 1, ina + 1); int ind = GetPalleteIndex(inr + 1, ing + 1, inb + 1, ina + 1);
this.vwt[ind]++; this.vwt[ind]++;
this.vmr[ind] += r; this.vmr[ind] += r;
@ -410,7 +393,7 @@ namespace ImageProcessorCore.Quantizers
for (int a = 1; a < IndexAlphaCount; a++) for (int a = 1; a < IndexAlphaCount; a++)
{ {
int ind1 = Ind(r, g, b, a); int ind1 = GetPalleteIndex(r, g, b, a);
line += this.vwt[ind1]; line += this.vwt[ind1];
lineR += this.vmr[ind1]; lineR += this.vmr[ind1];
@ -435,7 +418,7 @@ namespace ImageProcessorCore.Quantizers
volumeA[inv] += areaA[a]; volumeA[inv] += areaA[a];
volume2[inv] += area2[a]; volume2[inv] += area2[a];
int ind2 = ind1 - Ind(1, 0, 0, 0); int ind2 = ind1 - GetPalleteIndex(1, 0, 0, 0);
this.vwt[ind1] = this.vwt[ind2] + volume[inv]; this.vwt[ind1] = this.vwt[ind2] + volume[inv];
this.vmr[ind1] = this.vmr[ind2] + volumeR[inv]; this.vmr[ind1] = this.vmr[ind2] + volumeR[inv];
@ -462,22 +445,22 @@ namespace ImageProcessorCore.Quantizers
double da = Volume(cube, this.vma); double da = Volume(cube, this.vma);
double xx = double xx =
this.m2[Ind(cube.R1, cube.G1, cube.B1, cube.A1)] this.m2[GetPalleteIndex(cube.R1, cube.G1, cube.B1, cube.A1)]
- this.m2[Ind(cube.R1, cube.G1, cube.B1, cube.A0)] - this.m2[GetPalleteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
- this.m2[Ind(cube.R1, cube.G1, cube.B0, cube.A1)] - this.m2[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A1)]
+ this.m2[Ind(cube.R1, cube.G1, cube.B0, cube.A0)] + this.m2[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A0)]
- this.m2[Ind(cube.R1, cube.G0, cube.B1, cube.A1)] - this.m2[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A1)]
+ this.m2[Ind(cube.R1, cube.G0, cube.B1, cube.A0)] + this.m2[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A0)]
+ this.m2[Ind(cube.R1, cube.G0, cube.B0, cube.A1)] + this.m2[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A1)]
- this.m2[Ind(cube.R1, cube.G0, cube.B0, cube.A0)] - this.m2[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
- this.m2[Ind(cube.R0, cube.G1, cube.B1, cube.A1)] - this.m2[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A1)]
+ this.m2[Ind(cube.R0, cube.G1, cube.B1, cube.A0)] + this.m2[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A0)]
+ this.m2[Ind(cube.R0, cube.G1, cube.B0, cube.A1)] + this.m2[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A1)]
- this.m2[Ind(cube.R0, cube.G1, cube.B0, cube.A0)] - this.m2[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
+ this.m2[Ind(cube.R0, cube.G0, cube.B1, cube.A1)] + this.m2[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A1)]
- this.m2[Ind(cube.R0, cube.G0, cube.B1, cube.A0)] - this.m2[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
- this.m2[Ind(cube.R0, cube.G0, cube.B0, cube.A1)] - this.m2[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ this.m2[Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + this.m2[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / Volume(cube, this.vwt)); return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / Volume(cube, this.vwt));
} }
@ -660,7 +643,7 @@ namespace ImageProcessorCore.Quantizers
{ {
for (int a = cube.A0 + 1; a <= cube.A1; a++) for (int a = cube.A0 + 1; a <= cube.A1; a++)
{ {
this.tag[Ind(r, g, b, a)] = label; this.tag[GetPalleteIndex(r, g, b, a)] = label;
} }
} }
} }
@ -732,8 +715,8 @@ namespace ImageProcessorCore.Quantizers
{ {
List<Bgra32> pallette = new List<Bgra32>(); List<Bgra32> pallette = new List<Bgra32>();
byte[] pixels = new byte[image.Width * image.Height]; byte[] pixels = new byte[image.Width * image.Height];
int transparentIndex = 0;
// Can't make this parallel.
for (int k = 0; k < colorCount; k++) for (int k = 0; k < colorCount; k++)
{ {
this.Mark(cube[k], (byte)k); this.Mark(cube[k], (byte)k);
@ -747,11 +730,19 @@ namespace ImageProcessorCore.Quantizers
byte b = (byte)(Volume(cube[k], this.vmb) / weight); byte b = (byte)(Volume(cube[k], this.vmb) / weight);
byte a = (byte)(Volume(cube[k], this.vma) / weight); byte a = (byte)(Volume(cube[k], this.vma) / weight);
pallette.Add(new Bgra32(b, g, r, a)); var color = new Bgra32(b, g, r, a);
if (color == Bgra32.Empty)
{
transparentIndex = k;
}
pallette.Add(color);
} }
else else
{ {
pallette.Add(new Bgra32(0, 0, 0)); pallette.Add(Bgra32.Empty);
transparentIndex = k;
} }
} }
@ -767,12 +758,12 @@ namespace ImageProcessorCore.Quantizers
int g = color.G >> (8 - IndexBits); int g = color.G >> (8 - IndexBits);
int b = color.B >> (8 - IndexBits); int b = color.B >> (8 - IndexBits);
int ind = Ind(r + 1, g + 1, b + 1, a + 1); int ind = GetPalleteIndex(r + 1, g + 1, b + 1, a + 1);
pixels[i++] = this.tag[ind]; pixels[i++] = this.tag[ind];
} }
} }
return new QuantizedImage(image.Width, image.Height, pallette.ToArray(), pixels); return new QuantizedImage(image.Width, image.Height, pallette.ToArray(), pixels, transparentIndex);
} }
} }
} }

4
tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs

@ -57,8 +57,8 @@
using (FileStream stream = File.OpenRead(file)) using (FileStream stream = File.OpenRead(file))
{ {
Image image = new Image(stream); Image image = new Image(stream);
IQuantizer quantizer = new WuQuantizer(); IQuantizer quantizer = new OctreeQuantizer();
QuantizedImage quantizedImage = quantizer.Quantize(image); QuantizedImage quantizedImage = quantizer.Quantize(image, 256);
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/{Path.GetFileName(file)}")) using (FileStream output = File.OpenWrite($"TestOutput/Quantize/{Path.GetFileName(file)}"))
{ {

Loading…
Cancel
Save