Browse Source

Shift to new namespace

Former-commit-id: e196c8b9f6e869acc06059fa18c5af33dd02d537
Former-commit-id: 8763566c1d28c8606cbebe1d70fd35d17b880876
Former-commit-id: d44c34c382ab783f3f9b26906981970363f44b8f
af/merge-core
James Jackson-South 10 years ago
parent
commit
5e697a3ffc
  1. 6
      src/ImageProcessorCore/Formats/Gif/GifEncoder.cs
  2. 2
      src/ImageProcessorCore/Quantizers/IQuantizer.cs
  3. 15
      src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs
  4. 10
      src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
  5. 2
      src/ImageProcessorCore/Quantizers/QuantizedImage.cs
  6. 9
      src/ImageProcessorCore/Quantizers/Wu/Box.cs
  7. 220
      src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs
  8. 3
      tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs

6
src/ImageProcessorCore/Formats/Gif/GifEncoder.cs

@ -10,6 +10,8 @@ namespace ImageProcessorCore.Formats
using System.Linq;
using System.Threading.Tasks;
using ImageProcessorCore.Quantizers;
/// <summary>
/// Image encoder for writing image data to a stream in gif format.
/// </summary>
@ -21,6 +23,8 @@ namespace ImageProcessorCore.Formats
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
public int Quality { get; set; }
public IQuantizer Quantizer { get; set; }
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
public void Encode(ImageBase imageBase, Stream stream)
{

2
src/ImageProcessorCore/Formats/Quantizers/IQuantizer.cs → src/ImageProcessorCore/Quantizers/IQuantizer.cs

@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
namespace ImageProcessorCore.Quantizers
{
/// <summary>
/// Provides methods for allowing quantization of images pixels.

15
src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs → src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs

@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
namespace ImageProcessorCore.Quantizers
{
using System;
using System.Collections.Generic;
@ -120,6 +120,19 @@ namespace ImageProcessorCore.Formats
return palette;
}
/// <summary>
/// Returns how many bits are required to store the specified number of colors.
/// Performs a Log2() on the value.
/// </summary>
/// <param name="colors">The number of colors.</param>
/// <returns>
/// The <see cref="int"/>
/// </returns>
private int GetBitsNeededForColorDepth(int colors)
{
return (int)Math.Ceiling(Math.Log(colors, 2));
}
/// <summary>
/// Class which does the actual quantization
/// </summary>

10
src/ImageProcessorCore/Formats/Quantizers/Octree/Quantizer.cs → src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs

@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
namespace ImageProcessorCore.Quantizers
{
using System.Collections.Generic;
@ -121,9 +121,7 @@ namespace ImageProcessorCore.Formats
/// <summary>
/// Override this to process the pixel in the first pass of the algorithm
/// </summary>
/// <param name="pixel">
/// The pixel to quantize
/// </param>
/// <param name="pixel">The pixel to quantize</param>
/// <remarks>
/// 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
/// <summary>
/// Override this to process the pixel in the second pass of the algorithm
/// </summary>
/// <param name="pixel">
/// The pixel to quantize
/// </param>
/// <param name="pixel">The pixel to quantize</param>
/// <returns>
/// The quantized value
/// </returns>

2
src/ImageProcessorCore/Formats/Quantizers/QuantizedImage.cs → src/ImageProcessorCore/Quantizers/QuantizedImage.cs

@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
namespace ImageProcessorCore.Quantizers
{
using System;
using System.Threading.Tasks;

9
src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs → src/ImageProcessorCore/Quantizers/Wu/Box.cs

@ -1,7 +1,12 @@
namespace ImageProcessorCore.Formats
// <copyright file="Box.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Quantizers
{
/// <summary>
/// A box.
/// Represents a box color cube.
/// </summary>
internal sealed class Box
{

220
src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs → src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs

@ -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);
}
}

3
tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs

@ -7,6 +7,9 @@
using Xunit;
using System.Linq;
using ImageProcessorCore.Quantizers;
public class EncoderDecoderTests : ProcessorTestBase
{
[Fact]

Loading…
Cancel
Save