// // 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; /// /// Encodes and compresses the image data using dynamic Lempel-Ziv compression. /// /// /// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00 /// /// GIFCOMPR.C - GIF Image compression routines /// /// /// Lempel-Ziv compression based on 'compress'. GIF modifications by /// David Rowley (mgardi@watdcsu.waterloo.edu) /// /// GIF Image compression - modified 'compress' /// /// Based on: compress.c - File compression ala IEEE Computer, June 1984. /// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) /// Jim McKie (decvax!mcvax!jim) /// Steve Davies (decvax!vax135!petsd!peora!srd) /// Ken Turkowski (decvax!decwrl!turtlevax!ken) /// James A. Woods (decvax!ihnp4!ames!jaw) /// Joe Orost (decvax!vax135!petsd!joe) /// /// internal sealed class LzwEncoder : IDisposable { /// /// The end-of-file marker /// private const int Eof = -1; /// /// The maximum number of bits. /// private const int Bits = 12; /// /// 80% occupancy /// private const int HashSize = 5003; /// /// Mask used when shifting pixel values /// private static readonly int[] Masks = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; /// /// The working pixel array /// private readonly byte[] pixelArray; /// /// The initial code size. /// private readonly int initialCodeSize; /// /// The hash table. /// private readonly int[] hashTable; /// /// The code table. /// private readonly int[] codeTable; /// /// Define the storage for the packet accumulator. /// private readonly byte[] accumulators = new byte[256]; /// /// 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; /// /// The current pixel /// private int currentPixel; /// /// Number of bits/code /// private int bitCount; /// /// User settable max # bits/code /// private int maxbits = Bits; /// /// maximum code, given bitCount /// private int maxcode; /// /// should NEVER generate this code /// private int maxmaxcode = 1 << Bits; /// /// For dynamic table sizing /// private int hsize = HashSize; /// /// First unused entry /// private int freeEntry; /// /// Block compression parameters -- after all codes are used up, /// and compression rate changes, start over. /// private bool clearFlag; /// /// Algorithm: use open addressing double hashing (no chaining) on the /// prefix code / next character combination. We do a variant of Knuth's /// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime /// secondary probe. Here, the modular division first probe is gives way /// to a faster exclusive-or manipulation. Also do block compression with /// an adaptive reset, whereby the code table is cleared when the compression /// ratio decreases, but after the table fills. The variable-length output /// codes are re-sized at this point, and a special CLEAR code is generated /// for the decompressor. Late addition: construct the table according to /// file size for noticeable speed improvement on small files. Please direct /// questions about this implementation to ames!jaw. /// private int globalInitialBits; /// /// The clear code. /// private int clearCode; /// /// The end-of-file code. /// private int eofCode; /// /// Output the given code. /// Inputs: /// code: A bitCount-bit integer. If == -1, then EOF. This assumes /// that bitCount =< wordsize - 1. /// Outputs: /// Outputs code to the file. /// Assumptions: /// Chars are 8 bits long. /// Algorithm: /// Maintain a BITS character long buffer (so that 8 codes will /// fit in it exactly). Use the VAX insv instruction to insert each /// code in turn. When the buffer fills up empty it and start over. /// private int currentAccumulator; /// /// The current bits. /// private int currentBits; /// /// Number of characters so far in this 'packet' /// private int accumulatorCount; /// /// Initializes a new instance of the class. /// /// The array of indexed pixels. /// The color depth in bits. public LzwEncoder(byte[] indexedPixels, int colorDepth) { this.pixelArray = indexedPixels; this.initialCodeSize = Math.Max(2, colorDepth); this.hashTable = ArrayPool.Shared.Rent(HashSize); this.codeTable = ArrayPool.Shared.Rent(HashSize); Array.Clear(this.hashTable, 0, HashSize); Array.Clear(this.codeTable, 0, HashSize); } /// /// Encodes and compresses the indexed pixels to the stream. /// /// The stream to write to. public void Encode(Stream stream) { // Write "initial code size" byte stream.WriteByte((byte)this.initialCodeSize); this.currentPixel = 0; // Compress and write the pixel data this.Compress(this.initialCodeSize + 1, stream); // Write block terminator stream.WriteByte(GifConstants.Terminator); } /// public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. this.Dispose(true); } /// /// Gets the maximum code value /// /// The number of bits /// See private static int GetMaxcode(int bitCount) { return (1 << bitCount) - 1; } /// /// Add a character to the end of the current packet, and if it is 254 characters, /// flush the packet to disk. /// /// The character to add. /// The stream to write to. private void AddCharacter(byte c, Stream stream) { this.accumulators[this.accumulatorCount++] = c; if (this.accumulatorCount >= 254) { this.FlushPacket(stream); } } /// /// Table clear for block compress /// /// The output stream. private void ClearBlock(Stream stream) { this.ResetCodeTable(this.hsize); this.freeEntry = this.clearCode + 2; this.clearFlag = true; this.Output(this.clearCode, stream); } /// /// Reset the code table. /// /// The hash size. private void ResetCodeTable(int size) { for (int i = 0; i < size; ++i) { this.hashTable[i] = -1; } } /// /// Compress the packets to the stream. /// /// The initial bits. /// The stream to write to. private void Compress(int intialBits, Stream stream) { int fcode; int c; int ent; int hsizeReg; int hshift; // Set up the globals: globalInitialBits - initial number of bits this.globalInitialBits = intialBits; // Set up the necessary values this.clearFlag = false; this.bitCount = this.globalInitialBits; this.maxcode = GetMaxcode(this.bitCount); this.clearCode = 1 << (intialBits - 1); this.eofCode = this.clearCode + 1; this.freeEntry = this.clearCode + 2; this.accumulatorCount = 0; // clear packet ent = this.NextPixel(); hshift = 0; for (fcode = this.hsize; fcode < 65536; fcode *= 2) { ++hshift; } hshift = 8 - hshift; // set hash code range bound hsizeReg = this.hsize; this.ResetCodeTable(hsizeReg); // clear hash table this.Output(this.clearCode, stream); while ((c = this.NextPixel()) != Eof) { fcode = (c << this.maxbits) + ent; int i = (c << hshift) ^ ent /* = 0 */; if (this.hashTable[i] == fcode) { ent = this.codeTable[i]; continue; } // Non-empty slot if (this.hashTable[i] >= 0) { int disp = hsizeReg - i; if (i == 0) { disp = 1; } do { if ((i -= disp) < 0) { i += hsizeReg; } if (this.hashTable[i] == fcode) { ent = this.codeTable[i]; break; } } while (this.hashTable[i] >= 0); if (this.hashTable[i] == fcode) { continue; } } this.Output(ent, stream); ent = c; if (this.freeEntry < this.maxmaxcode) { this.codeTable[i] = this.freeEntry++; // code -> hashtable this.hashTable[i] = fcode; } else { this.ClearBlock(stream); } } // Put out the final code. this.Output(ent, stream); this.Output(this.eofCode, stream); } /// /// Flush the packet to disk, and reset the accumulator. /// /// The output stream. private void FlushPacket(Stream outStream) { if (this.accumulatorCount > 0) { outStream.WriteByte((byte)this.accumulatorCount); outStream.Write(this.accumulators, 0, this.accumulatorCount); this.accumulatorCount = 0; } } /// /// Return the next pixel from the image /// /// /// The /// private int NextPixel() { if (this.currentPixel == this.pixelArray.Length) { return Eof; } this.currentPixel++; return this.pixelArray[this.currentPixel - 1] & 0xff; } /// /// Output the current code to the stream. /// /// The code. /// The stream to write to. private void Output(int code, Stream outs) { this.currentAccumulator &= Masks[this.currentBits]; if (this.currentBits > 0) { this.currentAccumulator |= code << this.currentBits; } else { this.currentAccumulator = code; } this.currentBits += this.bitCount; while (this.currentBits >= 8) { this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); this.currentAccumulator >>= 8; this.currentBits -= 8; } // If the next entry is going to be too big for the code size, // then increase it, if possible. if (this.freeEntry > this.maxcode || this.clearFlag) { if (this.clearFlag) { this.maxcode = GetMaxcode(this.bitCount = this.globalInitialBits); this.clearFlag = false; } else { ++this.bitCount; this.maxcode = this.bitCount == this.maxbits ? this.maxmaxcode : GetMaxcode(this.bitCount); } } if (code == this.eofCode) { // At EOF, write the rest of the buffer. while (this.currentBits > 0) { this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); this.currentAccumulator >>= 8; this.currentBits -= 8; } this.FlushPacket(outs); } } /// /// 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.hashTable); ArrayPool.Shared.Return(this.codeTable); } this.isDisposed = true; } } }