Browse Source

Fixing encoder for animated gifs

Lots of cleanup to do yet.


Former-commit-id: 2606ad6ffb5da07a1aef2ee1d5af81437367b8c3
Former-commit-id: a444cddddae006b71c32de7e4dc1619b604d1115
Former-commit-id: 5dffd13674d2d3ae1b2c366989cc1c97b1bd18ac
af/merge-core
James South 10 years ago
parent
commit
9c691d6537
  1. 1
      src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs
  2. 349
      src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs
  3. 432
      src/ImageProcessorCore/Formats/Gif/LzwEncoder2.cs
  4. 191
      src/ImageProcessorCore/Formats/Gif/PackedByte.cs

1
src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs

@ -8,6 +8,7 @@ namespace ImageProcessorCore.Formats
/// <summary>
/// Provides enumeration for instructing the decoder what to do with the last image
/// in an animation sequence.
/// <see href="http://www.w3.org/Graphics/GIF/spec-gif89a.txt"/> section 23
/// </summary>
public enum DisposalMethod
{

349
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;
/// <summary>
@ -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);
/// <summary>
/// Writes the file header signature and version to the stream.
/// </summary>
/// <param name="writer">The writer to write to the stream with.</param>
private void WriteHeader(EndianBinaryWriter writer)
{
writer.Write((GifConstants.FileType + GifConstants.FileVersion).ToCharArray());
}
/// <summary>
/// Writes the logical screen descriptor to the stream.
/// </summary>
/// <param name="image">The image to encode.</param>
/// <param name="stream">The stream to write to.</param>
/// <returns>The <see cref="GifLogicalScreenDescriptor"/></returns>
private bool WriteGlobalLogicalScreenDescriptor(Image image, Stream stream)
/// <param name="writer">The writer to write to the stream with.</param>
/// <param name="tranparencyIndex">The transparency index to set the default backgound index to.</param>
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);
}
/// <summary>
/// Writes the color table to the stream.
/// Writes the application exstension to the stream.
/// </summary>
/// <param name="image">The <see cref="ImageBase"/> to encode.</param>
/// <param name="stream">The stream to write to.</param>
/// <param name="quality">The quality (number of colors) to encode the image to.</param>
/// <returns>The <see cref="QuantizedImage"/></returns>
private QuantizedImage WriteColorTable(ImageBase image, Stream stream, int quality)
/// <param name="writer">The writer to write to the stream with.</param>
/// <param name="repeatCount">The animated image repeat count.</param>
/// <param name="frames">Th number of image frames.</param>
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
}
}
/// <summary>
/// Writes the graphics control extension to the stream.
/// </summary>
/// <param name="image">The <see cref="ImageBase"/> to encode.</param>
/// <param name="stream">The stream to write to.</param>
/// <param name="writer">The stream to write to.</param>
/// <param name="transparencyIndex">The index of the color in the color palette to make transparent.</param>
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
};
/// <summary>
/// Writes the application exstension to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="repeatCount">The animated image repeat count.</param>
/// <param name="frames">Th number of image frames.</param>
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);
/// <summary>
/// Writes the image descriptor to the stream.
/// </summary>
/// <param name="image">The <see cref="QuantizedImage"/> containing indexed pixels.</param>
/// <param name="quality">The quality (number of colors) to encode the image to.</param>
/// <param name="stream">The stream to write to.</param>
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);
}
/// <summary>
/// Writes the image descriptor to the stream.
/// </summary>
/// <param name="image">The <see cref="ImageBase"/> to be encoded.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteFrameImageDescriptor(ImageBase image, Stream stream)
/// <param name="writer">The stream to write to.</param>
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);
}
/// <summary>
/// Writes the image pixel data to the stream.
/// Writes the color table to the stream.
/// </summary>
/// <param name="image">The <see cref="QuantizedImage"/> containing indexed pixels.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteImageData(QuantizedImage image, Stream stream)
/// <param name="image">The <see cref="ImageBase"/> to encode.</param>
/// <param name="writer">The writer to write to the stream with.</param>
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];
/// <summary>
/// Writes a short to the given stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="value">The value to write.</param>
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;
});
/// <summary>
/// Writes a byte to the given stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="value">The value to write.</param>
private void WriteByte(Stream stream, int value)
{
stream.WriteByte(Convert.ToByte(value));
writer.Write(colorTable, 0, colorTableLength);
}
/// <summary>
/// Writes a string to the given stream.
/// Writes the image pixel data to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="value">The value to write.</param>
private void WriteString(Stream stream, string value)
/// <param name="image">The <see cref="QuantizedImage"/> containing indexed pixels.</param>
/// <param name="writer">The stream to write to.</param>
private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer)
{
char[] chars = value.ToCharArray();
foreach (char c in chars)
{
stream.WriteByte((byte)c);
}
}
byte[] indexedPixels = image.Pixels;
/// <summary>
/// Returns how many bits are required to store the specified number of colors.
/// Performs a Log2() on the value.
/// </summary>
/// <param name="colors">The number of colors.</param>
/// <returns>
/// The <see cref="int"/>
/// </returns>
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);
}
}
}

432
src/ImageProcessorCore/Formats/Gif/LzwEncoder2.cs

@ -0,0 +1,432 @@
namespace ImageProcessorCore.Formats
{
using System;
using System.IO;
/// <summary>
/// An implementation of the Lempel-Ziv-Welch lossless compression algorithm.
/// See http://en.wikipedia.org/wiki/Lzw
/// </summary>
public class LzwEncoder2
{
#region declarations
private const int _EOF = -1;
private const int _BITS = 12;
private const int _HSIZE = 5003; // 80% occupancy
/// <summary>
/// A collection of indices within the active colour table of the
/// colours of the pixels within the image.
/// </summary>
private byte[] _pixels;
private int _initCodeSize;
/// <summary>
/// Number of pixels still to process.
/// </summary>
private int _pixelsRemaining;
/// <summary>
/// Index of the current position within the IndexedPixels collection.
/// </summary>
private int _pixelIndex;
/// <summary>
/// Number of bits per encoded code.
/// </summary>
int _codeSize; // number of bits/code
/// <summary>
/// Maximum number of bits per encoded code.
/// </summary>
int _maxCodeSize = _BITS; // user settable max # bits/code
/// <summary>
/// The largest possible code given the current value of _codeSize.
/// </summary>
int _maxCode; // maximum code, given n_bits
/// <summary>
/// The largest possible code given the largest possible value of
/// _codeSize, plus 1. We should never output this code.
/// </summary>
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
/// <summary>
/// The next unused code. Initially set to the clear code plus 2.
/// </summary>
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;
/// <summary>
/// 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
/// </summary>
int _clearCode;
/// <summary>
/// End of information code. This is set to the clear code plus 1 and
/// marks the end of the encoded data.
/// </summary>
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'
/// <summary>
/// Number of bytes that have been added to the packet so far.
/// </summary>
int _byteCountInPacket;
// Define the storage for the packet accumulator
/// <summary>
/// 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.
/// </summary>
byte[] _packet = new byte[256];
#endregion
#region constructor
/// <summary>
/// Constructor.
/// </summary>
/// <param name="pixels">
/// Indices in the active colour table of the colours of the pixel
/// making up the image.
/// </param>
/// <exception cref="ArgumentNullException">
/// The supplied pixel collection is null.
/// </exception>
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
/// <summary>
/// Encodes the data and writes it to the supplied output stream.
/// </summary>
/// <param name="outputStream">Output stream</param>
/// <exception cref="ArgumentNullException">
/// The supplied output stream is null.
/// </exception>
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
/// <summary>
/// Add a character to the end of the current packet, and if the packet
/// is 254 characters long, flush the packet to disk.
/// </summary>
/// <param name="c">The character to add</param>
/// <param name="outputStream">Output stream</param>
private void Add(byte c, Stream outputStream)
{
_packet[_byteCountInPacket++] = c;
if (_byteCountInPacket >= 254)
{
Flush(outputStream);
}
}
#endregion
#region private ClearTable method
/// <summary>
/// Clears out the hash table.
/// </summary>
/// <param name="outs">Output stream</param>
private void ClearTable(Stream outs)
{
ResetCodeTable(_hsize);
_nextAvailableCode = _clearCode + 2;
_clear_flg = true;
Output(_clearCode, outs);
}
#endregion
#region private ResetCodeTable method
/// <summary>
/// Resets the code table
/// </summary>
/// <param name="hsize"></param>
private void ResetCodeTable(int hsize)
{
for (int i = 0; i < hsize; ++i)
{
_htab[i] = -1;
}
}
#endregion
#region private Compress method
/// <summary>
/// Compress method
/// </summary>
/// <param name="init_bits"></param>
/// <param name="outs"></param>
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
/// <summary>
/// Flush the packet to disk, and reset the accumulator
/// </summary>
/// <param name="outs"></param>
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
/// <summary>
/// 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.
/// </summary>
/// <param name="codeSize">
/// Code size in bits.
/// </param>
/// <returns></returns>
private static int MaxCode(int codeSize)
{
return (1 << codeSize) - 1;
}
#endregion
#region private NextPixel method
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
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
/// <summary>
/// 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.
/// </summary>
/// <param name="code"></param>
/// <param name="outputStream">
/// The output stream to write to.
/// </param>
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
}
}

191
src/ImageProcessorCore/Formats/Gif/PackedByte.cs

@ -0,0 +1,191 @@
// <copyright file="PackedByte.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
{
using System;
/// <summary>
/// Represents a byte of data in a GIF data stream which contains a number
/// of data items.
/// TODO: Finish me.
/// </summary>
internal struct PackedByte
{
/// <summary>
/// The individual bits representing the packed byte.
/// </summary>
private static readonly bool[] Bits = new bool[8];
#region constructor( int )
///// <summary>
///// Constructor.
///// Sets the bits in the packed fields to the corresponding bits from
///// the supplied byte.
///// </summary>
///// <param name="data">
///// A single byte of data, consisting of fields which may be of one or
///// more bits.
///// </param>
//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
/// <summary>
/// Gets the byte which represents the data items held in this instance.
/// </summary>
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);
}
}
/// <summary>
/// Sets the specified bit within the packed fields to the supplied
/// value.
/// </summary>
/// <param name="index">
/// The zero-based index within the packed fields of the bit to set.
/// </param>
/// <param name="valueToSet">
/// The value to set the bit to.
/// </param>
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;
}
/// <summary>
/// Sets the specified bits within the packed fields to the supplied
/// value.
/// </summary>
/// <param name="startIndex">
/// The zero-based index within the packed fields of the first bit to
/// set.
/// </param>
/// <param name="length">
/// The number of bits to set.
/// </param>
/// <param name="valueToSet">
/// The value to set the bits to.
/// </param>
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--;
}
}
/// <summary>
/// Gets the value of the specified bit within the byte.
/// </summary>
/// <param name="index">
/// The zero-based index of the bit to get.
/// </param>
/// <returns>
/// The value of the specified bit within the byte.
/// </returns>
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];
}
/// <summary>
/// Gets the value of the specified bits within the byte.
/// </summary>
/// <param name="startIndex">
/// The zero-based index of the first bit to get.
/// </param>
/// <param name="length">
/// The number of bits to get.
/// </param>
/// <returns>
/// The value of the specified bits within the byte.
/// </returns>
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;
}
}
}
Loading…
Cancel
Save