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.Width);
writer.Write((ushort)descriptor.Height); writer.Write((ushort)descriptor.Height);
PackedByte pb = new PackedByte(); PackedField field = new PackedField();
pb.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used) field.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 field.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution
pb.SetBit(4, false); // 5 : GCT sort flag = 0 field.SetBit(4, false); // 5 : GCT sort flag = 0
pb.SetBits(5, 3, descriptor.GlobalColorTableSize); // 6-8 : GCT size. 2^(N+1) field.SetBits(5, 3, descriptor.GlobalColorTableSize); // 6-8 : GCT size. 2^(N+1)
// Reduce the number of writes // Reduce the number of writes
byte[] arr = { byte[] arr = {
pb.Byte, field.Byte,
descriptor.BackgroundColorIndex, // Background Color Index descriptor.BackgroundColorIndex, // Background Color Index
descriptor.PixelAspectRatio // Pixel aspect ratio. Assume 1:1 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) writer.Write((byte)1); // Data sub-block index (always 1)
// 0 means loop indefinitely. Count is set as play n + 1 times. // 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. writer.Write(repeatCount); // Repeat count for images.
@ -208,14 +208,14 @@ namespace ImageProcessorCore.Formats
writer.Write(intro); writer.Write(intro);
PackedByte pb = new PackedByte(); PackedField field = new PackedField();
pb.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal
// TODO: Allow this as an option. // TODO: Allow this as an option.
pb.SetBit(6, false); // 7 : User input - 0 = none field.SetBit(6, false); // 7 : User input - 0 = none
pb.SetBit(7, extension.TransparencyFlag); // 8: Has transparent. field.SetBit(7, extension.TransparencyFlag); // 8: Has transparent.
writer.Write(pb.Byte); writer.Write(field.Byte);
writer.Write((ushort)extension.DelayTime); writer.Write((ushort)extension.DelayTime);
writer.Write((byte)(extension.TransparencyIndex == -1 ? 255 : extension.TransparencyIndex)); writer.Write((byte)(extension.TransparencyIndex == -1 ? 255 : extension.TransparencyIndex));
writer.Write(GifConstants.Terminator); writer.Write(GifConstants.Terminator);
@ -235,13 +235,13 @@ namespace ImageProcessorCore.Formats
writer.Write((ushort)image.Width); writer.Write((ushort)image.Width);
writer.Write((ushort)image.Height); writer.Write((ushort)image.Height);
PackedByte pb = new PackedByte(); PackedField field = new PackedField();
pb.SetBit(0, true); // 1: Local color table flag = 1 (LCT used) field.SetBit(0, true); // 1: Local color table flag = 1 (LCT used)
pb.SetBit(1, false); // 2: Interlace flag 0 field.SetBit(1, false); // 2: Interlace flag 0
pb.SetBit(2, false); // 3: Sort flag 0 field.SetBit(2, false); // 3: Sort flag 0
pb.SetBits(5, 3, this.bitDepth - 1); // 4-5: Reserved, 6-8 : LCT size. 2^(N+1) 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> /// <summary>
@ -283,12 +283,7 @@ namespace ImageProcessorCore.Formats
byte[] indexedPixels = image.Pixels; byte[] indexedPixels = image.Pixels;
LzwEncoder encoder = new LzwEncoder(indexedPixels, (byte)this.bitDepth); LzwEncoder encoder = new LzwEncoder(indexedPixels, (byte)this.bitDepth);
encoder.Encode(writer.BaseStream);
LzwEncoder2 e = new LzwEncoder2(indexedPixels);
e.Encode(writer.BaseStream);
//encoder.Encode(writer.BaseStream);
//writer.Write(GifConstants.Terminator);
} }
} }
} }

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

@ -9,12 +9,12 @@ namespace ImageProcessorCore.Formats
using System.IO; using System.IO;
/// <summary> /// <summary>
/// Decompresses data using the LZW algorithms. /// Decompresses and decodes data using the dynamic LZW algorithms.
/// </summary> /// </summary>
internal sealed class LzwDecoder internal sealed class LzwDecoder
{ {
/// <summary> /// <summary>
/// One more than the maximum value 12 bit integer. /// The max decoder pixel stack size.
/// </summary> /// </summary>
private const int MaxStackSize = 4096; private const int MaxStackSize = 4096;
@ -24,7 +24,7 @@ namespace ImageProcessorCore.Formats
private const int NullCode = -1; private const int NullCode = -1;
/// <summary> /// <summary>
/// The stream. /// The stream to decode.
/// </summary> /// </summary>
private readonly Stream stream; private readonly Stream stream;
@ -68,167 +68,131 @@ namespace ImageProcessorCore.Formats
// Calculate the available code. // Calculate the available code.
int availableCode = clearCode + 2; int availableCode = clearCode + 2;
// Jillzhangs Code (Not From Me) see: http://giflib.codeplex.com/ // Jillzhangs Code see: http://giflib.codeplex.com/
// TODO: It's imperative that this code is cleaned up and commented properly. // Adapted from John Cristy's ImageMagick.
// TODO: Unfortunately I can't figure out the character encoding to translate from the original Chinese. int code;
int code; // ÓÃÓÚ´æ´¢µ±Ç°µÄ±àÂëÖµ int oldCode = NullCode;
int oldCode = NullCode; // ÓÃÓÚ´æ´¢ÉÏÒ»´ÎµÄ±àÂëÖµ int codeMask = (1 << codeSize) - 1;
int codeMask = (1 << codeSize) - 1; // ±íʾ±àÂëµÄ×î´óÖµ£¬Èç¹ûcodeSize=5,Ôòcode_mask=31 int bits = 0;
int bits = 0; // ÔÚ±àÂëÁ÷ÖÐÊý¾ÝµÄ±£´æÐÎʽΪbyte£¬¶øÊµ¼Ê±àÂë¹ý³ÌÖÐÊÇÕÒʵ¼Ê±àÂëλÀ´´æ´¢µÄ£¬±ÈÈçµ±codeSize=5µÄʱºò£¬ÄÇôʵ¼ÊÉÏ5bitµÄÊý¾Ý¾ÍÓ¦¸Ã¿ÉÒÔ±íʾһ¸ö±àÂ룬ÕâÑùÈ¡³öÀ´µÄ1¸ö×ֽھ͸»ÓàÁË3¸öbit£¬Õâ3¸öbitÓÃÓں͵ڶþ¸ö×ֽڵĺóÁ½¸öbit½øÐÐ×éºÏ£¬ÔÙ´ÎÐγɱàÂëÖµ£¬Èç´ËÀàÍÆ
int[] prefix = new int[MaxStackSize]; // ÓÃÓÚ±£´æÇ°×ºµÄ¼¯ºÏ int[] prefix = new int[MaxStackSize];
int[] suffix = new int[MaxStackSize]; // ÓÃÓÚ±£´æºó׺ int[] suffix = new int[MaxStackSize];
int[] pixelStatck = new int[MaxStackSize + 1]; // ÓÃÓÚÁÙʱ±£´æÊý¾ÝÁ÷ int[] pixelStatck = new int[MaxStackSize + 1];
int top = 0; int top = 0;
int count = 0; // ÔÚÏÂÃæµÄÑ­»·ÖУ¬Ã¿´Î»á»ñȡһ¶¨Á¿µÄ±àÂëµÄ×Ö½ÚÊý×飬¶ø´¦ÀíÕâÐ(c)Êý×éµÄʱºòÐèÒª1¸ö¸ö×Ö½ÚÀ´´¦Àí£¬count¾ÍÊDZíʾ»¹Òª´¦ÀíµÄ×Ö½ÚÊýÄ¿ int count = 0;
int bi = 0; // count±íʾ»¹Ê£¶àÉÙ×Ö½ÚÐèÒª´¦Àí£¬¶øbiÔò±íʾ±¾´ÎÒѾ­´¦ÀíµÄ¸öÊý int bi = 0;
int xyz = 0; // i´ú±íµ±Ç°´¦ÀíµÃµ½ÏñËØÊý int xyz = 0;
int data = 0; // ±íʾµ±Ç°´¦ÀíµÄÊý¾ÝµÄÖµ int data = 0;
int first = 0; // Ò»¸ö×Ö·û´®ÖصĵÚÒ»¸ö×Ö½Ú int first = 0;
// ÏÈÉú³ÉÔªÊý¾ÝµÄǰ׺¼¯ºÏºÍºó׺¼¯ºÏ£¬ÔªÊý¾ÝµÄǰ׺¾ùΪ0£¬¶øºó׺ÓëÔªÊý¾ÝÏàµÈ£¬Í¬Ê±±àÂëÒ²ÓëÔªÊý¾ÝÏàµÈ
for (code = 0; code < clearCode; code++) for (code = 0; code < clearCode; code++)
{ {
// ǰ׺³õʼΪ0
prefix[code] = 0; prefix[code] = 0;
// ºó׺=ÔªÊý¾Ý=±àÂë
suffix[code] = (byte)code; suffix[code] = (byte)code;
} }
byte[] buffer = null; byte[] buffer = null;
while (xyz < pixels.Length) while (xyz < pixels.Length)
{ {
// ×î´óÏñËØÊýÒѾ­È·¶¨ÎªpixelCount = width * width
if (top == 0) if (top == 0)
{ {
if (bits < codeSize) if (bits < codeSize)
{ {
// Èç¹ûµ±Ç°µÄÒª´¦ÀíµÄbitÊýСÓÚ±àÂëλ´óС£¬ÔòÐèÒª¼ÓÔØÊý¾Ý // Load bytes until there are enough bits for a code.
if (count == 0) if (count == 0)
{ {
// Èç¹ûcountΪ0£¬±íʾҪ´Ó±àÂëÁ÷ÖжÁÒ»¸öÊý¾Ý¶ÎÀ´½øÐзÖÎö // Read a new data block.
buffer = this.ReadBlock(); buffer = this.ReadBlock();
count = buffer.Length; count = buffer.Length;
if (count == 0) if (count == 0)
{ {
// ÔÙ´ÎÏë¶ÁÈ¡Êý¾Ý¶Î£¬È´Ã»ÓжÁµ½Êý¾Ý£¬´Ëʱ¾Í±íÃ÷ÒѾ­´¦ÀíÍêÁË
break; break;
} }
// ÖØÐ¶Áȡһ¸öÊý¾Ý¶Îºó£¬Ó¦¸Ã½«ÒѾ­´¦ÀíµÄ¸öÊýÖÃ0
bi = 0; bi = 0;
} }
// »ñÈ¡±¾´ÎÒª´¦ÀíµÄÊý¾ÝµÄÖµ
if (buffer != null) if (buffer != null)
{ {
data += buffer[bi] << bits; // ´Ë´¦ÎªºÎÒªÒÆÎ»ÄØ£¬±ÈÈçµÚÒ»´Î´¦ÀíÁË1¸ö×Ö½ÚΪ176£¬µÚÒ»´ÎÖ»Òª´¦Àí5bit¾Í¹»ÁË£¬Ê£ÏÂ3bitÁô¸øÏ¸ö×Ö½Ú½øÐÐ×éºÏ¡£Ò²¾ÍÊǵڶþ¸ö×ֽڵĺóÁ½Î»+µÚÒ»¸ö×Ö½ÚµÄǰÈýλ×é³ÉµÚ¶þ´ÎÊä³öÖµ data += buffer[bi] << bits;
} }
bits += 8; // ±¾´ÎÓÖ´¦ÀíÁËÒ»¸ö×Ö½Ú£¬ËùÒÔÐèÒª+8 bits += 8;
bi++; // ½«´¦ÀíÏÂÒ»¸ö×Ö½Ú bi++;
count--; // ÒѾ­´¦Àí¹ýµÄ×Ö½ÚÊý+1 count--;
continue; continue;
} }
// Èç¹ûÒѾ­ÓÐ×ã¹»µÄbitÊý¿É¹(c)´¦Àí£¬ÏÂÃæ¾ÍÊÇ´¦Àí¹ý³Ì // Get the next code
// »ñÈ¡±àÂë code = data & codeMask;
code = data & codeMask; // »ñÈ¡dataÊý¾ÝµÄ±àÂëλ´óСbitµÄÊý¾Ý data >>= codeSize;
data >>= codeSize; // ½«±àÂëÊý¾Ý½ØÈ¡ºó£¬Ô­À´µÄÊý¾Ý¾Íʣϼ¸¸öbitÁË£¬´Ëʱ½«ÕâÐ(c)bitÓÒÒÆ£¬ÎªÏ´Î×÷×¼±¸ bits -= codeSize;
bits -= codeSize; // ͬʱÐèÒª½«µ±Ç°Êý¾ÝµÄbitÊý¼õÈ¥±àÂë볤£¬ÒòΪÒѾ­µÃµ½ÁË´¦Àí¡£
// ÏÂÃæ¸ù¾Ý»ñÈ¡µÄcodeÖµÀ´½øÐд¦Àí // Interpret the code
if (code > availableCode || code == endCode) if (code > availableCode || code == endCode)
{ {
// µ±±àÂëÖµ´óÓÚ×î´ó±àÂëÖµ»òÕßΪ½áÊø±ê¼ÇµÄʱºò£¬Í£Ö¹´¦Àí
break; break;
} }
if (code == clearCode) if (code == clearCode)
{ {
// Èç¹ûµ±Ç°ÊÇÇå³ý±ê¼Ç£¬ÔòÖØÐ³õʼ»¯±äÁ¿£¬ºÃÖØÐÂÔÙÀ´ // Reset the decoder
codeSize = dataSize + 1; codeSize = dataSize + 1;
// ÖØÐ³õʼ»¯×î´ó±àÂëÖµ
codeMask = (1 << codeSize) - 1; codeMask = (1 << codeSize) - 1;
// ³õʼ»¯ÏÂÒ»²½Ó¦¸Ã´¦ÀíµÃ±àÂëÖµ
availableCode = clearCode + 2; availableCode = clearCode + 2;
// ½«±£´æµ½old_codeÖеÄÖµÇå³ý£¬ÒÔ±ãÖØÍ·ÔÙÀ´
oldCode = NullCode; oldCode = NullCode;
continue; continue;
} }
// ÏÂÃæÊÇcodeÊôÓÚÄÜѹËõµÄ±àÂ뷶ΧÄڵĵĴ¦Àí¹ý³Ì
if (oldCode == NullCode) if (oldCode == NullCode)
{ {
// Èç¹ûµ±Ç°±àÂëֵΪ¿Õ,±íʾÊǵÚÒ»´Î»ñÈ¡±àÂë
pixelStatck[top++] = suffix[code]; // »ñÈ¡µ½1¸öÊý¾ÝÁ÷µÄÊý¾Ý pixelStatck[top++] = suffix[code]; // »ñÈ¡µ½1¸öÊý¾ÝÁ÷µÄÊý¾Ý
// ±¾´Î±àÂë´¦ÀíÍê³É£¬½«±àÂëÖµ±£´æµ½old_codeÖÐ
oldCode = code; oldCode = code;
// µÚÒ»¸ö×Ö·ûΪµ±Ç°±àÂë
first = code; first = code;
continue; continue;
} }
int inCode = code; // ÔÚlzwÖУ¬Èç¹ûÈÏʶÁËÒ»¸ö±àÂëËù´ú±íµÄÊý¾Ýentry£¬Ôò½«±àÂë×÷ΪÏÂÒ»´ÎµÄprefix£¬´Ë´¦inCode´ú±í´«µÝ¸øÏÂÒ»´Î×÷Ϊǰ׺µÄ±àÂëÖµ int inCode = code;
if (code == availableCode) if (code == availableCode)
{ {
// Èç¹ûµ±Ç°±àÂëºÍ±¾´ÎÓ¦¸ÃÉú³ÉµÄ±àÂëÏàͬ
// ÄÇôÏÂÒ»¸öÊý¾Ý×ֽھ͵ÈÓÚµ±Ç°´¦Àí×Ö·û´®µÄµÚÒ»¸ö×Ö½Ú
pixelStatck[top++] = (byte)first; pixelStatck[top++] = (byte)first;
code = oldCode; // »ØËݵ½ÉÏÒ»¸ö±àÂë code = oldCode;
} }
while (code > clearCode) while (code > clearCode)
{ {
// Èç¹ûµ±Ç°±àÂë´óÓÚÇå³ý±ê¼Ç£¬±íʾ±àÂëÖµÊÇÄÜѹËõÊý¾ÝµÄ
pixelStatck[top++] = suffix[code]; pixelStatck[top++] = suffix[code];
code = prefix[code]; // »ØËݵ½ÉÏÒ»¸ö±àÂë code = prefix[code];
} }
first = suffix[code]; first = suffix[code];
// »ñÈ¡ÏÂÒ»¸öÊý¾Ý
pixelStatck[top++] = suffix[code]; pixelStatck[top++] = suffix[code];
// Fix for Gifs that have "deferred clear code" as per here : // Fix for Gifs that have "deferred clear code" as per here :
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918 // https://bugzilla.mozilla.org/show_bug.cgi?id=55918
if (availableCode < MaxStackSize) if (availableCode < MaxStackSize)
{ {
// ÉèÖõ±Ç°Ó¦¸Ã±àÂëλÖõÄǰ׺
prefix[availableCode] = oldCode; prefix[availableCode] = oldCode;
// ÉèÖõ±Ç°Ó¦¸Ã±àÂëλÖõĺó׺
suffix[availableCode] = first; suffix[availableCode] = first;
// Ï´ÎÓ¦¸ÃµÃµ½µÄ±àÂëÖµ
availableCode++; availableCode++;
if (availableCode == codeMask + 1 && availableCode < MaxStackSize) if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
{ {
// Ôö¼Ó±àÂëλÊý
codeSize++; codeSize++;
// ÖØÉè×î´ó±àÂëÖµ
codeMask = (1 << codeSize) - 1; codeMask = (1 << codeSize) - 1;
} }
} }
// »¹Ô­old_code
oldCode = inCode; oldCode = inCode;
} }
// »ØËݵ½ÉÏÒ»¸ö´¦ÀíλÖà // Pop a pixel off the pixel stack.
top--; top--;
// »ñȡԪÊý¾Ý // Clear missing pixels
pixels[xyz++] = (byte)pixelStatck[top]; pixels[xyz++] = (byte)pixelStatck[top];
} }

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

@ -6,192 +6,380 @@
namespace ImageProcessorCore.Formats namespace ImageProcessorCore.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
/// <summary> /// <summary>
/// Encodes an image pixels used on a method based on LZW compression. /// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
/// <see href="http://matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp"/>
/// </summary> /// </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> /// <summary>
/// One more than the maximum value 12 bit integer. /// Number of bits/code
/// </summary> /// </summary>
private const int MaxStackSize = 4096; private int bitCount;
/// <summary> /// <summary>
/// The initial bit depth. /// User settable max # bits/code
/// </summary> /// </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> /// <summary>
/// The indexed pixels to encode. /// For dynamic table sizing
/// </summary> /// </summary>
private readonly byte[] indexedPixels; private int hsize = HashSize;
/// <summary> /// <summary>
/// The color depth in bits. /// First unused entry
/// </summary> /// </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> /// <summary>
/// Initializes a new instance of the <see cref="LzwEncoder"/> class. /// Initializes a new instance of the <see cref="LzwEncoder"/> class.
/// </summary> /// </summary>
/// <param name="indexedPixels">The array of indexed pixels.</param> /// <param name="indexedPixels">The array of indexed pixels.</param>
/// <param name="colorDepth">The color depth in bits.</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.pixelArray = indexedPixels;
this.colorDepth = colorDepth.Clamp(2, 8); this.initialCodeSize = Math.Max(2, colorDepth);
this.initDataSize = this.colorDepth;
} }
/// <summary> /// <summary>
/// Encodes the compressed indexed pixel data to the given stream. /// Encodes and compresses the indexed pixels to the stream.
/// </summary> /// </summary>
/// <param name="stream">The stream to add the data to.</param> /// <param name="stream">The stream to write to.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
public void Encode(Stream stream) 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. /// <summary>
bool first = true; /// 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. /// <summary>
int suffix = 0; /// 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. /// <summary>
int clearCode = 1 << this.colorDepth; /// 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 // Set up the globals: globalInitialBits - initial number of bits
int endOfInformation = clearCode + 1; this.globalInitialBits = intialBits;
// The code table for storing encoded colors. // Set up the necessary values
Dictionary<string, int> codeTable = new Dictionary<string, int>(); this.clearFlag = false;
this.bitCount = this.globalInitialBits;
this.maxcode = GetMaxcode(this.bitCount);
// The current number of index bytes processed. this.clearCode = 1 << (intialBits - 1);
int releaseCount = 0; this.eofCode = this.clearCode + 1;
this.freeEntry = this.clearCode + 2;
// Calculate the code available. this.accumulatorCount = 0; // clear packet
byte codeSize = (byte)(this.colorDepth + 1);
int availableCode = endOfInformation + 1;
// Initialise. ent = this.NextPixel();
BitEncoder bitEncoder = new BitEncoder(codeSize);
stream.WriteByte(this.colorDepth);
bitEncoder.Add(clearCode);
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. ent = this.codeTable[i];
suffix = this.indexedPixels[releaseCount++]; 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); if ((i -= disp) < 0) { i += hsizeReg; }
bitEncoder.Add(endOfInformation);
bitEncoder.End(); if (this.hashTable[i] == fcode)
stream.WriteByte((byte)bitEncoder.Length); {
stream.Write(bitEncoder.ToArray(), 0, bitEncoder.Length); ent = this.codeTable[i];
bitEncoder.Clear(); break;
break; }
} }
while (this.hashTable[i] >= 0);
first = false; if (this.hashTable[i] == fcode) { continue; }
continue;
} }
// Switch this.Output(ent, stream);
int prefix = suffix; 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. // Put out the final code.
suffix = this.indexedPixels[releaseCount++]; this.Output(ent, stream);
// Acts as a key for code table entries. this.Output(this.eofCode, stream);
string key = $"{prefix},{suffix}"; }
// Is index buffer + the index in our code table? // Flush the packet to disk, and reset the accumulator
if (!codeTable.ContainsKey(key)) private void FlushPacket(Stream outs)
{ {
// If the current entity is not coded add the prefix. if (this.accumulatorCount > 0)
bitEncoder.Add(prefix); {
outs.WriteByte((byte)this.accumulatorCount);
outs.Write(this.accumulators, 0, this.accumulatorCount);
this.accumulatorCount = 0;
}
}
// Add the current bytes /// <summary>
codeTable.Add(key, availableCode++); /// 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)) if (this.curPixel == this.pixelArray.Length)
{ return Eof;
// 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 (bitEncoder.Length >= 255) this.curPixel++;
{ return this.pixelArray[this.curPixel - 1] & 0xff;
stream.WriteByte(255); }
stream.Write(bitEncoder.ToArray(), 0, 255);
if (bitEncoder.Length > 255) /// <summary>
{ /// Output the current code to the stream.
byte[] leftBuffer = new byte[bitEncoder.Length - 255]; /// </summary>
bitEncoder.CopyTo(255, leftBuffer, 0, leftBuffer.Length); /// <param name="code">The code.</param>
bitEncoder.Clear(); /// <param name="outs">The stream to write to.</param>
bitEncoder.AddRange(leftBuffer); private void Output(int code, Stream outs)
} {
else this.currentAccumulator &= this.masks[this.currentBits];
{
bitEncoder.Clear(); 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 else
{ {
// Set the suffix to the current byte. ++this.bitCount;
suffix = codeTable[key]; this.maxcode = this.bitCount == this.maxbits
? this.maxmaxcode
: GetMaxcode(this.bitCount);
} }
}
// Output code for contents of index buffer. if (code == this.eofCode)
// Output end-of-information code. {
if (releaseCount == this.indexedPixels.Length) // At EOF, write the rest of the buffer.
while (this.currentBits > 0)
{ {
bitEncoder.Add(suffix); this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs);
bitEncoder.Add(endOfInformation); this.currentAccumulator >>= 8;
bitEncoder.End(); this.currentBits -= 8;
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.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. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
@ -10,37 +10,14 @@ namespace ImageProcessorCore.Formats
/// <summary> /// <summary>
/// Represents a byte of data in a GIF data stream which contains a number /// Represents a byte of data in a GIF data stream which contains a number
/// of data items. /// of data items.
/// TODO: Finish me.
/// </summary> /// </summary>
internal struct PackedByte internal struct PackedField : IEquatable<PackedField>
{ {
/// <summary> /// <summary>
/// The individual bits representing the packed byte. /// The individual bits representing the packed byte.
/// </summary> /// </summary>
private static readonly bool[] Bits = new bool[8]; 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> /// <summary>
/// Gets the byte which represents the data items held in this instance. /// Gets the byte which represents the data items held in this instance.
/// </summary> /// </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> /// <summary>
/// Sets the specified bit within the packed fields to the supplied /// Sets the specified bit within the packed fields to the supplied
/// value. /// value.
@ -94,16 +84,9 @@ namespace ImageProcessorCore.Formats
/// Sets the specified bits within the packed fields to the supplied /// Sets the specified bits within the packed fields to the supplied
/// value. /// value.
/// </summary> /// </summary>
/// <param name="startIndex"> /// <param name="startIndex">The zero-based index within the packed fields of the first bit to set.</param>
/// The zero-based index within the packed fields of the first bit to /// <param name="length">The number of bits to set.</param>
/// set. /// <param name="valueToSet">The value to set the bits to.</param>
/// </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) public void SetBits(int startIndex, int length, int valueToSet)
{ {
if (startIndex < 0 || startIndex > 7) if (startIndex < 0 || startIndex > 7)
@ -133,9 +116,7 @@ namespace ImageProcessorCore.Formats
/// <summary> /// <summary>
/// Gets the value of the specified bit within the byte. /// Gets the value of the specified bit within the byte.
/// </summary> /// </summary>
/// <param name="index"> /// <param name="index">The zero-based index of the bit to get.</param>
/// The zero-based index of the bit to get.
/// </param>
/// <returns> /// <returns>
/// The value of the specified bit within the byte. /// The value of the specified bit within the byte.
/// </returns> /// </returns>
@ -152,12 +133,8 @@ namespace ImageProcessorCore.Formats
/// <summary> /// <summary>
/// Gets the value of the specified bits within the byte. /// Gets the value of the specified bits within the byte.
/// </summary> /// </summary>
/// <param name="startIndex"> /// <param name="startIndex">The zero-based index of the first bit to get.</param>
/// The zero-based index of the first bit to get. /// <param name="length">The number of bits to get.</param>
/// </param>
/// <param name="length">
/// The number of bits to get.
/// </param>
/// <returns> /// <returns>
/// The value of the specified bits within the byte. /// The value of the specified bits within the byte.
/// </returns> /// </returns>
@ -187,5 +164,31 @@ namespace ImageProcessorCore.Formats
} }
return returnValue; 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