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] png (Need updating for saving indexed support)
- [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)
- [x] Color - Float based, premultiplied alpha, No limit to r, g, b, a values allowing for a fuller color range.
- [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>
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/>
public string Extension => "gif";
@ -39,7 +42,7 @@ namespace ImageProcessorCore.Formats
extension = extension.StartsWith(".") ? extension.Substring(1) : extension;
return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase);
}
/// <inheritdoc/>
public void Encode(ImageBase imageBase, Stream stream)
{
@ -65,7 +68,7 @@ namespace ImageProcessorCore.Formats
this.WriteGlobalLogicalScreenDescriptor(image, stream, bitDepth);
QuantizedImage quantized = this.WriteColorTable(imageBase, stream, quality, bitDepth);
this.WriteGraphicalControlExtension(imageBase, stream);
this.WriteGraphicalControlExtension(imageBase, stream, quantized.TransparentIndex);
this.WriteImageDescriptor(quantized, quality, stream);
if (image.Frames.Any())
@ -73,7 +76,7 @@ namespace ImageProcessorCore.Formats
this.WriteApplicationExtension(stream, image.RepeatCount, image.Frames.Count);
foreach (ImageFrame frame in image.Frames)
{
this.WriteGraphicalControlExtension(frame, stream);
this.WriteGraphicalControlExtension(frame, stream, quantized.TransparentIndex);
this.WriteFrameImageDescriptor(frame, stream);
}
}
@ -128,8 +131,7 @@ namespace ImageProcessorCore.Formats
private QuantizedImage WriteColorTable(ImageBase image, Stream stream, int quality, int bitDepth)
{
// Quantize the image returning a pallete.
IQuantizer quantizer = new OctreeQuantizer(quality.Clamp(1, 255), bitDepth);
QuantizedImage quantizedImage = quantizer.Quantize(image);
QuantizedImage quantizedImage = this.Quantizer.Quantize(image, quality.Clamp(1, 255));
// Grab the pallete and write it to the stream.
Bgra32[] pallete = quantizedImage.Palette;
@ -160,14 +162,10 @@ namespace ImageProcessorCore.Formats
/// </summary>
/// <param name="image">The <see cref="ImageBase"/> to encode.</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.
bool hasTransparent = quality > 1;
bool hasTransparent = transparencyIndex > -1;
DisposalMethod disposalMethod = hasTransparent
? DisposalMethod.RestoreToBackground
: DisposalMethod.Unspecified;
@ -176,7 +174,7 @@ namespace ImageProcessorCore.Formats
{
DisposalMethod = disposalMethod,
TransparencyFlag = hasTransparent,
TransparencyIndex = quality - 1, // Quantizer sets last index as transparent.
TransparencyIndex = transparencyIndex,
DelayTime = image.FrameDelay
};

5
src/ImageProcessorCore/Quantizers/IQuantizer.cs

@ -13,10 +13,11 @@ namespace ImageProcessorCore.Quantizers
/// <summary>
/// Quantize an image and return the resulting output pixels.
/// </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>
/// A <see cref="T:QuantizedImage"/> representing a quantized version of the image pixels.
/// </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>
/// Stores the tree
/// </summary>
private readonly Octree octree;
private Octree octree;
/// <summary>
/// Maximum allowed color depth
/// </summary>
private readonly int maxColors;
private int colors;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
/// <remarks>
/// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree,
/// the second pass quantizes a color based on the nodes in the tree.
/// <para>
/// Defaults to return a maximum of 255 colors plus transparency with 8 significant bits.
/// </para>
/// the second pass quantizes a color based on the nodes in the tree
/// </remarks>
public OctreeQuantizer()
: this(255, 8)
: base(false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// Gets or sets the transparency threshold.
/// </summary>
/// <remarks>
/// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree,
/// the second pass quantizes a color based on the nodes in the tree
/// </remarks>
/// <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)
public byte Threshold { get; set; } = 128;
/// <inheritdoc/>
public override QuantizedImage Quantize(ImageBase image, int maxColors)
{
Guard.MustBeBetweenOrEqualTo(maxColors, 1, 255, nameof(maxColors));
Guard.MustBeBetweenOrEqualTo(maxColorBits, 1, 8, nameof(maxColorBits));
this.colors = maxColors.Clamp(1, 255);
// Construct the Octree
this.octree = new Octree(maxColorBits);
if (this.octree == null)
{
// 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>
/// Process the pixel in the first pass of the algorithm
/// </summary>
@ -93,7 +83,7 @@ namespace ImageProcessorCore.Quantizers
protected override byte QuantizePixel(Bgra32 pixel)
{
// 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.
if (pixel.A > this.Threshold)
@ -113,9 +103,10 @@ namespace ImageProcessorCore.Quantizers
protected override List<Bgra32> GetPalette()
{
// 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);
this.TransparentIndex = this.colors;
return palette;
}
@ -124,13 +115,13 @@ namespace ImageProcessorCore.Quantizers
/// Returns how many bits are required to store the specified number of colors.
/// Performs a Log2() on the value.
/// </summary>
/// <param name="colors">The number of colors.</param>
/// <param name="colorCount">The number of colors.</param>
/// <returns>
/// The <see cref="int"/>
/// </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>

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

@ -5,6 +5,7 @@
namespace ImageProcessorCore.Quantizers
{
using System;
using System.Collections.Generic;
/// <summary>
@ -33,19 +34,26 @@ namespace ImageProcessorCore.Quantizers
this.singlePass = singlePass;
}
/// <summary>
/// Gets or sets the transparency index.
/// </summary>
public int TransparentIndex { get; protected set; }
/// <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
int height = imageBase.Height;
int width = imageBase.Width;
int height = image.Height;
int width = image.Width;
// Call the FirstPass function if not a single pass algorithm.
// For something like an Octree quantizer, this will run through
// all image pixels, build a data structure, and create a palette.
if (!this.singlePass)
{
this.FirstPass(imageBase, width, height);
this.FirstPass(image, width, height);
}
byte[] quantizedPixels = new byte[width * height];
@ -53,9 +61,9 @@ namespace ImageProcessorCore.Quantizers
// Get the pallete
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>

9
src/ImageProcessorCore/Quantizers/QuantizedImage.cs

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

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

@ -61,11 +61,6 @@ namespace ImageProcessorCore.Quantizers
/// </summary>
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
/// <summary>
/// Maximum allowed color depth
/// </summary>
private readonly int maxColors;
/// <summary>
/// Moment of <c>P(c)</c>.
/// </summary>
@ -105,19 +100,7 @@ namespace ImageProcessorCore.Quantizers
/// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// </summary>
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.vmr = new long[TableLength];
this.vmg = new long[TableLength];
@ -128,11 +111,11 @@ namespace ImageProcessorCore.Quantizers
}
/// <inheritdoc/>
public QuantizedImage Quantize(ImageBase image)
public QuantizedImage Quantize(ImageBase image, int maxColors)
{
Guard.NotNull(image, nameof(image));
int colorCount = this.maxColors;
int colorCount = maxColors.Clamp(1, 256);
this.Clear();
@ -153,7 +136,7 @@ namespace ImageProcessorCore.Quantizers
/// <param name="b">The blue value.</param>
/// <param name="a">The alpha value.</param>
/// <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))
+ (r << (IndexBits + IndexAlphaBits + 1))
@ -173,22 +156,22 @@ namespace ImageProcessorCore.Quantizers
/// <returns>The result.</returns>
private static double Volume(Box cube, long[] moment)
{
return moment[Ind(cube.R1, cube.G1, cube.B1, cube.A1)]
- moment[Ind(cube.R1, cube.G1, cube.B1, cube.A0)]
- moment[Ind(cube.R1, cube.G1, cube.B0, cube.A1)]
+ moment[Ind(cube.R1, cube.G1, cube.B0, cube.A0)]
- moment[Ind(cube.R1, cube.G0, cube.B1, cube.A1)]
+ moment[Ind(cube.R1, cube.G0, cube.B1, cube.A0)]
+ moment[Ind(cube.R1, cube.G0, cube.B0, cube.A1)]
- moment[Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
- moment[Ind(cube.R0, cube.G1, cube.B1, cube.A1)]
+ moment[Ind(cube.R0, cube.G1, cube.B1, cube.A0)]
+ moment[Ind(cube.R0, cube.G1, cube.B0, cube.A1)]
- moment[Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
+ moment[Ind(cube.R0, cube.G0, cube.B1, cube.A1)]
- moment[Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
- moment[Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
return moment[GetPalleteIndex(cube.R1, cube.G1, cube.B1, cube.A1)]
- moment[GetPalleteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
- moment[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A1)]
+ moment[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A0)]
- moment[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A1)]
+ moment[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A0)]
+ moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A1)]
- moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
- moment[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A1)]
+ moment[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A0)]
+ moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A1)]
- moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
+ moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A1)]
- moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
- moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
}
/// <summary>
@ -204,47 +187,47 @@ namespace ImageProcessorCore.Quantizers
{
// Red
case 0:
return -moment[Ind(cube.R0, cube.G1, cube.B1, cube.A1)]
+ moment[Ind(cube.R0, cube.G1, cube.B1, cube.A0)]
+ moment[Ind(cube.R0, cube.G1, cube.B0, cube.A1)]
- moment[Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
+ moment[Ind(cube.R0, cube.G0, cube.B1, cube.A1)]
- moment[Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
- moment[Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
return -moment[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A1)]
+ moment[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A0)]
+ moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A1)]
- moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
+ moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A1)]
- moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
- moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
// Green
case 1:
return -moment[Ind(cube.R1, cube.G0, cube.B1, cube.A1)]
+ moment[Ind(cube.R1, cube.G0, cube.B1, cube.A0)]
+ moment[Ind(cube.R1, cube.G0, cube.B0, cube.A1)]
- moment[Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
+ moment[Ind(cube.R0, cube.G0, cube.B1, cube.A1)]
- moment[Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
- moment[Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
return -moment[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A1)]
+ moment[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A0)]
+ moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A1)]
- moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
+ moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A1)]
- moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
- moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
// Blue
case 2:
return -moment[Ind(cube.R1, cube.G1, cube.B0, cube.A1)]
+ moment[Ind(cube.R1, cube.G1, cube.B0, cube.A0)]
+ moment[Ind(cube.R1, cube.G0, cube.B0, cube.A1)]
- moment[Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
+ moment[Ind(cube.R0, cube.G1, cube.B0, cube.A1)]
- moment[Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
- moment[Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
return -moment[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A1)]
+ moment[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A0)]
+ moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A1)]
- moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
+ moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A1)]
- moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
- moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
// Alpha
case 3:
return -moment[Ind(cube.R1, cube.G1, cube.B1, cube.A0)]
+ moment[Ind(cube.R1, cube.G1, cube.B0, cube.A0)]
+ moment[Ind(cube.R1, cube.G0, cube.B1, cube.A0)]
- moment[Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
+ moment[Ind(cube.R0, cube.G1, cube.B1, cube.A0)]
- moment[Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
- moment[Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
+ moment[Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
return -moment[GetPalleteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
+ moment[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A0)]
+ moment[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A0)]
- moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
+ moment[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A0)]
- moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
- moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
+ moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
default:
throw new ArgumentOutOfRangeException(nameof(direction));
@ -265,47 +248,47 @@ namespace ImageProcessorCore.Quantizers
{
// Red
case 0:
return moment[Ind(position, cube.G1, cube.B1, cube.A1)]
- moment[Ind(position, cube.G1, cube.B1, cube.A0)]
- moment[Ind(position, cube.G1, cube.B0, cube.A1)]
+ moment[Ind(position, cube.G1, cube.B0, cube.A0)]
- moment[Ind(position, cube.G0, cube.B1, cube.A1)]
+ moment[Ind(position, cube.G0, cube.B1, cube.A0)]
+ moment[Ind(position, cube.G0, cube.B0, cube.A1)]
- moment[Ind(position, cube.G0, cube.B0, cube.A0)];
return moment[GetPalleteIndex(position, cube.G1, cube.B1, cube.A1)]
- moment[GetPalleteIndex(position, cube.G1, cube.B1, cube.A0)]
- moment[GetPalleteIndex(position, cube.G1, cube.B0, cube.A1)]
+ moment[GetPalleteIndex(position, cube.G1, cube.B0, cube.A0)]
- moment[GetPalleteIndex(position, cube.G0, cube.B1, cube.A1)]
+ moment[GetPalleteIndex(position, cube.G0, cube.B1, cube.A0)]
+ moment[GetPalleteIndex(position, cube.G0, cube.B0, cube.A1)]
- moment[GetPalleteIndex(position, cube.G0, cube.B0, cube.A0)];
// Green
case 1:
return moment[Ind(cube.R1, position, cube.B1, cube.A1)]
- moment[Ind(cube.R1, position, cube.B1, cube.A0)]
- moment[Ind(cube.R1, position, cube.B0, cube.A1)]
+ moment[Ind(cube.R1, position, cube.B0, cube.A0)]
- moment[Ind(cube.R0, position, cube.B1, cube.A1)]
+ moment[Ind(cube.R0, position, cube.B1, cube.A0)]
+ moment[Ind(cube.R0, position, cube.B0, cube.A1)]
- moment[Ind(cube.R0, position, cube.B0, cube.A0)];
return moment[GetPalleteIndex(cube.R1, position, cube.B1, cube.A1)]
- moment[GetPalleteIndex(cube.R1, position, cube.B1, cube.A0)]
- moment[GetPalleteIndex(cube.R1, position, cube.B0, cube.A1)]
+ moment[GetPalleteIndex(cube.R1, position, cube.B0, cube.A0)]
- moment[GetPalleteIndex(cube.R0, position, cube.B1, cube.A1)]
+ moment[GetPalleteIndex(cube.R0, position, cube.B1, cube.A0)]
+ moment[GetPalleteIndex(cube.R0, position, cube.B0, cube.A1)]
- moment[GetPalleteIndex(cube.R0, position, cube.B0, cube.A0)];
// Blue
case 2:
return moment[Ind(cube.R1, cube.G1, position, cube.A1)]
- moment[Ind(cube.R1, cube.G1, position, cube.A0)]
- moment[Ind(cube.R1, cube.G0, position, cube.A1)]
+ moment[Ind(cube.R1, cube.G0, position, cube.A0)]
- moment[Ind(cube.R0, cube.G1, position, cube.A1)]
+ moment[Ind(cube.R0, cube.G1, position, cube.A0)]
+ moment[Ind(cube.R0, cube.G0, position, cube.A1)]
- moment[Ind(cube.R0, cube.G0, position, cube.A0)];
return moment[GetPalleteIndex(cube.R1, cube.G1, position, cube.A1)]
- moment[GetPalleteIndex(cube.R1, cube.G1, position, cube.A0)]
- moment[GetPalleteIndex(cube.R1, cube.G0, position, cube.A1)]
+ moment[GetPalleteIndex(cube.R1, cube.G0, position, cube.A0)]
- moment[GetPalleteIndex(cube.R0, cube.G1, position, cube.A1)]
+ moment[GetPalleteIndex(cube.R0, cube.G1, position, cube.A0)]
+ moment[GetPalleteIndex(cube.R0, cube.G0, position, cube.A1)]
- moment[GetPalleteIndex(cube.R0, cube.G0, position, cube.A0)];
// Alpha
case 3:
return moment[Ind(cube.R1, cube.G1, cube.B1, position)]
- moment[Ind(cube.R1, cube.G1, cube.B0, position)]
- moment[Ind(cube.R1, cube.G0, cube.B1, position)]
+ moment[Ind(cube.R1, cube.G0, cube.B0, position)]
- moment[Ind(cube.R0, cube.G1, cube.B1, position)]
+ moment[Ind(cube.R0, cube.G1, cube.B0, position)]
+ moment[Ind(cube.R0, cube.G0, cube.B1, position)]
- moment[Ind(cube.R0, cube.G0, cube.B0, position)];
return moment[GetPalleteIndex(cube.R1, cube.G1, cube.B1, position)]
- moment[GetPalleteIndex(cube.R1, cube.G1, cube.B0, position)]
- moment[GetPalleteIndex(cube.R1, cube.G0, cube.B1, position)]
+ moment[GetPalleteIndex(cube.R1, cube.G0, cube.B0, position)]
- moment[GetPalleteIndex(cube.R0, cube.G1, cube.B1, position)]
+ moment[GetPalleteIndex(cube.R0, cube.G1, cube.B0, position)]
+ moment[GetPalleteIndex(cube.R0, cube.G0, cube.B1, position)]
- moment[GetPalleteIndex(cube.R0, cube.G0, cube.B0, position)];
default:
throw new ArgumentOutOfRangeException(nameof(direction));
@ -349,7 +332,7 @@ namespace ImageProcessorCore.Quantizers
int inb = b >> (8 - IndexBits);
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.vmr[ind] += r;
@ -410,7 +393,7 @@ namespace ImageProcessorCore.Quantizers
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];
lineR += this.vmr[ind1];
@ -435,7 +418,7 @@ namespace ImageProcessorCore.Quantizers
volumeA[inv] += areaA[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.vmr[ind1] = this.vmr[ind2] + volumeR[inv];
@ -462,22 +445,22 @@ namespace ImageProcessorCore.Quantizers
double da = Volume(cube, this.vma);
double xx =
this.m2[Ind(cube.R1, cube.G1, cube.B1, cube.A1)]
- this.m2[Ind(cube.R1, cube.G1, cube.B1, cube.A0)]
- this.m2[Ind(cube.R1, cube.G1, cube.B0, cube.A1)]
+ this.m2[Ind(cube.R1, cube.G1, cube.B0, cube.A0)]
- this.m2[Ind(cube.R1, cube.G0, cube.B1, cube.A1)]
+ this.m2[Ind(cube.R1, cube.G0, cube.B1, cube.A0)]
+ this.m2[Ind(cube.R1, cube.G0, cube.B0, cube.A1)]
- this.m2[Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
- this.m2[Ind(cube.R0, cube.G1, cube.B1, cube.A1)]
+ this.m2[Ind(cube.R0, cube.G1, cube.B1, cube.A0)]
+ this.m2[Ind(cube.R0, cube.G1, cube.B0, cube.A1)]
- this.m2[Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
+ this.m2[Ind(cube.R0, cube.G0, cube.B1, cube.A1)]
- this.m2[Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
- this.m2[Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
+ this.m2[Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
this.m2[GetPalleteIndex(cube.R1, cube.G1, cube.B1, cube.A1)]
- this.m2[GetPalleteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
- this.m2[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A1)]
+ this.m2[GetPalleteIndex(cube.R1, cube.G1, cube.B0, cube.A0)]
- this.m2[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A1)]
+ this.m2[GetPalleteIndex(cube.R1, cube.G0, cube.B1, cube.A0)]
+ this.m2[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A1)]
- this.m2[GetPalleteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
- this.m2[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A1)]
+ this.m2[GetPalleteIndex(cube.R0, cube.G1, cube.B1, cube.A0)]
+ this.m2[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A1)]
- this.m2[GetPalleteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
+ this.m2[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A1)]
- this.m2[GetPalleteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
- this.m2[GetPalleteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ 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));
}
@ -660,7 +643,7 @@ namespace ImageProcessorCore.Quantizers
{
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>();
byte[] pixels = new byte[image.Width * image.Height];
int transparentIndex = 0;
// Can't make this parallel.
for (int k = 0; k < colorCount; k++)
{
this.Mark(cube[k], (byte)k);
@ -747,11 +730,19 @@ namespace ImageProcessorCore.Quantizers
byte b = (byte)(Volume(cube[k], this.vmb) / 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
{
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 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];
}
}
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))
{
Image image = new Image(stream);
IQuantizer quantizer = new WuQuantizer();
QuantizedImage quantizedImage = quantizer.Quantize(image);
IQuantizer quantizer = new OctreeQuantizer();
QuantizedImage quantizedImage = quantizer.Quantize(image, 256);
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/{Path.GetFileName(file)}"))
{

Loading…
Cancel
Save