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)}")) {