From a36be6c76a2dc69c66d40f3937ec263fd39c06ab Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Mon, 10 Apr 2017 16:28:39 +0100 Subject: [PATCH 1/4] add alpha pallette if any alpha channel set --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 36 +++++++++----------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 552673179..1e2203e36 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -495,35 +495,31 @@ namespace ImageSharp.Formats // Grab the palette and write it to the stream. TColor[] palette = quantized.Palette; - int pixelCount = palette.Length; - List transparentPixels = new List(); + byte pixelCount = (byte)palette.Length; // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; byte[] colorTable = ArrayPool.Shared.Rent(colorTableLength); + byte[] alphaTable = ArrayPool.Shared.Rent(pixelCount); byte[] bytes = ArrayPool.Shared.Rent(4); - + bool anyAlpha = false; try { - for (int i = 0; i < pixelCount; i++) + for (byte i = 0; i < pixelCount; i++) { - int offset = i * 3; - palette[i].ToXyzwBytes(bytes, 0); + if (quantized.Pixels.Contains(i)) + { + int offset = i * 3; + palette[i].ToXyzwBytes(bytes, 0); - int alpha = bytes[3]; + byte alpha = bytes[3]; - colorTable[offset] = bytes[0]; - colorTable[offset + 1] = bytes[1]; - colorTable[offset + 2] = bytes[2]; + colorTable[offset] = bytes[0]; + colorTable[offset + 1] = bytes[1]; + colorTable[offset + 2] = bytes[2]; - if (alpha < 255 && alpha <= this.options.Threshold) - { - // Ensure the index is actually being used in our array. - // I'd like to find a faster way of doing this. - if (quantized.Pixels.Contains((byte)i)) - { - transparentPixels.Add((byte)i); - } + anyAlpha = anyAlpha || alpha < 255; + alphaTable[i] = alpha; } } @@ -536,9 +532,9 @@ namespace ImageSharp.Formats } // Write the transparency data - if (transparentPixels.Any()) + if (anyAlpha) { - this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray()); + this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount); } return quantized; From ba81ec0e2e704e5a96a06ff288900381966224a7 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 11 Apr 2017 09:20:00 +0100 Subject: [PATCH 2/4] remove theashold alpha theshold doesn't make sense for palette indexed colors. --- .../Formats/Png/IPngEncoderOptions.cs | 5 --- .../Formats/Png/PngEncoderOptions.cs | 5 --- .../Formats/Png/PngSmokeTests.cs | 40 +++++++++++++++++++ 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs index 0008080d3..3f5b33c0c 100644 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -40,11 +40,6 @@ namespace ImageSharp.Formats /// IQuantizer Quantizer { get; } - /// - /// Gets the transparency threshold. - /// - byte Threshold { get; } - /// /// Gets a value indicating whether this instance should write /// gamma information to the stream. diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs index 2891f1974..3c5bb1f62 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs @@ -57,11 +57,6 @@ namespace ImageSharp.Formats /// public IQuantizer Quantizer { get; set; } - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 0; - /// /// Gets or sets a value indicating whether this instance should write /// gamma information to the stream. The default value is false. diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 882f903d6..4bdfd288c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -58,6 +58,46 @@ namespace ImageSharp.Tests.Formats.Png } } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Color)] + public void CanSaveIndexedPngTwice(TestImageProvider provider) + where TColor : struct, IPixel + { + // does saving a file then repoening mean both files are identical??? + using (Image source = provider.GetImage()) + using (MemoryStream ms = new MemoryStream()) + { + // image.Save(provider.Utility.GetTestOutputFileName("bmp")); + source.MetaData.Quality = 256; + source.Save(ms, new PngEncoder()); + ms.Position = 0; + using (Image img1 = Image.Load(ms, new PngDecoder())) + { + using (MemoryStream ms2 = new MemoryStream()) + { + img1.Save(ms2, new PngEncoder()); + ms2.Position = 0; + using (Image img2 = Image.Load(ms2, new PngDecoder())) + { + using (PixelAccessor pixels1 = img1.Lock()) + using (PixelAccessor pixels2 = img2.Lock()) + { + for (int y = 0; y < img1.Height; y++) + { + for (int x = 0; x < img1.Width; x++) + { + Assert.Equal(pixels1[x, y], pixels2[x, y]); + } + } + } + // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); + ImageComparer.CheckSimilarity(source, img2); + } + } + } + } + } + [Theory] [WithTestPatternImages(300, 300, PixelTypes.All)] public void Resize(TestImageProvider provider) From b4fba454d59c49aeac5e488d4ee534299442c725 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 11 Apr 2017 10:24:48 +0100 Subject: [PATCH 3/4] re-enstate threashold re-enstate the pngencoderoptions threashold but how set the default to 255 so all alpha values are retained. --- src/ImageSharp/Formats/Png/IPngEncoderOptions.cs | 5 +++++ src/ImageSharp/Formats/Png/PngEncoderCore.cs | 7 ++++++- src/ImageSharp/Formats/Png/PngEncoderOptions.cs | 5 +++++ tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs | 13 ++++++++----- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs index 3f5b33c0c..0008080d3 100644 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -40,6 +40,11 @@ namespace ImageSharp.Formats /// IQuantizer Quantizer { get; } + /// + /// Gets the transparency threshold. + /// + byte Threshold { get; } + /// /// Gets a value indicating whether this instance should write /// gamma information to the stream. diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 1e2203e36..e6ade1df0 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -495,7 +495,7 @@ namespace ImageSharp.Formats // Grab the palette and write it to the stream. TColor[] palette = quantized.Palette; - byte pixelCount = (byte)palette.Length; + byte pixelCount = palette.Length.ToByte(); // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; @@ -518,6 +518,11 @@ namespace ImageSharp.Formats colorTable[offset + 1] = bytes[1]; colorTable[offset + 2] = bytes[2]; + if (alpha > this.options.Threshold) + { + alpha = 255; + } + anyAlpha = anyAlpha || alpha < 255; alphaTable[i] = alpha; } diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs index 3c5bb1f62..90175c6d6 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs @@ -57,6 +57,11 @@ namespace ImageSharp.Formats /// public IQuantizer Quantizer { get; set; } + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 255; + /// /// Gets or sets a value indicating whether this instance should write /// gamma information to the stream. The default value is false. diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 4bdfd288c..2ba570453 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -13,6 +13,7 @@ namespace ImageSharp.Tests.Formats.Png using ImageSharp.Formats; using System.Linq; using ImageSharp.IO; + using System.Numerics; public class PngSmokeTests { @@ -67,15 +68,19 @@ namespace ImageSharp.Tests.Formats.Png using (Image source = provider.GetImage()) using (MemoryStream ms = new MemoryStream()) { - // image.Save(provider.Utility.GetTestOutputFileName("bmp")); source.MetaData.Quality = 256; - source.Save(ms, new PngEncoder()); + source.Save(ms, new PngEncoder(), new PngEncoderOptions { + Threshold = 200 + }); ms.Position = 0; using (Image img1 = Image.Load(ms, new PngDecoder())) { using (MemoryStream ms2 = new MemoryStream()) { - img1.Save(ms2, new PngEncoder()); + img1.Save(ms2, new PngEncoder(), new PngEncoderOptions + { + Threshold = 200 + }); ms2.Position = 0; using (Image img2 = Image.Load(ms2, new PngDecoder())) { @@ -90,8 +95,6 @@ namespace ImageSharp.Tests.Formats.Png } } } - // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); - ImageComparer.CheckSimilarity(source, img2); } } } From 44d1aae2b367150c5c895c4c9cec26f222ee4119 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 11 Apr 2017 10:42:14 +0100 Subject: [PATCH 4/4] return alpha array to array pool --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index e6ade1df0..469c459fb 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -529,19 +529,20 @@ namespace ImageSharp.Formats } this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength); + + // Write the transparency data + if (anyAlpha) + { + this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount); + } } finally { ArrayPool.Shared.Return(colorTable); + ArrayPool.Shared.Return(alphaTable); ArrayPool.Shared.Return(bytes); } - // Write the transparency data - if (anyAlpha) - { - this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount); - } - return quantized; }