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);
+ }
}
}