diff --git a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs
index d6dade770..1b0f8ad09 100644
--- a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs
+++ b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs
@@ -137,17 +137,6 @@ namespace SixLabors.ImageSharp
return value;
}
- ///
- /// Converts an to a first restricting the value between the
- /// minimum and maximum allowable ranges.
- ///
- /// The this method extends.
- /// The
- public static byte ToByte(this int value)
- {
- return (byte)value.Clamp(0, 255);
- }
-
///
/// Converts an to a first restricting the value between the
/// minimum and maximum allowable ranges.
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 7837c2da5..928192701 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -217,7 +217,7 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : struct, IPixel
{
var metaData = new ImageMetaData();
- var pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
+ PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
this.currentStream = stream;
this.currentStream.Skip(8);
Image image = null;
@@ -307,7 +307,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public IImageInfo Identify(Stream stream)
{
var metaData = new ImageMetaData();
- var pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
+ PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
this.currentStream = stream;
this.currentStream.Skip(8);
try
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 0b47c1c63..e4d2fc510 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -243,7 +243,7 @@ namespace SixLabors.ImageSharp.Formats.Png
// Collect the indexed pixel data
if (quantized != null)
{
- this.WritePaletteChunk(stream, header, quantized);
+ this.WritePaletteChunk(stream, quantized);
}
this.WritePhysicalChunk(stream, metaData);
@@ -555,30 +555,27 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// The pixel format.
/// The containing image data.
- /// The .
/// The quantized frame.
- private void WritePaletteChunk(Stream stream, in PngHeader header, QuantizedFrame quantized)
+ private void WritePaletteChunk(Stream stream, QuantizedFrame quantized)
where TPixel : struct, IPixel
{
// Grab the palette and write it to the stream.
TPixel[] palette = quantized.Palette;
- byte pixelCount = palette.Length.ToByte();
-
- // Get max colors for bit depth.
- int colorTableLength = ImageMaths.GetColorCountForBitDepth(header.BitDepth) * 3;
+ int paletteLength = Math.Min(palette.Length, 256);
+ int colorTableLength = paletteLength * 3;
Rgba32 rgba = default;
bool anyAlpha = false;
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength))
- using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(pixelCount))
+ using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength))
{
Span colorTableSpan = colorTable.GetSpan();
Span alphaTableSpan = alphaTable.GetSpan();
Span 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;
palette[i].ToRgba32(ref rgba);
@@ -604,7 +601,7 @@ namespace SixLabors.ImageSharp.Formats.Png
// Write the transparency data
if (anyAlpha)
{
- this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, pixelCount);
+ this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, paletteLength);
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
index d71221b9d..13bc057da 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
@@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : struct, IPixel
{
// TODO: The WuFrameQuantizer 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?
// (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!
@@ -46,12 +46,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
///
/// The index bits.
///
- private const int IndexBits = 6;
+ private const int IndexBits = 5;
///
- /// The index alpha bits.
+ /// The index alpha bits. Keep separate for now to allow easy adjustment.
///
- private const int IndexAlphaBits = 3;
+ private const int IndexAlphaBits = 5;
///
/// The index count.
@@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1;
///
- /// The table length.
+ /// The table length. Now 1185921.
///
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
@@ -179,18 +179,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (this.palette is null)
{
this.palette = new TPixel[this.colors];
+ Span vwtSpan = this.vwt.GetSpan();
+ Span vmrSpan = this.vmr.GetSpan();
+ Span vmgSpan = this.vmg.GetSpan();
+ Span vmbSpan = this.vmb.GetSpan();
+ Span vmaSpan = this.vma.GetSpan();
+
for (int k = 0; k < this.colors; 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)
{
- float r = Volume(ref this.colorCube[k], this.vmr.GetSpan());
- float g = Volume(ref this.colorCube[k], this.vmg.GetSpan());
- float b = Volume(ref this.colorCube[k], this.vmb.GetSpan());
- float a = Volume(ref this.colorCube[k], this.vma.GetSpan());
+ float r = Volume(ref this.colorCube[k], vmrSpan);
+ float g = Volume(ref this.colorCube[k], vmgSpan);
+ float b = Volume(ref this.colorCube[k], vmbSpan);
+ float a = Volume(ref this.colorCube[k], vmaSpan);
ref TPixel color = ref this.palette[k];
color.PackFromScaledVector4(new Vector4(r, g, b, a) / weight / 255F);
@@ -201,57 +207,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return this.palette;
}
- ///
- /// Quantizes the pixel
- ///
- /// The rgba used to quantize the pixel input
- 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 vwtSpan = this.vwt.GetSpan();
- Span vmrSpan = this.vmr.GetSpan();
- Span vmgSpan = this.vmg.GetSpan();
- Span vmbSpan = this.vmb.GetSpan();
- Span vmaSpan = this.vma.GetSpan();
- Span 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);
- }
-
///
protected override void FirstPass(ImageFrame source, int width, int height)
{
- // Build up the 3-D color histogram
- // Loop through each row
- for (int y = 0; y < height; y++)
- {
- Span 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.Build3DHistogram(source, width, height);
this.Get3DMoments(source.MemoryAllocator);
this.BuildCube();
}
@@ -466,6 +425,54 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
}
+ ///
+ /// Builds a 3-D color histogram of counts, r/g/b, c^2.
+ ///
+ /// The source data.
+ /// The width in pixels of the image.
+ /// The height in pixels of the image.
+ private void Build3DHistogram(ImageFrame source, int width, int height)
+ {
+ Span vwtSpan = this.vwt.GetSpan();
+ Span vmrSpan = this.vmr.GetSpan();
+ Span vmgSpan = this.vmg.GetSpan();
+ Span vmbSpan = this.vmb.GetSpan();
+ Span vmaSpan = this.vma.GetSpan();
+ Span m2Span = this.m2.GetSpan();
+
+ // Build up the 3-D color histogram
+ // Loop through each row
+ for (int y = 0; y < height; y++)
+ {
+ Span 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);
+ }
+ }
+ }
+
///
/// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
///
@@ -631,22 +638,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The .
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());
- long baseG = Bottom(ref cube, direction, this.vmg.GetSpan());
- long baseB = Bottom(ref cube, direction, this.vmb.GetSpan());
- long baseA = Bottom(ref cube, direction, this.vma.GetSpan());
- long baseW = Bottom(ref cube, direction, this.vwt.GetSpan());
+ Span vwtSpan = this.vwt.GetSpan();
+ Span vmrSpan = this.vmr.GetSpan();
+ Span vmgSpan = this.vmg.GetSpan();
+ Span vmbSpan = this.vmb.GetSpan();
+ Span 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;
cut = -1;
for (int i = first; i < last; i++)
{
- float halfR = baseR + Top(ref cube, direction, i, this.vmr.GetSpan());
- float halfG = baseG + Top(ref cube, direction, i, this.vmg.GetSpan());
- float halfB = baseB + Top(ref cube, direction, i, this.vmb.GetSpan());
- float halfA = baseA + Top(ref cube, direction, i, this.vma.GetSpan());
- float halfW = baseW + Top(ref cube, direction, i, this.vwt.GetSpan());
+ float halfR = baseR + Top(ref cube, direction, i, vmrSpan);
+ float halfG = baseG + Top(ref cube, direction, i, vmgSpan);
+ float halfB = baseB + Top(ref cube, direction, i, vmbSpan);
+ float halfA = baseA + Top(ref cube, direction, i, vmaSpan);
+ float halfW = baseW + Top(ref cube, direction, i, vwtSpan);
if (MathF.Abs(halfW) < Constants.Epsilon)
{