diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index e0bd153e6..2ff6a4308 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -28,10 +28,10 @@ namespace ImageSharp.Formats private static readonly Dictionary ColorTypes = new Dictionary() { [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.GrayscaleWithAlpha] = new byte[] { 8 }, - [PngColorType.RgbWithAlpha] = new byte[] { 8 }, + [PngColorType.RgbWithAlpha] = new byte[] { 8, 16 } }; /// @@ -147,12 +147,12 @@ namespace ImageSharp.Formats /// /// The current pass for an interlaced PNG /// - private int pass = 0; + private int pass; /// /// The current number of bytes read in the current scanline /// - private int currentRowBytesRead = 0; + private int currentRowBytesRead; /// /// Initializes a new instance of the class. @@ -325,11 +325,6 @@ namespace ImageSharp.Formats private void InitializeImage(ImageMetaData metadata, out Image image) where TPixel : struct, IPixel { - if (this.header.Width > Image.MaxWidth || this.header.Height > Image.MaxHeight) - { - throw new ArgumentOutOfRangeException($"The input png '{this.header.Width}x{this.header.Height}' is bigger than the max allowed size '{Image.MaxWidth}x{Image.MaxHeight}'"); - } - image = new Image(this.configuration, this.header.Width, this.header.Height, metadata); this.bytesPerPixel = this.CalculateBytesPerPixel(); this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; @@ -361,10 +356,20 @@ namespace ImageSharp.Formats return 1; case PngColorType.Rgb: + if (this.header.BitDepth == 16) + { + return 6; + } + return 3; - // PngColorType.RgbWithAlpha: + case PngColorType.RgbWithAlpha: default: + if (this.header.BitDepth == 16) + { + return 8; + } + return 4; } } @@ -378,15 +383,16 @@ namespace ImageSharp.Formats /// private int CalculateScanlineLength(int width) { + int mod = this.header.BitDepth == 16 ? 16 : 8; int scanlineLength = width * this.header.BitDepth * this.bytesPerPixel; - int amount = scanlineLength % 8; + int amount = scanlineLength % mod; if (amount != 0) { - scanlineLength += 8 - amount; + scanlineLength += mod - amount; } - return scanlineLength / 8; + return scanlineLength / mod; } /// @@ -589,7 +595,7 @@ namespace ImageSharp.Formats byte intensity = defilteredScanline[offset]; byte alpha = defilteredScanline[offset + this.bytesPerSample]; - color.PackFromRgba32(new Rgba32(intensity, intensity, intensity)); + color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, alpha)); rowSpan[x] = color; } @@ -603,18 +609,59 @@ namespace ImageSharp.Formats case PngColorType.Rgb: - PixelOperations.Instance.PackFromRgb24Bytes(scanlineBuffer, rowSpan, this.header.Width); + if (this.header.BitDepth == 16) + { + int length = this.header.Width * 3; + using (var compressed = new Buffer(length)) + { + // TODO: Should we use pack from vector here instead? + this.From16BitTo8Bit(scanlineBuffer, compressed, length); + PixelOperations.Instance.PackFromRgb24Bytes(compressed, rowSpan, this.header.Width); + } + } + else + { + PixelOperations.Instance.PackFromRgb24Bytes(scanlineBuffer, rowSpan, this.header.Width); + } break; case PngColorType.RgbWithAlpha: - PixelOperations.Instance.PackFromRgba32Bytes(scanlineBuffer, rowSpan, this.header.Width); + if (this.header.BitDepth == 16) + { + int length = this.header.Width * 4; + using (var compressed = new Buffer(length)) + { + // TODO: Should we use pack from vector here instead? + this.From16BitTo8Bit(scanlineBuffer, compressed, length); + PixelOperations.Instance.PackFromRgba32Bytes(compressed, rowSpan, this.header.Width); + } + } + else + { + PixelOperations.Instance.PackFromRgba32Bytes(scanlineBuffer, rowSpan, this.header.Width); + } break; } } + /// + /// Compresses the given span from 16bpp to 8bpp + /// + /// The source buffer + /// The target buffer + /// The target length + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void From16BitTo8Bit(Span source, Span target, int length) + { + for (int i = 0, j = 0; i < length; i++, j += 2) + { + target[i] = (byte)((source[j + 1] << 8) + source[j]); + } + } + /// /// Processes a scanline that uses a palette /// @@ -625,10 +672,10 @@ namespace ImageSharp.Formats where TPixel : struct, IPixel { byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); - byte[] palette = this.palette; + byte[] pal = this.palette; var color = default(TPixel); - Rgba32 rgba = default(Rgba32); + var rgba = default(Rgba32); if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) { @@ -643,7 +690,7 @@ namespace ImageSharp.Formats if (rgba.A > 0) { - rgba.Rgb = palette.GetRgb24(pixelOffset); + rgba.Rgb = pal.GetRgb24(pixelOffset); } else { @@ -663,7 +710,7 @@ namespace ImageSharp.Formats int index = newScanline[x + 1]; int pixelOffset = index * 3; - rgba.Rgb = palette.GetRgb24(pixelOffset); + rgba.Rgb = pal.GetRgb24(pixelOffset); color.PackFromRgba32(rgba); row[x] = color; @@ -688,7 +735,10 @@ namespace ImageSharp.Formats { case PngColorType.Grayscale: 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++) { byte intensity = (byte)(newScanline1[o] * factor); @@ -712,8 +762,11 @@ namespace ImageSharp.Formats case PngColorType.Palette: - byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); - Rgba32 rgba = default(Rgba32); + byte[] newScanline = ToArrayByBitsLength( + defilteredScanline, + this.bytesPerScanline, + this.header.BitDepth); + var rgba = default(Rgba32); if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) { @@ -760,29 +813,75 @@ namespace ImageSharp.Formats case PngColorType.Rgb: 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]; - rgba.G = defilteredScanline[o + this.bytesPerSample]; - rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)]; + int length = this.header.Width * 3; + using (var compressed = new Buffer(length)) + { + // TODO: Should we use pack from vector here instead? + this.From16BitTo8Bit(new Span(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); - rowSpan[x] = color; + color.PackFromRgba32(rgba); + 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; 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]; - rgba.G = defilteredScanline[o + this.bytesPerSample]; - rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)]; - rgba.A = defilteredScanline[o + (3 * this.bytesPerSample)]; + int length = this.header.Width * 4; + using (var compressed = new Buffer(length)) + { + // TODO: Should we use pack from vector here instead? + this.From16BitTo8Bit(new Span(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); - rowSpan[x] = color; + color.PackFromRgba32(rgba); + 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; @@ -868,7 +967,7 @@ namespace ImageSharp.Formats 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; } /// @@ -985,13 +1084,13 @@ namespace ImageSharp.Formats /// /// Returns the correct number of columns for each interlaced pass. /// - /// Th current pass index + /// Th current pass index /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ComputeColumnsAdam7(int pass) + private int ComputeColumnsAdam7(int passIndex) { int width = this.header.Width; - switch (pass) + switch (passIndex) { case 0: return (width + 7) / 8; case 1: return (width + 3) / 8; @@ -1000,7 +1099,7 @@ namespace ImageSharp.Formats case 4: return (width + 1) / 2; case 5: return width / 2; case 6: return width; - default: throw new ArgumentException($"Not a valid pass index: {pass}"); + default: throw new ArgumentException($"Not a valid pass index: {passIndex}"); } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 195a8dcb3..5ab1fac2f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Tests public static readonly string[] TestFiles = { 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] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 471d61abb..f35720f19 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -25,6 +25,7 @@ namespace ImageSharp.Tests public const string Powerpoint = "Png/pp.png"; public const string SplashInterlaced = "Png/splash-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 public const string Filter0 = "Png/filter0.png"; diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp.png new file mode 100644 index 000000000..04a2ddd71 Binary files /dev/null and b/tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp.png differ