Browse Source

Merge pull request #196 from Drawaes/master

Fixed Interlaced decoding for multi frames
pull/201/head
James Jackson-South 9 years ago
committed by GitHub
parent
commit
0f84c32a44
  1. 106
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 13
      src/ImageSharp/Formats/Png/Zlib/Adler32.cs
  3. 121
      src/ImageSharp/Formats/Png/Zlib/DeframeStream.cs
  4. 4
      src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
  5. 199
      src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs

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

@ -141,7 +141,12 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// The index of the current scanline being processed /// The index of the current scanline being processed
/// </summary> /// </summary>
private int currentRow = 0; private int currentRow = Adam7FirstRow[0];
/// <summary>
/// The current pass for an interlaced PNG
/// </summary>
private int pass = 0;
/// <summary> /// <summary>
/// The current number of bytes read in the current scanline /// The current number of bytes read in the current scanline
@ -186,7 +191,7 @@ namespace ImageSharp.Formats
PixelAccessor<TPixel> pixels = null; PixelAccessor<TPixel> pixels = null;
try try
{ {
using (DeframeStream deframeStream = new DeframeStream(this.currentStream)) using (ZlibInflateStream deframeStream = new ZlibInflateStream(this.currentStream))
{ {
PngChunk currentChunk; PngChunk currentChunk;
while (!this.isEndChunkReached && (currentChunk = this.ReadChunk()) != null) while (!this.isEndChunkReached && (currentChunk = this.ReadChunk()) != null)
@ -487,82 +492,85 @@ namespace ImageSharp.Formats
private void DecodeInterlacedPixelData<TPixel>(Stream compressedStream, PixelAccessor<TPixel> pixels) private void DecodeInterlacedPixelData<TPixel>(Stream compressedStream, PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
byte[] previousScanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline); while (true)
byte[] scanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline);
try
{ {
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. this.pass++;
Array.Clear(scanline, 0, this.bytesPerScanline);
Array.Clear(previousScanline, 0, this.bytesPerScanline);
int y = Adam7FirstRow[pass]; // This pass contains no data; skip to next pass
int numColumns = this.ComputeColumnsAdam7(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 return;
continue;
} }
int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; this.currentRowBytesRead = 0;
while (y < this.header.Height) FilterType filterType = (FilterType)this.scanline[0];
{
compressedStream.Read(scanline, 0, bytesPerInterlaceScanline);
FilterType filterType = (FilterType)scanline[0]; switch (filterType)
{
case FilterType.None:
switch (filterType) NoneFilter.Decode(this.scanline);
{
case FilterType.None:
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: this.ProcessInterlacedDefilteredScanline(this.scanline, this.currentRow, pixels, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]);
throw new ImageFormatException("Unknown filter type.");
}
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<byte>.Shared.Return(previousScanline);
ArrayPool<byte>.Shared.Return(scanline);
} }
} }

13
src/ImageSharp/Formats/Png/Zlib/Adler32.cs

@ -132,6 +132,11 @@ namespace ImageSharp.Formats
throw new ArgumentOutOfRangeException(nameof(count), "cannot be negative"); 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) if (offset + count > buffer.Length)
{ {
throw new ArgumentOutOfRangeException(nameof(count), "exceeds buffer size"); throw new ArgumentOutOfRangeException(nameof(count), "exceeds buffer size");
@ -146,10 +151,14 @@ namespace ImageSharp.Formats
// We can defer the modulo operation: // We can defer the modulo operation:
// s1 maximally grows from 65521 to 65521 + 255 * 3800 // s1 maximally grows from 65521 to 65521 + 255 * 3800
// s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 // 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; count -= n;
while (--n > -1) while (--n >= 0)
{ {
s1 = s1 + (uint)(buffer[offset++] & 0xff); s1 = s1 + (uint)(buffer[offset++] & 0xff);
s2 = s2 + s1; s2 = s2 + s1;

121
src/ImageSharp/Formats/Png/Zlib/DeframeStream.cs

@ -1,121 +0,0 @@
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
/// <summary>
/// Provides methods and properties for deframing streams from PNGs.
/// </summary>
internal class DeframeStream : Stream
{
/// <summary>
/// The inner raw memory stream
/// </summary>
private readonly Stream innerStream;
/// <summary>
/// The compressed stream sitting over the top of the deframer
/// </summary>
private ZlibInflateStream compressedStream;
/// <summary>
/// The current data remaining to be read
/// </summary>
private int currentDataRemaining;
/// <summary>
/// Initializes a new instance of the <see cref="DeframeStream"/> class.
/// </summary>
/// <param name="innerStream">The inner raw stream</param>
public DeframeStream(Stream innerStream)
{
this.innerStream = innerStream;
}
/// <inheritdoc/>
public override bool CanRead => this.innerStream.CanRead;
/// <inheritdoc/>
public override bool CanSeek => false;
/// <inheritdoc/>
public override bool CanWrite => throw new NotSupportedException();
/// <inheritdoc/>
public override long Length => throw new NotSupportedException();
/// <inheritdoc/>
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
/// <summary>
/// Gets the compressed stream over the deframed inner stream
/// </summary>
public ZlibInflateStream CompressedStream => this.compressedStream;
/// <summary>
/// Adds new bytes from a frame found in the original stream
/// </summary>
/// <param name="bytes">blabla</param>
public void AllocateNewBytes(int bytes)
{
this.currentDataRemaining = bytes;
if (this.compressedStream == null)
{
this.compressedStream = new ZlibInflateStream(this);
}
}
/// <inheritdoc/>
public override void Flush()
{
throw new NotSupportedException();
}
/// <inheritdoc/>
public override int ReadByte()
{
this.currentDataRemaining--;
return this.innerStream.ReadByte();
}
/// <inheritdoc/>
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);
}
/// <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)
{
this.compressedStream.Dispose();
base.Dispose(disposing);
}
}
}

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

@ -40,7 +40,7 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// The stream responsible for compressing the input stream. /// The stream responsible for compressing the input stream.
/// </summary> /// </summary>
private DeflateStream deflateStream; private System.IO.Compression.DeflateStream deflateStream;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ZlibDeflateStream"/> class. /// Initializes a new instance of the <see cref="ZlibDeflateStream"/> class.
@ -102,7 +102,7 @@ namespace ImageSharp.Formats
level = CompressionLevel.NoCompression; level = CompressionLevel.NoCompression;
} }
this.deflateStream = new DeflateStream(this.rawStream, level, true); this.deflateStream = new System.IO.Compression.DeflateStream(this.rawStream, level, true);
} }
/// <inheritdoc/> /// <inheritdoc/>

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

@ -1,23 +1,25 @@
// <copyright file="ZlibInflateStream.cs" company="James Jackson-South"> namespace ImageSharp.Formats
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Text;
/// <summary> /// <summary>
/// Provides methods and properties for decompressing streams by using the Zlib Deflate algorithm. /// Provides methods and properties for deframing streams from PNGs.
/// </summary> /// </summary>
internal sealed class ZlibInflateStream : Stream internal class ZlibInflateStream : Stream
{ {
/// <summary> /// <summary>
/// The raw stream containing the uncompressed image data. /// The inner raw memory stream
/// </summary>
private readonly Stream innerStream;
/// <summary>
/// The compressed stream sitting over the top of the deframer
/// </summary> /// </summary>
private readonly Stream rawStream; private DeflateStream compressedStream;
/// <summary> /// <summary>
/// A value indicating whether this instance of the given entity has been disposed. /// A value indicating whether this instance of the given entity has been disposed.
@ -38,123 +40,76 @@ namespace ImageSharp.Formats
private byte[] crcread; private byte[] crcread;
/// <summary> /// <summary>
/// The stream responsible for decompressing the input stream. /// The current data remaining to be read
/// </summary> /// </summary>
private DeflateStream deflateStream; private int currentDataRemaining;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class. /// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
/// </summary> /// </summary>
/// <param name="stream">The stream.</param> /// <param name="innerStream">The inner raw stream</param>
/// <exception cref="Exception"> public ZlibInflateStream(Stream innerStream)
/// Thrown if the compression method is incorrect.
/// </exception>
public ZlibInflateStream(Stream stream)
{ {
// The DICT dictionary identifier identifying the used dictionary. this.innerStream = innerStream;
// 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/> /// <inheritdoc/>
public override bool CanRead => true; public override bool CanRead => this.innerStream.CanRead;
/// <inheritdoc/> /// <inheritdoc/>
public override bool CanSeek => false; public override bool CanSeek => false;
/// <inheritdoc/> /// <inheritdoc/>
public override bool CanWrite => false; public override bool CanWrite => throw new NotSupportedException();
/// <inheritdoc/> /// <inheritdoc/>
public override long Length public override long Length => throw new NotSupportedException();
/// <inheritdoc/>
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
/// <summary>
/// Gets the compressed stream over the deframed inner stream
/// </summary>
public DeflateStream CompressedStream => this.compressedStream;
/// <summary>
/// Adds new bytes from a frame found in the original stream
/// </summary>
/// <param name="bytes">blabla</param>
public void AllocateNewBytes(int bytes)
{ {
get this.currentDataRemaining = bytes;
if (this.compressedStream == null)
{ {
throw new NotSupportedException(); this.InitializeInflateStream();
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
public override long Position public override void Flush()
{ {
get throw new NotSupportedException();
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Flush() public override int ReadByte()
{ {
this.deflateStream?.Flush(); this.currentDataRemaining--;
return this.innerStream.ReadByte();
} }
/// <inheritdoc/> /// <inheritdoc/>
public override int Read(byte[] buffer, int offset, int count) public override int Read(byte[] buffer, int offset, int count)
{ {
// We dont't check CRC on reading if (this.currentDataRemaining == 0)
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) return 0;
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; int bytesToRead = Math.Min(count, this.currentDataRemaining);
this.currentDataRemaining -= bytesToRead;
return this.innerStream.Read(buffer, offset, bytesToRead);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -186,10 +141,10 @@ namespace ImageSharp.Formats
if (disposing) if (disposing)
{ {
// dispose managed resources // dispose managed resources
if (this.deflateStream != null) if (this.compressedStream != null)
{ {
this.deflateStream.Dispose(); this.compressedStream.Dispose();
this.deflateStream = null; this.compressedStream = null;
if (this.crcread == null) if (this.crcread == null)
{ {
@ -197,7 +152,7 @@ namespace ImageSharp.Formats
this.crcread = new byte[4]; this.crcread = new byte[4];
for (int i = 0; i < 4; i++) 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. // Note disposing is done.
this.isDisposed = true; 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);
}
} }
} }

Loading…
Cancel
Save