Browse Source

Merge pull request #764 from devedse/master

[WIP] Preserving isTrans data
pull/780/head
James Jackson-South 7 years ago
committed by GitHub
parent
commit
0483efaa0e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      src/ImageSharp/Formats/Png/PngChunkType.cs
  2. 95
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  3. 55
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  4. 32
      src/ImageSharp/Formats/Png/PngMetaData.cs
  5. 16
      src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
  6. 2
      tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs
  7. 2
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
  8. 67
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  9. 3
      tests/ImageSharp.Tests/TestImages.cs
  10. BIN
      tests/Images/Input/Png/gray-2-tRNS.png
  11. BIN
      tests/Images/Input/Png/gray-4-tRNS.png
  12. BIN
      tests/Images/Input/Png/gray-8-tRNS.png

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

@ -56,10 +56,10 @@ namespace SixLabors.ImageSharp.Formats.Png
Text = 0x74455874U, Text = 0x74455874U,
/// <summary> /// <summary>
/// This chunk specifies that the image uses simple transparency: /// The tRNS chunk specifies that the image uses simple transparency:
/// either alpha values associated with palette entries (for indexed-color images) /// either alpha values associated with palette entries (for indexed-color images)
/// or a single transparent color (for grayscale and true color images). /// or a single transparent color (for grayscale and true color images).
/// </summary> /// </summary>
PaletteAlpha = 0x74524E53U Transparency = 0x74524E53U
} }
} }

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

@ -124,31 +124,6 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
private PngColorType pngColorType; private PngColorType pngColorType;
/// <summary>
/// Represents any color in an 8 bit Rgb24 encoded png that should be transparent
/// </summary>
private Rgb24 rgb24Trans;
/// <summary>
/// Represents any color in a 16 bit Rgb24 encoded png that should be transparent
/// </summary>
private Rgb48 rgb48Trans;
/// <summary>
/// Represents any color in an 8 bit grayscale encoded png that should be transparent
/// </summary>
private byte luminanceTrans;
/// <summary>
/// Represents any color in a 16 bit grayscale encoded png that should be transparent
/// </summary>
private ushort luminance16Trans;
/// <summary>
/// Whether the image has transparency chunk and markers were decoded
/// </summary>
private bool hasTrans;
/// <summary> /// <summary>
/// The next chunk of data to return /// The next chunk of data to return
/// </summary> /// </summary>
@ -213,7 +188,7 @@ namespace SixLabors.ImageSharp.Formats.Png
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk)) using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
{ {
deframeStream.AllocateNewBytes(chunk.Length); deframeStream.AllocateNewBytes(chunk.Length);
this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame); this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame, pngMetaData);
} }
break; break;
@ -222,11 +197,11 @@ namespace SixLabors.ImageSharp.Formats.Png
Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length); Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length);
this.palette = pal; this.palette = pal;
break; break;
case PngChunkType.PaletteAlpha: case PngChunkType.Transparency:
byte[] alpha = new byte[chunk.Length]; byte[] alpha = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length); Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length);
this.paletteAlpha = alpha; this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha); this.AssignTransparentMarkers(alpha, pngMetaData);
break; break;
case PngChunkType.Text: case PngChunkType.Text:
this.ReadTextChunk(metaData, chunk.Data.Array.AsSpan(0, chunk.Length)); this.ReadTextChunk(metaData, chunk.Data.Array.AsSpan(0, chunk.Length));
@ -496,16 +471,17 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param> /// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param>
/// <param name="image"> The pixel data.</param> /// <param name="image"> The pixel data.</param>
private void ReadScanlines<TPixel>(Stream dataStream, ImageFrame<TPixel> image) /// <param name="pngMetaData">The png meta data</param>
private void ReadScanlines<TPixel>(Stream dataStream, ImageFrame<TPixel> image, PngMetaData pngMetaData)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) if (this.header.InterlaceMethod == PngInterlaceMode.Adam7)
{ {
this.DecodeInterlacedPixelData(dataStream, image); this.DecodeInterlacedPixelData(dataStream, image, pngMetaData);
} }
else else
{ {
this.DecodePixelData(dataStream, image); this.DecodePixelData(dataStream, image, pngMetaData);
} }
} }
@ -515,7 +491,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="compressedStream">The compressed pixel data stream.</param> /// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="image">The image to decode to.</param> /// <param name="image">The image to decode to.</param>
private void DecodePixelData<TPixel>(Stream compressedStream, ImageFrame<TPixel> image) /// <param name="pngMetaData">The png meta data</param>
private void DecodePixelData<TPixel>(Stream compressedStream, ImageFrame<TPixel> image, PngMetaData pngMetaData)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
while (this.currentRow < this.header.Height) while (this.currentRow < this.header.Height)
@ -555,7 +532,7 @@ namespace SixLabors.ImageSharp.Formats.Png
throw new ImageFormatException("Unknown filter type."); throw new ImageFormatException("Unknown filter type.");
} }
this.ProcessDefilteredScanline(scanlineSpan, image); this.ProcessDefilteredScanline(scanlineSpan, image, pngMetaData);
this.SwapBuffers(); this.SwapBuffers();
this.currentRow++; this.currentRow++;
@ -569,7 +546,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="compressedStream">The compressed pixel data stream.</param> /// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="image">The current image.</param> /// <param name="image">The current image.</param>
private void DecodeInterlacedPixelData<TPixel>(Stream compressedStream, ImageFrame<TPixel> image) /// <param name="pngMetaData">The png meta data</param>
private void DecodeInterlacedPixelData<TPixel>(Stream compressedStream, ImageFrame<TPixel> image, PngMetaData pngMetaData)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
while (true) while (true)
@ -626,7 +604,7 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
Span<TPixel> rowSpan = image.GetPixelRowSpan(this.currentRow); Span<TPixel> rowSpan = image.GetPixelRowSpan(this.currentRow);
this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, Adam7.FirstColumn[this.pass], Adam7.ColumnIncrement[this.pass]); this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetaData, Adam7.FirstColumn[this.pass], Adam7.ColumnIncrement[this.pass]);
this.SwapBuffers(); this.SwapBuffers();
@ -654,7 +632,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="defilteredScanline">The de-filtered scanline</param> /// <param name="defilteredScanline">The de-filtered scanline</param>
/// <param name="pixels">The image</param> /// <param name="pixels">The image</param>
private void ProcessDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScanline, ImageFrame<TPixel> pixels) /// <param name="pngMetaData">The png meta data</param>
private void ProcessDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScanline, ImageFrame<TPixel> pixels, PngMetaData pngMetaData)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
Span<TPixel> rowSpan = pixels.GetPixelRowSpan(this.currentRow); Span<TPixel> rowSpan = pixels.GetPixelRowSpan(this.currentRow);
@ -674,9 +653,9 @@ namespace SixLabors.ImageSharp.Formats.Png
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
this.hasTrans, pngMetaData.HasTrans,
this.luminance16Trans, pngMetaData.TransparentGray16.GetValueOrDefault(),
this.luminanceTrans); pngMetaData.TransparentGray8.GetValueOrDefault());
break; break;
@ -708,9 +687,9 @@ namespace SixLabors.ImageSharp.Formats.Png
rowSpan, rowSpan,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample, this.bytesPerSample,
this.hasTrans, pngMetaData.HasTrans,
this.rgb48Trans, pngMetaData.TransparentRgb48.GetValueOrDefault(),
this.rgb24Trans); pngMetaData.TransparentRgb24.GetValueOrDefault());
break; break;
@ -735,9 +714,10 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="defilteredScanline">The de-filtered scanline</param> /// <param name="defilteredScanline">The de-filtered scanline</param>
/// <param name="rowSpan">The current image row.</param> /// <param name="rowSpan">The current image row.</param>
/// <param name="pngMetaData">The png meta data</param>
/// <param name="pixelOffset">The column start index. Always 0 for none interlaced images.</param> /// <param name="pixelOffset">The column start index. Always 0 for none interlaced images.</param>
/// <param name="increment">The column increment. Always 1 for none interlaced images.</param> /// <param name="increment">The column increment. Always 1 for none interlaced images.</param>
private void ProcessInterlacedDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScanline, Span<TPixel> rowSpan, int pixelOffset = 0, int increment = 1) private void ProcessInterlacedDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScanline, Span<TPixel> rowSpan, PngMetaData pngMetaData, int pixelOffset = 0, int increment = 1)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// Trim the first marker byte from the buffer // Trim the first marker byte from the buffer
@ -757,9 +737,9 @@ namespace SixLabors.ImageSharp.Formats.Png
rowSpan, rowSpan,
pixelOffset, pixelOffset,
increment, increment,
this.hasTrans, pngMetaData.HasTrans,
this.luminance16Trans, pngMetaData.TransparentGray16.GetValueOrDefault(),
this.luminanceTrans); pngMetaData.TransparentGray8.GetValueOrDefault());
break; break;
@ -796,9 +776,9 @@ namespace SixLabors.ImageSharp.Formats.Png
increment, increment,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample, this.bytesPerSample,
this.hasTrans, pngMetaData.HasTrans,
this.rgb48Trans, pngMetaData.TransparentRgb48.GetValueOrDefault(),
this.rgb24Trans); pngMetaData.TransparentRgb24.GetValueOrDefault());
break; break;
@ -822,7 +802,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Decodes and assigns marker colors that identify transparent pixels in non indexed images /// Decodes and assigns marker colors that identify transparent pixels in non indexed images
/// </summary> /// </summary>
/// <param name="alpha">The alpha tRNS array</param> /// <param name="alpha">The alpha tRNS array</param>
private void AssignTransparentMarkers(ReadOnlySpan<byte> alpha) /// <param name="pngMetaData">The png meta data</param>
private void AssignTransparentMarkers(ReadOnlySpan<byte> alpha, PngMetaData pngMetaData)
{ {
if (this.pngColorType == PngColorType.Rgb) if (this.pngColorType == PngColorType.Rgb)
{ {
@ -834,16 +815,16 @@ namespace SixLabors.ImageSharp.Formats.Png
ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2)); ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2));
ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2)); ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2));
this.rgb48Trans = new Rgb48(rc, gc, bc); pngMetaData.TransparentRgb48 = new Rgb48(rc, gc, bc);
this.hasTrans = true; pngMetaData.HasTrans = true;
return; return;
} }
byte r = ReadByteLittleEndian(alpha, 0); byte r = ReadByteLittleEndian(alpha, 0);
byte g = ReadByteLittleEndian(alpha, 2); byte g = ReadByteLittleEndian(alpha, 2);
byte b = ReadByteLittleEndian(alpha, 4); byte b = ReadByteLittleEndian(alpha, 4);
this.rgb24Trans = new Rgb24(r, g, b); pngMetaData.TransparentRgb24 = new Rgb24(r, g, b);
this.hasTrans = true; pngMetaData.HasTrans = true;
} }
} }
else if (this.pngColorType == PngColorType.Grayscale) else if (this.pngColorType == PngColorType.Grayscale)
@ -852,14 +833,14 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
if (this.header.BitDepth == 16) if (this.header.BitDepth == 16)
{ {
this.luminance16Trans = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2)); pngMetaData.TransparentGray16 = new Gray16(BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2)));
} }
else else
{ {
this.luminanceTrans = ReadByteLittleEndian(alpha, 0); pngMetaData.TransparentGray8 = new Gray8(ReadByteLittleEndian(alpha, 0));
} }
this.hasTrans = true; pngMetaData.HasTrans = true;
} }
} }
} }

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

@ -290,6 +290,11 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WritePaletteChunk(stream, quantized); this.WritePaletteChunk(stream, quantized);
} }
if (pngMetaData.HasTrans)
{
this.WriteTransparencyChunk(stream, pngMetaData);
}
this.WritePhysicalChunk(stream, metaData); this.WritePhysicalChunk(stream, metaData);
this.WriteGammaChunk(stream); this.WriteGammaChunk(stream);
this.WriteExifChunk(stream, metaData); this.WriteExifChunk(stream, metaData);
@ -326,7 +331,6 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.pngColorType.Equals(PngColorType.Grayscale)) if (this.pngColorType.Equals(PngColorType.Grayscale))
{ {
// TODO: Research and add support for grayscale plus tRNS
if (this.use16Bit) if (this.use16Bit)
{ {
// 16 bit grayscale // 16 bit grayscale
@ -701,7 +705,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, paletteLength); this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength);
} }
} }
} }
@ -749,6 +753,51 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
} }
/// <summary>
/// Writes the transparency chunk to the stream
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="pngMetaData">The image meta data.</param>
private void WriteTransparencyChunk(Stream stream, PngMetaData pngMetaData)
{
Span<byte> alpha = this.chunkDataBuffer.AsSpan();
if (pngMetaData.ColorType.Equals(PngColorType.Rgb))
{
if (pngMetaData.TransparentRgb48.HasValue && this.use16Bit)
{
Rgb48 rgb = pngMetaData.TransparentRgb48.Value;
BinaryPrimitives.WriteUInt16LittleEndian(alpha, rgb.R);
BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(2, 2), rgb.G);
BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(4, 2), rgb.B);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6);
}
else if (pngMetaData.TransparentRgb24.HasValue)
{
alpha.Clear();
Rgb24 rgb = pngMetaData.TransparentRgb24.Value;
alpha[1] = rgb.R;
alpha[3] = rgb.G;
alpha[5] = rgb.B;
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6);
}
}
else if (pngMetaData.ColorType.Equals(PngColorType.Grayscale))
{
if (pngMetaData.TransparentGray16.HasValue && this.use16Bit)
{
BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetaData.TransparentGray16.Value.PackedValue);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2);
}
else if (pngMetaData.TransparentGray8.HasValue)
{
alpha.Clear();
alpha[1] = pngMetaData.TransparentGray8.Value.PackedValue;
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2);
}
}
}
/// <summary> /// <summary>
/// Writes the pixel information to the stream. /// Writes the pixel information to the stream.
/// </summary> /// </summary>
@ -947,4 +996,4 @@ namespace SixLabors.ImageSharp.Formats.Png
return scanlineLength / mod; return scanlineLength / mod;
} }
} }
} }

32
src/ImageSharp/Formats/Png/PngMetaData.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png namespace SixLabors.ImageSharp.Formats.Png
{ {
/// <summary> /// <summary>
@ -24,6 +26,11 @@ namespace SixLabors.ImageSharp.Formats.Png
this.BitDepth = other.BitDepth; this.BitDepth = other.BitDepth;
this.ColorType = other.ColorType; this.ColorType = other.ColorType;
this.Gamma = other.Gamma; this.Gamma = other.Gamma;
this.HasTrans = other.HasTrans;
this.TransparentGray8 = other.TransparentGray8;
this.TransparentGray16 = other.TransparentGray16;
this.TransparentRgb24 = other.TransparentRgb24;
this.TransparentRgb48 = other.TransparentRgb48;
} }
/// <summary> /// <summary>
@ -42,6 +49,31 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
public float Gamma { get; set; } public float Gamma { get; set; }
/// <summary>
/// Gets or sets the Rgb 24 transparent color. This represents any color in an 8 bit Rgb24 encoded png that should be transparent
/// </summary>
public Rgb24? TransparentRgb24 { get; set; }
/// <summary>
/// Gets or sets the Rgb 48 transparent color. This represents any color in a 16 bit Rgb24 encoded png that should be transparent
/// </summary>
public Rgb48? TransparentRgb48 { get; set; }
/// <summary>
/// Gets or sets the 8 bit grayscale transparent color. This represents any color in an 8 bit grayscale encoded png that should be transparent
/// </summary>
public Gray8? TransparentGray8 { get; set; }
/// <summary>
/// Gets or sets the 16 bit grayscale transparent color. This represents any color in a 16 bit grayscale encoded png that should be transparent
/// </summary>
public Gray16? TransparentGray16 { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the image has transparency chunk and markers were decoded
/// </summary>
public bool HasTrans { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public IDeepCloneable DeepClone() => new PngMetaData(this); public IDeepCloneable DeepClone() => new PngMetaData(this);
} }

16
src/ImageSharp/Formats/Png/PngScanlineProcessor.cs

@ -20,8 +20,8 @@ namespace SixLabors.ImageSharp.Formats.Png
ReadOnlySpan<byte> scanlineSpan, ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
bool hasTrans, bool hasTrans,
ushort luminance16Trans, Gray16 luminance16Trans,
byte luminanceTrans) Gray8 luminanceTrans)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
TPixel pixel = default; TPixel pixel = default;
@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Png
rgba64.R = luminance; rgba64.R = luminance;
rgba64.G = luminance; rgba64.G = luminance;
rgba64.B = luminance; rgba64.B = luminance;
rgba64.A = luminance.Equals(luminance16Trans) ? ushort.MinValue : ushort.MaxValue; rgba64.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue;
pixel.FromRgba64(rgba64); pixel.FromRgba64(rgba64);
Unsafe.Add(ref rowSpanRef, x) = pixel; Unsafe.Add(ref rowSpanRef, x) = pixel;
@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
else else
{ {
byte scaledLuminanceTrans = (byte)(luminanceTrans * scaleFactor); byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor);
Rgba32 rgba32 = default; Rgba32 rgba32 = default;
for (int x = 0; x < header.Width; x++) for (int x = 0; x < header.Width; x++)
{ {
@ -93,8 +93,8 @@ namespace SixLabors.ImageSharp.Formats.Png
int pixelOffset, int pixelOffset,
int increment, int increment,
bool hasTrans, bool hasTrans,
ushort luminance16Trans, Gray16 luminance16Trans,
byte luminanceTrans) Gray8 luminanceTrans)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
TPixel pixel = default; TPixel pixel = default;
@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Formats.Png
rgba64.R = luminance; rgba64.R = luminance;
rgba64.G = luminance; rgba64.G = luminance;
rgba64.B = luminance; rgba64.B = luminance;
rgba64.A = luminance.Equals(luminance16Trans) ? ushort.MinValue : ushort.MaxValue; rgba64.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue;
pixel.FromRgba64(rgba64); pixel.FromRgba64(rgba64);
Unsafe.Add(ref rowSpanRef, x) = pixel; Unsafe.Add(ref rowSpanRef, x) = pixel;
@ -143,7 +143,7 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
else else
{ {
byte scaledLuminanceTrans = (byte)(luminanceTrans * scaleFactor); byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor);
Rgba32 rgba32 = default; Rgba32 rgba32 = default;
for (int x = pixelOffset; x < header.Width; x += increment) for (int x = pixelOffset; x < header.Width; x += increment)
{ {

2
tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
Assert.Equal(PngChunkType.Palette, GetType("PLTE")); Assert.Equal(PngChunkType.Palette, GetType("PLTE"));
Assert.Equal(PngChunkType.Data, GetType("IDAT")); Assert.Equal(PngChunkType.Data, GetType("IDAT"));
Assert.Equal(PngChunkType.End, GetType("IEND")); Assert.Equal(PngChunkType.End, GetType("IEND"));
Assert.Equal(PngChunkType.PaletteAlpha, GetType("tRNS")); Assert.Equal(PngChunkType.Transparency, GetType("tRNS"));
Assert.Equal(PngChunkType.Text, GetType("tEXt")); Assert.Equal(PngChunkType.Text, GetType("tEXt"));
Assert.Equal(PngChunkType.Gamma, GetType("gAMA")); Assert.Equal(PngChunkType.Gamma, GetType("gAMA"));
Assert.Equal(PngChunkType.Physical, GetType("pHYs")); Assert.Equal(PngChunkType.Physical, GetType("pHYs"));

2
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs

@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[Theory] [Theory]
[InlineData((uint)PngChunkType.Gamma)] // gAMA [InlineData((uint)PngChunkType.Gamma)] // gAMA
[InlineData((uint)PngChunkType.PaletteAlpha)] // tRNS [InlineData((uint)PngChunkType.Transparency)] // tRNS
[InlineData((uint)PngChunkType.Physical)] // pHYs: It's ok to test physical as we don't throw for duplicate chunks. [InlineData((uint)PngChunkType.Physical)] // pHYs: It's ok to test physical as we don't throw for duplicate chunks.
//[InlineData(PngChunkTypes.Text)] //TODO: Figure out how to test this //[InlineData(PngChunkTypes.Text)] //TODO: Figure out how to test this
public void Decode_IncorrectCRCForNonCriticalChunk_ExceptionIsThrown(uint chunkType) public void Decode_IncorrectCRCForNonCriticalChunk_ExceptionIsThrown(uint chunkType)

67
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -25,6 +25,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
{ TestImages.Png.Bpp1, PngBitDepth.Bit1 } { TestImages.Png.Bpp1, PngBitDepth.Bit1 }
}; };
public static readonly TheoryData<string, PngBitDepth, PngColorType> PngTrnsFiles =
new TheoryData<string, PngBitDepth, PngColorType>
{
{ TestImages.Png.Gray1BitTrans, PngBitDepth.Bit1, PngColorType.Grayscale },
{ TestImages.Png.Gray2BitTrans, PngBitDepth.Bit2, PngColorType.Grayscale },
{ TestImages.Png.Gray4BitTrans, PngBitDepth.Bit4, PngColorType.Grayscale },
{ TestImages.Png.Gray8BitTrans, PngBitDepth.Bit8, PngColorType.Grayscale },
{ TestImages.Png.GrayTrns16BitInterlaced, PngBitDepth.Bit16, PngColorType.Grayscale },
{ TestImages.Png.Rgb24BppTrans, PngBitDepth.Bit8, PngColorType.Rgb },
{ TestImages.Png.Rgb48BppTrans, PngBitDepth.Bit16, PngColorType.Rgb }
};
/// <summary> /// <summary>
/// All types except Palette /// All types except Palette
/// </summary> /// </summary>
@ -249,6 +261,61 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
} }
} }
[Theory]
[MemberData(nameof(PngTrnsFiles))]
public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType)
{
var options = new PngEncoder();
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateImage())
{
PngMetaData inMeta = input.MetaData.GetFormatMetaData(PngFormat.Instance);
Assert.True(inMeta.HasTrans);
using (var memStream = new MemoryStream())
{
input.Save(memStream, options);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
{
PngMetaData outMeta = output.MetaData.GetFormatMetaData(PngFormat.Instance);
Assert.True(outMeta.HasTrans);
switch (pngColorType)
{
case PngColorType.Grayscale:
if (pngBitDepth.Equals(PngBitDepth.Bit16))
{
Assert.True(outMeta.TransparentGray16.HasValue);
Assert.Equal(inMeta.TransparentGray16, outMeta.TransparentGray16);
}
else
{
Assert.True(outMeta.TransparentGray8.HasValue);
Assert.Equal(inMeta.TransparentGray8, outMeta.TransparentGray8);
}
break;
case PngColorType.Rgb:
if (pngBitDepth.Equals(PngBitDepth.Bit16))
{
Assert.True(outMeta.TransparentRgb48.HasValue);
Assert.Equal(inMeta.TransparentRgb48, outMeta.TransparentRgb48);
}
else
{
Assert.True(outMeta.TransparentRgb24.HasValue);
Assert.Equal(inMeta.TransparentRgb24, outMeta.TransparentRgb24);
}
break;
}
}
}
}
}
private static void TestPngEncoderCore<TPixel>( private static void TestPngEncoderCore<TPixel>(
TestImageProvider<TPixel> provider, TestImageProvider<TPixel> provider,
PngColorType pngColorType, PngColorType pngColorType,

3
tests/ImageSharp.Tests/TestImages.cs

@ -46,6 +46,9 @@ namespace SixLabors.ImageSharp.Tests
public const string PDSrc = "Png/pd-source.png"; public const string PDSrc = "Png/pd-source.png";
public const string PDDest = "Png/pd-dest.png"; public const string PDDest = "Png/pd-dest.png";
public const string Gray1BitTrans = "Png/gray-1-trns.png"; public const string Gray1BitTrans = "Png/gray-1-trns.png";
public const string Gray2BitTrans = "Png/gray-2-tRNS.png";
public const string Gray4BitTrans = "Png/gray-4-tRNS.png";
public const string Gray8BitTrans = "Png/gray-8-tRNS.png";
// Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html
public const string Filter0 = "Png/filter0.png"; public const string Filter0 = "Png/filter0.png";

BIN
tests/Images/Input/Png/gray-2-tRNS.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

BIN
tests/Images/Input/Png/gray-4-tRNS.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

BIN
tests/Images/Input/Png/gray-8-tRNS.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Loading…
Cancel
Save