Browse Source

Fixed Interlaced decoding for multi frames

Removed the ZlibInflateStream as extra layer of un-needed indirection
pull/196/head
Drawaes 9 years ago
parent
commit
1c338a9520
  1. 96
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 3
      src/ImageSharp/Formats/Png/Zlib/Adler32.cs
  3. 108
      src/ImageSharp/Formats/Png/Zlib/DeframeStream.cs
  4. 4
      src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
  5. 214
      src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs

96
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -141,7 +141,12 @@ namespace ImageSharp.Formats
/// <summary>
/// The index of the current scanline being processed
/// </summary>
private int currentRow = 0;
private int currentRow = Adam7FirstRow[0];
/// <summary>
/// The current pass for an interlaced PNG
/// </summary>
private int pass = 0;
/// <summary>
/// The current number of bytes read in the current scanline
@ -487,82 +492,75 @@ namespace ImageSharp.Formats
private void DecodeInterlacedPixelData<TPixel>(Stream compressedStream, PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
byte[] previousScanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline);
byte[] scanline = ArrayPool<byte>.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<byte>.Shared.Return(previousScanline);
ArrayPool<byte>.Shared.Return(scanline);
this.pass++;
}
}

3
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.)"
/// </remarks>
/// <see cref="ZlibInflateStream"/>
/// <see cref="ZlibDeflateStream"/>
/// <see cref="DeframeStream"/>
internal sealed class Adler32 : IChecksum
{
/// <summary>

108
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;
/// <summary>
@ -18,7 +19,25 @@
/// <summary>
/// The compressed stream sitting over the top of the deframer
/// </summary>
private ZlibInflateStream compressedStream;
private DeflateStream compressedStream;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// 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.
/// </remarks>
private bool isDisposed;
/// <summary>
/// The read crc data.
/// </summary>
private byte[] crcread;
/// <summary>
/// The current data remaining to be read
@ -52,7 +71,7 @@
/// <summary>
/// Gets the compressed stream over the deframed inner stream
/// </summary>
public ZlibInflateStream CompressedStream => this.compressedStream;
public DeflateStream CompressedStream => this.compressedStream;
/// <summary>
/// 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 @@
/// <inheritdoc/>
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);
}
}
}

4
src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs

@ -40,7 +40,7 @@ namespace ImageSharp.Formats
/// <summary>
/// The stream responsible for compressing the input stream.
/// </summary>
private DeflateStream deflateStream;
private System.IO.Compression.DeflateStream deflateStream;
/// <summary>
/// Initializes a new instance of the <see cref="ZlibDeflateStream"/> 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);
}
/// <inheritdoc/>

214
src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs

@ -1,214 +0,0 @@
// <copyright file="ZlibInflateStream.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.IO;
using System.IO.Compression;
/// <summary>
/// Provides methods and properties for decompressing streams by using the Zlib Deflate algorithm.
/// </summary>
internal sealed class ZlibInflateStream : Stream
{
/// <summary>
/// The raw stream containing the uncompressed image data.
/// </summary>
private readonly Stream rawStream;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// 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.
/// </remarks>
private bool isDisposed;
/// <summary>
/// The read crc data.
/// </summary>
private byte[] crcread;
/// <summary>
/// The stream responsible for decompressing the input stream.
/// </summary>
private DeflateStream deflateStream;
/// <summary>
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
/// </summary>
/// <param name="stream">The stream.</param>
/// <exception cref="Exception">
/// Thrown if the compression method is incorrect.
/// </exception>
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);
}
/// <inheritdoc/>
public override bool CanRead => true;
/// <inheritdoc/>
public override bool CanSeek => false;
/// <inheritdoc/>
public override bool CanWrite => false;
/// <inheritdoc/>
public override long Length
{
get
{
throw new NotSupportedException();
}
}
/// <inheritdoc/>
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
/// <inheritdoc/>
public override void Flush()
{
this.deflateStream?.Flush();
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
public override void SetLength(long value)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
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;
}
}
}
Loading…
Cancel
Save