From eaab4f072817329da28ffc3a33f734081ed99f9e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 17 Sep 2018 23:02:02 +0100 Subject: [PATCH] Fix png quantization and reduce Wu memory pressure --- .../Common/Extensions/ComparableExtensions.cs | 11 -- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 4 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 19 ++-- .../Quantization/WuFrameQuantizer{TPixel}.cs | 101 +++++++++--------- 4 files changed, 61 insertions(+), 74 deletions(-) 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..5cd3d53ea 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -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. /// - private const int IndexAlphaBits = 3; + private const int IndexAlphaBits = 5; /// /// The index count. @@ -201,57 +201,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 +419,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) + { + // Build up the 3-D color histogram + // Loop through each row + 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(); + + 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. ///