From ea7167a3f301db714cc648009fca717ac89af4c4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 15 Dec 2016 11:37:15 +1100 Subject: [PATCH] Reduce allocations in LZW decoder --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 43 +++++---- src/ImageSharp/Formats/Gif/LzwDecoder.cs | 93 ++++++++++++++++---- src/ImageSharp/Formats/Gif/LzwEncoder.cs | 2 +- src/ImageSharp/Formats/Gif/PackedField.cs | 4 +- 4 files changed, 98 insertions(+), 44 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index c8a22610d..775b3be98 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -258,9 +258,11 @@ namespace ImageSharp.Formats private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor) { int dataSize = this.currentStream.ReadByte(); - LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream); - - byte[] indices = lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize); + byte[] indices; + using (LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream)) + { + indices = lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize); + } return indices; } @@ -355,17 +357,17 @@ namespace ImageSharp.Formats interlacePass++; switch (interlacePass) { - case 1: - interlaceY = 4; - break; - case 2: - interlaceY = 2; - interlaceIncrement = 4; - break; - case 3: - interlaceY = 1; - interlaceIncrement = 2; - break; + case 1: + interlaceY = 4; + break; + case 2: + interlaceY = 2; + interlaceIncrement = 4; + break; + case 3: + interlaceY = 1; + interlaceIncrement = 2; + break; } } @@ -411,14 +413,7 @@ namespace ImageSharp.Formats return; } - if (currentFrame == null) - { - this.nextFrame = this.decodedImage.ToFrame(); - } - else - { - this.nextFrame = currentFrame.Clone(); - } + this.nextFrame = currentFrame == null ? this.decodedImage.ToFrame() : currentFrame.Clone(); if (this.graphicsControlExtension != null && this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground) @@ -427,6 +422,10 @@ namespace ImageSharp.Formats } } + /// + /// Restores the current frame background. + /// + /// The frame. private void RestoreToBackground(ImageBase frame) { if (this.restoreArea == null) diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs index c1b300dff..001f775ed 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs @@ -2,15 +2,16 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageSharp.Formats { + using System; + using System.Buffers; using System.IO; /// /// Decompresses and decodes data using the dynamic LZW algorithms. /// - internal sealed class LzwDecoder + internal sealed class LzwDecoder : IDisposable { /// /// The max decoder pixel stack size. @@ -27,6 +28,34 @@ namespace ImageSharp.Formats /// private readonly Stream stream; + /// + /// The prefix buffer. + /// + private readonly int[] prefix; + + /// + /// The suffix buffer. + /// + private readonly int[] suffix; + + /// + /// The pixel stack buffer. + /// + private readonly int[] pixelStack; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// 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. + /// + private bool isDisposed; + /// /// Initializes a new instance of the class /// and sets the stream, where the compressed data should be read from. @@ -38,6 +67,10 @@ namespace ImageSharp.Formats Guard.NotNull(stream, nameof(stream)); this.stream = stream; + + this.prefix = ArrayPool.Shared.Rent(MaxStackSize); + this.suffix = ArrayPool.Shared.Rent(MaxStackSize); + this.pixelStack = ArrayPool.Shared.Rent(MaxStackSize + 1); } /// @@ -72,10 +105,6 @@ namespace ImageSharp.Formats int codeMask = (1 << codeSize) - 1; int bits = 0; - int[] prefix = new int[MaxStackSize]; - int[] suffix = new int[MaxStackSize]; - int[] pixelStatck = new int[MaxStackSize + 1]; - int top = 0; int count = 0; int bi = 0; @@ -86,8 +115,8 @@ namespace ImageSharp.Formats for (code = 0; code < clearCode; code++) { - prefix[code] = 0; - suffix[code] = (byte)code; + this.prefix[code] = 0; + this.suffix[code] = (byte)code; } byte[] buffer = new byte[255]; @@ -141,7 +170,7 @@ namespace ImageSharp.Formats if (oldCode == NullCode) { - pixelStatck[top++] = suffix[code]; + this.pixelStack[top++] = this.suffix[code]; oldCode = code; first = code; continue; @@ -150,27 +179,27 @@ namespace ImageSharp.Formats int inCode = code; if (code == availableCode) { - pixelStatck[top++] = (byte)first; + this.pixelStack[top++] = (byte)first; code = oldCode; } while (code > clearCode) { - pixelStatck[top++] = suffix[code]; - code = prefix[code]; + this.pixelStack[top++] = this.suffix[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 : // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 if (availableCode < MaxStackSize) { - prefix[availableCode] = oldCode; - suffix[availableCode] = first; + this.prefix[availableCode] = oldCode; + this.suffix[availableCode] = first; availableCode++; if (availableCode == codeMask + 1 && availableCode < MaxStackSize) { @@ -186,12 +215,19 @@ namespace ImageSharp.Formats top--; // Clear missing pixels - pixels[xyz++] = (byte)pixelStatck[top]; + pixels[xyz++] = (byte)this.pixelStack[top]; } return pixels; } + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + this.Dispose(true); + } + /// /// 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. @@ -211,5 +247,26 @@ namespace ImageSharp.Formats int count = this.stream.Read(buffer, 0, bufferSize); return count != bufferSize ? 0 : bufferSize; } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// If true, the object gets disposed. + private void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + ArrayPool.Shared.Return(this.prefix); + ArrayPool.Shared.Return(this.suffix); + ArrayPool.Shared.Return(this.pixelStack); + } + + this.isDisposed = true; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index fcfadfbba..4cc125e2f 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/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 /// life in the Garbage Collector. /// - private bool isDisposed = false; + private bool isDisposed; /// /// The current pixel diff --git a/src/ImageSharp/Formats/Gif/PackedField.cs b/src/ImageSharp/Formats/Gif/PackedField.cs index cabf4f5ab..21d8f91f2 100644 --- a/src/ImageSharp/Formats/Gif/PackedField.cs +++ b/src/ImageSharp/Formats/Gif/PackedField.cs @@ -74,9 +74,7 @@ namespace ImageSharp.Formats { if (index < 0 || index > 7) { - string message - = "Index must be between 0 and 7. Supplied index: " - + index; + string message = $"Index must be between 0 and 7. Supplied index: {index}"; throw new ArgumentOutOfRangeException(nameof(index), message); }