Browse Source

Merge pull request #705 from SixLabors/js/quantizer-fixes

Png and Wu Quantizer fixes
af/merge-core
James Jackson-South 8 years ago
committed by GitHub
parent
commit
00d869f3ef
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      src/ImageSharp/Common/Extensions/ComparableExtensions.cs
  2. 4
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  3. 19
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  4. 149
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

11
src/ImageSharp/Common/Extensions/ComparableExtensions.cs

@ -137,17 +137,6 @@ namespace SixLabors.ImageSharp
return value; return value;
} }
/// <summary>
/// Converts an <see cref="int"/> to a <see cref="byte"/> first restricting the value between the
/// minimum and maximum allowable ranges.
/// </summary>
/// <param name="value">The <see cref="int"/> this method extends.</param>
/// <returns>The <see cref="byte"/></returns>
public static byte ToByte(this int value)
{
return (byte)value.Clamp(0, 255);
}
/// <summary> /// <summary>
/// Converts an <see cref="float"/> to a <see cref="byte"/> first restricting the value between the /// Converts an <see cref="float"/> to a <see cref="byte"/> first restricting the value between the
/// minimum and maximum allowable ranges. /// minimum and maximum allowable ranges.

4
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -217,7 +217,7 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var metaData = new ImageMetaData(); var metaData = new ImageMetaData();
var pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
this.currentStream = stream; this.currentStream = stream;
this.currentStream.Skip(8); this.currentStream.Skip(8);
Image<TPixel> image = null; Image<TPixel> image = null;
@ -307,7 +307,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public IImageInfo Identify(Stream stream) public IImageInfo Identify(Stream stream)
{ {
var metaData = new ImageMetaData(); var metaData = new ImageMetaData();
var pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
this.currentStream = stream; this.currentStream = stream;
this.currentStream.Skip(8); this.currentStream.Skip(8);
try try

19
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -243,7 +243,7 @@ namespace SixLabors.ImageSharp.Formats.Png
// Collect the indexed pixel data // Collect the indexed pixel data
if (quantized != null) if (quantized != null)
{ {
this.WritePaletteChunk(stream, header, quantized); this.WritePaletteChunk(stream, quantized);
} }
this.WritePhysicalChunk(stream, metaData); this.WritePhysicalChunk(stream, metaData);
@ -555,30 +555,27 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="header">The <see cref="PngHeader"/>.</param>
/// <param name="quantized">The quantized frame.</param> /// <param name="quantized">The quantized frame.</param>
private void WritePaletteChunk<TPixel>(Stream stream, in PngHeader header, QuantizedFrame<TPixel> quantized) private void WritePaletteChunk<TPixel>(Stream stream, QuantizedFrame<TPixel> quantized)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// Grab the palette and write it to the stream. // Grab the palette and write it to the stream.
TPixel[] palette = quantized.Palette; TPixel[] palette = quantized.Palette;
byte pixelCount = palette.Length.ToByte(); int paletteLength = Math.Min(palette.Length, 256);
int colorTableLength = paletteLength * 3;
// Get max colors for bit depth.
int colorTableLength = ImageMaths.GetColorCountForBitDepth(header.BitDepth) * 3;
Rgba32 rgba = default; Rgba32 rgba = default;
bool anyAlpha = false; bool anyAlpha = false;
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength))
using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(pixelCount)) using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength))
{ {
Span<byte> colorTableSpan = colorTable.GetSpan(); Span<byte> colorTableSpan = colorTable.GetSpan();
Span<byte> alphaTableSpan = alphaTable.GetSpan(); Span<byte> alphaTableSpan = alphaTable.GetSpan();
Span<byte> quantizedSpan = quantized.GetPixelSpan(); Span<byte> quantizedSpan = quantized.GetPixelSpan();
for (byte i = 0; i < pixelCount; i++) for (int i = 0; i < paletteLength; i++)
{ {
if (quantizedSpan.IndexOf(i) > -1) if (quantizedSpan.IndexOf((byte)i) > -1)
{ {
int offset = i * 3; int offset = i * 3;
palette[i].ToRgba32(ref rgba); palette[i].ToRgba32(ref rgba);
@ -604,7 +601,7 @@ namespace SixLabors.ImageSharp.Formats.Png
// Write the transparency data // Write the transparency data
if (anyAlpha) if (anyAlpha)
{ {
this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, pixelCount); this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, paletteLength);
} }
} }
} }

149
src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// TODO: The WuFrameQuantizer<TPixel> code is rising several questions: // TODO: The WuFrameQuantizer<TPixel> code is rising several questions:
// - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes ) // - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes ) JS. I'm afraid so.
// - Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case? // - Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case?
// (T, R, G, B, A, M2) could be grouped together! // (T, R, G, B, A, M2) could be grouped together!
// - It's a frequently used class, we need tests! (So we can optimize safely.) There are tests in the original!!! We should just adopt them! // - It's a frequently used class, we need tests! (So we can optimize safely.) There are tests in the original!!! We should just adopt them!
@ -46,12 +46,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary> /// <summary>
/// The index bits. /// The index bits.
/// </summary> /// </summary>
private const int IndexBits = 6; private const int IndexBits = 5;
/// <summary> /// <summary>
/// The index alpha bits. /// The index alpha bits. Keep separate for now to allow easy adjustment.
/// </summary> /// </summary>
private const int IndexAlphaBits = 3; private const int IndexAlphaBits = 5;
/// <summary> /// <summary>
/// The index count. /// The index count.
@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1;
/// <summary> /// <summary>
/// The table length. /// The table length. Now 1185921.
/// </summary> /// </summary>
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
@ -179,18 +179,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (this.palette is null) if (this.palette is null)
{ {
this.palette = new TPixel[this.colors]; this.palette = new TPixel[this.colors];
Span<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
for (int k = 0; k < this.colors; k++) for (int k = 0; k < this.colors; k++)
{ {
this.Mark(ref this.colorCube[k], (byte)k); this.Mark(ref this.colorCube[k], (byte)k);
float weight = Volume(ref this.colorCube[k], this.vwt.GetSpan()); float weight = Volume(ref this.colorCube[k], vwtSpan);
if (MathF.Abs(weight) > Constants.Epsilon) if (MathF.Abs(weight) > Constants.Epsilon)
{ {
float r = Volume(ref this.colorCube[k], this.vmr.GetSpan()); float r = Volume(ref this.colorCube[k], vmrSpan);
float g = Volume(ref this.colorCube[k], this.vmg.GetSpan()); float g = Volume(ref this.colorCube[k], vmgSpan);
float b = Volume(ref this.colorCube[k], this.vmb.GetSpan()); float b = Volume(ref this.colorCube[k], vmbSpan);
float a = Volume(ref this.colorCube[k], this.vma.GetSpan()); float a = Volume(ref this.colorCube[k], vmaSpan);
ref TPixel color = ref this.palette[k]; ref TPixel color = ref this.palette[k];
color.PackFromScaledVector4(new Vector4(r, g, b, a) / weight / 255F); color.PackFromScaledVector4(new Vector4(r, g, b, a) / weight / 255F);
@ -201,57 +207,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return this.palette; return this.palette;
} }
/// <summary>
/// Quantizes the pixel
/// </summary>
/// <param name="rgba">The rgba used to quantize the pixel input</param>
private void QuantizePixel(ref Rgba32 rgba)
{
// Add the color to a 3-D color histogram.
int r = rgba.R >> (8 - IndexBits);
int g = rgba.G >> (8 - IndexBits);
int b = rgba.B >> (8 - IndexBits);
int a = rgba.A >> (8 - IndexAlphaBits);
int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1);
Span<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
Span<float> m2Span = this.m2.GetSpan();
vwtSpan[index]++;
vmrSpan[index] += rgba.R;
vmgSpan[index] += rgba.G;
vmbSpan[index] += rgba.B;
vmaSpan[index] += rgba.A;
var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A);
m2Span[index] += Vector4.Dot(vector, vector);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void FirstPass(ImageFrame<TPixel> source, int width, int height) protected override void FirstPass(ImageFrame<TPixel> source, int width, int height)
{ {
// Build up the 3-D color histogram this.Build3DHistogram(source, width, height);
// Loop through each row
for (int y = 0; y < height; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row);
// And loop through each column
Rgba32 rgba = default;
for (int x = 0; x < width; x++)
{
ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x);
pixel.ToRgba32(ref rgba);
this.QuantizePixel(ref rgba);
}
}
this.Get3DMoments(source.MemoryAllocator); this.Get3DMoments(source.MemoryAllocator);
this.BuildCube(); this.BuildCube();
} }
@ -466,6 +425,54 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
} }
/// <summary>
/// Builds a 3-D color histogram of <c>counts, r/g/b, c^2</c>.
/// </summary>
/// <param name="source">The source data.</param>
/// <param name="width">The width in pixels of the image.</param>
/// <param name="height">The height in pixels of the image.</param>
private void Build3DHistogram(ImageFrame<TPixel> source, int width, int height)
{
Span<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
Span<float> m2Span = this.m2.GetSpan();
// Build up the 3-D color histogram
// Loop through each row
for (int y = 0; y < height; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row);
// And loop through each column
Rgba32 rgba = default;
for (int x = 0; x < width; x++)
{
ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x);
pixel.ToRgba32(ref rgba);
int r = rgba.R >> (8 - IndexBits);
int g = rgba.G >> (8 - IndexBits);
int b = rgba.B >> (8 - IndexBits);
int a = rgba.A >> (8 - IndexAlphaBits);
int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1);
vwtSpan[index]++;
vmrSpan[index] += rgba.R;
vmgSpan[index] += rgba.G;
vmbSpan[index] += rgba.B;
vmaSpan[index] += rgba.A;
var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A);
m2Span[index] += Vector4.Dot(vector, vector);
}
}
}
/// <summary> /// <summary>
/// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
/// </summary> /// </summary>
@ -631,22 +638,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The <see cref="float"/>.</returns> /// <returns>The <see cref="float"/>.</returns>
private float Maximize(ref Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW) private float Maximize(ref Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW)
{ {
long baseR = Bottom(ref cube, direction, this.vmr.GetSpan()); Span<long> vwtSpan = this.vwt.GetSpan();
long baseG = Bottom(ref cube, direction, this.vmg.GetSpan()); Span<long> vmrSpan = this.vmr.GetSpan();
long baseB = Bottom(ref cube, direction, this.vmb.GetSpan()); Span<long> vmgSpan = this.vmg.GetSpan();
long baseA = Bottom(ref cube, direction, this.vma.GetSpan()); Span<long> vmbSpan = this.vmb.GetSpan();
long baseW = Bottom(ref cube, direction, this.vwt.GetSpan()); Span<long> vmaSpan = this.vma.GetSpan();
long baseR = Bottom(ref cube, direction, vmrSpan);
long baseG = Bottom(ref cube, direction, vmgSpan);
long baseB = Bottom(ref cube, direction, vmbSpan);
long baseA = Bottom(ref cube, direction, vmaSpan);
long baseW = Bottom(ref cube, direction, vwtSpan);
float max = 0F; float max = 0F;
cut = -1; cut = -1;
for (int i = first; i < last; i++) for (int i = first; i < last; i++)
{ {
float halfR = baseR + Top(ref cube, direction, i, this.vmr.GetSpan()); float halfR = baseR + Top(ref cube, direction, i, vmrSpan);
float halfG = baseG + Top(ref cube, direction, i, this.vmg.GetSpan()); float halfG = baseG + Top(ref cube, direction, i, vmgSpan);
float halfB = baseB + Top(ref cube, direction, i, this.vmb.GetSpan()); float halfB = baseB + Top(ref cube, direction, i, vmbSpan);
float halfA = baseA + Top(ref cube, direction, i, this.vma.GetSpan()); float halfA = baseA + Top(ref cube, direction, i, vmaSpan);
float halfW = baseW + Top(ref cube, direction, i, this.vwt.GetSpan()); float halfW = baseW + Top(ref cube, direction, i, vwtSpan);
if (MathF.Abs(halfW) < Constants.Epsilon) if (MathF.Abs(halfW) < Constants.Epsilon)
{ {

Loading…
Cancel
Save