From 5e697a3ffc3159e295a7a90f6002cea7bedd2e14 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 18 Mar 2016 18:03:32 +1100 Subject: [PATCH] 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 981dfb241..0ef36ebd2 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 b54334a10..49ef056cd 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 b064fc7e6..c681b4308 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 9b8371e04..74b0d7131 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 d46f5748f..d0f7928ee 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 618e1475c..b9300b087 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 19b95bd00..578eddd04 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 42e5022ce..9950fa5d9 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]