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