// // 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 : IDisposable { /// /// The max decoder pixel stack size. /// private const int MaxStackSize = 4096; /// /// The null code. /// private const int NullCode = -1; /// /// The stream to decode. /// 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. /// /// The stream to read from. /// is null. public LzwDecoder(Stream stream) { 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); Array.Clear(this.prefix, 0, MaxStackSize); Array.Clear(this.suffix, 0, MaxStackSize); Array.Clear(this.pixelStack, 0, MaxStackSize + 1); } /// /// Decodes and decompresses all pixel indices from the stream. /// /// The width of the pixel index array. /// The height of the pixel index array. /// Size of the data. /// The pixel array to decode to. public void DecodePixels(int width, int height, int dataSize, byte[] pixels) { Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); // The resulting index table length. int length = width * height; // Calculate the clear code. The value of the clear code is 2 ^ dataSize int clearCode = 1 << dataSize; int codeSize = dataSize + 1; // Calculate the end code int endCode = clearCode + 1; // Calculate the available code. int availableCode = clearCode + 2; // Jillzhangs Code see: http://giflib.codeplex.com/ // Adapted from John Cristy's ImageMagick. int code; int oldCode = NullCode; int codeMask = (1 << codeSize) - 1; int bits = 0; int top = 0; int count = 0; int bi = 0; int xyz = 0; int data = 0; int first = 0; for (code = 0; code < clearCode; code++) { this.prefix[code] = 0; this.suffix[code] = (byte)code; } byte[] buffer = new byte[255]; while (xyz < length) { if (top == 0) { if (bits < codeSize) { // Load bytes until there are enough bits for a code. if (count == 0) { // Read a new data block. count = this.ReadBlock(buffer); if (count == 0) { break; } bi = 0; } data += buffer[bi] << bits; bits += 8; bi++; count--; continue; } // Get the next code code = data & codeMask; data >>= codeSize; bits -= codeSize; // Interpret the code if (code > availableCode || code == endCode) { break; } if (code == clearCode) { // Reset the decoder codeSize = dataSize + 1; codeMask = (1 << codeSize) - 1; availableCode = clearCode + 2; oldCode = NullCode; continue; } if (oldCode == NullCode) { this.pixelStack[top++] = this.suffix[code]; oldCode = code; first = code; continue; } int inCode = code; if (code == availableCode) { this.pixelStack[top++] = (byte)first; code = oldCode; } while (code > clearCode) { this.pixelStack[top++] = this.suffix[code]; code = this.prefix[code]; } first = this.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) { this.prefix[availableCode] = oldCode; this.suffix[availableCode] = first; availableCode++; if (availableCode == codeMask + 1 && availableCode < MaxStackSize) { codeSize++; codeMask = (1 << codeSize) - 1; } } oldCode = inCode; } // Pop a pixel off the pixel stack. top--; // Clear missing pixels pixels[xyz++] = (byte)this.pixelStack[top]; } } /// 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. /// /// The buffer to store the block in. /// /// The . /// private int ReadBlock(byte[] buffer) { int bufferSize = this.stream.ReadByte(); if (bufferSize < 1) { return 0; } 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; } } }