diff --git a/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs b/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs index d3c7d7155..28f55b047 100644 --- a/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs @@ -23,7 +23,7 @@ private Image image; private Stream currentStream; private byte[] globalColorTable; - private byte[] _currentFrame; + private byte[] currentFrame; private GifLogicalScreenDescriptor logicalScreenDescriptor; private GifGraphicsControlExtension graphicsControlExtension; @@ -235,9 +235,9 @@ int imageWidth = this.logicalScreenDescriptor.Width; int imageHeight = this.logicalScreenDescriptor.Height; - if (this._currentFrame == null) + if (this.currentFrame == null) { - this._currentFrame = new byte[imageWidth * imageHeight * 4]; + this.currentFrame = new byte[imageWidth * imageHeight * 4]; } byte[] lastFrame = null; @@ -247,7 +247,7 @@ { lastFrame = new byte[imageWidth * imageHeight * 4]; - Array.Copy(this._currentFrame, lastFrame, lastFrame.Length); + Array.Copy(this.currentFrame, lastFrame, lastFrame.Length); } int offset = 0, i = 0, index = -1; @@ -303,10 +303,10 @@ this.graphicsControlExtension.TransparencyFlag == false || this.graphicsControlExtension.TransparencyIndex != index) { - _currentFrame[offset * 4 + 0] = colorTable[index * 3 + 2]; - _currentFrame[offset * 4 + 1] = colorTable[index * 3 + 1]; - _currentFrame[offset * 4 + 2] = colorTable[index * 3 + 0]; - _currentFrame[offset * 4 + 3] = (byte)255; + this.currentFrame[offset * 4 + 0] = colorTable[index * 3 + 2]; + this.currentFrame[offset * 4 + 1] = colorTable[index * 3 + 1]; + this.currentFrame[offset * 4 + 2] = colorTable[index * 3 + 0]; + this.currentFrame[offset * 4 + 3] = (byte)255; } i++; @@ -315,7 +315,7 @@ byte[] pixels = new byte[imageWidth * imageHeight * 4]; - Array.Copy(_currentFrame, pixels, pixels.Length); + Array.Copy(this.currentFrame, pixels, pixels.Length); ImageBase currentImage; @@ -349,16 +349,16 @@ { offset = y * imageWidth + x; - _currentFrame[offset * 4 + 0] = 0; - _currentFrame[offset * 4 + 1] = 0; - _currentFrame[offset * 4 + 2] = 0; - _currentFrame[offset * 4 + 3] = 0; + this.currentFrame[offset * 4 + 0] = 0; + this.currentFrame[offset * 4 + 1] = 0; + this.currentFrame[offset * 4 + 2] = 0; + this.currentFrame[offset * 4 + 3] = 0; } } } else if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) { - _currentFrame = lastFrame; + this.currentFrame = lastFrame; } } } diff --git a/src/ImageProcessor/Formats/Gif/LzwEncoder.cs b/src/ImageProcessor/Formats/Gif/LzwEncoder.cs new file mode 100644 index 000000000..28057496b --- /dev/null +++ b/src/ImageProcessor/Formats/Gif/LzwEncoder.cs @@ -0,0 +1,335 @@ +namespace ImageProcessor.Formats +{ + using System; + using System.IO; + + internal class LzwEncoder + { + private const int EOF = -1; + + private readonly int imgW; + + private readonly int imgH; + + private readonly byte[] pixAry; + + private readonly int initCodeSize; + private int remaining; + private int curPixel; + + // GIFCOMPR.C - GIF Image compression routines + // + // Lempel-Ziv compression based on 'compress'. GIF modifications by + // David Rowley (mgardi@watdcsu.waterloo.edu) + + // General DEFINEs + + private const int BITS = 12; + + private const int HSIZE = 5003; // 80% occupancy + + // 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) + + private int numberOfBits; // number of bits/code + private int maxbits = BITS; // user settable max # bits/code + private int maxcode; // maximum code, given n_bits + private int maxmaxcode = 1 << BITS; // should NEVER generate this code + + private int[] htab = new int[HSIZE]; + private int[] codetab = new int[HSIZE]; + + private int hsize = HSIZE; // for dynamic table sizing + + private int free_ent = 0; // first unused entry + + // block compression parameters -- after all codes are used up, + // and compression rate changes, start over. + private bool clear_flg; + + // 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; + + private int ClearCode; + private int EOFCode; + + // output + // + // Output the given code. + // Inputs: + // code: A n_bits-bit integer. If == -1, then EOF. This assumes + // that n_bits =< 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 cur_accum = 0; + private int cur_bits = 0; + + private readonly int[] masks = + { + 0x0000, // 0 + 0x0001, // 1 + 0x0003, // 3 + 0x0007, // 7 + 0x000F, // 15 + 0x001F, // 31 + 0x003F, // 63 + 0x007F, // 127 + 0x00FF, // 255 + 0x01FF, // 511 + 0x03FF, // 1023 + 0x07FF, // 2047 + 0x0FFF, // 4095 + 0x1FFF, // 8191 + 0x3FFF, // 16383 + 0x7FFF, // 32767 + 0xFFFF }; // 65535 + + // Number of characters so far in this 'packet' + private int accumulatorCount; + + // Define the storage for the packet accumulator + private readonly byte[] accumulatorBytes = new byte[256]; + + public LzwEncoder(int width, int height, byte[] pixels, int colorDepth) + { + this.imgW = width; + this.imgH = height; + this.pixAry = pixels; + this.initCodeSize = Math.Max(2, colorDepth); + } + + // Add a character to the end of the current packet, and if it is 254 + // characters, flush the packet to disk. + private void CharOut(byte character, Stream stream) + { + this.accumulatorBytes[this.accumulatorCount++] = character; + if (this.accumulatorCount >= 254) + this.FlushChar(stream); + } + + // Clear out the hash table + + // table clear for block compress + private void ClearBlock(Stream outs) + { + this.ClearHashTable(this.hsize); + this.free_ent = this.ClearCode + 2; + this.clear_flg = true; + + this.Output(this.ClearCode, outs); + } + + // reset code table + private void ClearHashTable(int hsize) + { + for (int i = 0; i < hsize; ++i) + { + this.htab[i] = -1; + + } + } + + private void Compress(int init_bits, Stream outs) + { + int fcode; + int i /* = 0 */; + int c; + int ent; + int disp; + int hsize_reg; + int hshift; + + // Set up the globals: globalInitialBits - initial number of bits + this.globalInitialBits = init_bits; + + // Set up the necessary values + this.clear_flg = false; + this.numberOfBits = this.globalInitialBits; + this.maxcode = this.MAXCODE(this.numberOfBits); + + this.ClearCode = 1 << (init_bits - 1); + this.EOFCode = this.ClearCode + 1; + this.free_ent = 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 + + hsize_reg = this.hsize; + this.ClearHashTable(hsize_reg); // clear hash table + + this.Output(this.ClearCode, outs); + + // TODO: Refactor this. Goto is baaaaaaad! + outer_loop: + while ((c = this.NextPixel()) != EOF) + { + fcode = (c << this.maxbits) + ent; + i = c << hshift ^ ent; // xor hashing + + if (this.htab[i] == fcode) + { + ent = this.codetab[i]; + continue; + } + + if (this.htab[i] >= 0) // non-empty slot + { + disp = hsize_reg - i; // secondary hash (after G. Knott) + if (i == 0) disp = 1; + do + { + if ((i -= disp) < 0) i += hsize_reg; + + if (this.htab[i] == fcode) + { + ent = this.codetab[i]; + goto outer_loop; + } + + } while (this.htab[i] >= 0); + } + + this.Output(ent, outs); + ent = c; + + if (this.free_ent < this.maxmaxcode) + { + this.codetab[i] = this.free_ent++; // code -> hashtable + this.htab[i] = fcode; + } + else + { + this.ClearBlock(outs); + } + } + + // Put out the final code. + this.Output(ent, outs); + this.Output(this.EOFCode, outs); + } + + //---------------------------------------------------------------------------- + public void Encode(Stream stream) + { + stream.WriteByte((byte)this.initCodeSize); // write "initial code size" byte + + this.remaining = this.imgW * this.imgH; // reset navigation variables + this.curPixel = 0; + + this.Compress(this.initCodeSize + 1, stream); // compress and write the pixel data + + stream.WriteByte(0); // write block terminator + } + + // Flush the packet to disk, and reset the accumulator + private void FlushChar(Stream stream) + { + if (this.accumulatorCount > 0) + { + stream.WriteByte((byte)this.accumulatorCount); + stream.Write(this.accumulatorBytes, 0, this.accumulatorCount); + this.accumulatorCount = 0; + } + } + + private int MAXCODE(int bits) + { + return (1 << bits) - 1; + } + + //---------------------------------------------------------------------------- + // Return the next pixel from the image + //---------------------------------------------------------------------------- + private int NextPixel() + { + if (this.remaining == 0) + return EOF; + + --this.remaining; + + byte pix = this.pixAry[this.curPixel++]; + + return pix & 0xff; + } + + void Output(int code, Stream outs) + { + this.cur_accum &= this.masks[this.cur_bits]; + + if (this.cur_bits > 0) this.cur_accum |= (code << this.cur_bits); + else this.cur_accum = code; + + this.cur_bits += this.numberOfBits; + + while (this.cur_bits >= 8) + { + this.CharOut((byte)(this.cur_accum & 0xff), outs); + this.cur_accum >>= 8; + this.cur_bits -= 8; + } + + // If the next entry is going to be too big for the code size, + // then increase it, if possible. + if (this.free_ent > this.maxcode || this.clear_flg) + { + if (this.clear_flg) + { + this.maxcode = this.MAXCODE(this.numberOfBits = this.globalInitialBits); + this.clear_flg = false; + } + else + { + ++this.numberOfBits; + this.maxcode = this.numberOfBits == this.maxbits + ? this.maxmaxcode + : this.MAXCODE(this.numberOfBits); + } + } + + if (code == this.EOFCode) + { + // At EOF, write the rest of the buffer. + while (this.cur_bits > 0) + { + this.CharOut((byte)(this.cur_accum & 0xff), outs); + this.cur_accum >>= 8; + this.cur_bits -= 8; + } + + this.FlushChar(outs); + } + } + } +} diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index f74fc83d5..f5db1868f 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -51,6 +51,7 @@ +