Browse Source

Cleaned up Lzw (En/De)coder

Fix #359


Former-commit-id: e140a2e3bb28ec1332e0d4caf0de1eaeed154376
Former-commit-id: db6a61bfbe42c744ef7ad47444019a77a9d439dc
Former-commit-id: 88b7684fa215b5e7641a8f012fef6b97313a6935
af/merge-core
James South 10 years ago
parent
commit
33eb2f2f9c
  1. 43
      src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs
  2. 104
      src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs
  3. 442
      src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs
  4. 432
      src/ImageProcessorCore/Formats/Gif/LzwEncoder2.cs
  5. 91
      src/ImageProcessorCore/Formats/Gif/PackedField.cs

43
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);
}
/// <summary>
@ -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);
}
}
}

104
src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs

@ -9,12 +9,12 @@ namespace ImageProcessorCore.Formats
using System.IO;
/// <summary>
/// Decompresses data using the LZW algorithms.
/// Decompresses and decodes data using the dynamic LZW algorithms.
/// </summary>
internal sealed class LzwDecoder
{
/// <summary>
/// One more than the maximum value 12 bit integer.
/// The max decoder pixel stack size.
/// </summary>
private const int MaxStackSize = 4096;
@ -24,7 +24,7 @@ namespace ImageProcessorCore.Formats
private const int NullCode = -1;
/// <summary>
/// The stream.
/// The stream to decode.
/// </summary>
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];
}

442
src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs

@ -6,192 +6,380 @@
namespace ImageProcessorCore.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
/// <summary>
/// Encodes an image pixels used on a method based on LZW compression.
/// <see href="http://matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp"/>
/// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
/// </summary>
internal class LzwEncoder
/// <remarks>
/// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00
/// <para>
/// GIFCOMPR.C - GIF Image compression routines
///
/// Lempel-Ziv compression based on 'compress'. GIF modifications by
/// David Rowley (mgardi@watdcsu.waterloo.edu)
/// </para>
/// <para>
/// 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)
/// </para>
/// </remarks>
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;
/// <summary>
/// One more than the maximum value 12 bit integer.
/// Number of bits/code
/// </summary>
private const int MaxStackSize = 4096;
private int bitCount;
/// <summary>
/// The initial bit depth.
/// User settable max # bits/code
/// </summary>
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];
/// <summary>
/// The indexed pixels to encode.
/// For dynamic table sizing
/// </summary>
private readonly byte[] indexedPixels;
private int hsize = HashSize;
/// <summary>
/// The color depth in bits.
/// First unused entry
/// </summary>
private byte colorDepth;
private int freeEntry;
/// <summary>
/// Block compression parameters -- after all codes are used up,
/// and compression rate changes, start over.
/// </summary>
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
};
/// <summary>
/// Number of characters so far in this 'packet'
/// </summary>
private int accumulatorCount;
/// <summary>
/// Define the storage for the packet accumulator.
/// </summary>
private readonly byte[] accumulators = new byte[256];
/// <summary>
/// Initializes a new instance of the <see cref="LzwEncoder"/> class.
/// </summary>
/// <param name="indexedPixels">The array of indexed pixels.</param>
/// <param name="colorDepth">The color depth in bits.</param>
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);
}
/// <summary>
/// Encodes the compressed indexed pixel data to the given stream.
/// Encodes and compresses the indexed pixels to the stream.
/// </summary>
/// <param name="stream">The stream to add the data to.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <param name="stream">The stream to write to.</param>
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);
}
/// <summary>
/// Gets the maximum code value
/// </summary>
/// <param name="bitCount">The number of bits</param>
/// <returns>See <see cref="int"/></returns>
private static int GetMaxcode(int bitCount)
{
return (1 << bitCount) - 1;
}
// Whether it is a first step.
bool first = true;
/// <summary>
/// Add a character to the end of the current packet, and if it is 254 characters,
/// flush the packet to disk.
/// </summary>
/// <param name="c">The character to add.</param>
/// <param name="stream">The stream to write to.</param>
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;
/// <summary>
/// Table clear for block compress
/// </summary>
/// <param name="stream">The output stream.</param>
private void ClearBlock(Stream stream)
{
this.ResetCodeTable(this.hsize);
this.freeEntry = this.clearCode + 2;
this.clearFlag = true;
this.Output(this.clearCode, stream);
}
/// <summary>
/// Reset the code table.
/// </summary>
/// <param name="size">The hash size.</param>
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;
/// <summary>
/// Compress the packets to the stream.
/// </summary>
/// <param name="intialBits">The inital bits.</param>
/// <param name="stream">The stream to write to.</param>
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<string, int> codeTable = new Dictionary<string, int>();
// 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++);
/// <summary>
/// Return the next pixel from the image
/// </summary>
/// <returns>
/// The <see cref="int"/>
/// </returns>
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;
}
/// <summary>
/// Output the current code to the stream.
/// </summary>
/// <param name="code">The code.</param>
/// <param name="outs">The stream to write to.</param>
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);
}
}
}
}
}

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

@ -1,432 +0,0 @@
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
}
}

91
src/ImageProcessorCore/Formats/Gif/PackedByte.cs → src/ImageProcessorCore/Formats/Gif/PackedField.cs

@ -1,4 +1,4 @@
// <copyright file="PackedByte.cs" company="James Jackson-South">
// <copyright file="PackedField.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -10,37 +10,14 @@ namespace ImageProcessorCore.Formats
/// <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
internal struct PackedField : IEquatable<PackedField>
{
/// <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>
@ -68,6 +45,19 @@ namespace ImageProcessorCore.Formats
}
}
/// <summary>
/// Returns a new <see cref="PackedField"/> with the bits in the packed fields to
/// the corresponding bits from the supplied byte.
/// </summary>
/// <param name="value">The value to pack.</param>
/// <returns>The <see cref="PackedField"/></returns>
public static PackedField FromInt(byte value)
{
PackedField packed = new PackedField();
packed.SetBits(0, 8, value);
return packed;
}
/// <summary>
/// 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.
/// </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>
/// <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)
@ -133,9 +116,7 @@ namespace ImageProcessorCore.Formats
/// <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>
/// <param name="index">The zero-based index of the bit to get.</param>
/// <returns>
/// The value of the specified bit within the byte.
/// </returns>
@ -152,12 +133,8 @@ namespace ImageProcessorCore.Formats
/// <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>
/// <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>
@ -187,5 +164,31 @@ namespace ImageProcessorCore.Formats
}
return returnValue;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
PackedField? field = obj as PackedField?;
return this.Byte == field?.Byte;
}
/// <inheritdoc/>
public bool Equals(PackedField other)
{
return this.Byte.Equals(other.Byte);
}
/// <inheritdoc/>
public override string ToString()
{
return $"PackedField [ Byte={this.Byte} ]";
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.Byte.GetHashCode();
}
}
}
Loading…
Cancel
Save