diff --git a/README.md b/README.md
index 75b30ff22..9c904656b 100644
--- a/README.md
+++ b/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
diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs b/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs
index 0ef36ebd2..995cab207 100644
--- a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs
+++ b/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs
@@ -23,7 +23,10 @@ namespace ImageProcessorCore.Formats
/// For gifs the value ranges from 1 to 256.
public int Quality { get; set; }
- public IQuantizer Quantizer { get; set; }
+ ///
+ /// The quantizer for reducing the color count.
+ ///
+ public IQuantizer Quantizer { get; set; } = new WuQuantizer();
///
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);
}
-
+
///
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
///
/// The to encode.
/// The stream to write to.
- 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
};
diff --git a/src/ImageProcessorCore/Quantizers/IQuantizer.cs b/src/ImageProcessorCore/Quantizers/IQuantizer.cs
index 49ef056cd..68f7554da 100644
--- a/src/ImageProcessorCore/Quantizers/IQuantizer.cs
+++ b/src/ImageProcessorCore/Quantizers/IQuantizer.cs
@@ -13,10 +13,11 @@ namespace ImageProcessorCore.Quantizers
///
/// Quantize an image and return the resulting output pixels.
///
- /// The image to quantize.
+ /// The image to quantize.
+ /// The maximum number of colors to return.
///
/// A representing a quantized version of the image pixels.
///
- QuantizedImage Quantize(ImageBase imageBase);
+ QuantizedImage Quantize(ImageBase image, int maxColors);
}
}
diff --git a/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs
index c681b4308..c95665174 100644
--- a/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs
+++ b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs
@@ -17,54 +17,44 @@ namespace ImageProcessorCore.Quantizers
///
/// Stores the tree
///
- private readonly Octree octree;
+ private Octree octree;
///
/// Maximum allowed color depth
///
- private readonly int maxColors;
+ private int colors;
///
/// Initializes a new instance of the class.
///
///
/// 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.
- ///
- /// Defaults to return a maximum of 255 colors plus transparency with 8 significant bits.
- ///
+ /// the second pass quantizes a color based on the nodes in the tree
///
public OctreeQuantizer()
- : this(255, 8)
+ : base(false)
{
}
///
- /// Initializes a new instance of the class.
+ /// Gets or sets the transparency threshold.
///
- ///
- /// 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 maximum number of colors to return
- /// The number of significant bits
- public OctreeQuantizer(int maxColors, int maxColorBits)
- : base(false)
+ public byte Threshold { get; set; } = 128;
+
+ ///
+ 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);
}
- ///
- /// Gets or sets the transparency threshold.
- ///
- public byte Threshold { get; set; } = 128;
-
///
/// Process the pixel in the first pass of the algorithm
///
@@ -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 GetPalette()
{
// First off convert the Octree to maxColors colors
- List palette = this.octree.Palletize(Math.Max(this.maxColors - 1, 1));
+ List 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.
///
- /// The number of colors.
+ /// The number of colors.
///
/// The
///
- 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));
}
///
diff --git a/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
index 74b0d7131..2a330f782 100644
--- a/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
+++ b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
@@ -5,6 +5,7 @@
namespace ImageProcessorCore.Quantizers
{
+ using System;
using System.Collections.Generic;
///
@@ -33,19 +34,26 @@ namespace ImageProcessorCore.Quantizers
this.singlePass = singlePass;
}
+ ///
+ /// Gets or sets the transparency index.
+ ///
+ public int TransparentIndex { get; protected set; }
+
///
- 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 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);
}
///
diff --git a/src/ImageProcessorCore/Quantizers/QuantizedImage.cs b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs
index d0f7928ee..fdf93abd3 100644
--- a/src/ImageProcessorCore/Quantizers/QuantizedImage.cs
+++ b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs
@@ -20,7 +20,8 @@ namespace ImageProcessorCore.Quantizers
/// The image height.
/// The color palette.
/// The quantized pixels.
- public QuantizedImage(int width, int height, Bgra32[] palette, byte[] pixels)
+ /// The transparency index.
+ 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;
}
///
@@ -59,6 +61,11 @@ namespace ImageProcessorCore.Quantizers
///
public byte[] Pixels { get; }
+ ///
+ /// Gets the transparent index
+ ///
+ public int TransparentIndex { get; }
+
///
/// Converts this quantized image to a normal image.
///
diff --git a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs
index 578eddd04..aa2a88048 100644
--- a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs
+++ b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs
@@ -61,11 +61,6 @@ namespace ImageProcessorCore.Quantizers
///
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
- ///
- /// Maximum allowed color depth
- ///
- private readonly int maxColors;
-
///
/// Moment of P(c).
///
@@ -105,19 +100,7 @@ namespace ImageProcessorCore.Quantizers
/// Initializes a new instance of the class.
///
public WuQuantizer()
- : this(256)
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The maximum number of colors to return
- 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
}
///
- 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
/// The blue value.
/// The alpha value.
/// The index.
- 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
/// The result.
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)];
}
///
@@ -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 pallette = new List();
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);
}
}
}
\ No newline at end of file
diff --git a/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs b/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs
index 9950fa5d9..c5eea6ba0 100644
--- a/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs
+++ b/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)}"))
{