diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index d5b5bfd0eb..05ba5ee60c 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 @@ -186,7 +191,7 @@ namespace ImageSharp.Formats PixelAccessor pixels = null; try { - using (DeframeStream deframeStream = new DeframeStream(this.currentStream)) + using (ZlibInflateStream deframeStream = new ZlibInflateStream(this.currentStream)) { PngChunk currentChunk; while (!this.isEndChunkReached && (currentChunk = this.ReadChunk()) != null) @@ -487,82 +492,85 @@ 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 (true) { - 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++; - int y = Adam7FirstRow[pass]; - int numColumns = this.ComputeColumnsAdam7(pass); + // This pass contains no data; skip to next pass + continue; + } + + 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; + this.currentRowBytesRead = 0; - while (y < this.header.Height) - { - compressedStream.Read(scanline, 0, bytesPerInterlaceScanline); + FilterType filterType = (FilterType)this.scanline[0]; - FilterType filterType = (FilterType)scanline[0]; + switch (filterType) + { + case FilterType.None: - switch (filterType) - { - case FilterType.None: + NoneFilter.Decode(this.scanline); - NoneFilter.Decode(scanline); + break; - break; + case FilterType.Sub: - case FilterType.Sub: + SubFilter.Decode(this.scanline, bytesPerInterlaceScanline, this.bytesPerPixel); - SubFilter.Decode(scanline, bytesPerInterlaceScanline, this.bytesPerPixel); + break; - break; + case FilterType.Up: - case FilterType.Up: + UpFilter.Decode(this.scanline, this.previousScanline, bytesPerInterlaceScanline); - UpFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline); + break; - break; + case FilterType.Average: - case FilterType.Average: + AverageFilter.Decode(this.scanline, this.previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); - AverageFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); + break; - break; + case FilterType.Paeth: - case FilterType.Paeth: + PaethFilter.Decode(this.scanline, this.previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); - PaethFilter.Decode(scanline, previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); + break; - break; + default: + throw new ImageFormatException("Unknown filter type."); + } - default: - throw new ImageFormatException("Unknown filter type."); - } + this.ProcessInterlacedDefilteredScanline(this.scanline, this.currentRow, pixels, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]); - this.ProcessInterlacedDefilteredScanline(scanline, y, pixels, Adam7FirstColumn[pass], Adam7ColumnIncrement[pass]); + Swap(ref this.scanline, ref this.previousScanline); - Swap(ref scanline, ref previousScanline); + this.currentRow += Adam7RowIncrement[this.pass]; + } - y += Adam7RowIncrement[pass]; - } + this.pass++; + if (this.pass < 7) + { + this.currentRow = Adam7FirstRow[this.pass]; + } + else + { + break; } - } - finally - { - ArrayPool.Shared.Return(previousScanline); - ArrayPool.Shared.Return(scanline); } } diff --git a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs index 7cf20768b8..5f92cc9e09 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs @@ -132,6 +132,11 @@ namespace ImageSharp.Formats throw new ArgumentOutOfRangeException(nameof(count), "cannot be negative"); } + if (offset >= buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset), "not a valid index into buffer"); + } + if (offset + count > buffer.Length) { throw new ArgumentOutOfRangeException(nameof(count), "exceeds buffer size"); @@ -146,10 +151,14 @@ namespace ImageSharp.Formats // We can defer the modulo operation: // s1 maximally grows from 65521 to 65521 + 255 * 3800 // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 - int n = Math.Min(3800, count); + int n = 3800; + if (n > count) + { + n = count; + } count -= n; - while (--n > -1) + while (--n >= 0) { s1 = s1 + (uint)(buffer[offset++] & 0xff); s2 = s2 + s1; diff --git a/src/ImageSharp/Formats/Png/Zlib/DeframeStream.cs b/src/ImageSharp/Formats/Png/Zlib/DeframeStream.cs deleted file mode 100644 index 9b0a61b675..0000000000 --- a/src/ImageSharp/Formats/Png/Zlib/DeframeStream.cs +++ /dev/null @@ -1,121 +0,0 @@ -namespace ImageSharp.Formats -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - - /// - /// Provides methods and properties for deframing streams from PNGs. - /// - internal class DeframeStream : Stream - { - /// - /// The inner raw memory stream - /// - private readonly Stream innerStream; - - /// - /// The compressed stream sitting over the top of the deframer - /// - private ZlibInflateStream compressedStream; - - /// - /// The current data remaining to be read - /// - private int currentDataRemaining; - - /// - /// Initializes a new instance of the class. - /// - /// The inner raw stream - public DeframeStream(Stream innerStream) - { - this.innerStream = innerStream; - } - - /// - public override bool CanRead => this.innerStream.CanRead; - - /// - public override bool CanSeek => false; - - /// - public override bool CanWrite => throw new NotSupportedException(); - - /// - public override long Length => throw new NotSupportedException(); - - /// - public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - /// - /// Gets the compressed stream over the deframed inner stream - /// - public ZlibInflateStream CompressedStream => this.compressedStream; - - /// - /// Adds new bytes from a frame found in the original stream - /// - /// blabla - public void AllocateNewBytes(int bytes) - { - this.currentDataRemaining = bytes; - if (this.compressedStream == null) - { - this.compressedStream = new ZlibInflateStream(this); - } - } - - /// - public override void Flush() - { - throw new NotSupportedException(); - } - - /// - public override int ReadByte() - { - this.currentDataRemaining--; - return this.innerStream.ReadByte(); - } - - /// - public override int Read(byte[] buffer, int offset, int count) - { - if (this.currentDataRemaining == 0) - { - return 0; - } - - int bytesToRead = Math.Min(count, this.currentDataRemaining); - this.currentDataRemaining -= bytesToRead; - return this.innerStream.Read(buffer, offset, bytesToRead); - } - - /// - 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) - { - this.compressedStream.Dispose(); - base.Dispose(disposing); - } - } -} diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs index 2deb7dcf07..c1f04fa981 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 index 977a4a167c..0743d8ded3 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs @@ -1,23 +1,25 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats +namespace ImageSharp.Formats { using System; + using System.Collections.Generic; using System.IO; using System.IO.Compression; + using System.Text; /// - /// Provides methods and properties for decompressing streams by using the Zlib Deflate algorithm. + /// Provides methods and properties for deframing streams from PNGs. /// - internal sealed class ZlibInflateStream : Stream + internal class ZlibInflateStream : Stream { /// - /// The raw stream containing the uncompressed image data. + /// The inner raw memory stream + /// + private readonly Stream innerStream; + + /// + /// The compressed stream sitting over the top of the deframer /// - private readonly Stream rawStream; + private DeflateStream compressedStream; /// /// A value indicating whether this instance of the given entity has been disposed. @@ -38,123 +40,76 @@ namespace ImageSharp.Formats private byte[] crcread; /// - /// The stream responsible for decompressing the input stream. + /// The current data remaining to be read /// - private DeflateStream deflateStream; + private int currentDataRemaining; /// /// Initializes a new instance of the class. /// - /// The stream. - /// - /// Thrown if the compression method is incorrect. - /// - public ZlibInflateStream(Stream stream) + /// The inner raw stream + public ZlibInflateStream(Stream innerStream) { - // 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); + this.innerStream = innerStream; } /// - public override bool CanRead => true; + public override bool CanRead => this.innerStream.CanRead; /// public override bool CanSeek => false; /// - public override bool CanWrite => false; + public override bool CanWrite => throw new NotSupportedException(); /// - public override long Length + public override long Length => throw new NotSupportedException(); + + /// + public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + /// + /// Gets the compressed stream over the deframed inner stream + /// + public DeflateStream CompressedStream => this.compressedStream; + + /// + /// Adds new bytes from a frame found in the original stream + /// + /// blabla + public void AllocateNewBytes(int bytes) { - get + this.currentDataRemaining = bytes; + if (this.compressedStream == null) { - throw new NotSupportedException(); + this.InitializeInflateStream(); } } /// - public override long Position + public override void Flush() { - get - { - throw new NotSupportedException(); - } - - set - { - throw new NotSupportedException(); - } + throw new NotSupportedException(); } /// - public override void Flush() + public override int ReadByte() { - this.deflateStream?.Flush(); + this.currentDataRemaining--; + return this.innerStream.ReadByte(); } /// 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) + if (this.currentDataRemaining == 0) { - // 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 0; } - return read; + int bytesToRead = Math.Min(count, this.currentDataRemaining); + this.currentDataRemaining -= bytesToRead; + return this.innerStream.Read(buffer, offset, bytesToRead); } /// @@ -186,10 +141,10 @@ namespace ImageSharp.Formats if (disposing) { // dispose managed resources - if (this.deflateStream != null) + if (this.compressedStream != null) { - this.deflateStream.Dispose(); - this.deflateStream = null; + this.compressedStream.Dispose(); + this.compressedStream = null; if (this.crcread == null) { @@ -197,7 +152,7 @@ namespace ImageSharp.Formats this.crcread = new byte[4]; for (int i = 0; i < 4; i++) { - this.crcread[i] = (byte)this.rawStream.ReadByte(); + this.crcread[i] = (byte)this.innerStream.ReadByte(); } } } @@ -210,5 +165,57 @@ namespace ImageSharp.Formats // 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); + } } }