diff --git a/src/ImageProcessorCore/Formats/Gif/Quantizer/IQuantizer.cs b/src/ImageProcessorCore/Formats/Quantizers/IQuantizer.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/Quantizer/IQuantizer.cs rename to src/ImageProcessorCore/Formats/Quantizers/IQuantizer.cs diff --git a/src/ImageProcessorCore/Formats/Gif/Quantizer/OctreeQuantizer.cs b/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/Quantizer/OctreeQuantizer.cs rename to src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs diff --git a/src/ImageProcessorCore/Formats/Gif/Quantizer/Quantizer.cs b/src/ImageProcessorCore/Formats/Quantizers/Octree/Quantizer.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/Quantizer/Quantizer.cs rename to src/ImageProcessorCore/Formats/Quantizers/Octree/Quantizer.cs diff --git a/src/ImageProcessorCore/Formats/Gif/Quantizer/QuantizedImage.cs b/src/ImageProcessorCore/Formats/Quantizers/QuantizedImage.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/Quantizer/QuantizedImage.cs rename to src/ImageProcessorCore/Formats/Quantizers/QuantizedImage.cs diff --git a/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs b/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs new file mode 100644 index 000000000..54abb20e0 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs @@ -0,0 +1,53 @@ +namespace ImageProcessorCore.Formats +{ + /// + /// A box. + /// + internal sealed class Box + { + /// + /// Gets or sets the min red value, exclusive. + /// + public int R0 { get; set; } + + /// + /// Gets or sets the max red value, inclusive. + /// + public int R1 { get; set; } + + /// + /// Gets or sets the min green value, exclusive. + /// + public int G0 { get; set; } + + /// + /// Gets or sets the max green value, inclusive. + /// + public int G1 { get; set; } + + /// + /// Gets or sets the min blue value, exclusive. + /// + public int B0 { get; set; } + + /// + /// Gets or sets the max green value, inclusive. + /// + public int B1 { get; set; } + + /// + /// Gets or sets the min alpha value, exclusive. + /// + public int A0 { get; set; } + + /// + /// Gets or sets the max alpha value, inclusive. + /// + public int A1 { get; set; } + + /// + /// Gets or sets the volume. + /// + public int Volume { get; set; } + } +} diff --git a/src/ImageProcessorCore/Formats/Quantizers/Wu/WuAlphaColorQuantizer.cs b/src/ImageProcessorCore/Formats/Quantizers/Wu/WuAlphaColorQuantizer.cs new file mode 100644 index 000000000..2d63d76a7 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Quantizers/Wu/WuAlphaColorQuantizer.cs @@ -0,0 +1,796 @@ +// +// Copyright (c) 2014-2015 Jérémy Ansel +// +// +// Licensed under the MIT license. See LICENSE.txt +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + + /// + /// A Wu's color quantizer with alpha channel. + /// + /// + /// + /// Based on C Implementation of Xiaolin Wu's Color Quantizer (v. 2) + /// (see Graphics Gems volume II, pages 126-133) + /// (). + /// + /// + /// Algorithm: Greedy orthogonal bipartition of RGB space for variance + /// minimization aided by inclusion-exclusion tricks. + /// For speed no nearest neighbor search is done. Slightly + /// better performance can be expected by more sophisticated + /// but more expensive versions. + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Wu", Justification = "Reviewed")] + public sealed class WuAlphaColorQuantizer : IQuantizer + { + /// + /// The index bits. + /// + private const int IndexBits = 6; + + /// + /// The index alpha bits. + /// + private const int IndexAlphaBits = 3; + + /// + /// The index count. + /// + private const int IndexCount = (1 << WuAlphaColorQuantizer.IndexBits) + 1; + + /// + /// The index alpha count. + /// + private const int IndexAlphaCount = (1 << WuAlphaColorQuantizer.IndexAlphaBits) + 1; + + /// + /// The table length. + /// + private const int TableLength = WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount; + + /// + /// Moment of P(c). + /// + private long[] vwt; + + /// + /// Moment of r*P(c). + /// + private long[] vmr; + + /// + /// Moment of g*P(c). + /// + private long[] vmg; + + /// + /// Moment of b*P(c). + /// + private long[] vmb; + + /// + /// Moment of a*P(c). + /// + private long[] vma; + + /// + /// Moment of c^2*P(c). + /// + private double[] m2; + + /// + /// Color space tag. + /// + private byte[] tag; + + /// + /// Initializes a new instance of the class. + /// + public WuAlphaColorQuantizer() + { + this.vwt = new long[WuAlphaColorQuantizer.TableLength]; + this.vmr = new long[WuAlphaColorQuantizer.TableLength]; + this.vmg = new long[WuAlphaColorQuantizer.TableLength]; + this.vmb = new long[WuAlphaColorQuantizer.TableLength]; + this.vma = new long[WuAlphaColorQuantizer.TableLength]; + this.m2 = new double[WuAlphaColorQuantizer.TableLength]; + + this.tag = new byte[WuAlphaColorQuantizer.TableLength]; + } + + /// + /// Quantizes an image. + /// + /// The image (ARGB). + /// The result. + public QuantizedImage Quantize(ImageBase image) + { + return this.Quantize(image, 256); + } + + /// + /// Quantizes an image. + /// + /// The image (ARGB). + /// The color count. + /// The result. + public QuantizedImage Quantize(ImageBase image, int colorCount) + { + if (image == null) + { + throw new ArgumentNullException("image"); + } + + if (colorCount < 1 || colorCount > 256) + { + throw new ArgumentOutOfRangeException("colorCount"); + } + + this.Clear(); + + this.Hist3d(image); + this.M3d(); + + Box[] cube; + this.BuildCube(out cube, ref colorCount); + + return this.GenerateResult(image, colorCount, cube); + } + + /// + /// Gets an index. + /// + /// The red value. + /// The green value. + /// The blue value. + /// The alpha value. + /// The index. + private static int Ind(int r, int g, int b, int a) + { + return (r << ((WuAlphaColorQuantizer.IndexBits * 2) + WuAlphaColorQuantizer.IndexAlphaBits)) + + (r << (WuAlphaColorQuantizer.IndexBits + WuAlphaColorQuantizer.IndexAlphaBits + 1)) + + (g << (WuAlphaColorQuantizer.IndexBits + WuAlphaColorQuantizer.IndexAlphaBits)) + + (r << (WuAlphaColorQuantizer.IndexBits * 2)) + + (r << (WuAlphaColorQuantizer.IndexBits + 1)) + + (g << WuAlphaColorQuantizer.IndexBits) + + ((r + g + b) << WuAlphaColorQuantizer.IndexAlphaBits) + + r + g + b + a; + } + + /// + /// Computes sum over a box of any given statistic. + /// + /// The cube. + /// The moment. + /// The result. + private static double Volume(Box cube, long[] moment) + { + return moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + } + + /// + /// Computes part of Volume(cube, moment) that doesn't depend on r1, g1, or b1 (depending on direction). + /// + /// The cube. + /// The direction. + /// The moment. + /// The result. + private static long Bottom(Box cube, int direction, long[] moment) + { + switch (direction) + { + // Red + case 3: + return -moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + + // Green + case 2: + return -moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + + // Blue + case 1: + return -moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + + // Alpha + case 0: + return -moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + + default: + throw new ArgumentOutOfRangeException("direction"); + } + } + + /// + /// Computes remainder of Volume(cube, moment), substituting position for r1, g1, or b1 (depending on direction). + /// + /// The cube. + /// The direction. + /// The position. + /// The moment. + /// The result. + private static long Top(Box cube, int direction, int position, long[] moment) + { + switch (direction) + { + // Red + case 3: + return moment[WuAlphaColorQuantizer.Ind(position, cube.G1, cube.B1, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(position, cube.G1, cube.B1, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(position, cube.G1, cube.B0, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(position, cube.G1, cube.B0, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(position, cube.G0, cube.B1, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(position, cube.G0, cube.B1, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(position, cube.G0, cube.B0, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(position, cube.G0, cube.B0, cube.A0)]; + + // Green + case 2: + return moment[WuAlphaColorQuantizer.Ind(cube.R1, position, cube.B1, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, position, cube.B1, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, position, cube.B0, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R1, position, cube.B0, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, position, cube.B1, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, position, cube.B1, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, position, cube.B0, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, position, cube.B0, cube.A0)]; + + // Blue + case 1: + return moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, position, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, position, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, position, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, position, cube.A0)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, position, cube.A1)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, position, cube.A0)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, position, cube.A1)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, position, cube.A0)]; + + // Alpha + case 0: + return moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B1, position)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, position)] + - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, position)] + + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, position)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, position)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, position)] + + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, position)] + - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, position)]; + + default: + throw new ArgumentOutOfRangeException("direction"); + } + } + + /// + /// Clears the tables. + /// + private void Clear() + { + Array.Clear(this.vwt, 0, WuAlphaColorQuantizer.TableLength); + Array.Clear(this.vmr, 0, WuAlphaColorQuantizer.TableLength); + Array.Clear(this.vmg, 0, WuAlphaColorQuantizer.TableLength); + Array.Clear(this.vmb, 0, WuAlphaColorQuantizer.TableLength); + Array.Clear(this.vma, 0, WuAlphaColorQuantizer.TableLength); + Array.Clear(this.m2, 0, WuAlphaColorQuantizer.TableLength); + + Array.Clear(this.tag, 0, WuAlphaColorQuantizer.TableLength); + } + + /// + /// Builds a 3-D color histogram of counts, r/g/b, c^2. + /// + /// The image. + private void Hist3d(ImageBase image) + { + // TODO: Parallel + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + Bgra32 color = image[x, y]; + + byte r = color.R; + byte g = color.G; + byte b = color.B; + byte a = color.A; + + int inr = r >> (8 - WuAlphaColorQuantizer.IndexBits); + int ing = g >> (8 - WuAlphaColorQuantizer.IndexBits); + int inb = b >> (8 - WuAlphaColorQuantizer.IndexBits); + int ina = a >> (8 - WuAlphaColorQuantizer.IndexAlphaBits); + + int ind = WuAlphaColorQuantizer.Ind(inr + 1, ing + 1, inb + 1, ina + 1); + + this.vwt[ind]++; + this.vmr[ind] += r; + this.vmg[ind] += g; + this.vmb[ind] += b; + this.vma[ind] += a; + this.m2[ind] += (r * r) + (g * g) + (b * b) + (a * a); + } + } + } + + /// + /// Converts the histogram into moments so that we can rapidly calculate + /// the sums of the above quantities over any desired box. + /// + private void M3d() + { + long[] volume = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount]; + long[] volume_r = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount]; + long[] volume_g = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount]; + long[] volume_b = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount]; + long[] volume_a = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount]; + double[] volume2 = new double[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount]; + + long[] area = new long[WuAlphaColorQuantizer.IndexAlphaCount]; + long[] area_r = new long[WuAlphaColorQuantizer.IndexAlphaCount]; + long[] area_g = new long[WuAlphaColorQuantizer.IndexAlphaCount]; + long[] area_b = new long[WuAlphaColorQuantizer.IndexAlphaCount]; + long[] area_a = new long[WuAlphaColorQuantizer.IndexAlphaCount]; + double[] area2 = new double[WuAlphaColorQuantizer.IndexAlphaCount]; + + for (int r = 1; r < WuAlphaColorQuantizer.IndexCount; r++) + { + Array.Clear(volume, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount); + Array.Clear(volume_r, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount); + Array.Clear(volume_g, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount); + Array.Clear(volume_b, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount); + Array.Clear(volume_a, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount); + Array.Clear(volume2, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount); + + for (int g = 1; g < WuAlphaColorQuantizer.IndexCount; g++) + { + Array.Clear(area, 0, WuAlphaColorQuantizer.IndexAlphaCount); + Array.Clear(area_r, 0, WuAlphaColorQuantizer.IndexAlphaCount); + Array.Clear(area_g, 0, WuAlphaColorQuantizer.IndexAlphaCount); + Array.Clear(area_b, 0, WuAlphaColorQuantizer.IndexAlphaCount); + Array.Clear(area_a, 0, WuAlphaColorQuantizer.IndexAlphaCount); + Array.Clear(area2, 0, WuAlphaColorQuantizer.IndexAlphaCount); + + for (int b = 1; b < WuAlphaColorQuantizer.IndexCount; b++) + { + long line = 0; + long line_r = 0; + long line_g = 0; + long line_b = 0; + long line_a = 0; + double line2 = 0; + + for (int a = 1; a < WuAlphaColorQuantizer.IndexAlphaCount; a++) + { + int ind1 = WuAlphaColorQuantizer.Ind(r, g, b, a); + + line += this.vwt[ind1]; + line_r += this.vmr[ind1]; + line_g += this.vmg[ind1]; + line_b += this.vmb[ind1]; + line_a += this.vma[ind1]; + line2 += this.m2[ind1]; + + area[a] += line; + area_r[a] += line_r; + area_g[a] += line_g; + area_b[a] += line_b; + area_a[a] += line_a; + area2[a] += line2; + + int inv = (b * WuAlphaColorQuantizer.IndexAlphaCount) + a; + + volume[inv] += area[a]; + volume_r[inv] += area_r[a]; + volume_g[inv] += area_g[a]; + volume_b[inv] += area_b[a]; + volume_a[inv] += area_a[a]; + volume2[inv] += area2[a]; + + int ind2 = ind1 - WuAlphaColorQuantizer.Ind(1, 0, 0, 0); + + this.vwt[ind1] = this.vwt[ind2] + volume[inv]; + this.vmr[ind1] = this.vmr[ind2] + volume_r[inv]; + this.vmg[ind1] = this.vmg[ind2] + volume_g[inv]; + this.vmb[ind1] = this.vmb[ind2] + volume_b[inv]; + this.vma[ind1] = this.vma[ind2] + volume_a[inv]; + this.m2[ind1] = this.m2[ind2] + volume2[inv]; + } + } + } + } + } + + /// + /// Computes the weighted variance of a box. + /// + /// The cube. + /// The result. + private double Var(Box cube) + { + double dr = WuAlphaColorQuantizer.Volume(cube, this.vmr); + double dg = WuAlphaColorQuantizer.Volume(cube, this.vmg); + double db = WuAlphaColorQuantizer.Volume(cube, this.vmb); + double da = WuAlphaColorQuantizer.Volume(cube, this.vma); + + double xx = + this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A1)] + - this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)] + - this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)] + + this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)] + - this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)] + + this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)] + + this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)] + - this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] + - this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)] + + this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)] + + this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)] + - this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] + + this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)] + - this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] + - this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] + + this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + + return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / WuAlphaColorQuantizer.Volume(cube, this.vwt)); + } + + /// + /// We want to minimize the sum of the variances of two sub-boxes. + /// The sum(c^2) terms can be ignored since their sum over both sub-boxes + /// is the same (the sum for the whole box) no matter where we split. + /// The remaining terms have a minus sign in the variance formula, + /// so we drop the minus sign and maximize the sum of the two terms. + /// + /// The cube. + /// The direction. + /// The first position. + /// The last position. + /// The cutting point. + /// The whole red. + /// The whole green. + /// The whole blue. + /// The whole alpha. + /// The whole weight. + /// The result. + private double Maximize(Box cube, int direction, int first, int last, out int cut, double whole_r, double whole_g, double whole_b, double whole_a, double whole_w) + { + long base_r = WuAlphaColorQuantizer.Bottom(cube, direction, this.vmr); + long base_g = WuAlphaColorQuantizer.Bottom(cube, direction, this.vmg); + long base_b = WuAlphaColorQuantizer.Bottom(cube, direction, this.vmb); + long base_a = WuAlphaColorQuantizer.Bottom(cube, direction, this.vma); + long base_w = WuAlphaColorQuantizer.Bottom(cube, direction, this.vwt); + + double max = 0.0; + cut = -1; + + for (int i = first; i < last; i++) + { + double half_r = base_r + WuAlphaColorQuantizer.Top(cube, direction, i, this.vmr); + double half_g = base_g + WuAlphaColorQuantizer.Top(cube, direction, i, this.vmg); + double half_b = base_b + WuAlphaColorQuantizer.Top(cube, direction, i, this.vmb); + double half_a = base_a + WuAlphaColorQuantizer.Top(cube, direction, i, this.vma); + double half_w = base_w + WuAlphaColorQuantizer.Top(cube, direction, i, this.vwt); + + double temp; + + if (half_w == 0) + { + continue; + } + else + { + temp = ((half_r * half_r) + (half_g * half_g) + (half_b * half_b) + (half_a * half_a)) / half_w; + } + + half_r = whole_r - half_r; + half_g = whole_g - half_g; + half_b = whole_b - half_b; + half_a = whole_a - half_a; + half_w = whole_w - half_w; + + if (half_w == 0) + { + continue; + } + else + { + temp += ((half_r * half_r) + (half_g * half_g) + (half_b * half_b) + (half_a * half_a)) / half_w; + } + + if (temp > max) + { + max = temp; + cut = i; + } + } + + return max; + } + + /// + /// Cuts a box. + /// + /// The first set. + /// The second set. + /// Returns a value indicating whether the box has been split. + private bool Cut(Box set1, Box set2) + { + double whole_r = WuAlphaColorQuantizer.Volume(set1, this.vmr); + double whole_g = WuAlphaColorQuantizer.Volume(set1, this.vmg); + double whole_b = WuAlphaColorQuantizer.Volume(set1, this.vmb); + double whole_a = WuAlphaColorQuantizer.Volume(set1, this.vma); + double whole_w = WuAlphaColorQuantizer.Volume(set1, this.vwt); + + int cutr; + int cutg; + int cutb; + int cuta; + + double maxr = this.Maximize(set1, 3, set1.R0 + 1, set1.R1, out cutr, whole_r, whole_g, whole_b, whole_a, whole_w); + double maxg = this.Maximize(set1, 2, set1.G0 + 1, set1.G1, out cutg, whole_r, whole_g, whole_b, whole_a, whole_w); + double maxb = this.Maximize(set1, 1, set1.B0 + 1, set1.B1, out cutb, whole_r, whole_g, whole_b, whole_a, whole_w); + double maxa = this.Maximize(set1, 0, set1.A0 + 1, set1.A1, out cuta, whole_r, whole_g, whole_b, whole_a, whole_w); + + int dir; + + if ((maxr >= maxg) && (maxr >= maxb) && (maxr >= maxa)) + { + dir = 3; + + if (cutr < 0) + { + return false; + } + } + else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa)) + { + dir = 2; + } + else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa)) + { + dir = 1; + } + else + { + dir = 0; + } + + set2.R1 = set1.R1; + set2.G1 = set1.G1; + set2.B1 = set1.B1; + set2.A1 = set1.A1; + + switch (dir) + { + // Red + case 3: + set2.R0 = set1.R1 = cutr; + set2.G0 = set1.G0; + set2.B0 = set1.B0; + set2.A0 = set1.A0; + break; + + // Green + case 2: + set2.G0 = set1.G1 = cutg; + set2.R0 = set1.R0; + set2.B0 = set1.B0; + set2.A0 = set1.A0; + break; + + // Blue + case 1: + set2.B0 = set1.B1 = cutb; + set2.R0 = set1.R0; + set2.G0 = set1.G0; + set2.A0 = set1.A0; + break; + + // Alpha + case 0: + set2.A0 = set1.A1 = cuta; + set2.R0 = set1.R0; + set2.G0 = set1.G0; + set2.B0 = set1.B0; + break; + } + + set1.Volume = (set1.R1 - set1.R0) * (set1.G1 - set1.G0) * (set1.B1 - set1.B0) * (set1.A1 - set1.A0); + set2.Volume = (set2.R1 - set2.R0) * (set2.G1 - set2.G0) * (set2.B1 - set2.B0) * (set2.A1 - set2.A0); + + return true; + } + + /// + /// Marks a color space tag. + /// + /// The cube. + /// A label. + private void Mark(Box cube, byte label) + { + for (int r = cube.R0 + 1; r <= cube.R1; r++) + { + for (int g = cube.G0 + 1; g <= cube.G1; g++) + { + for (int b = cube.B0 + 1; b <= cube.B1; b++) + { + for (int a = cube.A0 + 1; a <= cube.A1; a++) + { + this.tag[WuAlphaColorQuantizer.Ind(r, g, b, a)] = label; + } + } + } + } + } + + /// + /// Builds the cube. + /// + /// The cube. + /// The color count. + private void BuildCube(out Box[] cube, ref int colorCount) + { + cube = new Box[colorCount]; + double[] vv = new double[colorCount]; + + for (int i = 0; i < colorCount; i++) + { + cube[i] = new Box(); + } + + cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0; + cube[0].R1 = cube[0].G1 = cube[0].B1 = WuAlphaColorQuantizer.IndexCount - 1; + cube[0].A1 = WuAlphaColorQuantizer.IndexAlphaCount - 1; + + int next = 0; + + for (int i = 1; i < colorCount; i++) + { + if (this.Cut(cube[next], cube[i])) + { + vv[next] = cube[next].Volume > 1 ? this.Var(cube[next]) : 0.0; + vv[i] = cube[i].Volume > 1 ? this.Var(cube[i]) : 0.0; + } + else + { + vv[next] = 0.0; + i--; + } + + next = 0; + + double temp = vv[0]; + for (int k = 1; k <= i; k++) + { + if (vv[k] > temp) + { + temp = vv[k]; + next = k; + } + } + + if (temp <= 0.0) + { + colorCount = i + 1; + break; + } + } + } + + /// + /// Generates the quantized result. + /// + /// The image. + /// The color count. + /// The cube. + /// The result. + private QuantizedImage GenerateResult(ImageBase image, int colorCount, Box[] cube) + { + //var quantizedImage = new QuantizedImage(image.Length / 4, colorCount); + + List pallette = new List(); + byte[] pixels = new byte[image.Width * image.Height]; + + for (int k = 0; k < colorCount; k++) + { + this.Mark(cube[k], (byte)k); + + double weight = WuAlphaColorQuantizer.Volume(cube[k], this.vwt); + + // TODO: Epsilon + if (Math.Abs(weight) > .0001) + { + byte r = (byte)(WuAlphaColorQuantizer.Volume(cube[k], this.vmr) / weight); + byte g = (byte)(WuAlphaColorQuantizer.Volume(cube[k], this.vmg) / weight); + byte b = (byte)(WuAlphaColorQuantizer.Volume(cube[k], this.vmb) / weight); + byte a = (byte)(WuAlphaColorQuantizer.Volume(cube[k], this.vma) / weight); + + pallette.Add(new Bgra32(r, g, b, a)); + } + else + { + pallette.Add(new Bgra32(0, 0, 0)); + } + } + + int i = 0; + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + Bgra32 color = image[x, y]; + int a = color.A >> (8 - WuAlphaColorQuantizer.IndexAlphaBits); + int r = color.R >> (8 - WuAlphaColorQuantizer.IndexBits); + int g = color.G >> (8 - WuAlphaColorQuantizer.IndexBits); + int b = color.B >> (8 - WuAlphaColorQuantizer.IndexBits); + + int ind = WuAlphaColorQuantizer.Ind(r + 1, g + 1, b + 1, a + 1); + pixels[i++] = this.tag[ind]; + } + } + + //for (int i = 0; i < image.Length / 4; i++) + //{ + // int a = image[(i * 4) + 3] >> (8 - WuAlphaColorQuantizer.IndexAlphaBits); + // int r = image[(i * 4) + 2] >> (8 - WuAlphaColorQuantizer.IndexBits); + // int g = image[(i * 4) + 1] >> (8 - WuAlphaColorQuantizer.IndexBits); + // int b = image[i * 4] >> (8 - WuAlphaColorQuantizer.IndexBits); + + // int ind = WuAlphaColorQuantizer.Ind(r + 1, g + 1, b + 1, a + 1); + + // pixels[i] = this.tag[ind]; + //} + + return new QuantizedImage(image.Width, image.Height, pallette.ToArray(), pixels); + } + } +} \ 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 310fa4a3b..bec08f27c 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs @@ -54,7 +54,7 @@ using (FileStream stream = File.OpenRead(file)) { Image image = new Image(stream); - IQuantizer quantizer = new OctreeQuantizer(); + IQuantizer quantizer = new WuAlphaColorQuantizer(); QuantizedImage quantizedImage = quantizer.Quantize(image); using (FileStream output = File.OpenWrite($"TestOutput/Quantize/{Path.GetFileName(file)}"))