Browse Source

Fix #247

Allows reading of 48bpp images.
pull/249/head
James Jackson-South 9 years ago
parent
commit
eb207d41e9
  1. 181
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 2
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  3. 1
      tests/ImageSharp.Tests/TestImages.cs
  4. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp.png

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

@ -28,10 +28,10 @@ namespace ImageSharp.Formats
private static readonly Dictionary<PngColorType, byte[]> ColorTypes = new Dictionary<PngColorType, byte[]>() private static readonly Dictionary<PngColorType, byte[]> ColorTypes = new Dictionary<PngColorType, byte[]>()
{ {
[PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8 }, [PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8 },
[PngColorType.Rgb] = new byte[] { 8 }, [PngColorType.Rgb] = new byte[] { 8, 16 },
[PngColorType.Palette] = new byte[] { 1, 2, 4, 8 }, [PngColorType.Palette] = new byte[] { 1, 2, 4, 8 },
[PngColorType.GrayscaleWithAlpha] = new byte[] { 8 }, [PngColorType.GrayscaleWithAlpha] = new byte[] { 8 },
[PngColorType.RgbWithAlpha] = new byte[] { 8 }, [PngColorType.RgbWithAlpha] = new byte[] { 8, 16 }
}; };
/// <summary> /// <summary>
@ -147,12 +147,12 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// The current pass for an interlaced PNG /// The current pass for an interlaced PNG
/// </summary> /// </summary>
private int pass = 0; private int pass;
/// <summary> /// <summary>
/// The current number of bytes read in the current scanline /// The current number of bytes read in the current scanline
/// </summary> /// </summary>
private int currentRowBytesRead = 0; private int currentRowBytesRead;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class. /// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
@ -325,11 +325,6 @@ namespace ImageSharp.Formats
private void InitializeImage<TPixel>(ImageMetaData metadata, out Image<TPixel> image) private void InitializeImage<TPixel>(ImageMetaData metadata, out Image<TPixel> image)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.header.Width > Image<TPixel>.MaxWidth || this.header.Height > Image<TPixel>.MaxHeight)
{
throw new ArgumentOutOfRangeException($"The input png '{this.header.Width}x{this.header.Height}' is bigger than the max allowed size '{Image<TPixel>.MaxWidth}x{Image<TPixel>.MaxHeight}'");
}
image = new Image<TPixel>(this.configuration, this.header.Width, this.header.Height, metadata); image = new Image<TPixel>(this.configuration, this.header.Width, this.header.Height, metadata);
this.bytesPerPixel = this.CalculateBytesPerPixel(); this.bytesPerPixel = this.CalculateBytesPerPixel();
this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1;
@ -361,10 +356,20 @@ namespace ImageSharp.Formats
return 1; return 1;
case PngColorType.Rgb: case PngColorType.Rgb:
if (this.header.BitDepth == 16)
{
return 6;
}
return 3; return 3;
// PngColorType.RgbWithAlpha: case PngColorType.RgbWithAlpha:
default: default:
if (this.header.BitDepth == 16)
{
return 8;
}
return 4; return 4;
} }
} }
@ -378,15 +383,16 @@ namespace ImageSharp.Formats
/// </returns> /// </returns>
private int CalculateScanlineLength(int width) private int CalculateScanlineLength(int width)
{ {
int mod = this.header.BitDepth == 16 ? 16 : 8;
int scanlineLength = width * this.header.BitDepth * this.bytesPerPixel; int scanlineLength = width * this.header.BitDepth * this.bytesPerPixel;
int amount = scanlineLength % 8; int amount = scanlineLength % mod;
if (amount != 0) if (amount != 0)
{ {
scanlineLength += 8 - amount; scanlineLength += mod - amount;
} }
return scanlineLength / 8; return scanlineLength / mod;
} }
/// <summary> /// <summary>
@ -589,7 +595,7 @@ namespace ImageSharp.Formats
byte intensity = defilteredScanline[offset]; byte intensity = defilteredScanline[offset];
byte alpha = defilteredScanline[offset + this.bytesPerSample]; byte alpha = defilteredScanline[offset + this.bytesPerSample];
color.PackFromRgba32(new Rgba32(intensity, intensity, intensity)); color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, alpha));
rowSpan[x] = color; rowSpan[x] = color;
} }
@ -603,18 +609,59 @@ namespace ImageSharp.Formats
case PngColorType.Rgb: case PngColorType.Rgb:
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);
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(compressed, rowSpan, this.header.Width);
}
}
else
{
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(scanlineBuffer, rowSpan, this.header.Width);
}
break; break;
case PngColorType.RgbWithAlpha: case PngColorType.RgbWithAlpha:
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(scanlineBuffer, rowSpan, this.header.Width); if (this.header.BitDepth == 16)
{
int length = this.header.Width * 4;
using (var compressed = new Buffer<byte>(length))
{
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(compressed, rowSpan, this.header.Width);
}
}
else
{
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(scanlineBuffer, rowSpan, this.header.Width);
}
break; break;
} }
} }
/// <summary>
/// Compresses the given span from 16bpp to 8bpp
/// </summary>
/// <param name="source">The source buffer</param>
/// <param name="target">The target buffer</param>
/// <param name="length">The target length</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void From16BitTo8Bit(Span<byte> source, Span<byte> target, int length)
{
for (int i = 0, j = 0; i < length; i++, j += 2)
{
target[i] = (byte)((source[j + 1] << 8) + source[j]);
}
}
/// <summary> /// <summary>
/// Processes a scanline that uses a palette /// Processes a scanline that uses a palette
/// </summary> /// </summary>
@ -625,10 +672,10 @@ namespace ImageSharp.Formats
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth);
byte[] palette = this.palette; byte[] pal = this.palette;
var color = default(TPixel); var color = default(TPixel);
Rgba32 rgba = default(Rgba32); var rgba = default(Rgba32);
if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) if (this.paletteAlpha != null && this.paletteAlpha.Length > 0)
{ {
@ -643,7 +690,7 @@ namespace ImageSharp.Formats
if (rgba.A > 0) if (rgba.A > 0)
{ {
rgba.Rgb = palette.GetRgb24(pixelOffset); rgba.Rgb = pal.GetRgb24(pixelOffset);
} }
else else
{ {
@ -663,7 +710,7 @@ namespace ImageSharp.Formats
int index = newScanline[x + 1]; int index = newScanline[x + 1];
int pixelOffset = index * 3; int pixelOffset = index * 3;
rgba.Rgb = palette.GetRgb24(pixelOffset); rgba.Rgb = pal.GetRgb24(pixelOffset);
color.PackFromRgba32(rgba); color.PackFromRgba32(rgba);
row[x] = color; row[x] = color;
@ -688,7 +735,10 @@ namespace ImageSharp.Formats
{ {
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);
byte[] newScanline1 = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); byte[] newScanline1 = ToArrayByBitsLength(
defilteredScanline,
this.bytesPerScanline,
this.header.BitDepth);
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++) for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++)
{ {
byte intensity = (byte)(newScanline1[o] * factor); byte intensity = (byte)(newScanline1[o] * factor);
@ -712,8 +762,11 @@ namespace ImageSharp.Formats
case PngColorType.Palette: case PngColorType.Palette:
byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); byte[] newScanline = ToArrayByBitsLength(
Rgba32 rgba = default(Rgba32); defilteredScanline,
this.bytesPerScanline,
this.header.BitDepth);
var rgba = default(Rgba32);
if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) if (this.paletteAlpha != null && this.paletteAlpha.Length > 0)
{ {
@ -760,29 +813,75 @@ namespace ImageSharp.Formats
case PngColorType.Rgb: case PngColorType.Rgb:
rgba.A = 255; rgba.A = 255;
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel)
if (this.header.BitDepth == 16)
{ {
rgba.R = defilteredScanline[o]; int length = this.header.Width * 3;
rgba.G = defilteredScanline[o + this.bytesPerSample]; using (var compressed = new Buffer<byte>(length))
rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)]; {
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(new Span<byte>(defilteredScanline), compressed, length);
for (int x = pixelOffset, o = 1;
x < this.header.Width;
x += increment, o += this.bytesPerPixel)
{
rgba.R = compressed[o];
rgba.G = compressed[o + this.bytesPerSample];
rgba.B = compressed[o + (2 * this.bytesPerSample)];
color.PackFromRgba32(rgba); color.PackFromRgba32(rgba);
rowSpan[x] = color; rowSpan[x] = color;
}
}
}
else
{
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel)
{
rgba.R = defilteredScanline[o];
rgba.G = defilteredScanline[o + this.bytesPerSample];
rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)];
color.PackFromRgba32(rgba);
rowSpan[x] = color;
}
} }
break; break;
case PngColorType.RgbWithAlpha: case PngColorType.RgbWithAlpha:
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) if (this.header.BitDepth == 16)
{ {
rgba.R = defilteredScanline[o]; int length = this.header.Width * 4;
rgba.G = defilteredScanline[o + this.bytesPerSample]; using (var compressed = new Buffer<byte>(length))
rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)]; {
rgba.A = defilteredScanline[o + (3 * this.bytesPerSample)]; // TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(new Span<byte>(defilteredScanline), compressed, length);
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel)
{
rgba.R = compressed[o];
rgba.G = compressed[o + this.bytesPerSample];
rgba.B = compressed[o + (2 * this.bytesPerSample)];
rgba.A = compressed[o + (3 * this.bytesPerSample)];
color.PackFromRgba32(rgba); color.PackFromRgba32(rgba);
rowSpan[x] = color; rowSpan[x] = color;
}
}
}
else
{
for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel)
{
rgba.R = defilteredScanline[o];
rgba.G = defilteredScanline[o + this.bytesPerSample];
rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)];
rgba.A = defilteredScanline[o + (3 * this.bytesPerSample)];
color.PackFromRgba32(rgba);
rowSpan[x] = color;
}
} }
break; break;
@ -868,7 +967,7 @@ namespace ImageSharp.Formats
throw new NotSupportedException("The png specification only defines 'None' and 'Adam7' as interlaced methods."); throw new NotSupportedException("The png specification only defines 'None' and 'Adam7' as interlaced methods.");
} }
this.PngColorType = (PngColorType)this.header.ColorType; this.PngColorType = this.header.ColorType;
} }
/// <summary> /// <summary>
@ -985,13 +1084,13 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Returns the correct number of columns for each interlaced pass. /// Returns the correct number of columns for each interlaced pass.
/// </summary> /// </summary>
/// <param name="pass">Th current pass index</param> /// <param name="passIndex">Th current pass index</param>
/// <returns>The <see cref="int"/></returns> /// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ComputeColumnsAdam7(int pass) private int ComputeColumnsAdam7(int passIndex)
{ {
int width = this.header.Width; int width = this.header.Width;
switch (pass) switch (passIndex)
{ {
case 0: return (width + 7) / 8; case 0: return (width + 7) / 8;
case 1: return (width + 3) / 8; case 1: return (width + 3) / 8;
@ -1000,7 +1099,7 @@ namespace ImageSharp.Formats
case 4: return (width + 1) / 2; case 4: return (width + 1) / 2;
case 5: return width / 2; case 5: return width / 2;
case 6: return width; case 6: return width;
default: throw new ArgumentException($"Not a valid pass index: {pass}"); default: throw new ArgumentException($"Not a valid pass index: {passIndex}");
} }
} }
} }

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

@ -18,7 +18,7 @@ namespace ImageSharp.Tests
public static readonly string[] TestFiles = public static readonly string[] TestFiles =
{ {
TestImages.Png.Splash, TestImages.Png.Indexed, TestImages.Png.Interlaced, TestImages.Png.FilterVar, TestImages.Png.Splash, TestImages.Png.Indexed, TestImages.Png.Interlaced, TestImages.Png.FilterVar,
TestImages.Png.Bad.ChunkLength1, TestImages.Png.Bad.ChunkLength2 TestImages.Png.Bad.ChunkLength1, TestImages.Png.Bad.ChunkLength2, TestImages.Png.Rgb48Bpp
}; };
[Theory] [Theory]

1
tests/ImageSharp.Tests/TestImages.cs

@ -25,6 +25,7 @@ namespace ImageSharp.Tests
public const string Powerpoint = "Png/pp.png"; public const string Powerpoint = "Png/pp.png";
public const string SplashInterlaced = "Png/splash-interlaced.png"; public const string SplashInterlaced = "Png/splash-interlaced.png";
public const string Interlaced = "Png/interlaced.png"; public const string Interlaced = "Png/interlaced.png";
public const string Rgb48Bpp = "Png/rgb-48bpp.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/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 KiB

Loading…
Cancel
Save