diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs b/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs index a97694f51..b0892c5f0 100644 --- a/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs @@ -128,15 +128,15 @@ namespace ImageProcessorCore.Formats writer.Write((ushort)descriptor.Width); writer.Write((ushort)descriptor.Height); - PackedByte pb = new PackedByte(); - pb.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used) - pb.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution - pb.SetBit(4, false); // 5 : GCT sort flag = 0 - pb.SetBits(5, 3, descriptor.GlobalColorTableSize); // 6-8 : GCT size. 2^(N+1) + PackedField field = new PackedField(); + field.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used) + field.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution + field.SetBit(4, false); // 5 : GCT sort flag = 0 + field.SetBits(5, 3, descriptor.GlobalColorTableSize); // 6-8 : GCT size. 2^(N+1) // Reduce the number of writes byte[] arr = { - pb.Byte, + field.Byte, descriptor.BackgroundColorIndex, // Background Color Index descriptor.PixelAspectRatio // Pixel aspect ratio. Assume 1:1 }; @@ -169,7 +169,7 @@ namespace ImageProcessorCore.Formats writer.Write((byte)1); // Data sub-block index (always 1) // 0 means loop indefinitely. Count is set as play n + 1 times. - repeatCount = Math.Max((ushort)0, repeatCount); + repeatCount = (ushort)(Math.Max((ushort)0, repeatCount) - 1); writer.Write(repeatCount); // Repeat count for images. @@ -208,14 +208,14 @@ namespace ImageProcessorCore.Formats writer.Write(intro); - PackedByte pb = new PackedByte(); - pb.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal + PackedField field = new PackedField(); + field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal // TODO: Allow this as an option. - pb.SetBit(6, false); // 7 : User input - 0 = none - pb.SetBit(7, extension.TransparencyFlag); // 8: Has transparent. + field.SetBit(6, false); // 7 : User input - 0 = none + field.SetBit(7, extension.TransparencyFlag); // 8: Has transparent. - writer.Write(pb.Byte); + writer.Write(field.Byte); writer.Write((ushort)extension.DelayTime); writer.Write((byte)(extension.TransparencyIndex == -1 ? 255 : extension.TransparencyIndex)); writer.Write(GifConstants.Terminator); @@ -235,13 +235,13 @@ namespace ImageProcessorCore.Formats writer.Write((ushort)image.Width); writer.Write((ushort)image.Height); - PackedByte pb = new PackedByte(); - pb.SetBit(0, true); // 1: Local color table flag = 1 (LCT used) - pb.SetBit(1, false); // 2: Interlace flag 0 - pb.SetBit(2, false); // 3: Sort flag 0 - pb.SetBits(5, 3, this.bitDepth - 1); // 4-5: Reserved, 6-8 : LCT size. 2^(N+1) + PackedField field = new PackedField(); + field.SetBit(0, true); // 1: Local color table flag = 1 (LCT used) + field.SetBit(1, false); // 2: Interlace flag 0 + field.SetBit(2, false); // 3: Sort flag 0 + field.SetBits(5, 3, this.bitDepth - 1); // 4-5: Reserved, 6-8 : LCT size. 2^(N+1) - writer.Write(pb.Byte); + writer.Write(field.Byte); } /// @@ -283,12 +283,7 @@ namespace ImageProcessorCore.Formats byte[] indexedPixels = image.Pixels; LzwEncoder encoder = new LzwEncoder(indexedPixels, (byte)this.bitDepth); - - LzwEncoder2 e = new LzwEncoder2(indexedPixels); - e.Encode(writer.BaseStream); - - //encoder.Encode(writer.BaseStream); - //writer.Write(GifConstants.Terminator); + encoder.Encode(writer.BaseStream); } } } diff --git a/src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs b/src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs index a232e1819..5d7917292 100644 --- a/src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs +++ b/src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs @@ -9,12 +9,12 @@ namespace ImageProcessorCore.Formats using System.IO; /// - /// Decompresses data using the LZW algorithms. + /// Decompresses and decodes data using the dynamic LZW algorithms. /// internal sealed class LzwDecoder { /// - /// One more than the maximum value 12 bit integer. + /// The max decoder pixel stack size. /// private const int MaxStackSize = 4096; @@ -24,7 +24,7 @@ namespace ImageProcessorCore.Formats private const int NullCode = -1; /// - /// The stream. + /// The stream to decode. /// private readonly Stream stream; @@ -68,167 +68,131 @@ namespace ImageProcessorCore.Formats // Calculate the available code. int availableCode = clearCode + 2; - // Jillzhangs Code (Not From Me) see: http://giflib.codeplex.com/ - // TODO: It's imperative that this code is cleaned up and commented properly. - // TODO: Unfortunately I can't figure out the character encoding to translate from the original Chinese. - int code; // ÓÃÓÚ´æ´¢µ±Ç°µÄ±àÂëÖµ - int oldCode = NullCode; // ÓÃÓÚ´æ´¢ÉÏÒ»´ÎµÄ±àÂëÖµ - int codeMask = (1 << codeSize) - 1; // ±íʾ±àÂëµÄ×î´óÖµ£¬Èç¹ûcodeSize=5,Ôòcode_mask=31 - int bits = 0; // ÔÚ±àÂëÁ÷ÖÐÊý¾ÝµÄ±£´æÐÎʽΪbyte£¬¶øÊµ¼Ê±àÂë¹ý³ÌÖÐÊÇÕÒʵ¼Ê±àÂëλÀ´´æ´¢µÄ£¬±ÈÈçµ±codeSize=5µÄʱºò£¬ÄÇôʵ¼ÊÉÏ5bitµÄÊý¾Ý¾ÍÓ¦¸Ã¿ÉÒÔ±íʾһ¸ö±àÂ룬ÕâÑùÈ¡³öÀ´µÄ1¸ö×ֽھ͸»ÓàÁË3¸öbit£¬Õâ3¸öbitÓÃÓں͵ڶþ¸ö×ֽڵĺóÁ½¸öbit½øÐÐ×éºÏ£¬ÔÙ´ÎÐγɱàÂëÖµ£¬Èç´ËÀàÍÆ + // 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[] prefix = new int[MaxStackSize]; // ÓÃÓÚ±£´æÇ°×ºµÄ¼¯ºÏ - int[] suffix = new int[MaxStackSize]; // ÓÃÓÚ±£´æºó׺ - int[] pixelStatck = new int[MaxStackSize + 1]; // ÓÃÓÚÁÙʱ±£´æÊý¾ÝÁ÷ + int[] prefix = new int[MaxStackSize]; + int[] suffix = new int[MaxStackSize]; + int[] pixelStatck = new int[MaxStackSize + 1]; int top = 0; - int count = 0; // ÔÚÏÂÃæµÄÑ­»·ÖУ¬Ã¿´Î»á»ñȡһ¶¨Á¿µÄ±àÂëµÄ×Ö½ÚÊý×飬¶ø´¦ÀíÕâÐ(c)Êý×éµÄʱºòÐèÒª1¸ö¸ö×Ö½ÚÀ´´¦Àí£¬count¾ÍÊDZíʾ»¹Òª´¦ÀíµÄ×Ö½ÚÊýÄ¿ - int bi = 0; // count±íʾ»¹Ê£¶àÉÙ×Ö½ÚÐèÒª´¦Àí£¬¶øbiÔò±íʾ±¾´ÎÒѾ­´¦ÀíµÄ¸öÊý - int xyz = 0; // i´ú±íµ±Ç°´¦ÀíµÃµ½ÏñËØÊý + int count = 0; + int bi = 0; + int xyz = 0; - int data = 0; // ±íʾµ±Ç°´¦ÀíµÄÊý¾ÝµÄÖµ - int first = 0; // Ò»¸ö×Ö·û´®ÖصĵÚÒ»¸ö×Ö½Ú + int data = 0; + int first = 0; - // ÏÈÉú³ÉÔªÊý¾ÝµÄǰ׺¼¯ºÏºÍºó׺¼¯ºÏ£¬ÔªÊý¾ÝµÄǰ׺¾ùΪ0£¬¶øºó׺ÓëÔªÊý¾ÝÏàµÈ£¬Í¬Ê±±àÂëÒ²ÓëÔªÊý¾ÝÏàµÈ for (code = 0; code < clearCode; code++) { - // ǰ׺³õʼΪ0 prefix[code] = 0; - - // ºó׺=ÔªÊý¾Ý=±àÂë suffix[code] = (byte)code; } byte[] buffer = null; while (xyz < pixels.Length) { - // ×î´óÏñËØÊýÒѾ­È·¶¨ÎªpixelCount = width * width if (top == 0) { if (bits < codeSize) { - // Èç¹ûµ±Ç°µÄÒª´¦ÀíµÄbitÊýСÓÚ±àÂëλ´óС£¬ÔòÐèÒª¼ÓÔØÊý¾Ý + // Load bytes until there are enough bits for a code. if (count == 0) { - // Èç¹ûcountΪ0£¬±íʾҪ´Ó±àÂëÁ÷ÖжÁÒ»¸öÊý¾Ý¶ÎÀ´½øÐзÖÎö + // Read a new data block. buffer = this.ReadBlock(); count = buffer.Length; if (count == 0) { - // ÔÙ´ÎÏë¶ÁÈ¡Êý¾Ý¶Î£¬È´Ã»ÓжÁµ½Êý¾Ý£¬´Ëʱ¾Í±íÃ÷ÒѾ­´¦ÀíÍêÁË break; } - // ÖØÐ¶Áȡһ¸öÊý¾Ý¶Îºó£¬Ó¦¸Ã½«ÒѾ­´¦ÀíµÄ¸öÊýÖÃ0 bi = 0; } - // »ñÈ¡±¾´ÎÒª´¦ÀíµÄÊý¾ÝµÄÖµ if (buffer != null) { - data += buffer[bi] << bits; // ´Ë´¦ÎªºÎÒªÒÆÎ»ÄØ£¬±ÈÈçµÚÒ»´Î´¦ÀíÁË1¸ö×Ö½ÚΪ176£¬µÚÒ»´ÎÖ»Òª´¦Àí5bit¾Í¹»ÁË£¬Ê£ÏÂ3bitÁô¸øÏ¸ö×Ö½Ú½øÐÐ×éºÏ¡£Ò²¾ÍÊǵڶþ¸ö×ֽڵĺóÁ½Î»+µÚÒ»¸ö×Ö½ÚµÄǰÈýλ×é³ÉµÚ¶þ´ÎÊä³öÖµ + data += buffer[bi] << bits; } - bits += 8; // ±¾´ÎÓÖ´¦ÀíÁËÒ»¸ö×Ö½Ú£¬ËùÒÔÐèÒª+8 - bi++; // ½«´¦ÀíÏÂÒ»¸ö×Ö½Ú - count--; // ÒѾ­´¦Àí¹ýµÄ×Ö½ÚÊý+1 + bits += 8; + bi++; + count--; continue; } - // Èç¹ûÒѾ­ÓÐ×ã¹»µÄbitÊý¿É¹(c)´¦Àí£¬ÏÂÃæ¾ÍÊÇ´¦Àí¹ý³Ì - // »ñÈ¡±àÂë - code = data & codeMask; // »ñÈ¡dataÊý¾ÝµÄ±àÂëλ´óСbitµÄÊý¾Ý - data >>= codeSize; // ½«±àÂëÊý¾Ý½ØÈ¡ºó£¬Ô­À´µÄÊý¾Ý¾Íʣϼ¸¸öbitÁË£¬´Ëʱ½«ÕâÐ(c)bitÓÒÒÆ£¬ÎªÏ´Î×÷×¼±¸ - bits -= codeSize; // ͬʱÐèÒª½«µ±Ç°Êý¾ÝµÄbitÊý¼õÈ¥±àÂë볤£¬ÒòΪÒѾ­µÃµ½ÁË´¦Àí¡£ + // Get the next code + code = data & codeMask; + data >>= codeSize; + bits -= codeSize; - // ÏÂÃæ¸ù¾Ý»ñÈ¡µÄcodeÖµÀ´½øÐд¦Àí + // 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; - - // ½«±£´æµ½old_codeÖеÄÖµÇå³ý£¬ÒÔ±ãÖØÍ·ÔÙÀ´ oldCode = NullCode; continue; } - // ÏÂÃæÊÇcodeÊôÓÚÄÜѹËõµÄ±àÂ뷶ΧÄڵĵĴ¦Àí¹ý³Ì if (oldCode == NullCode) { - // Èç¹ûµ±Ç°±àÂëֵΪ¿Õ,±íʾÊǵÚÒ»´Î»ñÈ¡±àÂë pixelStatck[top++] = suffix[code]; // »ñÈ¡µ½1¸öÊý¾ÝÁ÷µÄÊý¾Ý - - // ±¾´Î±àÂë´¦ÀíÍê³É£¬½«±àÂëÖµ±£´æµ½old_codeÖÐ oldCode = code; - - // µÚÒ»¸ö×Ö·ûΪµ±Ç°±àÂë first = code; continue; } - int inCode = code; // ÔÚlzwÖУ¬Èç¹ûÈÏʶÁËÒ»¸ö±àÂëËù´ú±íµÄÊý¾Ýentry£¬Ôò½«±àÂë×÷ΪÏÂÒ»´ÎµÄprefix£¬´Ë´¦inCode´ú±í´«µÝ¸øÏÂÒ»´Î×÷Ϊǰ׺µÄ±àÂëÖµ + int inCode = code; if (code == availableCode) { - // Èç¹ûµ±Ç°±àÂëºÍ±¾´ÎÓ¦¸ÃÉú³ÉµÄ±àÂëÏàͬ - // ÄÇôÏÂÒ»¸öÊý¾Ý×ֽھ͵ÈÓÚµ±Ç°´¦Àí×Ö·û´®µÄµÚÒ»¸ö×Ö½Ú pixelStatck[top++] = (byte)first; - code = oldCode; // »ØËݵ½ÉÏÒ»¸ö±àÂë + code = oldCode; } while (code > clearCode) { - // Èç¹ûµ±Ç°±àÂë´óÓÚÇå³ý±ê¼Ç£¬±íʾ±àÂëÖµÊÇÄÜѹËõÊý¾ÝµÄ pixelStatck[top++] = suffix[code]; - code = prefix[code]; // »ØËݵ½ÉÏÒ»¸ö±àÂë + code = prefix[code]; } first = suffix[code]; - // »ñÈ¡ÏÂÒ»¸öÊý¾Ý pixelStatck[top++] = 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; - - // Ï´ÎÓ¦¸ÃµÃµ½µÄ±àÂëÖµ availableCode++; if (availableCode == codeMask + 1 && availableCode < MaxStackSize) { - // Ôö¼Ó±àÂëλÊý codeSize++; - - // ÖØÉè×î´ó±àÂëÖµ codeMask = (1 << codeSize) - 1; } } - // »¹Ô­old_code oldCode = inCode; } - // »ØËݵ½ÉÏÒ»¸ö´¦ÀíλÖà + // Pop a pixel off the pixel stack. top--; - // »ñȡԪÊý¾Ý + // Clear missing pixels pixels[xyz++] = (byte)pixelStatck[top]; } diff --git a/src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs b/src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs index 4179ec46f..a9681d2c5 100644 --- a/src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs +++ b/src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs @@ -6,192 +6,380 @@ namespace ImageProcessorCore.Formats { using System; - using System.Collections.Generic; using System.IO; /// - /// Encodes an image pixels used on a method based on LZW compression. - /// + /// Encodes and compresses the image data using dynamic Lempel-Ziv compression. /// - internal class LzwEncoder + /// + /// 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 { + private const int Eof = -1; + + private const int Bits = 12; + + private const int HashSize = 5003; // 80% occupancy + + private readonly byte[] pixelArray; + + private readonly int initialCodeSize; + + private int curPixel; + /// - /// One more than the maximum value 12 bit integer. + /// Number of bits/code /// - private const int MaxStackSize = 4096; + private int bitCount; /// - /// The initial bit depth. + /// User settable max # bits/code /// - private readonly byte initDataSize; + private int maxbits = Bits; + + private int maxcode; // maximum code, given bitCount + + private int maxmaxcode = 1 << Bits; // should NEVER generate this code + + private readonly int[] hashTable = new int[HashSize]; + + private readonly int[] codeTable = new int[HashSize]; /// - /// The indexed pixels to encode. + /// For dynamic table sizing /// - private readonly byte[] indexedPixels; + private int hsize = HashSize; /// - /// The color depth in bits. + /// First unused entry /// - private byte colorDepth; + 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; + + private int clearCode; + + private int eofCode; + + // output + // + // 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; + + private int currentBits; + + private readonly int[] masks = + { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF + }; + + /// + /// Number of characters so far in this 'packet' + /// + private int accumulatorCount; + + /// + /// Define the storage for the packet accumulator. + /// + private readonly byte[] accumulators = new byte[256]; /// /// Initializes a new instance of the class. /// /// The array of indexed pixels. /// The color depth in bits. - public LzwEncoder(byte[] indexedPixels, byte colorDepth) + public LzwEncoder(byte[] indexedPixels, int colorDepth) { - this.indexedPixels = indexedPixels; - this.colorDepth = colorDepth.Clamp(2, 8); - this.initDataSize = this.colorDepth; + this.pixelArray = indexedPixels; + this.initialCodeSize = Math.Max(2, colorDepth); } /// - /// Encodes the compressed indexed pixel data to the given stream. + /// Encodes and compresses the indexed pixels to the stream. /// - /// The stream to add the data to. - /// is null. + /// The stream to write to. public void Encode(Stream stream) { - Guard.NotNull(stream, nameof(stream)); + // Write "initial code size" byte + stream.WriteByte((byte)this.initialCodeSize); + + this.curPixel = 0; + + // Compress and write the pixel data + this.Compress(this.initialCodeSize + 1, stream); + + // Write block terminator + stream.WriteByte(GifConstants.Terminator); + } + + /// + /// Gets the maximum code value + /// + /// The number of bits + /// See + private static int GetMaxcode(int bitCount) + { + return (1 << bitCount) - 1; + } - // Whether it is a first step. - bool first = true; + /// + /// 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); + } + } - // The initial suffix. - int suffix = 0; + /// + /// 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; + } + } - // Indicator to reinitialize the code table. - int clearCode = 1 << this.colorDepth; + /// + /// Compress the packets to the stream. + /// + /// The inital bits. + /// The stream to write to. + private void Compress(int intialBits, Stream stream) + { + int fcode; + int c; + int ent; + int hsizeReg; + int hshift; - // End of information code - int endOfInformation = clearCode + 1; + // Set up the globals: globalInitialBits - initial number of bits + this.globalInitialBits = intialBits; - // The code table for storing encoded colors. - Dictionary codeTable = new Dictionary(); + // Set up the necessary values + this.clearFlag = false; + this.bitCount = this.globalInitialBits; + this.maxcode = GetMaxcode(this.bitCount); - // The current number of index bytes processed. - int releaseCount = 0; + this.clearCode = 1 << (intialBits - 1); + this.eofCode = this.clearCode + 1; + this.freeEntry = this.clearCode + 2; - // Calculate the code available. - byte codeSize = (byte)(this.colorDepth + 1); - int availableCode = endOfInformation + 1; + this.accumulatorCount = 0; // clear packet - // Initialise. - BitEncoder bitEncoder = new BitEncoder(codeSize); - stream.WriteByte(this.colorDepth); - bitEncoder.Add(clearCode); + ent = this.NextPixel(); - while (releaseCount < this.indexedPixels.Length) + 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) { - if (first) + fcode = (c << this.maxbits) + ent; + int i = (c << hshift) ^ ent /* = 0 */; + + if (this.hashTable[i] == fcode) { - // If this is the first byte the suffix is set to the first byte index. - suffix = this.indexedPixels[releaseCount++]; + ent = this.codeTable[i]; + continue; + } - if (releaseCount == this.indexedPixels.Length) + // Non-empty slot + if (this.hashTable[i] >= 0) + { + int disp = hsizeReg - i; + if (i == 0) disp = 1; + do { - bitEncoder.Add(suffix); - bitEncoder.Add(endOfInformation); - bitEncoder.End(); - stream.WriteByte((byte)bitEncoder.Length); - stream.Write(bitEncoder.ToArray(), 0, bitEncoder.Length); - bitEncoder.Clear(); - break; + if ((i -= disp) < 0) { i += hsizeReg; } + + if (this.hashTable[i] == fcode) + { + ent = this.codeTable[i]; + break; + } } + while (this.hashTable[i] >= 0); - first = false; - continue; + if (this.hashTable[i] == fcode) { continue; } } - // Switch - int prefix = suffix; + 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); + } - // Read the bytes at the index. - suffix = this.indexedPixels[releaseCount++]; + // Put out the final code. + this.Output(ent, stream); - // Acts as a key for code table entries. - string key = $"{prefix},{suffix}"; + this.Output(this.eofCode, stream); + } - // Is index buffer + the index in our code table? - if (!codeTable.ContainsKey(key)) - { - // If the current entity is not coded add the prefix. - bitEncoder.Add(prefix); + // Flush the packet to disk, and reset the accumulator + private void FlushPacket(Stream outs) + { + if (this.accumulatorCount > 0) + { + outs.WriteByte((byte)this.accumulatorCount); + outs.Write(this.accumulators, 0, this.accumulatorCount); + this.accumulatorCount = 0; + } + } - // Add the current bytes - codeTable.Add(key, availableCode++); + /// + /// Return the next pixel from the image + /// + /// + /// The + /// + private int NextPixel() + { + if (this.curPixel == this.pixelArray.Length) + { + return Eof; + } - if (availableCode > (MaxStackSize - 3)) - { - // Clear out and reset the wheel. - codeTable.Clear(); - this.colorDepth = this.initDataSize; - codeSize = (byte)(this.colorDepth + 1); - availableCode = endOfInformation + 1; - - bitEncoder.Add(clearCode); - bitEncoder.IntitialBit = codeSize; - } - else if (availableCode > (1 << codeSize)) - { - // If the currently available coding is greater than the current value. - // the coded bits can represent. - this.colorDepth++; - codeSize = (byte)(this.colorDepth + 1); - bitEncoder.IntitialBit = codeSize; - } + if (this.curPixel == this.pixelArray.Length) + return Eof; - if (bitEncoder.Length >= 255) - { - stream.WriteByte(255); - stream.Write(bitEncoder.ToArray(), 0, 255); - if (bitEncoder.Length > 255) - { - byte[] leftBuffer = new byte[bitEncoder.Length - 255]; - bitEncoder.CopyTo(255, leftBuffer, 0, leftBuffer.Length); - bitEncoder.Clear(); - bitEncoder.AddRange(leftBuffer); - } - else - { - bitEncoder.Clear(); - } - } + this.curPixel++; + return this.pixelArray[this.curPixel - 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 &= this.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 { - // Set the suffix to the current byte. - suffix = codeTable[key]; + ++this.bitCount; + this.maxcode = this.bitCount == this.maxbits + ? this.maxmaxcode + : GetMaxcode(this.bitCount); } + } - // Output code for contents of index buffer. - // Output end-of-information code. - if (releaseCount == this.indexedPixels.Length) + if (code == this.eofCode) + { + // At EOF, write the rest of the buffer. + while (this.currentBits > 0) { - bitEncoder.Add(suffix); - bitEncoder.Add(endOfInformation); - bitEncoder.End(); - if (bitEncoder.Length > 255) - { - byte[] leftBuffer = new byte[bitEncoder.Length - 255]; - bitEncoder.CopyTo(255, leftBuffer, 0, leftBuffer.Length); - bitEncoder.Clear(); - bitEncoder.AddRange(leftBuffer); - stream.WriteByte((byte)leftBuffer.Length); - stream.Write(leftBuffer, 0, leftBuffer.Length); - } - else - { - stream.WriteByte((byte)bitEncoder.Length); - stream.Write(bitEncoder.ToArray(), 0, bitEncoder.Length); - bitEncoder.Clear(); - } - - break; + this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); + this.currentAccumulator >>= 8; + this.currentBits -= 8; } + + this.FlushPacket(outs); } } } -} +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Gif/LzwEncoder2.cs b/src/ImageProcessorCore/Formats/Gif/LzwEncoder2.cs deleted file mode 100644 index 9d9106165..000000000 --- a/src/ImageProcessorCore/Formats/Gif/LzwEncoder2.cs +++ /dev/null @@ -1,432 +0,0 @@ -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - /// - /// An implementation of the Lempel-Ziv-Welch lossless compression algorithm. - /// See http://en.wikipedia.org/wiki/Lzw - /// - public class LzwEncoder2 - { - #region declarations - private const int _EOF = -1; - private const int _BITS = 12; - private const int _HSIZE = 5003; // 80% occupancy - - /// - /// A collection of indices within the active colour table of the - /// colours of the pixels within the image. - /// - private byte[] _pixels; - private int _initCodeSize; - - /// - /// Number of pixels still to process. - /// - private int _pixelsRemaining; - - /// - /// Index of the current position within the IndexedPixels collection. - /// - private int _pixelIndex; - - /// - /// Number of bits per encoded code. - /// - int _codeSize; // number of bits/code - - /// - /// Maximum number of bits per encoded code. - /// - int _maxCodeSize = _BITS; // user settable max # bits/code - - /// - /// The largest possible code given the current value of _codeSize. - /// - int _maxCode; // maximum code, given n_bits - - /// - /// The largest possible code given the largest possible value of - /// _codeSize, plus 1. We should never output this code. - /// - int _maxMaxCode = 1 << _BITS; // should NEVER generate this code - - int[] _htab = new int[_HSIZE]; - int[] _codetab = new int[_HSIZE]; - - int _hsize = _HSIZE; // for dynamic table sizing - - /// - /// The next unused code. Initially set to the clear code plus 2. - /// - int _nextAvailableCode; // first unused entry - - // block compression parameters -- after all codes are used up, - // and compression rate changes, start over. - bool _clear_flg; - - int _g_init_bits; - - /// - /// Clear code. This is written out by the encoder when the dictionary - /// is full, and is an instruction to the decoder to empty its dictionary - /// - int _clearCode; - - /// - /// End of information code. This is set to the clear code plus 1 and - /// marks the end of the encoded data. - /// - int _endOfInformationCode; - - int _cur_accum; - int _cur_bits; - - int[] _masks = - { - 0x0000, - 0x0001, - 0x0003, - 0x0007, - 0x000F, - 0x001F, - 0x003F, - 0x007F, - 0x00FF, - 0x01FF, - 0x03FF, - 0x07FF, - 0x0FFF, - 0x1FFF, - 0x3FFF, - 0x7FFF, - 0xFFFF }; - - // Number of characters so far in this 'packet' - /// - /// Number of bytes that have been added to the packet so far. - /// - int _byteCountInPacket; - - // Define the storage for the packet accumulator - /// - /// An array of encoded bytes which are waiting to be written to the - /// output stream. The bytes are written out once 254 of them have - /// been populated. - /// - byte[] _packet = new byte[256]; - #endregion - - #region constructor - /// - /// Constructor. - /// - /// - /// Indices in the active colour table of the colours of the pixel - /// making up the image. - /// - /// - /// The supplied pixel collection is null. - /// - public LzwEncoder2(byte[] pixels) - { - if (pixels == null) - { - throw new ArgumentNullException("pixels"); - } - - _pixels = pixels; - // _initCodeSize = Math.Max(2, colourDepth); - _initCodeSize = 8; // only seems to work reliably when 8, even if this is sometimes larger than needed - } - #endregion - - #region Encode method - /// - /// Encodes the data and writes it to the supplied output stream. - /// - /// Output stream - /// - /// The supplied output stream is null. - /// - public void Encode(Stream outputStream) - { - if (outputStream == null) - { - throw new ArgumentNullException("outputStream"); - } - - outputStream.WriteByte(Convert.ToByte(_initCodeSize)); // write "initial code size" byte - - _pixelsRemaining = _pixels.Length; - _pixelIndex = 0; - - Compress(_initCodeSize + 1, outputStream); // compress and write the pixel data - - outputStream.WriteByte(0); // write block terminator - } - #endregion - - #region private methods - - #region private Add method - /// - /// Add a character to the end of the current packet, and if the packet - /// is 254 characters long, flush the packet to disk. - /// - /// The character to add - /// Output stream - private void Add(byte c, Stream outputStream) - { - _packet[_byteCountInPacket++] = c; - if (_byteCountInPacket >= 254) - { - Flush(outputStream); - } - } - #endregion - - #region private ClearTable method - /// - /// Clears out the hash table. - /// - /// Output stream - private void ClearTable(Stream outs) - { - ResetCodeTable(_hsize); - _nextAvailableCode = _clearCode + 2; - _clear_flg = true; - - Output(_clearCode, outs); - } - #endregion - - #region private ResetCodeTable method - /// - /// Resets the code table - /// - /// - private void ResetCodeTable(int hsize) - { - for (int i = 0; i < hsize; ++i) - { - _htab[i] = -1; - } - } - #endregion - - #region private Compress method - /// - /// Compress method - /// - /// - /// - 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: g_init_bits - initial number of bits - _g_init_bits = init_bits; - - // Set up the necessary values - _clear_flg = false; - _codeSize = _g_init_bits; - _maxCode = MaxCode(_codeSize); - - _clearCode = 1 << (init_bits - 1); - _endOfInformationCode = _clearCode + 1; - _nextAvailableCode = _clearCode + 2; - - _byteCountInPacket = 0; // clear packet - - ent = NextPixel(); - - hshift = 0; - for (fcode = _hsize; fcode < 65536; fcode *= 2) - ++hshift; - hshift = 8 - hshift; // set hash code range bound - - hsize_reg = _hsize; - ResetCodeTable(hsize_reg); // clear hash table - - Output(_clearCode, outs); - - outer_loop: while ((c = NextPixel()) != _EOF) - { - fcode = (c << _maxCodeSize) + ent; - i = (c << hshift) ^ ent; // xor hashing - - if (_htab[i] == fcode) - { - ent = _codetab[i]; - continue; - } - else if (_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 (_htab[i] == fcode) - { - ent = _codetab[i]; - goto outer_loop; - } - } while (_htab[i] >= 0); - } - Output(ent, outs); - ent = c; - if (_nextAvailableCode < _maxMaxCode) - { - _codetab[i] = _nextAvailableCode++; // code -> hashtable - _htab[i] = fcode; - } - else - ClearTable(outs); - } - // Put out the final code. - Output(ent, outs); - Output(_endOfInformationCode, outs); - } - #endregion - - #region private Flush method - /// - /// Flush the packet to disk, and reset the accumulator - /// - /// - private void Flush(Stream outs) - { - if (_byteCountInPacket > 0) - { - outs.WriteByte(Convert.ToByte(_byteCountInPacket)); - outs.Write(_packet, 0, _byteCountInPacket); - _byteCountInPacket = 0; - } - } - #endregion - - #region private static MaxCode method - /// - /// Calculates and returns the maximum possible code given the supplied - /// code size. - /// This is calculated as 2 to the power of the code size, minus one. - /// - /// - /// Code size in bits. - /// - /// - private static int MaxCode(int codeSize) - { - return (1 << codeSize) - 1; - } - #endregion - - #region private NextPixel method - /// - /// Gets the next pixel from the supplied IndexedPixels collection, - /// increments the index of the current position within the collection, - /// and decrements the number of pixels remaining. - /// - /// - private int NextPixel() - { - if (_pixelsRemaining == 0) - { - // We've processed all the supplied pixel indices so return an - // end of file indicator. - return _EOF; - } - - --_pixelsRemaining; - - byte pix = _pixels[_pixelIndex++]; - - return pix; - } - #endregion - - #region private Output method - /// - /// Adds an encoded LZW code to a buffer ready to be written to the - /// output stream. Any full bytes contained in the buffer are then - /// written to the output stream and removed from the buffer. - /// - /// - /// - /// The output stream to write to. - /// - private void Output(int code, Stream outputStream) - { - _cur_accum &= _masks[_cur_bits]; - - if (_cur_bits > 0) - { - _cur_accum |= (code << _cur_bits); - } - else - { - _cur_accum = code; - } - - _cur_bits += _codeSize; - - while (_cur_bits >= 8) - { - Add((byte)(_cur_accum & 0xff), outputStream); - _cur_accum >>= 8; - _cur_bits -= 8; - } - - // If the next entry is going to be too big for the code size, - // then increase it, if possible. - if (_nextAvailableCode > _maxCode || _clear_flg) - { - if (_clear_flg) - { - _maxCode = MaxCode(_codeSize = _g_init_bits); - _clear_flg = false; - } - else - { - ++_codeSize; - if (_codeSize == _maxCodeSize) - { - _maxCode = _maxMaxCode; - } - else - { - _maxCode = MaxCode(_codeSize); - } - } - } - - if (code == _endOfInformationCode) - { - // At EOF, write the rest of the buffer. - while (_cur_bits > 0) - { - Add((byte)(_cur_accum & 0xff), outputStream); - _cur_accum >>= 8; - _cur_bits -= 8; - } - - Flush(outputStream); - } - } - #endregion - - #endregion - } -} diff --git a/src/ImageProcessorCore/Formats/Gif/PackedByte.cs b/src/ImageProcessorCore/Formats/Gif/PackedField.cs similarity index 74% rename from src/ImageProcessorCore/Formats/Gif/PackedByte.cs rename to src/ImageProcessorCore/Formats/Gif/PackedField.cs index 7e438caee..0141d36c6 100644 --- a/src/ImageProcessorCore/Formats/Gif/PackedByte.cs +++ b/src/ImageProcessorCore/Formats/Gif/PackedField.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -10,37 +10,14 @@ namespace ImageProcessorCore.Formats /// /// Represents a byte of data in a GIF data stream which contains a number /// of data items. - /// TODO: Finish me. /// - internal struct PackedByte + internal struct PackedField : IEquatable { /// /// The individual bits representing the packed byte. /// private static readonly bool[] Bits = new bool[8]; - #region constructor( int ) - ///// - ///// Constructor. - ///// Sets the bits in the packed fields to the corresponding bits from - ///// the supplied byte. - ///// - ///// - ///// A single byte of data, consisting of fields which may be of one or - ///// more bits. - ///// - //public PackedByte(int data) : this(data) - //{ - // for (int i = 0; i < 8; i++) - // { - // var bitShift = 7 - i; - // var bitValue = (data >> bitShift) & 1; - // bool bit = bitValue == 1; - // _bits[i] = bit; - // } - //} - #endregion - /// /// Gets the byte which represents the data items held in this instance. /// @@ -68,6 +45,19 @@ namespace ImageProcessorCore.Formats } } + /// + /// Returns a new with the bits in the packed fields to + /// the corresponding bits from the supplied byte. + /// + /// The value to pack. + /// The + public static PackedField FromInt(byte value) + { + PackedField packed = new PackedField(); + packed.SetBits(0, 8, value); + return packed; + } + /// /// Sets the specified bit within the packed fields to the supplied /// value. @@ -94,16 +84,9 @@ namespace ImageProcessorCore.Formats /// Sets the specified bits within the packed fields to the supplied /// value. /// - /// - /// The zero-based index within the packed fields of the first bit to - /// set. - /// - /// - /// The number of bits to set. - /// - /// - /// The value to set the bits to. - /// + /// The zero-based index within the packed fields of the first bit to set. + /// The number of bits to set. + /// The value to set the bits to. public void SetBits(int startIndex, int length, int valueToSet) { if (startIndex < 0 || startIndex > 7) @@ -133,9 +116,7 @@ namespace ImageProcessorCore.Formats /// /// Gets the value of the specified bit within the byte. /// - /// - /// The zero-based index of the bit to get. - /// + /// The zero-based index of the bit to get. /// /// The value of the specified bit within the byte. /// @@ -152,12 +133,8 @@ namespace ImageProcessorCore.Formats /// /// Gets the value of the specified bits within the byte. /// - /// - /// The zero-based index of the first bit to get. - /// - /// - /// The number of bits to get. - /// + /// The zero-based index of the first bit to get. + /// The number of bits to get. /// /// The value of the specified bits within the byte. /// @@ -187,5 +164,31 @@ namespace ImageProcessorCore.Formats } return returnValue; } + + /// + public override bool Equals(object obj) + { + PackedField? field = obj as PackedField?; + + return this.Byte == field?.Byte; + } + + /// + public bool Equals(PackedField other) + { + return this.Byte.Equals(other.Byte); + } + + /// + public override string ToString() + { + return $"PackedField [ Byte={this.Byte} ]"; + } + + /// + public override int GetHashCode() + { + return this.Byte.GetHashCode(); + } } } \ No newline at end of file