Browse Source

Merge pull request #187 from Drawaes/master

Performance improvements for PNG Decoding
af/merge-core
James Jackson-South 9 years ago
committed by GitHub
parent
commit
cd5a79f75e
  1. 398
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 4
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  3. 2
      src/ImageSharp/Formats/Png/PngHeader.cs
  4. 17
      src/ImageSharp/Formats/Png/Zlib/Adler32.cs
  5. 121
      src/ImageSharp/Formats/Png/Zlib/DeframeStream.cs

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

@ -24,7 +24,14 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// The dictionary of available color types. /// The dictionary of available color types.
/// </summary> /// </summary>
private static readonly Dictionary<int, byte[]> ColorTypes = new Dictionary<int, byte[]>(); private static readonly Dictionary<PngColorType, byte[]> ColorTypes = new Dictionary<PngColorType, byte[]>()
{
[PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8 },
[PngColorType.Rgb] = new byte[] { 8 },
[PngColorType.Palette] = new byte[] { 1, 2, 4, 8 },
[PngColorType.GrayscaleWithAlpha] = new byte[] { 8 },
[PngColorType.RgbWithAlpha] = new byte[] { 8 },
};
/// <summary> /// <summary>
/// The amount to increment when processing each column per scanline for each interlaced pass /// The amount to increment when processing each column per scanline for each interlaced pass
@ -122,20 +129,24 @@ namespace ImageSharp.Formats
private bool isEndChunkReached; private bool isEndChunkReached;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="PngDecoderCore"/> class. /// Previous scanline processed
/// </summary> /// </summary>
static PngDecoderCore() private byte[] previousScanline;
{
ColorTypes.Add((int)PngColorType.Grayscale, new byte[] { 1, 2, 4, 8 });
ColorTypes.Add((int)PngColorType.Rgb, new byte[] { 8 });
ColorTypes.Add((int)PngColorType.Palette, new byte[] { 1, 2, 4, 8 }); /// <summary>
/// The current scanline that is being processed
/// </summary>
private byte[] scanline;
ColorTypes.Add((int)PngColorType.GrayscaleWithAlpha, new byte[] { 8 }); /// <summary>
/// The index of the current scanline being processed
/// </summary>
private int currentRow = 0;
ColorTypes.Add((int)PngColorType.RgbWithAlpha, new byte[] { 8 }); /// <summary>
} /// The current number of bytes read in the current scanline
/// </summary>
private int currentRowBytesRead = 0;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class. /// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
@ -171,65 +182,76 @@ namespace ImageSharp.Formats
ImageMetaData metadata = new ImageMetaData(); ImageMetaData metadata = new ImageMetaData();
this.currentStream = stream; this.currentStream = stream;
this.currentStream.Skip(8); this.currentStream.Skip(8);
Image<TPixel> image = null;
using (MemoryStream dataStream = new MemoryStream()) PixelAccessor<TPixel> pixels = null;
try
{ {
PngChunk currentChunk; using (DeframeStream deframeStream = new DeframeStream(this.currentStream))
while (!this.isEndChunkReached && (currentChunk = this.ReadChunk()) != null)
{ {
try PngChunk currentChunk;
while (!this.isEndChunkReached && (currentChunk = this.ReadChunk()) != null)
{ {
switch (currentChunk.Type) try
{ {
case PngChunkTypes.Header: switch (currentChunk.Type)
this.ReadHeaderChunk(currentChunk.Data); {
this.ValidateHeader(); case PngChunkTypes.Header:
break; this.ReadHeaderChunk(currentChunk.Data);
case PngChunkTypes.Physical: this.ValidateHeader();
this.ReadPhysicalChunk(metadata, currentChunk.Data); break;
break; case PngChunkTypes.Physical:
case PngChunkTypes.Data: this.ReadPhysicalChunk(metadata, currentChunk.Data);
dataStream.Write(currentChunk.Data, 0, currentChunk.Length); break;
break; case PngChunkTypes.Data:
case PngChunkTypes.Palette: if (image == null)
byte[] pal = new byte[currentChunk.Length]; {
Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length); this.InitializeImage(metadata, out image, out pixels);
this.palette = pal; }
metadata.Quality = pal.Length / 3;
break; deframeStream.AllocateNewBytes(currentChunk.Length);
case PngChunkTypes.PaletteAlpha: this.ReadScanlines(deframeStream.CompressedStream, pixels);
byte[] alpha = new byte[currentChunk.Length]; stream.Read(this.crcBuffer, 0, 4);
Buffer.BlockCopy(currentChunk.Data, 0, alpha, 0, currentChunk.Length); break;
this.paletteAlpha = alpha; case PngChunkTypes.Palette:
break; byte[] pal = new byte[currentChunk.Length];
case PngChunkTypes.Text: Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length);
this.ReadTextChunk(metadata, currentChunk.Data, currentChunk.Length); this.palette = pal;
break; metadata.Quality = pal.Length / 3;
case PngChunkTypes.End: break;
this.isEndChunkReached = true; case PngChunkTypes.PaletteAlpha:
break; byte[] alpha = new byte[currentChunk.Length];
Buffer.BlockCopy(currentChunk.Data, 0, alpha, 0, currentChunk.Length);
this.paletteAlpha = alpha;
break;
case PngChunkTypes.Text:
this.ReadTextChunk(metadata, currentChunk.Data, currentChunk.Length);
break;
case PngChunkTypes.End:
this.isEndChunkReached = true;
break;
}
}
finally
{
// Data is rented in ReadChunkData()
if (currentChunk.Data != null)
{
ArrayPool<byte>.Shared.Return(currentChunk.Data);
}
} }
} }
finally
{
// Data is rented in ReadChunkData()
ArrayPool<byte>.Shared.Return(currentChunk.Data);
}
}
if (this.header.Width > Image<TPixel>.MaxWidth || this.header.Height > Image<TPixel>.MaxHeight)
{
throw new ArgumentOutOfRangeException($"The input png '{this.header.Width}x{this.header.Height}' is bigger than the max allowed size '{Image<TPixel>.MaxWidth}x{Image<TPixel>.MaxHeight}'");
} }
Image<TPixel> image = Image.Create<TPixel>(this.header.Width, this.header.Height, metadata, this.configuration); return image;
}
using (PixelAccessor<TPixel> pixels = image.Lock()) finally
{
pixels?.Dispose();
if (this.previousScanline != null)
{ {
this.ReadScanlines(dataStream, pixels); ArrayPool<byte>.Shared.Return(this.previousScanline);
ArrayPool<byte>.Shared.Return(this.scanline);
} }
return image;
} }
} }
@ -293,6 +315,39 @@ namespace ImageSharp.Formats
metadata.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; metadata.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d;
} }
/// <summary>
/// Initializes the image and various buffers needed for processing
/// </summary>
/// <typeparam name="TPixel">The type the pixels will be</typeparam>
/// <param name="metadata">The metadata information for the image</param>
/// <param name="image">The image that we will populate</param>
/// <param name="pixels">The pixel accessor</param>
private void InitializeImage<TPixel>(ImageMetaData metadata, out Image<TPixel> image, out PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
if (this.header.Width > Image<TPixel>.MaxWidth || this.header.Height > Image<TPixel>.MaxHeight)
{
throw new ArgumentOutOfRangeException($"The input png '{this.header.Width}x{this.header.Height}' is bigger than the max allowed size '{Image<TPixel>.MaxWidth}x{Image<TPixel>.MaxHeight}'");
}
image = Image.Create<TPixel>(this.header.Width, this.header.Height, metadata, this.configuration);
pixels = image.Lock();
this.bytesPerPixel = this.CalculateBytesPerPixel();
this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1;
this.bytesPerSample = 1;
if (this.header.BitDepth >= 8)
{
this.bytesPerSample = this.header.BitDepth / 8;
}
this.previousScanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline);
this.scanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline);
// Zero out the scanlines, because the bytes that are rented from the arraypool may not be zero.
Array.Clear(this.scanline, 0, this.bytesPerScanline);
Array.Clear(this.previousScanline, 0, this.bytesPerScanline);
}
/// <summary> /// <summary>
/// Calculates the correct number of bytes per pixel for the given color type. /// Calculates the correct number of bytes per pixel for the given color type.
/// </summary> /// </summary>
@ -345,28 +400,16 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param> /// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param>
/// <param name="pixels"> The pixel data.</param> /// <param name="pixels"> The pixel data.</param>
private void ReadScanlines<TPixel>(MemoryStream dataStream, PixelAccessor<TPixel> pixels) private void ReadScanlines<TPixel>(Stream dataStream, PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
this.bytesPerPixel = this.CalculateBytesPerPixel(); if (this.header.InterlaceMethod == PngInterlaceMode.Adam7)
this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1;
this.bytesPerSample = 1;
if (this.header.BitDepth >= 8)
{ {
this.bytesPerSample = this.header.BitDepth / 8; this.DecodeInterlacedPixelData(dataStream, pixels);
} }
else
dataStream.Position = 0;
using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream))
{ {
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) this.DecodePixelData(dataStream, pixels);
{
this.DecodeInterlacedPixelData(compressedStream, pixels);
}
else
{
this.DecodePixelData(compressedStream, pixels);
}
} }
} }
@ -379,66 +422,58 @@ namespace ImageSharp.Formats
private void DecodePixelData<TPixel>(Stream compressedStream, PixelAccessor<TPixel> pixels) private void DecodePixelData<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 (this.currentRow < this.header.Height)
byte[] scanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline);
// 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);
try
{ {
for (int y = 0; y < this.header.Height; y++) int bytesRead = compressedStream.Read(this.scanline, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead);
this.currentRowBytesRead += bytesRead;
if (this.currentRowBytesRead < this.bytesPerScanline)
{ {
compressedStream.Read(scanline, 0, this.bytesPerScanline); return;
}
FilterType filterType = (FilterType)scanline[0]; this.currentRowBytesRead = 0;
FilterType filterType = (FilterType)this.scanline[0];
switch (filterType) switch (filterType)
{ {
case FilterType.None: case FilterType.None:
NoneFilter.Decode(scanline); NoneFilter.Decode(this.scanline);
break; break;
case FilterType.Sub: case FilterType.Sub:
SubFilter.Decode(scanline, this.bytesPerScanline, this.bytesPerPixel); SubFilter.Decode(this.scanline, this.bytesPerScanline, this.bytesPerPixel);
break; break;
case FilterType.Up: case FilterType.Up:
UpFilter.Decode(scanline, previousScanline, this.bytesPerScanline); UpFilter.Decode(this.scanline, this.previousScanline, this.bytesPerScanline);
break; break;
case FilterType.Average: case FilterType.Average:
AverageFilter.Decode(scanline, previousScanline, this.bytesPerScanline, this.bytesPerPixel); AverageFilter.Decode(this.scanline, this.previousScanline, this.bytesPerScanline, this.bytesPerPixel);
break; break;
case FilterType.Paeth: case FilterType.Paeth:
PaethFilter.Decode(scanline, previousScanline, this.bytesPerScanline, this.bytesPerPixel); PaethFilter.Decode(this.scanline, this.previousScanline, this.bytesPerScanline, this.bytesPerPixel);
break; break;
default: default:
throw new ImageFormatException("Unknown filter type."); throw new ImageFormatException("Unknown filter type.");
} }
this.ProcessDefilteredScanline(scanline, y, pixels); this.ProcessDefilteredScanline(this.scanline, pixels);
Swap(ref scanline, ref previousScanline); Swap(ref this.scanline, ref this.previousScanline);
} this.currentRow++;
}
finally
{
ArrayPool<byte>.Shared.Return(previousScanline);
ArrayPool<byte>.Shared.Return(scanline);
} }
} }
@ -536,12 +571,13 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="defilteredScanline">The de-filtered scanline</param> /// <param name="defilteredScanline">The de-filtered scanline</param>
/// <param name="row">The current image row.</param>
/// <param name="pixels">The image pixels</param> /// <param name="pixels">The image pixels</param>
private void ProcessDefilteredScanline<TPixel>(byte[] defilteredScanline, int row, PixelAccessor<TPixel> pixels) private void ProcessDefilteredScanline<TPixel>(byte[] defilteredScanline, PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
TPixel color = default(TPixel); TPixel color = default(TPixel);
BufferSpan<TPixel> pixelBuffer = pixels.GetRowSpan(this.currentRow);
BufferSpan<byte> scanlineBuffer = new BufferSpan<byte>(defilteredScanline, 1);
switch (this.PngColorType) switch (this.PngColorType)
{ {
case PngColorType.Grayscale: case PngColorType.Grayscale:
@ -551,7 +587,7 @@ namespace ImageSharp.Formats
{ {
byte intensity = (byte)(newScanline1[x] * factor); byte intensity = (byte)(newScanline1[x] * factor);
color.PackFromBytes(intensity, intensity, intensity, 255); color.PackFromBytes(intensity, intensity, intensity, 255);
pixels[x, row] = color; pixels[x, this.currentRow] = color;
} }
break; break;
@ -566,91 +602,84 @@ namespace ImageSharp.Formats
byte alpha = defilteredScanline[offset + this.bytesPerSample]; byte alpha = defilteredScanline[offset + this.bytesPerSample];
color.PackFromBytes(intensity, intensity, intensity, alpha); color.PackFromBytes(intensity, intensity, intensity, alpha);
pixels[x, row] = color; pixels[x, this.currentRow] = color;
} }
break; break;
case PngColorType.Palette: case PngColorType.Palette:
byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); this.ProcessScanlineFromPalette(defilteredScanline, pixels);
if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) break;
{
// If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha
// channel and we should try to read it.
for (int x = 0; x < this.header.Width; x++)
{
int index = newScanline[x + 1];
int pixelOffset = index * 3;
byte a = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; case PngColorType.Rgb:
if (a > 0) BulkPixelOperations<TPixel>.Instance.PackFromXyzBytes(scanlineBuffer, pixelBuffer, this.header.Width);
{
byte r = this.palette[pixelOffset];
byte g = this.palette[pixelOffset + 1];
byte b = this.palette[pixelOffset + 2];
color.PackFromBytes(r, g, b, a);
}
else
{
color.PackFromBytes(0, 0, 0, 0);
}
pixels[x, row] = color; break;
}
}
else
{
for (int x = 0; x < this.header.Width; x++)
{
int index = newScanline[x + 1];
int pixelOffset = index * 3;
byte r = this.palette[pixelOffset]; case PngColorType.RgbWithAlpha:
byte g = this.palette[pixelOffset + 1];
byte b = this.palette[pixelOffset + 2];
color.PackFromBytes(r, g, b, 255); BulkPixelOperations<TPixel>.Instance.PackFromXyzwBytes(scanlineBuffer, pixelBuffer, this.header.Width);
pixels[x, row] = color;
}
}
break; break;
}
}
case PngColorType.Rgb: /// <summary>
/// Processes a scanline that uses a palette
/// </summary>
/// <typeparam name="TPixel">The type of pixel we are expanding to</typeparam>
/// <param name="defilteredScanline">The scanline</param>
/// <param name="pixels">The output pixels</param>
private void ProcessScanlineFromPalette<TPixel>(byte[] defilteredScanline, PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
{
byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth);
byte[] palette = this.palette;
TPixel color = default(TPixel);
for (int x = 0; x < this.header.Width; x++) if (this.paletteAlpha != null && this.paletteAlpha.Length > 0)
{ {
int offset = 1 + (x * this.bytesPerPixel); // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha
// channel and we should try to read it.
for (int x = 0; x < this.header.Width; x++)
{
int index = newScanline[x + 1];
int pixelOffset = index * 3;
byte r = defilteredScanline[offset]; byte a = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255;
byte g = defilteredScanline[offset + this.bytesPerSample];
byte b = defilteredScanline[offset + (2 * this.bytesPerSample)];
color.PackFromBytes(r, g, b, 255); if (a > 0)
pixels[x, row] = color; {
byte r = palette[pixelOffset];
byte g = palette[pixelOffset + 1];
byte b = palette[pixelOffset + 2];
color.PackFromBytes(r, g, b, a);
} }
else
break;
case PngColorType.RgbWithAlpha:
for (int x = 0; x < this.header.Width; x++)
{ {
int offset = 1 + (x * this.bytesPerPixel); color.PackFromBytes(0, 0, 0, 0);
}
byte r = defilteredScanline[offset]; pixels[x, this.currentRow] = color;
byte g = defilteredScanline[offset + this.bytesPerSample]; }
byte b = defilteredScanline[offset + (2 * this.bytesPerSample)]; }
byte a = defilteredScanline[offset + (3 * this.bytesPerSample)]; else
{
for (int x = 0; x < this.header.Width; x++)
{
int index = newScanline[x + 1];
int pixelOffset = index * 3;
color.PackFromBytes(r, g, b, a); byte r = palette[pixelOffset];
pixels[x, row] = color; byte g = palette[pixelOffset + 1];
} byte b = palette[pixelOffset + 2];
break; color.PackFromBytes(r, g, b, 255);
pixels[x, this.currentRow] = color;
}
} }
} }
@ -819,7 +848,7 @@ namespace ImageSharp.Formats
this.header.Height = BitConverter.ToInt32(data, 4); this.header.Height = BitConverter.ToInt32(data, 4);
this.header.BitDepth = data[8]; this.header.BitDepth = data[8];
this.header.ColorType = data[9]; this.header.ColorType = (PngColorType)data[9];
this.header.CompressionMethod = data[10]; this.header.CompressionMethod = data[10];
this.header.FilterMethod = data[11]; this.header.FilterMethod = data[11];
this.header.InterlaceMethod = (PngInterlaceMode)data[12]; this.header.InterlaceMethod = (PngInterlaceMode)data[12];
@ -872,6 +901,11 @@ namespace ImageSharp.Formats
} }
this.ReadChunkType(chunk); this.ReadChunkType(chunk);
if (chunk.Type == PngChunkTypes.Data)
{
return chunk;
}
this.ReadChunkData(chunk); this.ReadChunkData(chunk);
this.ReadChunkCrc(chunk); this.ReadChunkCrc(chunk);

4
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -181,7 +181,7 @@ namespace ImageSharp.Formats
{ {
Width = image.Width, Width = image.Width,
Height = image.Height, Height = image.Height,
ColorType = (byte)this.pngColorType, ColorType = this.pngColorType,
BitDepth = this.bitDepth, BitDepth = this.bitDepth,
FilterMethod = 0, // None FilterMethod = 0, // None
CompressionMethod = 0, CompressionMethod = 0,
@ -462,7 +462,7 @@ namespace ImageSharp.Formats
WriteInteger(this.chunkDataBuffer, 4, header.Height); WriteInteger(this.chunkDataBuffer, 4, header.Height);
this.chunkDataBuffer[8] = header.BitDepth; this.chunkDataBuffer[8] = header.BitDepth;
this.chunkDataBuffer[9] = header.ColorType; this.chunkDataBuffer[9] = (byte)header.ColorType;
this.chunkDataBuffer[10] = header.CompressionMethod; this.chunkDataBuffer[10] = header.CompressionMethod;
this.chunkDataBuffer[11] = header.FilterMethod; this.chunkDataBuffer[11] = header.FilterMethod;
this.chunkDataBuffer[12] = (byte)header.InterlaceMethod; this.chunkDataBuffer[12] = (byte)header.InterlaceMethod;

2
src/ImageSharp/Formats/Png/PngHeader.cs

@ -34,7 +34,7 @@ namespace ImageSharp.Formats
/// image data. Color type codes represent sums of the following values: /// image data. Color type codes represent sums of the following values:
/// 1 (palette used), 2 (color used), and 4 (alpha channel used). /// 1 (palette used), 2 (color used), and 4 (alpha channel used).
/// </summary> /// </summary>
public byte ColorType { get; set; } public PngColorType ColorType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the compression method. /// Gets or sets the compression method.

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

@ -132,18 +132,13 @@ 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");
} }
// (By Per Bothner) // (By Per Bothner)
uint s1 = this.checksum & 0xFFFF; uint s1 = this.checksum;
uint s2 = this.checksum >> 16; uint s2 = this.checksum >> 16;
while (count > 0) while (count > 0)
@ -151,16 +146,12 @@ 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 = 3800; int n = Math.Min(3800, count);
if (n > count)
{
n = count;
}
count -= n; count -= n;
while (--n >= 0) while (--n > -1)
{ {
s1 = s1 + (uint)(buffer[offset++] & 0xff); s1 = s1 + buffer[offset++];
s2 = s2 + s1; s2 = s2 + s1;
} }

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

@ -0,0 +1,121 @@
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);
}
}
}
Loading…
Cancel
Save