From b94a865d1c4933d1cab2c20167305d5800a23257 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 3 Dec 2016 15:38:01 +1100 Subject: [PATCH 1/6] Initial attempt to decode png. Touch #9 I can pull the pixels from the stream but something is wrong with my offset calculations. Fingers crossde it's something easy to spot. --- src/ImageSharp/Formats/Png/InterlaceMode.cs | 23 +++ src/ImageSharp/Formats/Png/PngDecoderCore.cs | 170 ++++++++++++++++-- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 2 +- src/ImageSharp/Formats/Png/PngHeader.cs | 2 +- tests/ImageSharp.Tests/FileTestBase.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 3 +- .../Formats/Png/splash-interlaced.png | 3 + 7 files changed, 185 insertions(+), 19 deletions(-) create mode 100644 src/ImageSharp/Formats/Png/InterlaceMode.cs create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/splash-interlaced.png diff --git a/src/ImageSharp/Formats/Png/InterlaceMode.cs b/src/ImageSharp/Formats/Png/InterlaceMode.cs new file mode 100644 index 0000000000..663c9611ca --- /dev/null +++ b/src/ImageSharp/Formats/Png/InterlaceMode.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Provides enumeration of available PNG interlace modes. + /// + public enum InterlaceMode : byte + { + /// + /// Non interlaced + /// + None, + + /// + /// Adam 7 interlacing. + /// + Adam7 + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 38042426c8..fd8972fbbc 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -24,6 +24,26 @@ namespace ImageSharp.Formats /// private static readonly Dictionary ColorTypes = new Dictionary(); + /// + /// The amount to increment when processing each column per scanline for each interlaced pass + /// + private static readonly int[] Adam7ColumnIncrement = { 8, 8, 4, 4, 2, 2, 1 }; + + /// + /// The index to start at when processing each column per scanline for each interlaced pass + /// + private static readonly int[] Adam7FirstColumn = { 0, 4, 0, 2, 0, 1, 0 }; + + /// + /// The index to start at when processing each row per scanline for each interlaced pass + /// + private static readonly int[] Adam7FirstRow = { 0, 0, 4, 0, 2, 0, 1 }; + + /// + /// The amount to increment when processing each row per scanline for each interlaced pass + /// + private static readonly int[] Adam7RowIncrement = { 8, 8, 8, 4, 4, 2, 2 }; + /// /// Reusable buffer for reading chunk types. /// @@ -285,10 +305,13 @@ namespace ImageSharp.Formats /// /// Calculates the scanline length. /// - /// The representing the length. - private int CalculateScanlineLength() + /// The width of the row. + /// + /// The representing the length. + /// + private int CalculateScanlineLength(int width) { - int scanlineLength = this.header.Width * this.header.BitDepth * this.bytesPerPixel; + int scanlineLength = width * this.header.BitDepth * this.bytesPerPixel; int amount = scanlineLength % 8; if (amount != 0) @@ -311,7 +334,7 @@ namespace ImageSharp.Formats where TPacked : struct { this.bytesPerPixel = this.CalculateBytesPerPixel(); - this.bytesPerScanline = this.CalculateScanlineLength() + 1; + this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; this.bytesPerSample = 1; if (this.header.BitDepth >= 8) { @@ -321,7 +344,14 @@ namespace ImageSharp.Formats dataStream.Position = 0; using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream)) { - this.DecodePixelData(compressedStream, pixels); + if (this.header.InterlaceMethod == InterlaceMode.Adam7) + { + this.DecodeInterlacedPixelData(compressedStream, pixels); + } + else + { + this.DecodePixelData(compressedStream, pixels); + } } } @@ -398,6 +428,96 @@ namespace ImageSharp.Formats } } + /// + /// Decodes the raw interlaced pixel data row by row + /// + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The compressed pixel data stream. + /// The image pixel accessor. + private void DecodeInterlacedPixelData(Stream compressedStream, PixelAccessor pixels) + where TColor : struct, IPackedPixel + where TPacked : struct + { + for (int pass = 0; pass < 7; pass++) + { + int y = Adam7FirstRow[pass]; + int numColumns = this.ComputeColumnsAdam7(pass); + + if (numColumns == 0) + { + // This pass contains no data; skip to next pass + continue; + } + + int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; + + while (y < this.header.Height) + { + byte[] previousScanline = ArrayPool.Shared.Rent(this.bytesPerScanline); + byte[] scanline = ArrayPool.Shared.Rent(this.bytesPerScanline); + + // Zero out the previousScanline, because the bytes that are rented from the arraypool may not be zero. + Array.Clear(previousScanline, 0, this.bytesPerScanline); + + try + { + compressedStream.Read(scanline, 0, bytesPerInterlaceScanline); + + FilterType filterType = (FilterType)scanline[0]; + + switch (filterType) + { + case FilterType.None: + + NoneFilter.Decode(scanline); + + break; + + case FilterType.Sub: + + SubFilter.Decode(scanline, bytesPerInterlaceScanline, this.bytesPerPixel); + + break; + + case FilterType.Up: + + UpFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline); + + break; + + case FilterType.Average: + + AverageFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); + + break; + + case FilterType.Paeth: + + PaethFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); + + break; + + default: + throw new ImageFormatException("Unknown filter type."); + } + + this.ProcessDefilteredScanline(scanline, y, pixels, Adam7FirstColumn[pass], Adam7ColumnIncrement[pass]); + + Swap(ref scanline, ref previousScanline); + } + finally + { + ArrayPool.Shared.Return(previousScanline); + ArrayPool.Shared.Return(scanline); + } + + y += Adam7RowIncrement[pass]; + } + } + } + /// /// Processes the de-filtered scanline filling the image pixel data /// @@ -406,17 +526,20 @@ namespace ImageSharp.Formats /// The de-filtered scanline /// The current image row. /// The image pixels - private void ProcessDefilteredScanline(byte[] defilteredScanline, int row, PixelAccessor pixels) + /// The column start index. Always 0 for none interlaced images. + /// The column increment. Always 1 for none interlaced images. + private void ProcessDefilteredScanline(byte[] defilteredScanline, int row, PixelAccessor pixels, int startIndex = 0, int increment = 1) where TColor : struct, IPackedPixel where TPacked : struct { TColor color = default(TColor); + switch (this.PngColorType) { case PngColorType.Grayscale: int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); byte[] newScanline1 = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); - for (int x = 0; x < this.header.Width; x++) + for (int x = startIndex; x < this.header.Width; x += increment) { byte intensity = (byte)(newScanline1[x] * factor); color.PackFromBytes(intensity, intensity, intensity, 255); @@ -427,7 +550,7 @@ namespace ImageSharp.Formats case PngColorType.GrayscaleWithAlpha: - for (int x = 0; x < this.header.Width; x++) + for (int x = startIndex; x < this.header.Width; x += increment) { int offset = 1 + (x * this.bytesPerPixel); @@ -448,7 +571,7 @@ namespace ImageSharp.Formats { // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha // channel and we should try to read it. - for (int x = 0; x < this.header.Width; x++) + for (int x = startIndex; x < this.header.Width; x += increment) { int index = newScanline[x]; int pixelOffset = index * 3; @@ -472,7 +595,7 @@ namespace ImageSharp.Formats } else { - for (int x = 0; x < this.header.Width; x++) + for (int x = startIndex; x < this.header.Width; x += increment) { int index = newScanline[x]; int pixelOffset = index * 3; @@ -490,7 +613,7 @@ namespace ImageSharp.Formats case PngColorType.Rgb: - for (int x = 0; x < this.header.Width; x++) + for (int x = startIndex; x < this.header.Width; x += increment) { int offset = 1 + (x * this.bytesPerPixel); @@ -506,7 +629,7 @@ namespace ImageSharp.Formats case PngColorType.RgbWithAlpha: - for (int x = 0; x < this.header.Width; x++) + for (int x = startIndex; x < this.header.Width; x += increment) { int offset = 1 + (x * this.bytesPerPixel); @@ -570,7 +693,7 @@ namespace ImageSharp.Formats this.header.ColorType = data[9]; this.header.CompressionMethod = data[10]; this.header.FilterMethod = data[11]; - this.header.InterlaceMethod = data[12]; + this.header.InterlaceMethod = (InterlaceMode)data[12]; } /// @@ -596,10 +719,9 @@ namespace ImageSharp.Formats throw new NotSupportedException("The png specification only defines 0 as filter method."); } - if (this.header.InterlaceMethod != 0) + if (this.header.InterlaceMethod != InterlaceMode.None && this.header.InterlaceMethod != InterlaceMode.Adam7) { - // TODO: Support interlacing - throw new NotSupportedException("Interlacing is not supported."); + throw new NotSupportedException("The png specification only defines 'None' and 'Adam7' as interlaced methods."); } this.PngColorType = (PngColorType)this.header.ColorType; @@ -715,5 +837,21 @@ namespace ImageSharp.Formats chunk.Length = BitConverter.ToInt32(this.chunkLengthBuffer, 0); } + + private int ComputeColumnsAdam7(int pass) + { + int width = this.header.Width; + switch (pass) + { + case 0: return (width + 7) / 8; + case 1: return (width + 3) / 8; + case 2: return (width + 3) / 4; + case 3: return (width + 1) / 4; + 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}"); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 010bd635d4..434089e08b 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -473,7 +473,7 @@ namespace ImageSharp.Formats this.chunkDataBuffer[9] = header.ColorType; this.chunkDataBuffer[10] = header.CompressionMethod; this.chunkDataBuffer[11] = header.FilterMethod; - this.chunkDataBuffer[12] = header.InterlaceMethod; + this.chunkDataBuffer[12] = (byte)header.InterlaceMethod; this.WriteChunk(stream, PngChunkTypes.Header, this.chunkDataBuffer, 0, 13); } diff --git a/src/ImageSharp/Formats/Png/PngHeader.cs b/src/ImageSharp/Formats/Png/PngHeader.cs index 3121969b6f..ffad043f0d 100644 --- a/src/ImageSharp/Formats/Png/PngHeader.cs +++ b/src/ImageSharp/Formats/Png/PngHeader.cs @@ -57,6 +57,6 @@ namespace ImageSharp.Formats /// Indicates the transmission order of the image data. /// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace). /// - public byte InterlaceMethod { get; set; } + public InterlaceMode InterlaceMethod { get; set; } } } diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 6ef94fb6f9..c2d0916bc2 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -32,6 +32,7 @@ namespace ImageSharp.Tests // new TestFile(TestImages.Png.Blur), // Perf: Enable for local testing only // new TestFile(TestImages.Png.Indexed), // Perf: Enable for local testing only new TestFile(TestImages.Png.Splash), + new TestFile(TestImages.Png.SplashInterlace), // new TestFile(TestImages.Png.Filter0), // Perf: Enable for local testing only // new TestFile(TestImages.Png.Filter1), // Perf: Enable for local testing only // new TestFile(TestImages.Png.Filter2), // Perf: Enable for local testing only diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index a5eebeebe9..23632e2332 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -18,8 +18,9 @@ namespace ImageSharp.Tests public static string Pd => folder + "pd.png"; public static string Blur => folder + "blur.png"; public static string Indexed => folder + "indexed.png"; - public static string Splash => folder + "splash.png"; + public static string Splash => folder + "splash-interlaced.png"; + public static string SplashInterlace => folder + "splash.png"; // filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public static string Filter0 => folder + "filter0.png"; public static string Filter1 => folder + "filter1.png"; diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/splash-interlaced.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/splash-interlaced.png new file mode 100644 index 0000000000..98e4517c58 --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Png/splash-interlaced.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a573ae07d97481e8e29441dd165735ad21c0a7bdab1de302d9e442fd6891ec7 +size 239173 From d61fae265e5d4bd3e0a8038460e09b452799fd81 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 3 Dec 2016 18:22:18 +1100 Subject: [PATCH 2/6] A little bit better --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 147 ++++++++++++++++-- tests/ImageSharp.Tests/FileTestBase.cs | 2 +- tests/ImageSharp.Tests/TestImages.cs | 4 +- .../TestImages/Formats/Png/interlaced.png | 3 + 4 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/interlaced.png diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index fd8972fbbc..dc76c27b82 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -503,7 +503,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("Unknown filter type."); } - this.ProcessDefilteredScanline(scanline, y, pixels, Adam7FirstColumn[pass], Adam7ColumnIncrement[pass]); + this.ProcessInterlacedDefilteredScanline(scanline, y, pixels, Adam7FirstColumn[pass], Adam7ColumnIncrement[pass]); Swap(ref scanline, ref previousScanline); } @@ -526,20 +526,17 @@ namespace ImageSharp.Formats /// The de-filtered scanline /// The current image row. /// The image pixels - /// The column start index. Always 0 for none interlaced images. - /// The column increment. Always 1 for none interlaced images. - private void ProcessDefilteredScanline(byte[] defilteredScanline, int row, PixelAccessor pixels, int startIndex = 0, int increment = 1) + private void ProcessDefilteredScanline(byte[] defilteredScanline, int row, PixelAccessor pixels) where TColor : struct, IPackedPixel where TPacked : struct { TColor color = default(TColor); - switch (this.PngColorType) { case PngColorType.Grayscale: int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); byte[] newScanline1 = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); - for (int x = startIndex; x < this.header.Width; x += increment) + for (int x = 0; x < this.header.Width; x++) { byte intensity = (byte)(newScanline1[x] * factor); color.PackFromBytes(intensity, intensity, intensity, 255); @@ -550,7 +547,7 @@ namespace ImageSharp.Formats case PngColorType.GrayscaleWithAlpha: - for (int x = startIndex; x < this.header.Width; x += increment) + for (int x = 0; x < this.header.Width; x++) { int offset = 1 + (x * this.bytesPerPixel); @@ -571,7 +568,7 @@ namespace ImageSharp.Formats { // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha // channel and we should try to read it. - for (int x = startIndex; x < this.header.Width; x += increment) + for (int x = 0; x < this.header.Width; x++) { int index = newScanline[x]; int pixelOffset = index * 3; @@ -595,7 +592,7 @@ namespace ImageSharp.Formats } else { - for (int x = startIndex; x < this.header.Width; x += increment) + for (int x = 0; x < this.header.Width; x++) { int index = newScanline[x]; int pixelOffset = index * 3; @@ -613,7 +610,7 @@ namespace ImageSharp.Formats case PngColorType.Rgb: - for (int x = startIndex; x < this.header.Width; x += increment) + for (int x = 0; x < this.header.Width; x++) { int offset = 1 + (x * this.bytesPerPixel); @@ -629,7 +626,7 @@ namespace ImageSharp.Formats case PngColorType.RgbWithAlpha: - for (int x = startIndex; x < this.header.Width; x += increment) + for (int x = 0; x < this.header.Width; x++) { int offset = 1 + (x * this.bytesPerPixel); @@ -646,6 +643,129 @@ namespace ImageSharp.Formats } } + /// + /// Processes the interlaced de-filtered scanline filling the image pixel data + /// + /// The pixel format. + /// The packed format. uint, long, float. + /// The de-filtered scanline + /// The current image row. + /// The image pixels + /// The column start index. Always 0 for none interlaced images. + /// The column increment. Always 1 for none interlaced images. + private void ProcessInterlacedDefilteredScanline(byte[] defilteredScanline, int row, PixelAccessor pixels, int pixelOffset = 0, int increment = 1) + where TColor : struct, IPackedPixel + where TPacked : struct + { + TColor color = default(TColor); + + switch (this.PngColorType) + { + case PngColorType.Grayscale: + int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); + byte[] newScanline1 = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); + for (int x = pixelOffset; x < this.header.Width; x += increment) + { + byte intensity = (byte)(newScanline1[x - pixelOffset] * factor); + color.PackFromBytes(intensity, intensity, intensity, 255); + pixels[x, row] = color; + } + + break; + + case PngColorType.GrayscaleWithAlpha: + + for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + { + byte intensity = defilteredScanline[o]; + byte alpha = defilteredScanline[o + this.bytesPerSample]; + + color.PackFromBytes(intensity, intensity, intensity, alpha); + pixels[x, row] = color; + } + + break; + + case PngColorType.Palette: + + byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); + + if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) + { + // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha + // channel and we should try to read it. + for (int x = pixelOffset; x < this.header.Width; x += increment) + { + int index = newScanline[x - pixelOffset]; + int offset = index * 3; + + byte a = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; + + if (a > 0) + { + byte r = this.palette[offset]; + byte g = this.palette[offset + 1]; + byte b = this.palette[offset + 2]; + color.PackFromBytes(r, g, b, a); + } + else + { + color.PackFromBytes(0, 0, 0, 0); + } + + pixels[x, row] = color; + } + } + else + { + for (int x = pixelOffset; x < this.header.Width; x += increment) + { + int index = newScanline[x - pixelOffset]; + int offset = index * 3; + + byte r = this.palette[offset]; + byte g = this.palette[offset + 1]; + byte b = this.palette[offset + 2]; + + color.PackFromBytes(r, g, b, 255); + pixels[x, row] = color; + } + } + + break; + + case PngColorType.Rgb: + + for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + { + byte r = defilteredScanline[o]; + byte g = defilteredScanline[o + this.bytesPerSample]; + byte b = defilteredScanline[o + (2 * this.bytesPerSample)]; + + color.PackFromBytes(r, g, b, 255); + pixels[x, row] = color; + } + + break; + + case PngColorType.RgbWithAlpha: + + for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + { + byte r = defilteredScanline[o]; + byte g = defilteredScanline[o + this.bytesPerSample]; + byte b = defilteredScanline[o + (2 * this.bytesPerSample)]; + byte a = defilteredScanline[o + (3 * this.bytesPerSample)]; + + color.PackFromBytes(r, g, b, a); + pixels[x, row] = color; + } + + break; + } + } + + /// /// Reads a text chunk containing image properties from the data. /// @@ -838,6 +958,11 @@ namespace ImageSharp.Formats chunk.Length = BitConverter.ToInt32(this.chunkLengthBuffer, 0); } + /// + /// Returns the correct number of columns for each interlaced pass. + /// + /// Th current pass index + /// The private int ComputeColumnsAdam7(int pass) { int width = this.header.Width; diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index c2d0916bc2..e8034ebfd1 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Tests // new TestFile(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only // new TestFile(TestImages.Png.Blur), // Perf: Enable for local testing only // new TestFile(TestImages.Png.Indexed), // Perf: Enable for local testing only - new TestFile(TestImages.Png.Splash), + //new TestFile(TestImages.Png.Splash), new TestFile(TestImages.Png.SplashInterlace), // new TestFile(TestImages.Png.Filter0), // Perf: Enable for local testing only // new TestFile(TestImages.Png.Filter1), // Perf: Enable for local testing only diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 23632e2332..a8117df685 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -18,9 +18,9 @@ namespace ImageSharp.Tests public static string Pd => folder + "pd.png"; public static string Blur => folder + "blur.png"; public static string Indexed => folder + "indexed.png"; - public static string Splash => folder + "splash-interlaced.png"; + public static string Splash => folder + "splash.png"; - public static string SplashInterlace => folder + "splash.png"; + public static string SplashInterlace => folder + "splash-interlaced.png"; // filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public static string Filter0 => folder + "filter0.png"; public static string Filter1 => folder + "filter1.png"; diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/interlaced.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/interlaced.png new file mode 100644 index 0000000000..d4b75523dd --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Png/interlaced.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e1c92b1a57b762f736e14d480eb016b551ce0dc67aa588b246d838d00c0fc3f +size 17372 From cea1b5baf2753454c948e6324ad39871c641ea95 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 4 Dec 2016 14:19:10 +1100 Subject: [PATCH 3/6] Much better output. Nearly there. Touch #9 Image in issue works perfectly as does rgba test image. rgb 8x8 test image shows 2 pixel error though and I don't know why. --- .../Formats/Png/Filters/AverageFilter.cs | 2 - src/ImageSharp/Formats/Png/PngDecoderCore.cs | 50 +++++++++---------- tests/ImageSharp.Tests/FileTestBase.cs | 5 +- tests/ImageSharp.Tests/TestImages.cs | 5 +- .../TestImages/Formats/Png/interlaced.png | 4 +- 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index de494d42d0..d5f8107082 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -5,8 +5,6 @@ namespace ImageSharp.Formats { - using System; - /// /// The Average filter uses the average of the two neighboring pixels (left and above) to predict /// the value of a pixel. diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index dc76c27b82..587f29a6d8 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -440,28 +440,28 @@ namespace ImageSharp.Formats where TColor : struct, IPackedPixel where TPacked : struct { - for (int pass = 0; pass < 7; pass++) - { - int y = Adam7FirstRow[pass]; - int numColumns = this.ComputeColumnsAdam7(pass); - - if (numColumns == 0) - { - // This pass contains no data; skip to next pass - continue; - } + byte[] previousScanline = ArrayPool.Shared.Rent(this.bytesPerScanline); + byte[] scanline = ArrayPool.Shared.Rent(this.bytesPerScanline); - int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; + // Zero out the previousScanline, because the bytes that are rented from the arraypool may not be zero. + Array.Clear(previousScanline, 0, this.bytesPerScanline); - while (y < this.header.Height) + try + { + for (int pass = 0; pass < 7; pass++) { - byte[] previousScanline = ArrayPool.Shared.Rent(this.bytesPerScanline); - byte[] scanline = ArrayPool.Shared.Rent(this.bytesPerScanline); + int y = Adam7FirstRow[pass]; + int numColumns = this.ComputeColumnsAdam7(pass); - // Zero out the previousScanline, because the bytes that are rented from the arraypool may not be zero. - Array.Clear(previousScanline, 0, this.bytesPerScanline); + if (numColumns == 0) + { + // This pass contains no data; skip to next pass + continue; + } - try + int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; + + while (y < this.header.Height) { compressedStream.Read(scanline, 0, bytesPerInterlaceScanline); @@ -506,16 +506,17 @@ namespace ImageSharp.Formats this.ProcessInterlacedDefilteredScanline(scanline, y, pixels, Adam7FirstColumn[pass], Adam7ColumnIncrement[pass]); Swap(ref scanline, ref previousScanline); - } - finally - { - ArrayPool.Shared.Return(previousScanline); - ArrayPool.Shared.Return(scanline); - } - y += Adam7RowIncrement[pass]; + + y += Adam7RowIncrement[pass]; + } } } + finally + { + ArrayPool.Shared.Return(previousScanline); + ArrayPool.Shared.Return(scanline); + } } /// @@ -765,7 +766,6 @@ namespace ImageSharp.Formats } } - /// /// Reads a text chunk containing image properties from the data. /// diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index e8034ebfd1..33f6307792 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -31,8 +31,9 @@ namespace ImageSharp.Tests // new TestFile(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only // new TestFile(TestImages.Png.Blur), // Perf: Enable for local testing only // new TestFile(TestImages.Png.Indexed), // Perf: Enable for local testing only - //new TestFile(TestImages.Png.Splash), - new TestFile(TestImages.Png.SplashInterlace), + new TestFile(TestImages.Png.Splash), + //new TestFile(TestImages.Png.SplashInterlaced), + new TestFile(TestImages.Png.Interlaced), // new TestFile(TestImages.Png.Filter0), // Perf: Enable for local testing only // new TestFile(TestImages.Png.Filter1), // Perf: Enable for local testing only // new TestFile(TestImages.Png.Filter2), // Perf: Enable for local testing only diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index a8117df685..fcd8d27cd8 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -20,7 +20,10 @@ namespace ImageSharp.Tests public static string Indexed => folder + "indexed.png"; public static string Splash => folder + "splash.png"; - public static string SplashInterlace => folder + "splash-interlaced.png"; + public static string SplashInterlaced => folder + "splash-interlaced.png"; + + public static string Interlaced => folder + "interlaced.png"; + // filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public static string Filter0 => folder + "filter0.png"; public static string Filter1 => folder + "filter1.png"; diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/interlaced.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/interlaced.png index d4b75523dd..c9d76336cc 100644 --- a/tests/ImageSharp.Tests/TestImages/Formats/Png/interlaced.png +++ b/tests/ImageSharp.Tests/TestImages/Formats/Png/interlaced.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e1c92b1a57b762f736e14d480eb016b551ce0dc67aa588b246d838d00c0fc3f -size 17372 +oid sha256:21d34b50acf6bf71c68673585a298b8ddce3a908a46c38a6be9530487ebab8dc +size 17949 From c68ac88f108ec7b7a3798313c92ea5bec7f595ba Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 5 Dec 2016 07:21:57 +1100 Subject: [PATCH 4/6] Fix #9 --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 6 +++--- tests/ImageSharp.Tests/FileTestBase.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 587f29a6d8..1337334624 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -443,13 +443,13 @@ namespace ImageSharp.Formats byte[] previousScanline = ArrayPool.Shared.Rent(this.bytesPerScanline); byte[] scanline = ArrayPool.Shared.Rent(this.bytesPerScanline); - // Zero out the previousScanline, because the bytes that are rented from the arraypool may not be zero. - Array.Clear(previousScanline, 0, this.bytesPerScanline); - try { for (int pass = 0; pass < 7; pass++) { + // Zero out the previousScanline, because the bytes that are rented from the arraypool may not be zero. + Array.Clear(previousScanline, 0, this.bytesPerScanline); + int y = Adam7FirstRow[pass]; int numColumns = this.ComputeColumnsAdam7(pass); diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 33f6307792..e3aa12460b 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -32,7 +32,7 @@ namespace ImageSharp.Tests // new TestFile(TestImages.Png.Blur), // Perf: Enable for local testing only // new TestFile(TestImages.Png.Indexed), // Perf: Enable for local testing only new TestFile(TestImages.Png.Splash), - //new TestFile(TestImages.Png.SplashInterlaced), + new TestFile(TestImages.Png.SplashInterlaced), new TestFile(TestImages.Png.Interlaced), // new TestFile(TestImages.Png.Filter0), // Perf: Enable for local testing only // new TestFile(TestImages.Png.Filter1), // Perf: Enable for local testing only From 9cd7c5e22aae6bca2c6cbbf9c13d07723510608a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 5 Dec 2016 07:23:45 +1100 Subject: [PATCH 5/6] Cleanup --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 7 +++---- src/ImageSharp/Formats/Png/PngHeader.cs | 2 +- .../Formats/Png/{InterlaceMode.cs => PngInterlaceMode.cs} | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) rename src/ImageSharp/Formats/Png/{InterlaceMode.cs => PngInterlaceMode.cs} (79%) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 1337334624..d5cb40e3fd 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -344,7 +344,7 @@ namespace ImageSharp.Formats dataStream.Position = 0; using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream)) { - if (this.header.InterlaceMethod == InterlaceMode.Adam7) + if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) { this.DecodeInterlacedPixelData(compressedStream, pixels); } @@ -507,7 +507,6 @@ namespace ImageSharp.Formats Swap(ref scanline, ref previousScanline); - y += Adam7RowIncrement[pass]; } } @@ -813,7 +812,7 @@ namespace ImageSharp.Formats this.header.ColorType = data[9]; this.header.CompressionMethod = data[10]; this.header.FilterMethod = data[11]; - this.header.InterlaceMethod = (InterlaceMode)data[12]; + this.header.InterlaceMethod = (PngInterlaceMode)data[12]; } /// @@ -839,7 +838,7 @@ namespace ImageSharp.Formats throw new NotSupportedException("The png specification only defines 0 as filter method."); } - if (this.header.InterlaceMethod != InterlaceMode.None && this.header.InterlaceMethod != InterlaceMode.Adam7) + if (this.header.InterlaceMethod != PngInterlaceMode.None && this.header.InterlaceMethod != PngInterlaceMode.Adam7) { throw new NotSupportedException("The png specification only defines 'None' and 'Adam7' as interlaced methods."); } diff --git a/src/ImageSharp/Formats/Png/PngHeader.cs b/src/ImageSharp/Formats/Png/PngHeader.cs index ffad043f0d..f1d332c044 100644 --- a/src/ImageSharp/Formats/Png/PngHeader.cs +++ b/src/ImageSharp/Formats/Png/PngHeader.cs @@ -57,6 +57,6 @@ namespace ImageSharp.Formats /// Indicates the transmission order of the image data. /// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace). /// - public InterlaceMode InterlaceMethod { get; set; } + public PngInterlaceMode InterlaceMethod { get; set; } } } diff --git a/src/ImageSharp/Formats/Png/InterlaceMode.cs b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs similarity index 79% rename from src/ImageSharp/Formats/Png/InterlaceMode.cs rename to src/ImageSharp/Formats/Png/PngInterlaceMode.cs index 663c9611ca..e32e808c1f 100644 --- a/src/ImageSharp/Formats/Png/InterlaceMode.cs +++ b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -8,7 +8,7 @@ namespace ImageSharp.Formats /// /// Provides enumeration of available PNG interlace modes. /// - public enum InterlaceMode : byte + public enum PngInterlaceMode : byte { /// /// Non interlaced From 4474077b3c86afb8ee2a00c2a8534ef9a79d9973 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 5 Dec 2016 07:36:07 +1100 Subject: [PATCH 6/6] Fix packed pixel tests. --- tests/ImageSharp.Tests/Colors/PackedPixelTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index 583191bfc0..14f43b8e69 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -49,10 +49,10 @@ namespace ImageSharp.Tests.Colors new Alpha8(.5F).ToBytes(rgba, 0, ComponentOrder.XYZW); Assert.Equal(rgba, new byte[] { 0, 0, 0, 128 }); - new Alpha8(.5F).ToBytes(rgb, 0, ComponentOrder.ZYX); + new Alpha8(.5F).ToBytes(bgr, 0, ComponentOrder.ZYX); Assert.Equal(bgr, new byte[] { 0, 0, 0 }); - new Alpha8(.5F).ToBytes(rgb, 0, ComponentOrder.ZYXW); + new Alpha8(.5F).ToBytes(bgra, 0, ComponentOrder.ZYXW); Assert.Equal(bgra, new byte[] { 0, 0, 0, 128 }); } @@ -468,7 +468,7 @@ namespace ImageSharp.Tests.Colors r.ToBytes(rgba, 0, ComponentOrder.XYZW); Assert.Equal(rgba, new byte[] { 9, 115, 202, 127 }); - r.PackedValue = 0x7FCA7309; + r.PackedValue = 0xff4af389; r.ToBytes(rgba, 0, ComponentOrder.XYZW); Assert.Equal(rgba, new byte[] { 9, 115, 202, 127 }); }