Browse Source

Reduce allocations in LZW decoder

pull/41/head
James Jackson-South 10 years ago
parent
commit
ea7167a3f3
  1. 43
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  2. 93
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  3. 2
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  4. 4
      src/ImageSharp/Formats/Gif/PackedField.cs

43
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -258,9 +258,11 @@ namespace ImageSharp.Formats
private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor) private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor)
{ {
int dataSize = this.currentStream.ReadByte(); int dataSize = this.currentStream.ReadByte();
LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream); byte[] indices;
using (LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream))
byte[] indices = lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize); {
indices = lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize);
}
return indices; return indices;
} }
@ -355,17 +357,17 @@ namespace ImageSharp.Formats
interlacePass++; interlacePass++;
switch (interlacePass) switch (interlacePass)
{ {
case 1: case 1:
interlaceY = 4; interlaceY = 4;
break; break;
case 2: case 2:
interlaceY = 2; interlaceY = 2;
interlaceIncrement = 4; interlaceIncrement = 4;
break; break;
case 3: case 3:
interlaceY = 1; interlaceY = 1;
interlaceIncrement = 2; interlaceIncrement = 2;
break; break;
} }
} }
@ -411,14 +413,7 @@ namespace ImageSharp.Formats
return; return;
} }
if (currentFrame == null) this.nextFrame = currentFrame == null ? this.decodedImage.ToFrame() : currentFrame.Clone();
{
this.nextFrame = this.decodedImage.ToFrame();
}
else
{
this.nextFrame = currentFrame.Clone();
}
if (this.graphicsControlExtension != null && if (this.graphicsControlExtension != null &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground) this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
@ -427,6 +422,10 @@ namespace ImageSharp.Formats
} }
} }
/// <summary>
/// Restores the current frame background.
/// </summary>
/// <param name="frame">The frame.</param>
private void RestoreToBackground(ImageBase<TColor, TPacked> frame) private void RestoreToBackground(ImageBase<TColor, TPacked> frame)
{ {
if (this.restoreArea == null) if (this.restoreArea == null)

93
src/ImageSharp/Formats/Gif/LzwDecoder.cs

@ -2,15 +2,16 @@
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.Buffers;
using System.IO; using System.IO;
/// <summary> /// <summary>
/// Decompresses and decodes data using the dynamic LZW algorithms. /// Decompresses and decodes data using the dynamic LZW algorithms.
/// </summary> /// </summary>
internal sealed class LzwDecoder internal sealed class LzwDecoder : IDisposable
{ {
/// <summary> /// <summary>
/// The max decoder pixel stack size. /// The max decoder pixel stack size.
@ -27,6 +28,34 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly Stream stream; private readonly Stream stream;
/// <summary>
/// The prefix buffer.
/// </summary>
private readonly int[] prefix;
/// <summary>
/// The suffix buffer.
/// </summary>
private readonly int[] suffix;
/// <summary>
/// The pixel stack buffer.
/// </summary>
private readonly int[] pixelStack;
/// <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> /// <summary>
/// Initializes a new instance of the <see cref="LzwDecoder"/> class /// Initializes a new instance of the <see cref="LzwDecoder"/> class
/// and sets the stream, where the compressed data should be read from. /// and sets the stream, where the compressed data should be read from.
@ -38,6 +67,10 @@ namespace ImageSharp.Formats
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
this.stream = stream; this.stream = stream;
this.prefix = ArrayPool<int>.Shared.Rent(MaxStackSize);
this.suffix = ArrayPool<int>.Shared.Rent(MaxStackSize);
this.pixelStack = ArrayPool<int>.Shared.Rent(MaxStackSize + 1);
} }
/// <summary> /// <summary>
@ -72,10 +105,6 @@ namespace ImageSharp.Formats
int codeMask = (1 << codeSize) - 1; int codeMask = (1 << codeSize) - 1;
int bits = 0; int bits = 0;
int[] prefix = new int[MaxStackSize];
int[] suffix = new int[MaxStackSize];
int[] pixelStatck = new int[MaxStackSize + 1];
int top = 0; int top = 0;
int count = 0; int count = 0;
int bi = 0; int bi = 0;
@ -86,8 +115,8 @@ namespace ImageSharp.Formats
for (code = 0; code < clearCode; code++) for (code = 0; code < clearCode; code++)
{ {
prefix[code] = 0; this.prefix[code] = 0;
suffix[code] = (byte)code; this.suffix[code] = (byte)code;
} }
byte[] buffer = new byte[255]; byte[] buffer = new byte[255];
@ -141,7 +170,7 @@ namespace ImageSharp.Formats
if (oldCode == NullCode) if (oldCode == NullCode)
{ {
pixelStatck[top++] = suffix[code]; this.pixelStack[top++] = this.suffix[code];
oldCode = code; oldCode = code;
first = code; first = code;
continue; continue;
@ -150,27 +179,27 @@ namespace ImageSharp.Formats
int inCode = code; int inCode = code;
if (code == availableCode) if (code == availableCode)
{ {
pixelStatck[top++] = (byte)first; this.pixelStack[top++] = (byte)first;
code = oldCode; code = oldCode;
} }
while (code > clearCode) while (code > clearCode)
{ {
pixelStatck[top++] = suffix[code]; this.pixelStack[top++] = this.suffix[code];
code = prefix[code]; code = this.prefix[code];
} }
first = suffix[code]; first = this.suffix[code];
pixelStatck[top++] = suffix[code]; this.pixelStack[top++] = this.suffix[code];
// Fix for Gifs that have "deferred clear code" as per here : // Fix for Gifs that have "deferred clear code" as per here :
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918 // https://bugzilla.mozilla.org/show_bug.cgi?id=55918
if (availableCode < MaxStackSize) if (availableCode < MaxStackSize)
{ {
prefix[availableCode] = oldCode; this.prefix[availableCode] = oldCode;
suffix[availableCode] = first; this.suffix[availableCode] = first;
availableCode++; availableCode++;
if (availableCode == codeMask + 1 && availableCode < MaxStackSize) if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
{ {
@ -186,12 +215,19 @@ namespace ImageSharp.Formats
top--; top--;
// Clear missing pixels // Clear missing pixels
pixels[xyz++] = (byte)pixelStatck[top]; pixels[xyz++] = (byte)this.pixelStack[top];
} }
return pixels; return pixels;
} }
/// <inheritdoc />
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
this.Dispose(true);
}
/// <summary> /// <summary>
/// Reads the next data block from the stream. A data block begins with a byte, /// Reads the next data block from the stream. A data block begins with a byte,
/// which defines the size of the block, followed by the block itself. /// which defines the size of the block, followed by the block itself.
@ -211,5 +247,26 @@ namespace ImageSharp.Formats
int count = this.stream.Read(buffer, 0, bufferSize); int count = this.stream.Read(buffer, 0, bufferSize);
return count != bufferSize ? 0 : bufferSize; return count != bufferSize ? 0 : bufferSize;
} }
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">If true, the object gets disposed.</param>
private void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
ArrayPool<int>.Shared.Return(this.prefix);
ArrayPool<int>.Shared.Return(this.suffix);
ArrayPool<int>.Shared.Return(this.pixelStack);
}
this.isDisposed = true;
}
} }
} }

2
src/ImageSharp/Formats/Gif/LzwEncoder.cs

@ -94,7 +94,7 @@ namespace ImageSharp.Formats
/// method will not dispose again. This help not to prolong the entity's /// method will not dispose again. This help not to prolong the entity's
/// life in the Garbage Collector. /// life in the Garbage Collector.
/// </remarks> /// </remarks>
private bool isDisposed = false; private bool isDisposed;
/// <summary> /// <summary>
/// The current pixel /// The current pixel

4
src/ImageSharp/Formats/Gif/PackedField.cs

@ -74,9 +74,7 @@ namespace ImageSharp.Formats
{ {
if (index < 0 || index > 7) if (index < 0 || index > 7)
{ {
string message string message = $"Index must be between 0 and 7. Supplied index: {index}";
= "Index must be between 0 and 7. Supplied index: "
+ index;
throw new ArgumentOutOfRangeException(nameof(index), message); throw new ArgumentOutOfRangeException(nameof(index), message);
} }

Loading…
Cancel
Save