Browse Source

Fix #301

Decode using tRNS chunk of present
pull/299/head
James Jackson-South 9 years ago
parent
commit
abeaf6b493
  1. 203
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 1
      tests/ImageSharp.Tests/FileTestBase.cs
  3. 21
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

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

@ -84,6 +84,16 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
private readonly Configuration configuration; private readonly Configuration configuration;
/// <summary>
/// Gets the encoding to use
/// </summary>
private readonly Encoding textEncoding;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
private readonly bool ignoreMetadata;
/// <summary> /// <summary>
/// The stream to decode from. /// The stream to decode from.
/// </summary> /// </summary>
@ -155,14 +165,19 @@ namespace SixLabors.ImageSharp.Formats.Png
private PngColorType pngColorType; private PngColorType pngColorType;
/// <summary> /// <summary>
/// Gets the encoding to use /// Represents any color in an Rgb24 encoded png that should be transparent
/// </summary> /// </summary>
private Encoding textEncoding; private Rgb24 rgb24Trans;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// Represents any color in a Grayscale encoded png that should be transparent
/// </summary> /// </summary>
private bool ignoreMetadata; private byte intensityTrans;
/// <summary>
/// Whether the image has transparency chunk and markers were decoded
/// </summary>
private bool hasTrans;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class. /// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
@ -232,6 +247,7 @@ namespace SixLabors.ImageSharp.Formats.Png
byte[] alpha = new byte[currentChunk.Length]; byte[] alpha = new byte[currentChunk.Length];
Buffer.BlockCopy(currentChunk.Data, 0, alpha, 0, currentChunk.Length); Buffer.BlockCopy(currentChunk.Data, 0, alpha, 0, currentChunk.Length);
this.paletteAlpha = alpha; this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha);
break; break;
case PngChunkTypes.Text: case PngChunkTypes.Text:
this.ReadTextChunk(metadata, currentChunk.Data, currentChunk.Length); this.ReadTextChunk(metadata, currentChunk.Data, currentChunk.Length);
@ -300,6 +316,11 @@ namespace SixLabors.ImageSharp.Formats.Png
return result; return result;
} }
/// <summary>
/// Returns a value indicating whether the given chunk is critical to decoding
/// </summary>
/// <param name="chunk">The chunk</param>
/// <returns>The <see cref="bool"/></returns>
private static bool IsCriticalChunk(PngChunk chunk) private static bool IsCriticalChunk(PngChunk chunk)
{ {
return return
@ -309,6 +330,17 @@ namespace SixLabors.ImageSharp.Formats.Png
chunk.Type == PngChunkTypes.End; chunk.Type == PngChunkTypes.End;
} }
/// <summary>
/// Reads an integer value from 2 consecutive bytes in LSB order
/// </summary>
/// <param name="buffer">The source buffer</param>
/// <param name="offset">THe offset</param>
/// <returns>The <see cref="int"/></returns>
public static int ReadIntFrom2Bytes(byte[] buffer, int offset)
{
return ((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF);
}
/// <summary> /// <summary>
/// Reads the data chunk containing physical dimension data. /// Reads the data chunk containing physical dimension data.
/// </summary> /// </summary>
@ -590,10 +622,19 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.Grayscale: case PngColorType.Grayscale:
int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
Span<byte> newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); Span<byte> newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth);
for (int x = 0; x < this.header.Width; x++) for (int x = 0; x < this.header.Width; x++)
{ {
byte intensity = (byte)(newScanline1[x] * factor); byte intensity = (byte)(newScanline1[x] * factor);
color.PackFromRgba32(new Rgba32(intensity, intensity, intensity)); if (this.hasTrans && intensity == this.intensityTrans)
{
color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, 0));
}
else
{
color.PackFromRgba32(new Rgba32(intensity, intensity, intensity));
}
rowSpan[x] = color; rowSpan[x] = color;
} }
@ -622,19 +663,60 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.Rgb: case PngColorType.Rgb:
if (this.header.BitDepth == 16) if (!this.hasTrans)
{ {
int length = this.header.Width * 3; if (this.header.BitDepth == 16)
using (var compressed = new Buffer<byte>(length))
{ {
// TODO: Should we use pack from vector here instead? int length = this.header.Width * 3;
this.From16BitTo8Bit(scanlineBuffer, compressed, length); using (var compressed = new Buffer<byte>(length))
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(compressed, rowSpan, this.header.Width); {
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(compressed, rowSpan, this.header.Width);
}
}
else
{
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(scanlineBuffer, rowSpan, this.header.Width);
} }
} }
else else
{ {
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(scanlineBuffer, rowSpan, this.header.Width); if (this.header.BitDepth == 16)
{
int length = this.header.Width * 3;
using (var compressed = new Buffer<byte>(length))
{
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
Span<Rgb24> rgb24Span = compressed.Span.NonPortableCast<byte, Rgb24>();
for (int x = 0; x < this.header.Width; x++)
{
ref Rgb24 rgb24 = ref rgb24Span[x];
var rgba32 = default(Rgba32);
rgba32.Rgb = rgb24;
rgba32.A = (byte)(rgb24.Equals(this.rgb24Trans) ? 0 : 255);
color.PackFromRgba32(rgba32);
rowSpan[x] = color;
}
}
}
else
{
Span<Rgb24> rgb24Span = scanlineBuffer.NonPortableCast<byte, Rgb24>();
for (int x = 0; x < this.header.Width; x++)
{
ref Rgb24 rgb24 = ref rgb24Span[x];
var rgba32 = default(Rgba32);
rgba32.Rgb = rgb24;
rgba32.A = (byte)(rgb24.Equals(this.rgb24Trans) ? 0 : 255);
color.PackFromRgba32(rgba32);
rowSpan[x] = color;
}
}
} }
break; break;
@ -675,6 +757,33 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
} }
/// <summary>
/// Decodes and assigns marker colors that identify transparent pixels in non indexed images
/// </summary>
/// <param name="alpha">The aplha tRNS array</param>
private void AssignTransparentMarkers(byte[] alpha)
{
if (this.pngColorType == PngColorType.Rgb)
{
if (alpha.Length >= 6)
{
byte r = (byte)ReadIntFrom2Bytes(alpha, 0);
byte g = (byte)ReadIntFrom2Bytes(alpha, 2);
byte b = (byte)ReadIntFrom2Bytes(alpha, 4);
this.rgb24Trans = new Rgb24(r, g, b);
this.hasTrans = true;
}
}
else if (this.pngColorType == PngColorType.Grayscale)
{
if (alpha.Length >= 2)
{
this.intensityTrans = (byte)ReadIntFrom2Bytes(alpha, 0);
this.hasTrans = true;
}
}
}
/// <summary> /// <summary>
/// Processes a scanline that uses a palette /// Processes a scanline that uses a palette
/// </summary> /// </summary>
@ -744,10 +853,19 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.Grayscale: case PngColorType.Grayscale:
int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
Span<byte> newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); Span<byte> newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth);
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
{ {
byte intensity = (byte)(newScanline1[o] * factor); byte intensity = (byte)(newScanline1[o] * factor);
color.PackFromRgba32(new Rgba32(intensity, intensity, intensity)); if (this.hasTrans && intensity == this.intensityTrans)
{
color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, 0));
}
else
{
color.PackFromRgba32(new Rgba32(intensity, intensity, intensity));
}
rowSpan[x] = color; rowSpan[x] = color;
} }
@ -815,27 +933,60 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
// TODO: Should we use pack from vector here instead? // TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length); this.From16BitTo8Bit(scanlineBuffer, compressed, length);
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3)
{
rgba.R = compressed[o];
rgba.G = compressed[o + 1];
rgba.B = compressed[o + 2];
color.PackFromRgba32(rgba); if (this.hasTrans)
rowSpan[x] = color; {
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3)
{
rgba.R = compressed[o];
rgba.G = compressed[o + 1];
rgba.B = compressed[o + 2];
rgba.A = (byte)(this.rgb24Trans.Equals(rgba.Rgb) ? 0 : 255);
color.PackFromRgba32(rgba);
rowSpan[x] = color;
}
}
else
{
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3)
{
rgba.R = compressed[o];
rgba.G = compressed[o + 1];
rgba.B = compressed[o + 2];
color.PackFromRgba32(rgba);
rowSpan[x] = color;
}
} }
} }
} }
else else
{ {
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) if (this.hasTrans)
{ {
rgba.R = scanlineBuffer[o]; for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel)
rgba.G = scanlineBuffer[o + this.bytesPerSample]; {
rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; rgba.R = scanlineBuffer[o];
rgba.G = scanlineBuffer[o + this.bytesPerSample];
rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)];
rgba.A = (byte)(this.rgb24Trans.Equals(rgba.Rgb) ? 0 : 255);
color.PackFromRgba32(rgba); color.PackFromRgba32(rgba);
rowSpan[x] = color; rowSpan[x] = color;
}
}
else
{
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel)
{
rgba.R = scanlineBuffer[o];
rgba.G = scanlineBuffer[o + this.bytesPerSample];
rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)];
color.PackFromRgba32(rgba);
rowSpan[x] = color;
}
} }
} }

1
tests/ImageSharp.Tests/FileTestBase.cs

@ -85,6 +85,7 @@ namespace SixLabors.ImageSharp.Tests
// TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only // TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Bmp.CoreHeader), // Perf: Enable for local testing only // TestFile.Create(TestImages.Bmp.CoreHeader), // Perf: Enable for local testing only
TestFile.Create(TestImages.Png.Splash), TestFile.Create(TestImages.Png.Splash),
// TestFile.Create(TestImages.Png.SnakeGame),
// TestFile.Create(TestImages.Png.Cross), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Cross), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Bad.ChunkLength1), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Bad.ChunkLength1), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Bad.ChunkLength2), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Bad.ChunkLength2), // Perf: Enable for local testing only

21
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -24,22 +24,19 @@ namespace SixLabors.ImageSharp.Tests
TestImages.Png.Splash, TestImages.Png.Indexed, TestImages.Png.Splash, TestImages.Png.Indexed,
TestImages.Png.FilterVar, TestImages.Png.FilterVar,
TestImages.Png.Bad.ChunkLength1, TestImages.Png.Bad.ChunkLength1,
TestImages.Png.VimImage1, TestImages.Png.VimImage1,
TestImages.Png.VersioningImage1, TestImages.Png.VersioningImage1,
TestImages.Png.VersioningImage2, TestImages.Png.VersioningImage2,
// BUG !!! Should work. TODO: Fix it !!!! TestImages.Png.SnakeGame
// TestImages.Png.SnakeGame
}; };
public static readonly string[] TestImages48Bpp = public static readonly string[] TestImages48Bpp =
{ {
TestImages.Png.Rgb48Bpp, TestImages.Png.Rgb48Bpp,
TestImages.Png.Rgb48BppInterlaced
// TODO: Re enable, when Decode_Interlaced is fixed!!!!
// TestImages.Png.Rgb48BppInterlaced
}; };
// This is a workaround for Mono-s decoder being incompatible with ours and GDI+. // This is a workaround for Mono-s decoder being incompatible with ours and GDI+.
@ -79,21 +76,19 @@ namespace SixLabors.ImageSharp.Tests
} }
} }
} }
[Theory] [Theory]
[WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)]
public void Decode_Interlaced_DoesNotThrow<TPixel>(TestImageProvider<TPixel> provider) public void Decode_Interlaced_DoesNotThrow<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// Ok, it's incorrect, but at least let's run our decoder on interlaced images! (Single-fact AAA :P)
using (Image<TPixel> image = provider.GetImage(new PngDecoder())) using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
{ {
image.DebugSave(provider); image.DebugSave(provider);
} }
} }
// BUG in decoding interlaced images !!! TODO: Fix it! [Theory]
[Theory(Skip = "Bug in decoding interlaced images.")]
[WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)]
public void Decode_Interlaced_ImageIsCorrect<TPixel>(TestImageProvider<TPixel> provider) public void Decode_Interlaced_ImageIsCorrect<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
@ -104,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests
image.CompareToOriginal(provider, ImageComparer.Exact); image.CompareToOriginal(provider, ImageComparer.Exact);
} }
} }
// TODO: We need to decode these into Rgba64 properly, and do 'CompareToOriginal' in a Rgba64 mode! (See #285) // TODO: We need to decode these into Rgba64 properly, and do 'CompareToOriginal' in a Rgba64 mode! (See #285)
[Theory] [Theory]
[WithFileCollection(nameof(TestImages48Bpp), PixelTypes.Rgba32)] [WithFileCollection(nameof(TestImages48Bpp), PixelTypes.Rgba32)]
@ -122,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests
} }
} }
} }
[Theory] [Theory]
[WithFile(TestImages.Png.Splash, PixelTypes)] [WithFile(TestImages.Png.Splash, PixelTypes)]
public void Decoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider) public void Decoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)

Loading…
Cancel
Save