|
|
|
@ -1,16 +1,12 @@ |
|
|
|
// <copyright file="WuAlphaColorQuantizer.cs" company="Jérémy Ansel">
|
|
|
|
// Copyright (c) 2014-2015 Jérémy Ansel
|
|
|
|
// <copyright file="WuQuantizer.cs" company="James Jackson-South">
|
|
|
|
// Copyright (c) James Jackson-South and contributors.
|
|
|
|
// Licensed under the Apache License, Version 2.0.
|
|
|
|
// </copyright>
|
|
|
|
// <license>
|
|
|
|
// Licensed under the MIT license. See LICENSE.txt
|
|
|
|
// </license>
|
|
|
|
|
|
|
|
namespace ImageProcessorCore.Formats |
|
|
|
namespace ImageProcessorCore.Quantizers |
|
|
|
{ |
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Diagnostics.CodeAnalysis; |
|
|
|
using System.Threading.Tasks; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// An implementation of Wu's color quantizer with alpha channel.
|
|
|
|
@ -22,6 +18,10 @@ namespace ImageProcessorCore.Formats |
|
|
|
/// (<see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/>).
|
|
|
|
/// </para>
|
|
|
|
/// <para>
|
|
|
|
/// This adaptation is based on the excellent JeremyAnsel.ColorQuant by Jérémy Ansel
|
|
|
|
/// <see href="https://github.com/JeremyAnsel/JeremyAnsel.ColorQuant"/>
|
|
|
|
/// </para>
|
|
|
|
/// <para>
|
|
|
|
/// 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.
|
|
|
|
/// </para>
|
|
|
|
/// </remarks>
|
|
|
|
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Wu", Justification = "Reviewed")] |
|
|
|
public sealed class WuQuantizer : IQuantizer |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
/// The epsilon for comparing floating point numbers.
|
|
|
|
/// </summary>
|
|
|
|
private const float Epsilon = 0.001f; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The index bits.
|
|
|
|
/// </summary>
|
|
|
|
@ -65,37 +69,37 @@ namespace ImageProcessorCore.Formats |
|
|
|
/// <summary>
|
|
|
|
/// Moment of <c>P(c)</c>.
|
|
|
|
/// </summary>
|
|
|
|
private long[] vwt; |
|
|
|
private readonly long[] vwt; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Moment of <c>r*P(c)</c>.
|
|
|
|
/// </summary>
|
|
|
|
private long[] vmr; |
|
|
|
private readonly long[] vmr; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Moment of <c>g*P(c)</c>.
|
|
|
|
/// </summary>
|
|
|
|
private long[] vmg; |
|
|
|
private readonly long[] vmg; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Moment of <c>b*P(c)</c>.
|
|
|
|
/// </summary>
|
|
|
|
private long[] vmb; |
|
|
|
private readonly long[] vmb; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Moment of <c>a*P(c)</c>.
|
|
|
|
/// </summary>
|
|
|
|
private long[] vma; |
|
|
|
private readonly long[] vma; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Moment of <c>c^2*P(c)</c>.
|
|
|
|
/// </summary>
|
|
|
|
private double[] m2; |
|
|
|
private readonly double[] m2; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Color space tag.
|
|
|
|
/// </summary>
|
|
|
|
private byte[] tag; |
|
|
|
private readonly byte[] tag; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="WuQuantizer"/> 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 |
|
|
|
/// <param name="image">The image.</param>
|
|
|
|
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.
|
|
|
|
/// </summary>
|
|
|
|
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 |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Computes the weighted variance of a box.
|
|
|
|
/// Computes the weighted variance of a box cube.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="cube">The cube.</param>
|
|
|
|
/// <returns>The result.</returns>
|
|
|
|
private double Var(Box cube) |
|
|
|
/// <returns>The <see cref="double"/>.</returns>
|
|
|
|
private double Variance(Box cube) |
|
|
|
{ |
|
|
|
double dr = Volume(cube, this.vmr); |
|
|
|
double dg = Volume(cube, this.vmg); |
|
|
|
@ -493,58 +494,52 @@ namespace ImageProcessorCore.Formats |
|
|
|
/// <param name="first">The first position.</param>
|
|
|
|
/// <param name="last">The last position.</param>
|
|
|
|
/// <param name="cut">The cutting point.</param>
|
|
|
|
/// <param name="whole_r">The whole red.</param>
|
|
|
|
/// <param name="whole_g">The whole green.</param>
|
|
|
|
/// <param name="whole_b">The whole blue.</param>
|
|
|
|
/// <param name="whole_a">The whole alpha.</param>
|
|
|
|
/// <param name="whole_w">The whole weight.</param>
|
|
|
|
/// <returns>The result.</returns>
|
|
|
|
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) |
|
|
|
/// <param name="wholeR">The whole red.</param>
|
|
|
|
/// <param name="wholeG">The whole green.</param>
|
|
|
|
/// <param name="wholeB">The whole blue.</param>
|
|
|
|
/// <param name="wholeA">The whole alpha.</param>
|
|
|
|
/// <param name="wholeW">The whole weight.</param>
|
|
|
|
/// <returns>The <see cref="double"/>.</returns>
|
|
|
|
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>Returns a value indicating whether the box has been split.</returns>
|
|
|
|
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); |
|
|
|
} |
|
|
|
} |