From 67ad4ae21e80fa2482a563753deafe8050023f65 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 18 Mar 2016 15:10:25 +1100 Subject: [PATCH 1/6] Add Wu Quantizer R & B are reversed. Former-commit-id: 92632679d9c7a1440e51e42bb23c264c2b8c4dad Former-commit-id: 84f98b9f6124e93b61c43dafc28442f8fac92487 Former-commit-id: 043e6b98103678de70bc41857c52b81d22daca6d --- .../Quantizer => Quantizers}/IQuantizer.cs | 0 .../Octree}/OctreeQuantizer.cs | 0 .../Octree}/Quantizer.cs | 0 .../QuantizedImage.cs | 0 .../Formats/Quantizers/Wu/Box.cs | 53 ++ .../Quantizers/Wu/WuAlphaColorQuantizer.cs | 796 ++++++++++++++++++ .../Processors/Formats/EncoderDecoderTests.cs | 2 +- 7 files changed, 850 insertions(+), 1 deletion(-) rename src/ImageProcessorCore/Formats/{Gif/Quantizer => Quantizers}/IQuantizer.cs (100%) rename src/ImageProcessorCore/Formats/{Gif/Quantizer => Quantizers/Octree}/OctreeQuantizer.cs (100%) rename src/ImageProcessorCore/Formats/{Gif/Quantizer => Quantizers/Octree}/Quantizer.cs (100%) rename src/ImageProcessorCore/Formats/{Gif/Quantizer => Quantizers}/QuantizedImage.cs (100%) create mode 100644 src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs create mode 100644 src/ImageProcessorCore/Formats/Quantizers/Wu/WuAlphaColorQuantizer.cs 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 0000000000..54abb20e0f --- /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 0000000000..2d63d76a72 --- /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 310fa4a3bc..bec08f27ce 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)}")) From 77aa1d8fd8f1e3158ac5975acab519fd70cf10a9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 18 Mar 2016 16:16:22 +1100 Subject: [PATCH 2/6] It works. Don't break it! Former-commit-id: 569020216c8921de469c664f7e61454ab347579f Former-commit-id: 9898b241446dd7ec5707821c0311d8aa42de2e09 Former-commit-id: 7bba69c1215a334bc83168ececcfe9948f296c33 --- .../Quantizers/Octree/OctreeQuantizer.cs | 4 +- .../Formats/Quantizers/Wu/Box.cs | 2 +- .../Quantizers/Wu/WuAlphaColorQuantizer.cs | 796 ------------------ .../Formats/Quantizers/Wu/WuQuantizer.cs | 796 ++++++++++++++++++ .../Processors/Formats/EncoderDecoderTests.cs | 2 +- 5 files changed, 800 insertions(+), 800 deletions(-) delete mode 100644 src/ImageProcessorCore/Formats/Quantizers/Wu/WuAlphaColorQuantizer.cs create mode 100644 src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs diff --git a/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs index 25c2171f91..f13f3aefed 100644 --- a/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs @@ -51,8 +51,8 @@ namespace ImageProcessorCore.Formats public OctreeQuantizer(int maxColors, int maxColorBits) : base(false) { - Guard.MustBeLessThanOrEqualTo(maxColors, 255, "maxColors"); - Guard.MustBeBetweenOrEqualTo(maxColorBits, 1, 8, "maxColorBits"); + Guard.MustBeBetweenOrEqualTo(maxColors, 1, 255, nameof(maxColors)); + Guard.MustBeBetweenOrEqualTo(maxColorBits, 1, 8, nameof(maxColorBits)); // Construct the Octree this.octree = new Octree(maxColorBits); diff --git a/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs b/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs index 54abb20e0f..618e1475ca 100644 --- a/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs +++ b/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs @@ -31,7 +31,7 @@ public int B0 { get; set; } /// - /// Gets or sets the max green value, inclusive. + /// Gets or sets the max blue value, inclusive. /// public int B1 { get; set; } diff --git a/src/ImageProcessorCore/Formats/Quantizers/Wu/WuAlphaColorQuantizer.cs b/src/ImageProcessorCore/Formats/Quantizers/Wu/WuAlphaColorQuantizer.cs deleted file mode 100644 index 2d63d76a72..0000000000 --- a/src/ImageProcessorCore/Formats/Quantizers/Wu/WuAlphaColorQuantizer.cs +++ /dev/null @@ -1,796 +0,0 @@ -// -// 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/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs new file mode 100644 index 0000000000..b2a5ac8488 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.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; + using System.Threading.Tasks; + + /// + /// An implementation of 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 WuQuantizer : 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 << WuQuantizer.IndexBits) + 1; + + /// + /// The index alpha count. + /// + private const int IndexAlphaCount = (1 << WuQuantizer.IndexAlphaBits) + 1; + + /// + /// The table length. + /// + private const int TableLength = WuQuantizer.IndexCount * WuQuantizer.IndexCount * WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount; + + /// + /// Maximum allowed color depth + /// + private readonly int maxColors; + + /// + /// 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 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[WuQuantizer.TableLength]; + this.vmr = new long[WuQuantizer.TableLength]; + this.vmg = new long[WuQuantizer.TableLength]; + this.vmb = new long[WuQuantizer.TableLength]; + this.vma = new long[WuQuantizer.TableLength]; + this.m2 = new double[WuQuantizer.TableLength]; + this.tag = new byte[WuQuantizer.TableLength]; + } + + /// + public QuantizedImage Quantize(ImageBase image) + { + Guard.NotNull(image, nameof(image)); + + int colorCount = this.maxColors; + + 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 << ((WuQuantizer.IndexBits * 2) + WuQuantizer.IndexAlphaBits)) + + (r << (WuQuantizer.IndexBits + WuQuantizer.IndexAlphaBits + 1)) + + (g << (WuQuantizer.IndexBits + WuQuantizer.IndexAlphaBits)) + + (r << (WuQuantizer.IndexBits * 2)) + + (r << (WuQuantizer.IndexBits + 1)) + + (g << WuQuantizer.IndexBits) + + ((r + g + b) << WuQuantizer.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[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A1)] + - moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)] + - moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)] + + moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)] + - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)] + + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)] + + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)] + - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] + - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)] + + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)] + + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)] + - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] + + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)] + - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] + - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[WuQuantizer.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 0: + return -moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)] + + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)] + + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)] + - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] + + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)] + - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] + - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + + // Green + case 1: + return -moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)] + + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)] + + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)] + - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] + + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)] + - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] + - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + + // Blue + case 2: + return -moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)] + + moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)] + + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)] + - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] + + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)] + - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] + - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + + // Alpha + case 3: + return -moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)] + + moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)] + + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)] + - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] + + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)] + - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] + - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] + + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + + default: + throw new ArgumentOutOfRangeException(nameof(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 0: + return moment[WuQuantizer.Ind(position, cube.G1, cube.B1, cube.A1)] + - moment[WuQuantizer.Ind(position, cube.G1, cube.B1, cube.A0)] + - moment[WuQuantizer.Ind(position, cube.G1, cube.B0, cube.A1)] + + moment[WuQuantizer.Ind(position, cube.G1, cube.B0, cube.A0)] + - moment[WuQuantizer.Ind(position, cube.G0, cube.B1, cube.A1)] + + moment[WuQuantizer.Ind(position, cube.G0, cube.B1, cube.A0)] + + moment[WuQuantizer.Ind(position, cube.G0, cube.B0, cube.A1)] + - moment[WuQuantizer.Ind(position, cube.G0, cube.B0, cube.A0)]; + + // Green + case 1: + return moment[WuQuantizer.Ind(cube.R1, position, cube.B1, cube.A1)] + - moment[WuQuantizer.Ind(cube.R1, position, cube.B1, cube.A0)] + - moment[WuQuantizer.Ind(cube.R1, position, cube.B0, cube.A1)] + + moment[WuQuantizer.Ind(cube.R1, position, cube.B0, cube.A0)] + - moment[WuQuantizer.Ind(cube.R0, position, cube.B1, cube.A1)] + + moment[WuQuantizer.Ind(cube.R0, position, cube.B1, cube.A0)] + + moment[WuQuantizer.Ind(cube.R0, position, cube.B0, cube.A1)] + - moment[WuQuantizer.Ind(cube.R0, position, cube.B0, cube.A0)]; + + // Blue + case 2: + return moment[WuQuantizer.Ind(cube.R1, cube.G1, position, cube.A1)] + - moment[WuQuantizer.Ind(cube.R1, cube.G1, position, cube.A0)] + - moment[WuQuantizer.Ind(cube.R1, cube.G0, position, cube.A1)] + + moment[WuQuantizer.Ind(cube.R1, cube.G0, position, cube.A0)] + - moment[WuQuantizer.Ind(cube.R0, cube.G1, position, cube.A1)] + + moment[WuQuantizer.Ind(cube.R0, cube.G1, position, cube.A0)] + + moment[WuQuantizer.Ind(cube.R0, cube.G0, position, cube.A1)] + - moment[WuQuantizer.Ind(cube.R0, cube.G0, position, cube.A0)]; + + // Alpha + case 3: + return moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, position)] + - moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, position)] + - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, position)] + + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, position)] + - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, position)] + + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, position)] + + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, position)] + - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, position)]; + + default: + throw new ArgumentOutOfRangeException("direction"); + } + } + + /// + /// Clears the tables. + /// + private void Clear() + { + Array.Clear(this.vwt, 0, WuQuantizer.TableLength); + Array.Clear(this.vmr, 0, WuQuantizer.TableLength); + Array.Clear(this.vmg, 0, WuQuantizer.TableLength); + Array.Clear(this.vmb, 0, WuQuantizer.TableLength); + Array.Clear(this.vma, 0, WuQuantizer.TableLength); + Array.Clear(this.m2, 0, WuQuantizer.TableLength); + + Array.Clear(this.tag, 0, WuQuantizer.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 - WuQuantizer.IndexBits); + int ing = g >> (8 - WuQuantizer.IndexBits); + int inb = b >> (8 - WuQuantizer.IndexBits); + int ina = a >> (8 - WuQuantizer.IndexAlphaBits); + + int ind = WuQuantizer.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[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount]; + long[] volume_r = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount]; + long[] volume_g = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount]; + long[] volume_b = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount]; + long[] volume_a = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount]; + double[] volume2 = new double[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount]; + + long[] area = new long[WuQuantizer.IndexAlphaCount]; + long[] area_r = new long[WuQuantizer.IndexAlphaCount]; + long[] area_g = new long[WuQuantizer.IndexAlphaCount]; + long[] area_b = new long[WuQuantizer.IndexAlphaCount]; + long[] area_a = new long[WuQuantizer.IndexAlphaCount]; + double[] area2 = new double[WuQuantizer.IndexAlphaCount]; + + for (int r = 1; r < WuQuantizer.IndexCount; r++) + { + Array.Clear(volume, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount); + Array.Clear(volume_r, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount); + Array.Clear(volume_g, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount); + Array.Clear(volume_b, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount); + Array.Clear(volume_a, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount); + Array.Clear(volume2, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount); + + for (int g = 1; g < WuQuantizer.IndexCount; g++) + { + Array.Clear(area, 0, WuQuantizer.IndexAlphaCount); + Array.Clear(area_r, 0, WuQuantizer.IndexAlphaCount); + Array.Clear(area_g, 0, WuQuantizer.IndexAlphaCount); + Array.Clear(area_b, 0, WuQuantizer.IndexAlphaCount); + Array.Clear(area_a, 0, WuQuantizer.IndexAlphaCount); + Array.Clear(area2, 0, WuQuantizer.IndexAlphaCount); + + for (int b = 1; b < WuQuantizer.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 < WuQuantizer.IndexAlphaCount; a++) + { + int ind1 = WuQuantizer.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 * WuQuantizer.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 - WuQuantizer.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 = WuQuantizer.Volume(cube, this.vmr); + double dg = WuQuantizer.Volume(cube, this.vmg); + double db = WuQuantizer.Volume(cube, this.vmb); + double da = WuQuantizer.Volume(cube, this.vma); + + double xx = + this.m2[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A1)] + - this.m2[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)] + - this.m2[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)] + + this.m2[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)] + - this.m2[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)] + + this.m2[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)] + + this.m2[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)] + - this.m2[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] + - this.m2[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)] + + this.m2[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)] + + this.m2[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)] + - this.m2[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] + + this.m2[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)] + - this.m2[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] + - this.m2[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] + + this.m2[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + + return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / WuQuantizer.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 = WuQuantizer.Bottom(cube, direction, this.vmr); + long base_g = WuQuantizer.Bottom(cube, direction, this.vmg); + long base_b = WuQuantizer.Bottom(cube, direction, this.vmb); + long base_a = WuQuantizer.Bottom(cube, direction, this.vma); + long base_w = WuQuantizer.Bottom(cube, direction, this.vwt); + + double max = 0.0; + cut = -1; + + for (int i = first; i < last; i++) + { + double half_r = base_r + WuQuantizer.Top(cube, direction, i, this.vmr); + double half_g = base_g + WuQuantizer.Top(cube, direction, i, this.vmg); + double half_b = base_b + WuQuantizer.Top(cube, direction, i, this.vmb); + double half_a = base_a + WuQuantizer.Top(cube, direction, i, this.vma); + double half_w = base_w + WuQuantizer.Top(cube, direction, i, this.vwt); + + double temp; + + // TODO: Epsilon + if (Math.Abs(half_w) < 0.001) + { + 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; + + // TODO: Epsilon + if (Math.Abs(half_w) < 0.001) + { + 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 = WuQuantizer.Volume(set1, this.vmr); + double whole_g = WuQuantizer.Volume(set1, this.vmg); + double whole_b = WuQuantizer.Volume(set1, this.vmb); + double whole_a = WuQuantizer.Volume(set1, this.vma); + double whole_w = WuQuantizer.Volume(set1, this.vwt); + + int cutr; + int cutg; + int cutb; + int cuta; + + double maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out cutr, whole_r, whole_g, whole_b, whole_a, whole_w); + double maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out cutg, whole_r, whole_g, whole_b, whole_a, whole_w); + double maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out cutb, whole_r, whole_g, whole_b, whole_a, whole_w); + double maxa = this.Maximize(set1, 3, 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 = 0; + + if (cutr < 0) + { + return false; + } + } + else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa)) + { + dir = 1; + } + else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa)) + { + dir = 2; + } + else + { + dir = 3; + } + + set2.R1 = set1.R1; + set2.G1 = set1.G1; + set2.B1 = set1.B1; + set2.A1 = set1.A1; + + switch (dir) + { + // Red + case 0: + set2.R0 = set1.R1 = cutr; + set2.G0 = set1.G0; + set2.B0 = set1.B0; + set2.A0 = set1.A0; + break; + + // Green + case 1: + set2.G0 = set1.G1 = cutg; + set2.R0 = set1.R0; + set2.B0 = set1.B0; + set2.A0 = set1.A0; + break; + + // Blue + case 2: + set2.B0 = set1.B1 = cutb; + set2.R0 = set1.R0; + set2.G0 = set1.G0; + set2.A0 = set1.A0; + break; + + // Alpha + case 3: + 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[WuQuantizer.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 = WuQuantizer.IndexCount - 1; + cube[0].A1 = WuQuantizer.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) + { + List pallette = new List(); + byte[] pixels = new byte[image.Width * image.Height]; + + Parallel.For( + 0, + colorCount, + k => + { + this.Mark(cube[k], (byte)k); + + double weight = WuQuantizer.Volume(cube[k], this.vwt); + + // TODO: Epsilon + if (Math.Abs(weight) > .0001) + { + byte r = (byte)(WuQuantizer.Volume(cube[k], this.vmr) / weight); + byte g = (byte)(WuQuantizer.Volume(cube[k], this.vmg) / weight); + byte b = (byte)(WuQuantizer.Volume(cube[k], this.vmb) / weight); + byte a = (byte)(WuQuantizer.Volume(cube[k], this.vma) / weight); + + pallette.Add(new Bgra32(b, g, r, a)); + } + else + { + pallette.Add(new Bgra32(0, 0, 0)); + } + }); + + // TODO: Optimize here. + 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 - WuQuantizer.IndexAlphaBits); + int r = color.R >> (8 - WuQuantizer.IndexBits); + int g = color.G >> (8 - WuQuantizer.IndexBits); + int b = color.B >> (8 - WuQuantizer.IndexBits); + + int ind = WuQuantizer.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 bec08f27ce..42e5022ced 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 WuAlphaColorQuantizer(); + IQuantizer quantizer = new WuQuantizer(); QuantizedImage quantizedImage = quantizer.Quantize(image); using (FileStream output = File.OpenWrite($"TestOutput/Quantize/{Path.GetFileName(file)}")) From 9f6ad2cf8495c0ad7956b34813572e3c9074d7fd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 18 Mar 2016 16:22:10 +1100 Subject: [PATCH 3/6] OK actually now it works. Former-commit-id: 6e7d3b8ca5887d468411011051e33ff0b375a676 Former-commit-id: 24528949db6743b12ad060fa19f5f6a889ba89d8 Former-commit-id: 5165f068be0bd610b313c692900c523d2f6ba8cb --- .../Quantizers/Octree/OctreeQuantizer.cs | 2 +- .../Formats/Quantizers/Wu/WuQuantizer.cs | 430 +++++++++--------- 2 files changed, 216 insertions(+), 216 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs index f13f3aefed..b064fc7e66 100644 --- a/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs @@ -12,7 +12,7 @@ namespace ImageProcessorCore.Formats /// Encapsulates methods to calculate the colour palette if an image using an Octree pattern. /// /// - public class OctreeQuantizer : Quantizer + public sealed class OctreeQuantizer : Quantizer { /// /// Stores the tree diff --git a/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs index b2a5ac8488..19b95bd006 100644 --- a/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs @@ -45,17 +45,17 @@ namespace ImageProcessorCore.Formats /// /// The index count. /// - private const int IndexCount = (1 << WuQuantizer.IndexBits) + 1; + private const int IndexCount = (1 << IndexBits) + 1; /// /// The index alpha count. /// - private const int IndexAlphaCount = (1 << WuQuantizer.IndexAlphaBits) + 1; + private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; /// /// The table length. /// - private const int TableLength = WuQuantizer.IndexCount * WuQuantizer.IndexCount * WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount; + private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; /// /// Maximum allowed color depth @@ -114,25 +114,25 @@ namespace ImageProcessorCore.Formats Guard.MustBeBetweenOrEqualTo(maxColors, 1, 256, nameof(maxColors)); this.maxColors = maxColors; - this.vwt = new long[WuQuantizer.TableLength]; - this.vmr = new long[WuQuantizer.TableLength]; - this.vmg = new long[WuQuantizer.TableLength]; - this.vmb = new long[WuQuantizer.TableLength]; - this.vma = new long[WuQuantizer.TableLength]; - this.m2 = new double[WuQuantizer.TableLength]; - this.tag = new byte[WuQuantizer.TableLength]; + this.vwt = new long[TableLength]; + this.vmr = new long[TableLength]; + this.vmg = new long[TableLength]; + this.vmb = new long[TableLength]; + this.vma = new long[TableLength]; + this.m2 = new double[TableLength]; + this.tag = new byte[TableLength]; } /// public QuantizedImage Quantize(ImageBase image) { Guard.NotNull(image, nameof(image)); - + int colorCount = this.maxColors; this.Clear(); - this.Hist3d(image); + this.Build3DHistogram(image); this.M3d(); Box[] cube; @@ -151,13 +151,13 @@ namespace ImageProcessorCore.Formats /// The index. private static int Ind(int r, int g, int b, int a) { - return (r << ((WuQuantizer.IndexBits * 2) + WuQuantizer.IndexAlphaBits)) - + (r << (WuQuantizer.IndexBits + WuQuantizer.IndexAlphaBits + 1)) - + (g << (WuQuantizer.IndexBits + WuQuantizer.IndexAlphaBits)) - + (r << (WuQuantizer.IndexBits * 2)) - + (r << (WuQuantizer.IndexBits + 1)) - + (g << WuQuantizer.IndexBits) - + ((r + g + b) << WuQuantizer.IndexAlphaBits) + return (r << ((IndexBits * 2) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (g << (IndexBits + IndexAlphaBits)) + + (r << (IndexBits * 2)) + + (r << (IndexBits + 1)) + + (g << IndexBits) + + ((r + g + b) << IndexAlphaBits) + r + g + b + a; } @@ -169,22 +169,22 @@ namespace ImageProcessorCore.Formats /// The result. private static double Volume(Box cube, long[] moment) { - return moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A1)] - - moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)] - - moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)] - + moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)] - - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)] - + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)] - + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)] - - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] - - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)] - + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)] - + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)] - - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] - + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)] - - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] - - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + 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)]; } /// @@ -200,47 +200,47 @@ namespace ImageProcessorCore.Formats { // Red case 0: - return -moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)] - + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)] - + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)] - - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] - + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)] - - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] - - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + 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)]; // Green case 1: - return -moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)] - + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)] - + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)] - - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] - + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)] - - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] - - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + 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)]; // Blue case 2: - return -moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)] - + moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)] - + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)] - - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] - + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)] - - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] - - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + 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)]; // Alpha case 3: - return -moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)] - + moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)] - + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)] - - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] - + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)] - - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] - - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] - + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; + 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)]; default: throw new ArgumentOutOfRangeException(nameof(direction)); @@ -261,50 +261,50 @@ namespace ImageProcessorCore.Formats { // Red case 0: - return moment[WuQuantizer.Ind(position, cube.G1, cube.B1, cube.A1)] - - moment[WuQuantizer.Ind(position, cube.G1, cube.B1, cube.A0)] - - moment[WuQuantizer.Ind(position, cube.G1, cube.B0, cube.A1)] - + moment[WuQuantizer.Ind(position, cube.G1, cube.B0, cube.A0)] - - moment[WuQuantizer.Ind(position, cube.G0, cube.B1, cube.A1)] - + moment[WuQuantizer.Ind(position, cube.G0, cube.B1, cube.A0)] - + moment[WuQuantizer.Ind(position, cube.G0, cube.B0, cube.A1)] - - moment[WuQuantizer.Ind(position, cube.G0, cube.B0, cube.A0)]; + 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)]; // Green case 1: - return moment[WuQuantizer.Ind(cube.R1, position, cube.B1, cube.A1)] - - moment[WuQuantizer.Ind(cube.R1, position, cube.B1, cube.A0)] - - moment[WuQuantizer.Ind(cube.R1, position, cube.B0, cube.A1)] - + moment[WuQuantizer.Ind(cube.R1, position, cube.B0, cube.A0)] - - moment[WuQuantizer.Ind(cube.R0, position, cube.B1, cube.A1)] - + moment[WuQuantizer.Ind(cube.R0, position, cube.B1, cube.A0)] - + moment[WuQuantizer.Ind(cube.R0, position, cube.B0, cube.A1)] - - moment[WuQuantizer.Ind(cube.R0, position, cube.B0, cube.A0)]; + 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)]; // Blue case 2: - return moment[WuQuantizer.Ind(cube.R1, cube.G1, position, cube.A1)] - - moment[WuQuantizer.Ind(cube.R1, cube.G1, position, cube.A0)] - - moment[WuQuantizer.Ind(cube.R1, cube.G0, position, cube.A1)] - + moment[WuQuantizer.Ind(cube.R1, cube.G0, position, cube.A0)] - - moment[WuQuantizer.Ind(cube.R0, cube.G1, position, cube.A1)] - + moment[WuQuantizer.Ind(cube.R0, cube.G1, position, cube.A0)] - + moment[WuQuantizer.Ind(cube.R0, cube.G0, position, cube.A1)] - - moment[WuQuantizer.Ind(cube.R0, cube.G0, position, cube.A0)]; + 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)]; // Alpha case 3: - return moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, position)] - - moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, position)] - - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, position)] - + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, position)] - - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, position)] - + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, position)] - + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, position)] - - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, position)]; + 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)]; default: - throw new ArgumentOutOfRangeException("direction"); + throw new ArgumentOutOfRangeException(nameof(direction)); } } @@ -313,22 +313,24 @@ namespace ImageProcessorCore.Formats /// private void Clear() { - Array.Clear(this.vwt, 0, WuQuantizer.TableLength); - Array.Clear(this.vmr, 0, WuQuantizer.TableLength); - Array.Clear(this.vmg, 0, WuQuantizer.TableLength); - Array.Clear(this.vmb, 0, WuQuantizer.TableLength); - Array.Clear(this.vma, 0, WuQuantizer.TableLength); - Array.Clear(this.m2, 0, WuQuantizer.TableLength); - - Array.Clear(this.tag, 0, WuQuantizer.TableLength); + Array.Clear(this.vwt, 0, TableLength); + Array.Clear(this.vmr, 0, TableLength); + Array.Clear(this.vmg, 0, TableLength); + Array.Clear(this.vmb, 0, TableLength); + Array.Clear(this.vma, 0, TableLength); + Array.Clear(this.m2, 0, TableLength); + + Array.Clear(this.tag, 0, TableLength); } /// /// Builds a 3-D color histogram of counts, r/g/b, c^2. /// /// The image. - private void Hist3d(ImageBase image) + private void Build3DHistogram(ImageBase image) { + + // TODO: Parallel for (int y = 0; y < image.Height; y++) { @@ -341,12 +343,12 @@ namespace ImageProcessorCore.Formats byte b = color.B; byte a = color.A; - int inr = r >> (8 - WuQuantizer.IndexBits); - int ing = g >> (8 - WuQuantizer.IndexBits); - int inb = b >> (8 - WuQuantizer.IndexBits); - int ina = a >> (8 - WuQuantizer.IndexAlphaBits); + int inr = r >> (8 - IndexBits); + int ing = g >> (8 - IndexBits); + int inb = b >> (8 - IndexBits); + int ina = a >> (8 - IndexAlphaBits); - int ind = WuQuantizer.Ind(inr + 1, ing + 1, inb + 1, ina + 1); + int ind = Ind(inr + 1, ing + 1, inb + 1, ina + 1); this.vwt[ind]++; this.vmr[ind] += r; @@ -364,39 +366,39 @@ namespace ImageProcessorCore.Formats /// private void M3d() { - long[] volume = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount]; - long[] volume_r = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount]; - long[] volume_g = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount]; - long[] volume_b = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount]; - long[] volume_a = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount]; - double[] volume2 = new double[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount]; - - long[] area = new long[WuQuantizer.IndexAlphaCount]; - long[] area_r = new long[WuQuantizer.IndexAlphaCount]; - long[] area_g = new long[WuQuantizer.IndexAlphaCount]; - long[] area_b = new long[WuQuantizer.IndexAlphaCount]; - long[] area_a = new long[WuQuantizer.IndexAlphaCount]; - double[] area2 = new double[WuQuantizer.IndexAlphaCount]; - - for (int r = 1; r < WuQuantizer.IndexCount; r++) + long[] volume = new long[IndexCount * IndexAlphaCount]; + long[] volume_r = new long[IndexCount * IndexAlphaCount]; + long[] volume_g = new long[IndexCount * IndexAlphaCount]; + long[] volume_b = new long[IndexCount * IndexAlphaCount]; + long[] volume_a = new long[IndexCount * IndexAlphaCount]; + double[] volume2 = new double[IndexCount * IndexAlphaCount]; + + long[] area = new long[IndexAlphaCount]; + long[] area_r = new long[IndexAlphaCount]; + long[] area_g = new long[IndexAlphaCount]; + long[] area_b = new long[IndexAlphaCount]; + long[] area_a = new long[IndexAlphaCount]; + double[] area2 = new double[IndexAlphaCount]; + + for (int r = 1; r < IndexCount; r++) { - Array.Clear(volume, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount); - Array.Clear(volume_r, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount); - Array.Clear(volume_g, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount); - Array.Clear(volume_b, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount); - Array.Clear(volume_a, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount); - Array.Clear(volume2, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount); - - for (int g = 1; g < WuQuantizer.IndexCount; g++) + Array.Clear(volume, 0, IndexCount * IndexAlphaCount); + Array.Clear(volume_r, 0, IndexCount * IndexAlphaCount); + Array.Clear(volume_g, 0, IndexCount * IndexAlphaCount); + Array.Clear(volume_b, 0, IndexCount * IndexAlphaCount); + Array.Clear(volume_a, 0, IndexCount * IndexAlphaCount); + Array.Clear(volume2, 0, IndexCount * IndexAlphaCount); + + for (int g = 1; g < IndexCount; g++) { - Array.Clear(area, 0, WuQuantizer.IndexAlphaCount); - Array.Clear(area_r, 0, WuQuantizer.IndexAlphaCount); - Array.Clear(area_g, 0, WuQuantizer.IndexAlphaCount); - Array.Clear(area_b, 0, WuQuantizer.IndexAlphaCount); - Array.Clear(area_a, 0, WuQuantizer.IndexAlphaCount); - Array.Clear(area2, 0, WuQuantizer.IndexAlphaCount); - - for (int b = 1; b < WuQuantizer.IndexCount; b++) + Array.Clear(area, 0, IndexAlphaCount); + Array.Clear(area_r, 0, IndexAlphaCount); + Array.Clear(area_g, 0, IndexAlphaCount); + Array.Clear(area_b, 0, IndexAlphaCount); + Array.Clear(area_a, 0, IndexAlphaCount); + Array.Clear(area2, 0, IndexAlphaCount); + + for (int b = 1; b < IndexCount; b++) { long line = 0; long line_r = 0; @@ -405,9 +407,9 @@ namespace ImageProcessorCore.Formats long line_a = 0; double line2 = 0; - for (int a = 1; a < WuQuantizer.IndexAlphaCount; a++) + for (int a = 1; a < IndexAlphaCount; a++) { - int ind1 = WuQuantizer.Ind(r, g, b, a); + int ind1 = Ind(r, g, b, a); line += this.vwt[ind1]; line_r += this.vmr[ind1]; @@ -423,7 +425,7 @@ namespace ImageProcessorCore.Formats area_a[a] += line_a; area2[a] += line2; - int inv = (b * WuQuantizer.IndexAlphaCount) + a; + int inv = (b * IndexAlphaCount) + a; volume[inv] += area[a]; volume_r[inv] += area_r[a]; @@ -432,7 +434,7 @@ namespace ImageProcessorCore.Formats volume_a[inv] += area_a[a]; volume2[inv] += area2[a]; - int ind2 = ind1 - WuQuantizer.Ind(1, 0, 0, 0); + int ind2 = ind1 - Ind(1, 0, 0, 0); this.vwt[ind1] = this.vwt[ind2] + volume[inv]; this.vmr[ind1] = this.vmr[ind2] + volume_r[inv]; @@ -453,30 +455,30 @@ namespace ImageProcessorCore.Formats /// The result. private double Var(Box cube) { - double dr = WuQuantizer.Volume(cube, this.vmr); - double dg = WuQuantizer.Volume(cube, this.vmg); - double db = WuQuantizer.Volume(cube, this.vmb); - double da = WuQuantizer.Volume(cube, this.vma); + double dr = Volume(cube, this.vmr); + double dg = Volume(cube, this.vmg); + double db = Volume(cube, this.vmb); + double da = Volume(cube, this.vma); double xx = - this.m2[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A1)] - - this.m2[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)] - - this.m2[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)] - + this.m2[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)] - - this.m2[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)] - + this.m2[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)] - + this.m2[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)] - - this.m2[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)] - - this.m2[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)] - + this.m2[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)] - + this.m2[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)] - - this.m2[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)] - + this.m2[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)] - - this.m2[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)] - - this.m2[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)] - + this.m2[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)]; - - return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / WuQuantizer.Volume(cube, this.vwt)); + 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)]; + + return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / Volume(cube, this.vwt)); } /// @@ -499,22 +501,22 @@ namespace ImageProcessorCore.Formats /// 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 = WuQuantizer.Bottom(cube, direction, this.vmr); - long base_g = WuQuantizer.Bottom(cube, direction, this.vmg); - long base_b = WuQuantizer.Bottom(cube, direction, this.vmb); - long base_a = WuQuantizer.Bottom(cube, direction, this.vma); - long base_w = WuQuantizer.Bottom(cube, direction, this.vwt); + long base_r = Bottom(cube, direction, this.vmr); + long base_g = Bottom(cube, direction, this.vmg); + long base_b = Bottom(cube, direction, this.vmb); + long base_a = Bottom(cube, direction, this.vma); + long base_w = Bottom(cube, direction, this.vwt); double max = 0.0; cut = -1; for (int i = first; i < last; i++) { - double half_r = base_r + WuQuantizer.Top(cube, direction, i, this.vmr); - double half_g = base_g + WuQuantizer.Top(cube, direction, i, this.vmg); - double half_b = base_b + WuQuantizer.Top(cube, direction, i, this.vmb); - double half_a = base_a + WuQuantizer.Top(cube, direction, i, this.vma); - double half_w = base_w + WuQuantizer.Top(cube, direction, i, this.vwt); + double half_r = base_r + Top(cube, direction, i, this.vmr); + double half_g = base_g + Top(cube, direction, i, this.vmg); + double half_b = base_b + Top(cube, direction, i, this.vmb); + double half_a = base_a + Top(cube, direction, i, this.vma); + double half_w = base_w + Top(cube, direction, i, this.vwt); double temp; @@ -562,11 +564,11 @@ namespace ImageProcessorCore.Formats /// Returns a value indicating whether the box has been split. private bool Cut(Box set1, Box set2) { - double whole_r = WuQuantizer.Volume(set1, this.vmr); - double whole_g = WuQuantizer.Volume(set1, this.vmg); - double whole_b = WuQuantizer.Volume(set1, this.vmb); - double whole_a = WuQuantizer.Volume(set1, this.vma); - double whole_w = WuQuantizer.Volume(set1, this.vwt); + double whole_r = Volume(set1, this.vmr); + double whole_g = Volume(set1, this.vmg); + double whole_b = Volume(set1, this.vmb); + double whole_a = Volume(set1, this.vma); + double whole_w = Volume(set1, this.vwt); int cutr; int cutg; @@ -663,7 +665,7 @@ namespace ImageProcessorCore.Formats { for (int a = cube.A0 + 1; a <= cube.A1; a++) { - this.tag[WuQuantizer.Ind(r, g, b, a)] = label; + this.tag[Ind(r, g, b, a)] = label; } } } @@ -686,8 +688,8 @@ namespace ImageProcessorCore.Formats } cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0; - cube[0].R1 = cube[0].G1 = cube[0].B1 = WuQuantizer.IndexCount - 1; - cube[0].A1 = WuQuantizer.IndexAlphaCount - 1; + cube[0].R1 = cube[0].G1 = cube[0].B1 = IndexCount - 1; + cube[0].A1 = IndexAlphaCount - 1; int next = 0; @@ -736,30 +738,28 @@ namespace ImageProcessorCore.Formats List pallette = new List(); byte[] pixels = new byte[image.Width * image.Height]; - Parallel.For( - 0, - colorCount, - k => - { - this.Mark(cube[k], (byte)k); + // Can't make this parallel. + for (int k = 0; k < colorCount; k++) + { + this.Mark(cube[k], (byte)k); - double weight = WuQuantizer.Volume(cube[k], this.vwt); + double weight = Volume(cube[k], this.vwt); - // TODO: Epsilon - if (Math.Abs(weight) > .0001) - { - byte r = (byte)(WuQuantizer.Volume(cube[k], this.vmr) / weight); - byte g = (byte)(WuQuantizer.Volume(cube[k], this.vmg) / weight); - byte b = (byte)(WuQuantizer.Volume(cube[k], this.vmb) / weight); - byte a = (byte)(WuQuantizer.Volume(cube[k], this.vma) / weight); + // TODO: Epsilon + if (Math.Abs(weight) > .0001) + { + byte r = (byte)(Volume(cube[k], this.vmr) / weight); + byte g = (byte)(Volume(cube[k], this.vmg) / weight); + 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)); - } - else - { - pallette.Add(new Bgra32(0, 0, 0)); - } - }); + pallette.Add(new Bgra32(b, g, r, a)); + } + else + { + pallette.Add(new Bgra32(0, 0, 0)); + } + } // TODO: Optimize here. int i = 0; @@ -768,12 +768,12 @@ namespace ImageProcessorCore.Formats for (int x = 0; x < image.Width; x++) { Bgra32 color = image[x, y]; - int a = color.A >> (8 - WuQuantizer.IndexAlphaBits); - int r = color.R >> (8 - WuQuantizer.IndexBits); - int g = color.G >> (8 - WuQuantizer.IndexBits); - int b = color.B >> (8 - WuQuantizer.IndexBits); + int a = color.A >> (8 - IndexAlphaBits); + int r = color.R >> (8 - IndexBits); + int g = color.G >> (8 - IndexBits); + int b = color.B >> (8 - IndexBits); - int ind = WuQuantizer.Ind(r + 1, g + 1, b + 1, a + 1); + int ind = Ind(r + 1, g + 1, b + 1, a + 1); pixels[i++] = this.tag[ind]; } } From 5e697a3ffc3159e295a7a90f6002cea7bedd2e14 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 18 Mar 2016 18:03:32 +1100 Subject: [PATCH 4/6] Shift to new namespace Former-commit-id: e196c8b9f6e869acc06059fa18c5af33dd02d537 Former-commit-id: 8763566c1d28c8606cbebe1d70fd35d17b880876 Former-commit-id: d44c34c382ab783f3f9b26906981970363f44b8f --- .../Formats/Gif/GifEncoder.cs | 6 +- .../{Formats => }/Quantizers/IQuantizer.cs | 2 +- .../Quantizers/Octree/OctreeQuantizer.cs | 15 +- .../Quantizers/Octree/Quantizer.cs | 10 +- .../Quantizers/QuantizedImage.cs | 2 +- .../{Formats => }/Quantizers/Wu/Box.cs | 9 +- .../Quantizers/Wu/WuQuantizer.cs | 220 ++++++++---------- .../Processors/Formats/EncoderDecoderTests.cs | 3 + 8 files changed, 135 insertions(+), 132 deletions(-) rename src/ImageProcessorCore/{Formats => }/Quantizers/IQuantizer.cs (94%) rename src/ImageProcessorCore/{Formats => }/Quantizers/Octree/OctreeQuantizer.cs (97%) rename src/ImageProcessorCore/{Formats => }/Quantizers/Octree/Quantizer.cs (96%) rename src/ImageProcessorCore/{Formats => }/Quantizers/QuantizedImage.cs (98%) rename src/ImageProcessorCore/{Formats => }/Quantizers/Wu/Box.cs (83%) rename src/ImageProcessorCore/{Formats => }/Quantizers/Wu/WuQuantizer.cs (81%) diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs b/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs index 981dfb241d..0ef36ebd26 100644 --- a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs +++ b/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs @@ -10,6 +10,8 @@ namespace ImageProcessorCore.Formats using System.Linq; using System.Threading.Tasks; + using ImageProcessorCore.Quantizers; + /// /// Image encoder for writing image data to a stream in gif format. /// @@ -21,6 +23,8 @@ namespace ImageProcessorCore.Formats /// For gifs the value ranges from 1 to 256. public int Quality { get; set; } + public IQuantizer Quantizer { get; set; } + /// public string Extension => "gif"; @@ -35,7 +39,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) { diff --git a/src/ImageProcessorCore/Formats/Quantizers/IQuantizer.cs b/src/ImageProcessorCore/Quantizers/IQuantizer.cs similarity index 94% rename from src/ImageProcessorCore/Formats/Quantizers/IQuantizer.cs rename to src/ImageProcessorCore/Quantizers/IQuantizer.cs index b54334a105..49ef056cd4 100644 --- a/src/ImageProcessorCore/Formats/Quantizers/IQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/IQuantizer.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageProcessorCore.Formats +namespace ImageProcessorCore.Quantizers { /// /// Provides methods for allowing quantization of images pixels. diff --git a/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs similarity index 97% rename from src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs rename to src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs index b064fc7e66..c681b43081 100644 --- a/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageProcessorCore.Formats +namespace ImageProcessorCore.Quantizers { using System; using System.Collections.Generic; @@ -120,6 +120,19 @@ namespace ImageProcessorCore.Formats return palette; } + /// + /// Returns how many bits are required to store the specified number of colors. + /// Performs a Log2() on the value. + /// + /// The number of colors. + /// + /// The + /// + private int GetBitsNeededForColorDepth(int colors) + { + return (int)Math.Ceiling(Math.Log(colors, 2)); + } + /// /// Class which does the actual quantization /// diff --git a/src/ImageProcessorCore/Formats/Quantizers/Octree/Quantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs similarity index 96% rename from src/ImageProcessorCore/Formats/Quantizers/Octree/Quantizer.cs rename to src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs index 9b8371e043..74b0d7131b 100644 --- a/src/ImageProcessorCore/Formats/Quantizers/Octree/Quantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageProcessorCore.Formats +namespace ImageProcessorCore.Quantizers { using System.Collections.Generic; @@ -121,9 +121,7 @@ namespace ImageProcessorCore.Formats /// /// Override this to process the pixel in the first pass of the algorithm /// - /// - /// The pixel to quantize - /// + /// The pixel to quantize /// /// This function need only be overridden if your quantize algorithm needs two passes, /// such as an Octree quantizer. @@ -135,9 +133,7 @@ namespace ImageProcessorCore.Formats /// /// Override this to process the pixel in the second pass of the algorithm /// - /// - /// The pixel to quantize - /// + /// The pixel to quantize /// /// The quantized value /// diff --git a/src/ImageProcessorCore/Formats/Quantizers/QuantizedImage.cs b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs similarity index 98% rename from src/ImageProcessorCore/Formats/Quantizers/QuantizedImage.cs rename to src/ImageProcessorCore/Quantizers/QuantizedImage.cs index d46f5748f7..d0f7928eed 100644 --- a/src/ImageProcessorCore/Formats/Quantizers/QuantizedImage.cs +++ b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageProcessorCore.Formats +namespace ImageProcessorCore.Quantizers { using System; using System.Threading.Tasks; diff --git a/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs b/src/ImageProcessorCore/Quantizers/Wu/Box.cs similarity index 83% rename from src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs rename to src/ImageProcessorCore/Quantizers/Wu/Box.cs index 618e1475ca..b9300b0870 100644 --- a/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs +++ b/src/ImageProcessorCore/Quantizers/Wu/Box.cs @@ -1,7 +1,12 @@ -namespace ImageProcessorCore.Formats +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Quantizers { /// - /// A box. + /// Represents a box color cube. /// internal sealed class Box { diff --git a/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs similarity index 81% rename from src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs rename to src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs index 19b95bd006..578eddd04a 100644 --- a/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs @@ -1,16 +1,12 @@ -// -// Copyright (c) 2014-2015 Jérémy Ansel +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. // -// -// Licensed under the MIT license. See LICENSE.txt -// -namespace ImageProcessorCore.Formats +namespace ImageProcessorCore.Quantizers { using System; using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Threading.Tasks; /// /// An implementation of Wu's color quantizer with alpha channel. @@ -22,6 +18,10 @@ namespace ImageProcessorCore.Formats /// (). /// /// + /// This adaptation is based on the excellent JeremyAnsel.ColorQuant by Jérémy Ansel + /// + /// + /// /// Algorithm: Greedy orthogonal bipartition of RGB space for variance /// minimization aided by inclusion-exclusion tricks. /// For speed no nearest neighbor search is done. Slightly @@ -29,9 +29,13 @@ namespace ImageProcessorCore.Formats /// but more expensive versions. /// /// - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Wu", Justification = "Reviewed")] public sealed class WuQuantizer : IQuantizer { + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + /// /// The index bits. /// @@ -65,37 +69,37 @@ namespace ImageProcessorCore.Formats /// /// Moment of P(c). /// - private long[] vwt; + private readonly long[] vwt; /// /// Moment of r*P(c). /// - private long[] vmr; + private readonly long[] vmr; /// /// Moment of g*P(c). /// - private long[] vmg; + private readonly long[] vmg; /// /// Moment of b*P(c). /// - private long[] vmb; + private readonly long[] vmb; /// /// Moment of a*P(c). /// - private long[] vma; + private readonly long[] vma; /// /// Moment of c^2*P(c). /// - private double[] m2; + private readonly double[] m2; /// /// Color space tag. /// - private byte[] tag; + private readonly byte[] tag; /// /// Initializes a new instance of the class. @@ -133,7 +137,7 @@ namespace ImageProcessorCore.Formats this.Clear(); this.Build3DHistogram(image); - this.M3d(); + this.Get3DMoments(); Box[] cube; this.BuildCube(out cube, ref colorCount); @@ -329,9 +333,6 @@ namespace ImageProcessorCore.Formats /// The image. private void Build3DHistogram(ImageBase image) { - - - // TODO: Parallel for (int y = 0; y < image.Height; y++) { for (int x = 0; x < image.Width; x++) @@ -364,47 +365,47 @@ namespace ImageProcessorCore.Formats /// Converts the histogram into moments so that we can rapidly calculate /// the sums of the above quantities over any desired box. /// - private void M3d() + private void Get3DMoments() { long[] volume = new long[IndexCount * IndexAlphaCount]; - long[] volume_r = new long[IndexCount * IndexAlphaCount]; - long[] volume_g = new long[IndexCount * IndexAlphaCount]; - long[] volume_b = new long[IndexCount * IndexAlphaCount]; - long[] volume_a = new long[IndexCount * IndexAlphaCount]; + long[] volumeR = new long[IndexCount * IndexAlphaCount]; + long[] volumeG = new long[IndexCount * IndexAlphaCount]; + long[] volumeB = new long[IndexCount * IndexAlphaCount]; + long[] volumeA = new long[IndexCount * IndexAlphaCount]; double[] volume2 = new double[IndexCount * IndexAlphaCount]; long[] area = new long[IndexAlphaCount]; - long[] area_r = new long[IndexAlphaCount]; - long[] area_g = new long[IndexAlphaCount]; - long[] area_b = new long[IndexAlphaCount]; - long[] area_a = new long[IndexAlphaCount]; + long[] areaR = new long[IndexAlphaCount]; + long[] areaG = new long[IndexAlphaCount]; + long[] areaB = new long[IndexAlphaCount]; + long[] areaA = new long[IndexAlphaCount]; double[] area2 = new double[IndexAlphaCount]; for (int r = 1; r < IndexCount; r++) { Array.Clear(volume, 0, IndexCount * IndexAlphaCount); - Array.Clear(volume_r, 0, IndexCount * IndexAlphaCount); - Array.Clear(volume_g, 0, IndexCount * IndexAlphaCount); - Array.Clear(volume_b, 0, IndexCount * IndexAlphaCount); - Array.Clear(volume_a, 0, IndexCount * IndexAlphaCount); + Array.Clear(volumeR, 0, IndexCount * IndexAlphaCount); + Array.Clear(volumeG, 0, IndexCount * IndexAlphaCount); + Array.Clear(volumeB, 0, IndexCount * IndexAlphaCount); + Array.Clear(volumeA, 0, IndexCount * IndexAlphaCount); Array.Clear(volume2, 0, IndexCount * IndexAlphaCount); for (int g = 1; g < IndexCount; g++) { Array.Clear(area, 0, IndexAlphaCount); - Array.Clear(area_r, 0, IndexAlphaCount); - Array.Clear(area_g, 0, IndexAlphaCount); - Array.Clear(area_b, 0, IndexAlphaCount); - Array.Clear(area_a, 0, IndexAlphaCount); + Array.Clear(areaR, 0, IndexAlphaCount); + Array.Clear(areaG, 0, IndexAlphaCount); + Array.Clear(areaB, 0, IndexAlphaCount); + Array.Clear(areaA, 0, IndexAlphaCount); Array.Clear(area2, 0, IndexAlphaCount); for (int b = 1; b < IndexCount; b++) { long line = 0; - long line_r = 0; - long line_g = 0; - long line_b = 0; - long line_a = 0; + long lineR = 0; + long lineG = 0; + long lineB = 0; + long lineA = 0; double line2 = 0; for (int a = 1; a < IndexAlphaCount; a++) @@ -412,35 +413,35 @@ namespace ImageProcessorCore.Formats int ind1 = 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]; + lineR += this.vmr[ind1]; + lineG += this.vmg[ind1]; + lineB += this.vmb[ind1]; + lineA += 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; + areaR[a] += lineR; + areaG[a] += lineG; + areaB[a] += lineB; + areaA[a] += lineA; area2[a] += line2; int inv = (b * 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]; + volumeR[inv] += areaR[a]; + volumeG[inv] += areaG[a]; + volumeB[inv] += areaB[a]; + volumeA[inv] += areaA[a]; volume2[inv] += area2[a]; int ind2 = ind1 - 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.vmr[ind1] = this.vmr[ind2] + volumeR[inv]; + this.vmg[ind1] = this.vmg[ind2] + volumeG[inv]; + this.vmb[ind1] = this.vmb[ind2] + volumeB[inv]; + this.vma[ind1] = this.vma[ind2] + volumeA[inv]; this.m2[ind1] = this.m2[ind2] + volume2[inv]; } } @@ -449,11 +450,11 @@ namespace ImageProcessorCore.Formats } /// - /// Computes the weighted variance of a box. + /// Computes the weighted variance of a box cube. /// /// The cube. - /// The result. - private double Var(Box cube) + /// The . + private double Variance(Box cube) { double dr = Volume(cube, this.vmr); double dg = Volume(cube, this.vmg); @@ -493,58 +494,52 @@ namespace ImageProcessorCore.Formats /// 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) + /// The whole red. + /// The whole green. + /// The whole blue. + /// The whole alpha. + /// The whole weight. + /// The . + private double Maximize(Box cube, int direction, int first, int last, out int cut, double wholeR, double wholeG, double wholeB, double wholeA, double wholeW) { - long base_r = Bottom(cube, direction, this.vmr); - long base_g = Bottom(cube, direction, this.vmg); - long base_b = Bottom(cube, direction, this.vmb); - long base_a = Bottom(cube, direction, this.vma); - long base_w = Bottom(cube, direction, this.vwt); + long baseR = Bottom(cube, direction, this.vmr); + long baseG = Bottom(cube, direction, this.vmg); + long baseB = Bottom(cube, direction, this.vmb); + long baseA = Bottom(cube, direction, this.vma); + long baseW = Bottom(cube, direction, this.vwt); double max = 0.0; cut = -1; for (int i = first; i < last; i++) { - double half_r = base_r + Top(cube, direction, i, this.vmr); - double half_g = base_g + Top(cube, direction, i, this.vmg); - double half_b = base_b + Top(cube, direction, i, this.vmb); - double half_a = base_a + Top(cube, direction, i, this.vma); - double half_w = base_w + Top(cube, direction, i, this.vwt); + double halfR = baseR + Top(cube, direction, i, this.vmr); + double halfG = baseG + Top(cube, direction, i, this.vmg); + double halfB = baseB + Top(cube, direction, i, this.vmb); + double halfA = baseA + Top(cube, direction, i, this.vma); + double halfW = baseW + Top(cube, direction, i, this.vwt); double temp; - // TODO: Epsilon - if (Math.Abs(half_w) < 0.001) + if (Math.Abs(halfW) < Epsilon) { 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; + temp = ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; + + halfR = wholeR - halfR; + halfG = wholeG - halfG; + halfB = wholeB - halfB; + halfA = wholeA - halfA; + halfW = wholeW - halfW; - // TODO: Epsilon - if (Math.Abs(half_w) < 0.001) + if (Math.Abs(halfW) < Epsilon) { continue; } - else - { - temp += ((half_r * half_r) + (half_g * half_g) + (half_b * half_b) + (half_a * half_a)) / half_w; - } + + temp += ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; if (temp > max) { @@ -564,21 +559,21 @@ namespace ImageProcessorCore.Formats /// Returns a value indicating whether the box has been split. private bool Cut(Box set1, Box set2) { - double whole_r = Volume(set1, this.vmr); - double whole_g = Volume(set1, this.vmg); - double whole_b = Volume(set1, this.vmb); - double whole_a = Volume(set1, this.vma); - double whole_w = Volume(set1, this.vwt); + double wholeR = Volume(set1, this.vmr); + double wholeG = Volume(set1, this.vmg); + double wholeB = Volume(set1, this.vmb); + double wholeA = Volume(set1, this.vma); + double wholeW = Volume(set1, this.vwt); int cutr; int cutg; int cutb; int cuta; - double maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out cutr, whole_r, whole_g, whole_b, whole_a, whole_w); - double maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out cutg, whole_r, whole_g, whole_b, whole_a, whole_w); - double maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out cutb, whole_r, whole_g, whole_b, whole_a, whole_w); - double maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out cuta, whole_r, whole_g, whole_b, whole_a, whole_w); + double maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out cutr, wholeR, wholeG, wholeB, wholeA, wholeW); + double maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out cutg, wholeR, wholeG, wholeB, wholeA, wholeW); + double maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out cutb, wholeR, wholeG, wholeB, wholeA, wholeW); + double maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out cuta, wholeR, wholeG, wholeB, wholeA, wholeW); int dir; @@ -697,8 +692,8 @@ namespace ImageProcessorCore.Formats { 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; + vv[next] = cube[next].Volume > 1 ? this.Variance(cube[next]) : 0.0; + vv[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0.0; } else { @@ -745,8 +740,7 @@ namespace ImageProcessorCore.Formats double weight = Volume(cube[k], this.vwt); - // TODO: Epsilon - if (Math.Abs(weight) > .0001) + if (Math.Abs(weight) > Epsilon) { byte r = (byte)(Volume(cube[k], this.vmr) / weight); byte g = (byte)(Volume(cube[k], this.vmg) / weight); @@ -778,18 +772,6 @@ namespace ImageProcessorCore.Formats } } - //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); } } diff --git a/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs b/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs index 42e5022ced..9950fa5d99 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs @@ -7,6 +7,9 @@ using Xunit; using System.Linq; + + using ImageProcessorCore.Quantizers; + public class EncoderDecoderTests : ProcessorTestBase { [Fact] From 36b3f2742dfdce74f7a96031d7725633687d2dd5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 18 Mar 2016 21:33:42 +1100 Subject: [PATCH 5/6] Interchangeable quantizers :balloon: Former-commit-id: d9c1d3a41b639781c0e44ac921a6de9de59321b2 Former-commit-id: 3b21b6cd0c539bf121d9f88fccf874793de71180 Former-commit-id: 4510bab7605f17c4c85d7793daaa7e24ea2dc55f --- README.md | 3 + .../Formats/Gif/GifEncoder.cs | 24 +- .../Quantizers/IQuantizer.cs | 5 +- .../Quantizers/Octree/OctreeQuantizer.cs | 53 ++-- .../Quantizers/Octree/Quantizer.cs | 20 +- .../Quantizers/QuantizedImage.cs | 9 +- .../Quantizers/Wu/WuQuantizer.cs | 241 +++++++++--------- .../Processors/Formats/EncoderDecoderTests.cs | 4 +- 8 files changed, 179 insertions(+), 180 deletions(-) diff --git a/README.md b/README.md index 75b30ff222..9c904656b6 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 0ef36ebd26..995cab2073 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 49ef056cd4..68f7554dab 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 c681b43081..c956651740 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 74b0d7131b..2a330f782e 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 d0f7928eed..fdf93abd33 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 578eddd04a..aa2a88048d 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 9950fa5d99..c5eea6ba05 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)}")) { From 83a73fe4ed7a966aff7438e2adad30a8d9675e8a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 18 Mar 2016 21:34:15 +1100 Subject: [PATCH 6/6] Save all the things Former-commit-id: 2200b253726edd121f8f96a5fed5083489cd9c18 Former-commit-id: 5d9d3f980e2f04aa5ea56348e86152523796eb13 Former-commit-id: f8fee9b0b95da223a0098d028029f2ce827f6c14 --- ImageProcessorCore.sln | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ImageProcessorCore.sln b/ImageProcessorCore.sln index a4df907b45..5c42e1cc1a 100644 --- a/ImageProcessorCore.sln +++ b/ImageProcessorCore.sln @@ -7,6 +7,11 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ImageProcessorCore", "src\I EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ImageProcessorCore.Tests", "tests\ImageProcessorCore.Tests\ImageProcessorCore.Tests.xproj", "{F836E8E6-B4D9-4208-8346-140C74678B91}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU