From 9c691d65377086d0ed18a916f7d7bcbf41adeef0 Mon Sep 17 00:00:00 2001 From: James South Date: Sat, 2 Apr 2016 18:06:48 +1100 Subject: [PATCH] Fixing encoder for animated gifs Lots of cleanup to do yet. Former-commit-id: 2606ad6ffb5da07a1aef2ee1d5af81437367b8c3 Former-commit-id: a444cddddae006b71c32de7e4dc1619b604d1115 Former-commit-id: 5dffd13674d2d3ae1b2c366989cc1c97b1bd18ac --- .../Formats/Gif/DisposalMethod.cs | 1 + .../Formats/Gif/GifEncoderCore.cs | 349 ++++++-------- .../Formats/Gif/LzwEncoder2.cs | 432 ++++++++++++++++++ .../Formats/Gif/PackedByte.cs | 191 ++++++++ 4 files changed, 774 insertions(+), 199 deletions(-) create mode 100644 src/ImageProcessorCore/Formats/Gif/LzwEncoder2.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/PackedByte.cs diff --git a/src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs b/src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs index 8c66d8b44..4b0a01973 100644 --- a/src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs +++ b/src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs @@ -8,6 +8,7 @@ namespace ImageProcessorCore.Formats /// /// Provides enumeration for instructing the decoder what to do with the last image /// in an animation sequence. + /// section 23 /// public enum DisposalMethod { diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs b/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs index 19e2f1df9..a97694f51 100644 --- a/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs @@ -10,6 +10,7 @@ namespace ImageProcessorCore.Formats using System.Linq; using System.Threading.Tasks; + using ImageProcessorCore.IO; using ImageProcessorCore.Quantizers; /// @@ -55,117 +56,134 @@ namespace ImageProcessorCore.Formats this.Quantizer = new OctreeQuantizer { Threshold = this.Threshold }; } - // Write the header. - // File Header signature and version. - this.WriteString(stream, GifConstants.FileType); - this.WriteString(stream, GifConstants.FileVersion); + using (EndianBinaryWriter writer = new EndianBinaryWriter(EndianBitConverter.Little, stream)) + { + // Ensure that quality can be set but has a fallback. + int quality = this.Quality > 0 ? this.Quality : imageBase.Quality; + this.Quality = quality > 0 ? quality.Clamp(1, 256) : 256; - // Ensure that quality can be set but has a fallback. - int quality = this.Quality > 0 ? this.Quality : imageBase.Quality; - quality = quality > 0 ? quality.Clamp(1, 256) : 256; + // Get the number of bits. + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.Quality); - // Get the number of bits. - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality); + // Quantize the image returning a palette. + QuantizedImage quantized = this.Quantizer.Quantize(image, this.Quality); - // Write the LSD and check to see if we need a global color table. - // Always true just now. - this.WriteGlobalLogicalScreenDescriptor(image, stream); - QuantizedImage quantized = this.WriteColorTable(imageBase, stream, quality); + // Write the header. + this.WriteHeader(writer); - this.WriteGraphicalControlExtension(imageBase, stream, quantized.TransparentIndex); - this.WriteImageDescriptor(quantized, quality, stream); + // Write the LSD. We'll use local color tables for now. + this.WriteLogicalScreenDescriptor(image, writer, quantized.TransparentIndex); - if (image.Frames.Any()) - { - this.WriteApplicationExtension(stream, image.RepeatCount, image.Frames.Count); - foreach (ImageFrame frame in image.Frames) + // Write the first frame. + this.WriteGraphicalControlExtension(imageBase, writer, quantized.TransparentIndex); + this.WriteImageDescriptor(image, writer); + this.WriteColorTable(quantized, writer); + this.WriteImageData(quantized, writer); + + // Write additional frames. + if (image.Frames.Any()) { - this.WriteGraphicalControlExtension(frame, stream, quantized.TransparentIndex); - this.WriteFrameImageDescriptor(frame, stream); + this.WriteApplicationExtension(writer, image.RepeatCount, image.Frames.Count); + foreach (ImageFrame frame in image.Frames) + { + QuantizedImage quantizedFrame = this.Quantizer.Quantize(frame, this.Quality); + this.WriteGraphicalControlExtension(frame, writer, quantizedFrame.TransparentIndex); + this.WriteImageDescriptor(frame, writer); + this.WriteColorTable(quantizedFrame, writer); + this.WriteImageData(quantizedFrame, writer); + } } + + // TODO: Write Comments extension etc + writer.Write(GifConstants.EndIntroducer); } + } - // TODO: Write Comments extension etc - this.WriteByte(stream, GifConstants.EndIntroducer); + /// + /// Writes the file header signature and version to the stream. + /// + /// The writer to write to the stream with. + private void WriteHeader(EndianBinaryWriter writer) + { + writer.Write((GifConstants.FileType + GifConstants.FileVersion).ToCharArray()); } /// /// Writes the logical screen descriptor to the stream. /// /// The image to encode. - /// The stream to write to. - /// The - private bool WriteGlobalLogicalScreenDescriptor(Image image, Stream stream) + /// The writer to write to the stream with. + /// The transparency index to set the default backgound index to. + private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int tranparencyIndex) { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor { Width = (short)image.Width, Height = (short)image.Height, - GlobalColorTableFlag = true, - GlobalColorTableSize = this.bitDepth + GlobalColorTableFlag = false, // Always false for now. + GlobalColorTableSize = this.bitDepth - 1, + BackgroundColorIndex = (byte)(tranparencyIndex > -1 ? tranparencyIndex : 255) }; - this.WriteShort(stream, descriptor.Width); - this.WriteShort(stream, descriptor.Height); + writer.Write((ushort)descriptor.Width); + writer.Write((ushort)descriptor.Height); - int packed = 0x80 | // 1 : Global color table flag = 1 (GCT used) - this.bitDepth - 1 | // 2-4 : color resolution - 0x00 | // 5 : GCT sort flag = 0 - this.bitDepth - 1; // 6-8 : GCT size TODO: Check this. + 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) - this.WriteByte(stream, packed); - this.WriteByte(stream, descriptor.BackgroundColorIndex); // Background Color Index - this.WriteByte(stream, descriptor.PixelAspectRatio); // Pixel aspect ratio. Assume 1:1 + // Reduce the number of writes + byte[] arr = { + pb.Byte, + descriptor.BackgroundColorIndex, // Background Color Index + descriptor.PixelAspectRatio // Pixel aspect ratio. Assume 1:1 + }; - return descriptor.GlobalColorTableFlag; + writer.Write(arr); } /// - /// Writes the color table to the stream. + /// Writes the application exstension to the stream. /// - /// The to encode. - /// The stream to write to. - /// The quality (number of colors) to encode the image to. - /// The - private QuantizedImage WriteColorTable(ImageBase image, Stream stream, int quality) + /// The writer to write to the stream with. + /// The animated image repeat count. + /// Th number of image frames. + private void WriteApplicationExtension(EndianBinaryWriter writer, ushort repeatCount, int frames) { - // Quantize the image returning a palette. - QuantizedImage quantizedImage = this.Quantizer.Quantize(image, quality); - - // Grab the palette and write it to the stream. - Bgra32[] palette = quantizedImage.Palette; - int pixelCount = palette.Length; + // Application Extension Header + if (repeatCount != 1 && frames > 0) + { + byte[] ext = + { + GifConstants.ExtensionIntroducer, + GifConstants.ApplicationExtensionLabel, + GifConstants.ApplicationBlockSize + }; - // Get max colors for bit depth. - int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; - byte[] colorTable = new byte[colorTableLength]; + writer.Write(ext); - Parallel.For(0, pixelCount, - i => - { - int offset = i * 3; - Bgra32 color = palette[i]; + writer.Write(GifConstants.ApplicationIdentification.ToCharArray()); // NETSCAPE2.0 + writer.Write((byte)3); // Application block length + writer.Write((byte)1); // Data sub-block index (always 1) - colorTable[offset] = color.R; - colorTable[offset + 1] = color.G; - colorTable[offset + 2] = color.B; - }); + // 0 means loop indefinitely. Count is set as play n + 1 times. + repeatCount = Math.Max((ushort)0, repeatCount); - stream.Write(colorTable, 0, colorTableLength); + writer.Write(repeatCount); // Repeat count for images. - return quantizedImage; + writer.Write(GifConstants.Terminator); // Terminator + } } /// /// Writes the graphics control extension to the stream. /// /// The to encode. - /// The stream to write to. + /// The stream to write to. /// The index of the color in the color palette to make transparent. - private void WriteGraphicalControlExtension(ImageBase image, Stream stream, int transparencyIndex) + private void WriteGraphicalControlExtension(ImageBase image, EndianBinaryWriter writer, int transparencyIndex) { // TODO: Check transparency logic. bool hasTransparent = transparencyIndex > -1; @@ -181,163 +199,96 @@ namespace ImageProcessorCore.Formats DelayTime = image.FrameDelay }; - this.WriteByte(stream, GifConstants.ExtensionIntroducer); - this.WriteByte(stream, GifConstants.GraphicControlLabel); - this.WriteByte(stream, 4); // Size - - int packed = 0 | // 1-3 : Reserved - (int)extension.DisposalMethod << 2 | // 4-6 : Disposal - 0 | // 7 : User input - 0 = none - (extension.TransparencyFlag ? 1 : 0); // 8: Has transparent. - - this.WriteByte(stream, packed); - this.WriteShort(stream, extension.DelayTime); - this.WriteByte(stream, extension.TransparencyIndex == -1 ? 255 : extension.TransparencyIndex); - this.WriteByte(stream, GifConstants.Terminator); - } + // Reduce the number of writes. + byte[] intro = { + GifConstants.ExtensionIntroducer, + GifConstants.GraphicControlLabel, + 4 // Size + }; - /// - /// Writes the application exstension to the stream. - /// - /// The stream to write to. - /// The animated image repeat count. - /// Th number of image frames. - private void WriteApplicationExtension(Stream stream, ushort repeatCount, int frames) - { - // Application Extension Header - if (repeatCount != 1 && frames > 0) - { - // 0 means loop indefinitely. count is set as play n + 1 times. - // TODO: Check this as the correct value might be pulled from the decoder. - repeatCount = (ushort)Math.Max(0, repeatCount - 1); - this.WriteByte(stream, GifConstants.ExtensionIntroducer); // NETSCAPE2.0 - this.WriteByte(stream, GifConstants.ApplicationExtensionLabel); - this.WriteByte(stream, GifConstants.ApplicationBlockSize); - - this.WriteString(stream, GifConstants.ApplicationIdentification); - this.WriteByte(stream, 3); // Application block length - this.WriteByte(stream, 1); // Data sub-block index (always 1) - this.WriteShort(stream, repeatCount); // Repeat count for images. - - this.WriteByte(stream, GifConstants.Terminator); // Terminator - } - } + writer.Write(intro); - /// - /// Writes the image descriptor to the stream. - /// - /// The containing indexed pixels. - /// The quality (number of colors) to encode the image to. - /// The stream to write to. - private void WriteImageDescriptor(QuantizedImage image, int quality, Stream stream) - { - this.WriteByte(stream, GifConstants.ImageDescriptorLabel); // 2c - // TODO: Can we capture this? - this.WriteShort(stream, 0); // Left position - this.WriteShort(stream, 0); // Top position - this.WriteShort(stream, image.Width); - this.WriteShort(stream, image.Height); + PackedByte pb = new PackedByte(); + pb.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal - // No LCT use GCT. - this.WriteByte(stream, 0); + // TODO: Allow this as an option. + pb.SetBit(6, false); // 7 : User input - 0 = none + pb.SetBit(7, extension.TransparencyFlag); // 8: Has transparent. - // Write the image data. - this.WriteImageData(image, stream); + writer.Write(pb.Byte); + writer.Write((ushort)extension.DelayTime); + writer.Write((byte)(extension.TransparencyIndex == -1 ? 255 : extension.TransparencyIndex)); + writer.Write(GifConstants.Terminator); } /// /// Writes the image descriptor to the stream. /// /// The to be encoded. - /// The stream to write to. - private void WriteFrameImageDescriptor(ImageBase image, Stream stream) + /// The stream to write to. + private void WriteImageDescriptor(ImageBase image, EndianBinaryWriter writer) { - this.WriteByte(stream, GifConstants.ImageDescriptorLabel); // 2c + writer.Write(GifConstants.ImageDescriptorLabel); // 2c // TODO: Can we capture this? - this.WriteShort(stream, 0); // Left position - this.WriteShort(stream, 0); // Top position - this.WriteShort(stream, image.Width); - this.WriteShort(stream, image.Height); - - // Calculate the quality. - int quality = this.Quality > 0 ? this.Quality : image.Quality; - quality = quality > 0 ? quality.Clamp(1, 256) : 256; - - int packed = 0x80 | // 1: Local color table flag = 1 (LCT used) - 0x00 | // 2: Interlace flag 0 - 0x00 | // 3: Sort flag 0 - 0 | // 4-5: Reserved - this.bitDepth - 1; - - this.WriteByte(stream, packed); - - // Now immediately follow with the color table. - QuantizedImage quantized = this.WriteColorTable(image, stream, quality); - this.WriteImageData(quantized, stream); + writer.Write((ushort)0); // Left position + writer.Write((ushort)0); // Top position + 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) + + writer.Write(pb.Byte); } /// - /// Writes the image pixel data to the stream. + /// Writes the color table to the stream. /// - /// The containing indexed pixels. - /// The stream to write to. - private void WriteImageData(QuantizedImage image, Stream stream) + /// The to encode. + /// The writer to write to the stream with. + private void WriteColorTable(QuantizedImage image, EndianBinaryWriter writer) { - byte[] indexedPixels = image.Pixels; + // Grab the palette and write it to the stream. + Bgra32[] palette = image.Palette; + int pixelCount = palette.Length; - LzwEncoder encoder = new LzwEncoder(indexedPixels, (byte)this.bitDepth); - encoder.Encode(stream); + // Get max colors for bit depth. + int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; + byte[] colorTable = new byte[colorTableLength]; - this.WriteByte(stream, GifConstants.Terminator); - } + Parallel.For(0, pixelCount, + i => + { + int offset = i * 3; + Bgra32 color = palette[i]; - /// - /// Writes a short to the given stream. - /// - /// The containing image data. - /// The value to write. - private void WriteShort(Stream stream, int value) - { - // Leave only one significant byte. - stream.WriteByte(Convert.ToByte(value & 0xff)); - stream.WriteByte(Convert.ToByte((value >> 8) & 0xff)); - } + colorTable[offset] = color.R; + colorTable[offset + 1] = color.G; + colorTable[offset + 2] = color.B; + }); - /// - /// Writes a byte to the given stream. - /// - /// The containing image data. - /// The value to write. - private void WriteByte(Stream stream, int value) - { - stream.WriteByte(Convert.ToByte(value)); + writer.Write(colorTable, 0, colorTableLength); } /// - /// Writes a string to the given stream. + /// Writes the image pixel data to the stream. /// - /// The containing image data. - /// The value to write. - private void WriteString(Stream stream, string value) + /// The containing indexed pixels. + /// The stream to write to. + private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer) { - char[] chars = value.ToCharArray(); - foreach (char c in chars) - { - stream.WriteByte((byte)c); - } - } + byte[] indexedPixels = image.Pixels; - /// - /// Returns how many bits are required to store the specified number of colors. - /// Performs a Log2() on the value. - /// - /// The number of colors. - /// - /// The - /// - private int GetBitsNeededForColorDepth(int colors) - { - return (int)Math.Ceiling(Math.Log(colors, 2)); + 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); } } } diff --git a/src/ImageProcessorCore/Formats/Gif/LzwEncoder2.cs b/src/ImageProcessorCore/Formats/Gif/LzwEncoder2.cs new file mode 100644 index 000000000..9d9106165 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/LzwEncoder2.cs @@ -0,0 +1,432 @@ +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/PackedByte.cs new file mode 100644 index 000000000..7e438caee --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/PackedByte.cs @@ -0,0 +1,191 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + + /// + /// Represents a byte of data in a GIF data stream which contains a number + /// of data items. + /// TODO: Finish me. + /// + internal struct PackedByte + { + /// + /// 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. + /// + public byte Byte + { + get + { + int returnValue = 0; + int bitShift = 7; + foreach (bool bit in Bits) + { + int bitValue; + if (bit) + { + bitValue = 1 << bitShift; + } + else + { + bitValue = 0; + } + returnValue |= bitValue; + bitShift--; + } + return Convert.ToByte(returnValue & 0xFF); + } + } + + /// + /// Sets the specified bit within the packed fields to the supplied + /// value. + /// + /// + /// The zero-based index within the packed fields of the bit to set. + /// + /// + /// The value to set the bit to. + /// + public void SetBit(int index, bool valueToSet) + { + if (index < 0 || index > 7) + { + string message + = "Index must be between 0 and 7. Supplied index: " + + index; + throw new ArgumentOutOfRangeException(nameof(index), message); + } + Bits[index] = valueToSet; + } + + /// + /// 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. + /// + public void SetBits(int startIndex, int length, int valueToSet) + { + if (startIndex < 0 || startIndex > 7) + { + string message = $"Start index must be between 0 and 7. Supplied index: {startIndex}"; + throw new ArgumentOutOfRangeException(nameof(startIndex), message); + } + + if (length < 1 || startIndex + length > 8) + { + string message = "Length must be greater than zero and the sum of length and start index must be less than 8. " + + $"Supplied length: {length}. Supplied start index: {startIndex}"; + throw new ArgumentOutOfRangeException(nameof(length), message); + } + + int bitShift = length - 1; + for (int i = startIndex; i < startIndex + length; i++) + { + int bitValueIfSet = (1 << bitShift); + int bitValue = (valueToSet & bitValueIfSet); + int bitIsSet = (bitValue >> bitShift); + Bits[i] = (bitIsSet == 1); + bitShift--; + } + } + + /// + /// Gets the value of the specified bit within the byte. + /// + /// + /// The zero-based index of the bit to get. + /// + /// + /// The value of the specified bit within the byte. + /// + public bool GetBit(int index) + { + if (index < 0 || index > 7) + { + string message = $"Index must be between 0 and 7. Supplied index: {index}"; + throw new ArgumentOutOfRangeException(nameof(index), message); + } + return Bits[index]; + } + + /// + /// 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 value of the specified bits within the byte. + /// + public int GetBits(int startIndex, int length) + { + if (startIndex < 0 || startIndex > 7) + { + string message = $"Start index must be between 0 and 7. Supplied index: {startIndex}"; + throw new ArgumentOutOfRangeException(nameof(startIndex), message); + } + + if (length < 1 || startIndex + length > 8) + { + string message = "Length must be greater than zero and the sum of length and start index must be less than 8. " + + $"Supplied length: {length}. Supplied start index: {startIndex}"; + + throw new ArgumentOutOfRangeException(nameof(length), message); + } + + int returnValue = 0; + int bitShift = length - 1; + for (int i = startIndex; i < startIndex + length; i++) + { + int bitValue = (Bits[i] ? 1 : 0) << bitShift; + returnValue += bitValue; + bitShift--; + } + return returnValue; + } + } +} \ No newline at end of file