diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index d5b5bfd0e..aece86700 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -141,7 +141,12 @@ namespace ImageSharp.Formats /// /// The index of the current scanline being processed /// - private int currentRow = 0; + private int currentRow = Adam7FirstRow[0]; + + /// + /// The current pass for an interlaced PNG + /// + private int pass = 0; /// /// The current number of bytes read in the current scanline @@ -487,82 +492,75 @@ namespace ImageSharp.Formats private void DecodeInterlacedPixelData(Stream compressedStream, PixelAccessor pixels) where TPixel : struct, IPixel { - byte[] previousScanline = ArrayPool.Shared.Rent(this.bytesPerScanline); - byte[] scanline = ArrayPool.Shared.Rent(this.bytesPerScanline); - - try + while (this.pass < 7) { - for (int pass = 0; pass < 7; pass++) + int numColumns = this.ComputeColumnsAdam7(this.pass); + + if (numColumns == 0) { - // Zero out the scanlines, because the bytes that are rented from the arraypool may not be zero. - Array.Clear(scanline, 0, this.bytesPerScanline); - Array.Clear(previousScanline, 0, this.bytesPerScanline); + // This pass contains no data; skip to next pass + continue; + } - int y = Adam7FirstRow[pass]; - int numColumns = this.ComputeColumnsAdam7(pass); + int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; - if (numColumns == 0) + while (this.currentRow < this.header.Height) + { + int bytesRead = compressedStream.Read(this.scanline, this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); + this.currentRowBytesRead += bytesRead; + if (this.currentRowBytesRead < bytesPerInterlaceScanline) { - // This pass contains no data; skip to next pass - continue; + return; } - int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; - - while (y < this.header.Height) - { - compressedStream.Read(scanline, 0, bytesPerInterlaceScanline); + this.currentRowBytesRead = 0; - FilterType filterType = (FilterType)scanline[0]; + FilterType filterType = (FilterType)this.scanline[0]; - switch (filterType) - { - case FilterType.None: + switch (filterType) + { + case FilterType.None: - NoneFilter.Decode(scanline); + NoneFilter.Decode(this.scanline); - break; + break; - case FilterType.Sub: + case FilterType.Sub: - SubFilter.Decode(scanline, bytesPerInterlaceScanline, this.bytesPerPixel); + SubFilter.Decode(this.scanline, bytesPerInterlaceScanline, this.bytesPerPixel); - break; + break; - case FilterType.Up: + case FilterType.Up: - UpFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline); + UpFilter.Decode(this.scanline, this.previousScanline, bytesPerInterlaceScanline); - break; + break; - case FilterType.Average: + case FilterType.Average: - AverageFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); + AverageFilter.Decode(this.scanline, this.previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); - break; + break; - case FilterType.Paeth: + case FilterType.Paeth: - PaethFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); + PaethFilter.Decode(this.scanline, this.previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); - break; + break; - default: - throw new ImageFormatException("Unknown filter type."); - } + default: + throw new ImageFormatException("Unknown filter type."); + } - this.ProcessInterlacedDefilteredScanline(scanline, y, pixels, Adam7FirstColumn[pass], Adam7ColumnIncrement[pass]); + this.ProcessInterlacedDefilteredScanline(this.scanline, this.currentRow, pixels, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]); - Swap(ref scanline, ref previousScanline); + Swap(ref this.scanline, ref this.previousScanline); - y += Adam7RowIncrement[pass]; - } + this.currentRow += Adam7RowIncrement[this.pass]; } - } - finally - { - ArrayPool.Shared.Return(previousScanline); - ArrayPool.Shared.Return(scanline); + + this.pass++; } } diff --git a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs index e5101532c..69681d030 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs @@ -52,8 +52,7 @@ namespace ImageSharp.Formats /// checked separately. (Any sequence of zeroes has a Fletcher /// checksum of zero.)" /// - /// - /// + /// internal sealed class Adler32 : IChecksum { /// diff --git a/src/ImageSharp/Formats/Png/Zlib/DeframeStream.cs b/src/ImageSharp/Formats/Png/Zlib/DeframeStream.cs index 9b0a61b67..2e92fd52d 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeframeStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeframeStream.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; + using System.IO.Compression; using System.Text; /// @@ -18,7 +19,25 @@ /// /// The compressed stream sitting over the top of the deframer /// - private ZlibInflateStream compressedStream; + private DeflateStream compressedStream; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// The read crc data. + /// + private byte[] crcread; /// /// The current data remaining to be read @@ -52,7 +71,7 @@ /// /// Gets the compressed stream over the deframed inner stream /// - public ZlibInflateStream CompressedStream => this.compressedStream; + public DeflateStream CompressedStream => this.compressedStream; /// /// Adds new bytes from a frame found in the original stream @@ -63,7 +82,7 @@ this.currentDataRemaining = bytes; if (this.compressedStream == null) { - this.compressedStream = new ZlibInflateStream(this); + this.InitializeInflateStream(); } } @@ -114,8 +133,89 @@ /// protected override void Dispose(bool disposing) { - this.compressedStream.Dispose(); + if (this.isDisposed) + { + return; + } + + if (disposing) + { + // dispose managed resources + if (this.compressedStream != null) + { + this.compressedStream.Dispose(); + this.compressedStream = null; + + if (this.crcread == null) + { + // Consume the trailing 4 bytes + this.crcread = new byte[4]; + for (int i = 0; i < 4; i++) + { + this.crcread[i] = (byte)this.innerStream.ReadByte(); + } + } + } + } + base.Dispose(disposing); + + // Call the appropriate methods to clean up + // unmanaged resources here. + // Note disposing is done. + this.isDisposed = true; + } + + private void InitializeInflateStream() + { + // The DICT dictionary identifier identifying the used dictionary. + + // The preset dictionary. + bool fdict; + + // Read the zlib header : http://tools.ietf.org/html/rfc1950 + // CMF(Compression Method and flags) + // This byte is divided into a 4 - bit compression method and a + // 4-bit information field depending on the compression method. + // bits 0 to 3 CM Compression method + // bits 4 to 7 CINFO Compression info + // + // 0 1 + // +---+---+ + // |CMF|FLG| + // +---+---+ + int cmf = this.innerStream.ReadByte(); + int flag = this.innerStream.ReadByte(); + this.currentDataRemaining -= 2; + if (cmf == -1 || flag == -1) + { + return; + } + + if ((cmf & 0x0f) != 8) + { + throw new Exception($"Bad compression method for ZLIB header: cmf={cmf}"); + } + + // CINFO is the base-2 logarithm of the LZ77 window size, minus eight. + // int cinfo = ((cmf & (0xf0)) >> 8); + fdict = (flag & 32) != 0; + + if (fdict) + { + // The DICT dictionary identifier identifying the used dictionary. + byte[] dictId = new byte[4]; + + for (int i = 0; i < 4; i++) + { + // We consume but don't use this. + dictId[i] = (byte)this.innerStream.ReadByte(); + this.currentDataRemaining--; + } + } + + // Initialize the deflate Stream. + this.compressedStream = new DeflateStream(this, CompressionMode.Decompress, true); } } } diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs index 2deb7dcf0..c1f04fa98 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs @@ -40,7 +40,7 @@ namespace ImageSharp.Formats /// /// The stream responsible for compressing the input stream. /// - private DeflateStream deflateStream; + private System.IO.Compression.DeflateStream deflateStream; /// /// Initializes a new instance of the class. @@ -102,7 +102,7 @@ namespace ImageSharp.Formats level = CompressionLevel.NoCompression; } - this.deflateStream = new DeflateStream(this.rawStream, level, true); + this.deflateStream = new System.IO.Compression.DeflateStream(this.rawStream, level, true); } /// diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs deleted file mode 100644 index 977a4a167..000000000 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs +++ /dev/null @@ -1,214 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System; - using System.IO; - using System.IO.Compression; - - /// - /// Provides methods and properties for decompressing streams by using the Zlib Deflate algorithm. - /// - internal sealed class ZlibInflateStream : Stream - { - /// - /// The raw stream containing the uncompressed image data. - /// - private readonly Stream rawStream; - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second - /// time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() - /// method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - /// - /// The read crc data. - /// - private byte[] crcread; - - /// - /// The stream responsible for decompressing the input stream. - /// - private DeflateStream deflateStream; - - /// - /// Initializes a new instance of the class. - /// - /// The stream. - /// - /// Thrown if the compression method is incorrect. - /// - public ZlibInflateStream(Stream stream) - { - // The DICT dictionary identifier identifying the used dictionary. - - // The preset dictionary. - bool fdict; - this.rawStream = stream; - - // Read the zlib header : http://tools.ietf.org/html/rfc1950 - // CMF(Compression Method and flags) - // This byte is divided into a 4 - bit compression method and a - // 4-bit information field depending on the compression method. - // bits 0 to 3 CM Compression method - // bits 4 to 7 CINFO Compression info - // - // 0 1 - // +---+---+ - // |CMF|FLG| - // +---+---+ - int cmf = this.rawStream.ReadByte(); - int flag = this.rawStream.ReadByte(); - if (cmf == -1 || flag == -1) - { - return; - } - - if ((cmf & 0x0f) != 8) - { - throw new Exception($"Bad compression method for ZLIB header: cmf={cmf}"); - } - - // CINFO is the base-2 logarithm of the LZ77 window size, minus eight. - // int cinfo = ((cmf & (0xf0)) >> 8); - fdict = (flag & 32) != 0; - - if (fdict) - { - // The DICT dictionary identifier identifying the used dictionary. - byte[] dictId = new byte[4]; - - for (int i = 0; i < 4; i++) - { - // We consume but don't use this. - dictId[i] = (byte)this.rawStream.ReadByte(); - } - } - - // Initialize the deflate Stream. - this.deflateStream = new DeflateStream(this.rawStream, CompressionMode.Decompress, true); - } - - /// - public override bool CanRead => true; - - /// - public override bool CanSeek => false; - - /// - public override bool CanWrite => false; - - /// - public override long Length - { - get - { - throw new NotSupportedException(); - } - } - - /// - public override long Position - { - get - { - throw new NotSupportedException(); - } - - set - { - throw new NotSupportedException(); - } - } - - /// - public override void Flush() - { - this.deflateStream?.Flush(); - } - - /// - public override int Read(byte[] buffer, int offset, int count) - { - // We dont't check CRC on reading - int read = this.deflateStream.Read(buffer, offset, count); - if (read < 1 && this.crcread == null) - { - // The deflater has ended. We try to read the next 4 bytes from raw stream (crc) - this.crcread = new byte[4]; - for (int i = 0; i < 4; i++) - { - // we dont really check/use this - this.crcread[i] = (byte)this.rawStream.ReadByte(); - } - } - - return read; - } - - /// - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - /// - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - /// - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - // dispose managed resources - if (this.deflateStream != null) - { - this.deflateStream.Dispose(); - this.deflateStream = null; - - if (this.crcread == null) - { - // Consume the trailing 4 bytes - this.crcread = new byte[4]; - for (int i = 0; i < 4; i++) - { - this.crcread[i] = (byte)this.rawStream.ReadByte(); - } - } - } - } - - base.Dispose(disposing); - - // Call the appropriate methods to clean up - // unmanaged resources here. - // Note disposing is done. - this.isDisposed = true; - } - } -}