diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 928192701..ef2f22655 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -187,6 +187,11 @@ namespace SixLabors.ImageSharp.Formats.Png
///
private bool hasTrans;
+ ///
+ /// The next chunk of data to return
+ ///
+ private PngChunk? nextChunk;
+
///
/// Initializes a new instance of the class.
///
@@ -223,67 +228,67 @@ namespace SixLabors.ImageSharp.Formats.Png
Image image = null;
try
{
- using (var deframeStream = new ZlibInflateStream(this.currentStream))
+ while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk))
{
- while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk))
+ try
{
- try
+ switch (chunk.Type)
{
- switch (chunk.Type)
- {
- case PngChunkType.Header:
- this.ReadHeaderChunk(pngMetaData, chunk.Data.Array);
- this.ValidateHeader();
- break;
- case PngChunkType.Physical:
- this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan());
- break;
- case PngChunkType.Gamma:
- this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan());
- break;
- case PngChunkType.Data:
- if (image is null)
- {
- this.InitializeImage(metaData, out image);
- }
+ case PngChunkType.Header:
+ this.ReadHeaderChunk(pngMetaData, chunk.Data.Array);
+ this.ValidateHeader();
+ break;
+ case PngChunkType.Physical:
+ this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan());
+ break;
+ case PngChunkType.Gamma:
+ this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan());
+ break;
+ case PngChunkType.Data:
+ if (image is null)
+ {
+ this.InitializeImage(metaData, out image);
+ }
+ using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
+ {
deframeStream.AllocateNewBytes(chunk.Length);
this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame);
- this.currentStream.Read(this.crcBuffer, 0, 4);
- break;
- case PngChunkType.Palette:
- byte[] pal = new byte[chunk.Length];
- Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length);
- this.palette = pal;
- break;
- case PngChunkType.PaletteAlpha:
- byte[] alpha = new byte[chunk.Length];
- Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length);
- this.paletteAlpha = alpha;
- this.AssignTransparentMarkers(alpha);
- break;
- case PngChunkType.Text:
- this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
- break;
- case PngChunkType.Exif:
- if (!this.ignoreMetadata)
- {
- byte[] exifData = new byte[chunk.Length];
- Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
- metaData.ExifProfile = new ExifProfile(exifData);
- }
-
- break;
- case PngChunkType.End:
- this.isEndChunkReached = true;
- break;
- }
- }
- finally
- {
- chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
+ }
+
+ break;
+ case PngChunkType.Palette:
+ byte[] pal = new byte[chunk.Length];
+ Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length);
+ this.palette = pal;
+ break;
+ case PngChunkType.PaletteAlpha:
+ byte[] alpha = new byte[chunk.Length];
+ Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length);
+ this.paletteAlpha = alpha;
+ this.AssignTransparentMarkers(alpha);
+ break;
+ case PngChunkType.Text:
+ this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
+ break;
+ case PngChunkType.Exif:
+ if (!this.ignoreMetadata)
+ {
+ byte[] exifData = new byte[chunk.Length];
+ Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
+ metaData.ExifProfile = new ExifProfile(exifData);
+ }
+
+ break;
+ case PngChunkType.End:
+ this.isEndChunkReached = true;
+ break;
}
}
+ finally
+ {
+ chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
+ }
}
if (image is null)
@@ -1366,6 +1371,32 @@ namespace SixLabors.ImageSharp.Formats.Png
metadata.Properties.Add(new ImageProperty(name, value));
}
+ ///
+ /// Reads the next data chunk.
+ ///
+ /// Count of bytes in the next data chunk, or 0 if there are no more data chunks left.
+ private int ReadNextDataChunk()
+ {
+ if (this.nextChunk != null)
+ {
+ return 0;
+ }
+
+ this.currentStream.Read(this.crcBuffer, 0, 4);
+
+ if (this.TryReadChunk(out PngChunk chunk))
+ {
+ if (chunk.Type == PngChunkType.Data)
+ {
+ return chunk.Length;
+ }
+
+ this.nextChunk = chunk;
+ }
+
+ return 0;
+ }
+
///
/// Reads a chunk from the stream.
///
@@ -1375,6 +1406,15 @@ namespace SixLabors.ImageSharp.Formats.Png
///
private bool TryReadChunk(out PngChunk chunk)
{
+ if (this.nextChunk != null)
+ {
+ chunk = this.nextChunk.Value;
+
+ this.nextChunk = null;
+
+ return true;
+ }
+
int length = this.ReadChunkLength();
if (length == -1)
diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
index 55432d60b..a92220a59 100644
--- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
+++ b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
@@ -43,22 +43,24 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private bool isDisposed;
///
- /// Whether the crc value has been read.
+ /// The current data remaining to be read
///
- private bool crcRead;
+ private int currentDataRemaining;
///
- /// The current data remaining to be read
+ /// Delegate to get more data once we've exhausted the current data remaining
///
- private int currentDataRemaining;
+ private Func getData;
///
/// Initializes a new instance of the class.
///
/// The inner raw stream
- public ZlibInflateStream(Stream innerStream)
+ /// A delegate to get more data from the inner stream
+ public ZlibInflateStream(Stream innerStream, Func getData)
{
this.innerStream = innerStream;
+ this.getData = getData;
}
///
@@ -112,12 +114,36 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
if (this.currentDataRemaining == 0)
{
- return 0;
+ // last buffer was read in its entirety, let's make sure we don't actually have more
+ this.currentDataRemaining = this.getData();
+
+ if (this.currentDataRemaining == 0)
+ {
+ return 0;
+ }
}
int bytesToRead = Math.Min(count, this.currentDataRemaining);
this.currentDataRemaining -= bytesToRead;
- return this.innerStream.Read(buffer, offset, bytesToRead);
+ int bytesRead = this.innerStream.Read(buffer, offset, bytesToRead);
+
+ // keep reading data until we've reached the end of the stream or filled the buffer
+ while (this.currentDataRemaining == 0 && bytesRead < count)
+ {
+ this.currentDataRemaining = this.getData();
+
+ if (this.currentDataRemaining == 0)
+ {
+ return bytesRead;
+ }
+
+ offset += bytesRead;
+ bytesToRead = Math.Min(count - bytesRead, this.currentDataRemaining);
+ this.currentDataRemaining -= bytesToRead;
+ bytesRead += this.innerStream.Read(buffer, offset, bytesToRead);
+ }
+
+ return bytesRead;
}
///
@@ -153,14 +179,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
this.compressedStream.Dispose();
this.compressedStream = null;
-
- if (!this.crcRead)
- {
- // Consume the trailing 4 bytes
- this.innerStream.Read(ChecksumBuffer, 0, 4);
- this.currentDataRemaining -= 4;
- this.crcRead = true;
- }
}
}