Browse Source

Merge pull request #171 from JimBobSquarePants/tocsoft/png-indexed-alpha

Fix alpha channel generation for indexed PNGs
af/merge-core
Scott Williams 9 years ago
committed by GitHub
parent
commit
c14dedd290
  1. 46
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  2. 2
      src/ImageSharp/Formats/Png/PngEncoderOptions.cs
  3. 43
      tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs

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

@ -495,52 +495,54 @@ namespace ImageSharp.Formats
// Grab the palette and write it to the stream.
TColor[] palette = quantized.Palette;
int pixelCount = palette.Length;
List<byte> transparentPixels = new List<byte>();
byte pixelCount = palette.Length.ToByte();
// Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
byte[] colorTable = ArrayPool<byte>.Shared.Rent(colorTableLength);
byte[] alphaTable = ArrayPool<byte>.Shared.Rent(pixelCount);
byte[] bytes = ArrayPool<byte>.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))
if (alpha > this.options.Threshold)
{
transparentPixels.Add((byte)i);
alpha = 255;
}
anyAlpha = anyAlpha || alpha < 255;
alphaTable[i] = alpha;
}
}
this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength);
// Write the transparency data
if (anyAlpha)
{
this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount);
}
}
finally
{
ArrayPool<byte>.Shared.Return(colorTable);
ArrayPool<byte>.Shared.Return(alphaTable);
ArrayPool<byte>.Shared.Return(bytes);
}
// Write the transparency data
if (transparentPixels.Any())
{
this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray());
}
return quantized;
}

2
src/ImageSharp/Formats/Png/PngEncoderOptions.cs

@ -60,7 +60,7 @@ namespace ImageSharp.Formats
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 0;
public byte Threshold { get; set; } = 255;
/// <summary>
/// Gets or sets a value indicating whether this instance should write

43
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
{
@ -58,6 +59,48 @@ namespace ImageSharp.Tests.Formats.Png
}
}
[Theory]
[WithTestPatternImages(100, 100, PixelTypes.Color)]
public void CanSaveIndexedPngTwice<TColor>(TestImageProvider<TColor> provider)
where TColor : struct, IPixel<TColor>
{
// does saving a file then repoening mean both files are identical???
using (Image<TColor> source = provider.GetImage())
using (MemoryStream ms = new MemoryStream())
{
source.MetaData.Quality = 256;
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(), new PngEncoderOptions
{
Threshold = 200
});
ms2.Position = 0;
using (Image img2 = Image.Load(ms2, new PngDecoder()))
{
using (PixelAccessor<Color> pixels1 = img1.Lock())
using (PixelAccessor<Color> 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]);
}
}
}
}
}
}
}
}
[Theory]
[WithTestPatternImages(300, 300, PixelTypes.All)]
public void Resize<TColor>(TestImageProvider<TColor> provider)

Loading…
Cancel
Save