diff --git a/.gitignore b/.gitignore
index 3fd18e8e1..4e9864be0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -170,4 +170,5 @@ build/*.nupkg
build/TestResult.xml
*.db
-_site/
\ No newline at end of file
+_site/
+.vs/config/applicationhost.config
\ No newline at end of file
diff --git a/src/ImageProcessor/Formats/Gif/BitEncoder.cs b/src/ImageProcessor/Formats/Gif/BitEncoder.cs
new file mode 100644
index 000000000..0ae595098
--- /dev/null
+++ b/src/ImageProcessor/Formats/Gif/BitEncoder.cs
@@ -0,0 +1,137 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Handles the encoding of bits for compression.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ using System.Collections.Generic;
+
+ ///
+ /// Handles the encoding of bits for compression.
+ ///
+ internal class BitEncoder
+ {
+ ///
+ /// The current working bit.
+ ///
+ private int currentBit;
+
+ ///
+ /// The current value.
+ ///
+ private int currentValue;
+
+ ///
+ /// The inner list for collecting the bits.
+ ///
+ private readonly List list = new List();
+
+ ///
+ /// The number of bytes in the encoder.
+ ///
+ internal int Length => this.list.Count;
+
+ ///
+ /// Gets or sets the intitial bit.
+ ///
+ public int IntitialBit { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The initial bits.
+ ///
+ public BitEncoder(int initial)
+ {
+ this.IntitialBit = initial;
+ }
+
+ ///
+ /// Adds the current byte to the end of the encoder.
+ ///
+ ///
+ /// The byte to add.
+ ///
+ public void Add(int item)
+ {
+ this.currentValue |= item << this.currentBit;
+
+ this.currentBit += this.IntitialBit;
+
+ while (this.currentBit >= 8)
+ {
+ byte value = (byte)(this.currentValue & 0XFF);
+ this.currentValue = this.currentValue >> 8;
+ this.currentBit -= 8;
+ this.list.Add(value);
+ }
+ }
+
+ ///
+ /// Adds the collection of bytes to the end of the encoder.
+ ///
+ ///
+ /// The collection of bytes to add.
+ /// The collection itself cannot be null but can contain elements that are null.
+ public void AddRange(byte[] collection)
+ {
+ this.list.AddRange(collection);
+ }
+
+ ///
+ /// The end.
+ ///
+ internal void End()
+ {
+ while (this.currentBit > 0)
+ {
+ byte value = (byte)(this.currentValue & 0XFF);
+ this.currentValue = this.currentValue >> 8;
+ this.currentBit -= 8;
+ this.list.Add(value);
+ }
+ }
+
+ ///
+ /// Copies a range of elements from the encoder to a compatible one-dimensional array,
+ /// starting at the specified index of the target array.
+ ///
+ ///
+ /// The zero-based index in the source at which copying begins.
+ ///
+ ///
+ /// The one-dimensional Array that is the destination of the elements copied
+ /// from . The Array must have zero-based indexing
+ ///
+ /// The zero-based index in array at which copying begins.
+ /// The number of bytes to copy.
+ public void CopyTo(int index, byte[] array, int arrayIndex, int count)
+ {
+ this.list.CopyTo(index, array, arrayIndex, count);
+ }
+
+ ///
+ /// Removes all the bytes from the encoder.
+ ///
+ public void Clear()
+ {
+ this.list.Clear();
+ }
+
+ ///
+ /// Copies the bytes into a new array.
+ ///
+ ///
+ public byte[] ToArray()
+ {
+ return this.list.ToArray();
+ }
+ }
+}
diff --git a/src/ImageProcessor/Formats/Gif/GifConstants.cs b/src/ImageProcessor/Formats/Gif/GifConstants.cs
index 70f84f42a..eebbc5479 100644
--- a/src/ImageProcessor/Formats/Gif/GifConstants.cs
+++ b/src/ImageProcessor/Formats/Gif/GifConstants.cs
@@ -16,40 +16,55 @@ namespace ImageProcessor.Formats
internal sealed class GifConstants
{
///
- /// The maximum comment length.
+ /// The file type.
///
- public const int MaxCommentLength = 1024 * 8;
+ public const string FileType = "GIF";
///
- /// The extension block introducer !.
+ /// The file version.
///
- public const byte ExtensionIntroducer = 0x21;
+ public const string FileVersion = "89a";
///
- /// The terminator.
+ /// The extension block introducer !.
///
- public const byte Terminator = 0;
+ public const byte ExtensionIntroducer = 0x21;
///
- /// The image label introducer ,.
+ /// The end introducer trailer ;.
///
- public const byte ImageLabel = 0x2C;
+ public const byte EndIntroducer = 0x3B;
///
- /// The end introducer trailer ;.
+ /// The graphic control label.
///
- public const byte EndIntroducer = 0x3B;
+ public const byte GraphicControlLabel = 0xF9;
///
/// The application extension label.
///
public const byte ApplicationExtensionLabel = 0xFF;
+ ///
+ /// The application identification.
+ ///
+ public const string ApplicationIdentification = "NETSCAPE2.0";
+
+ ///
+ /// The application block size.
+ ///
+ public const byte ApplicationBlockSize = 0x0b;
+
///
/// The comment label.
///
public const byte CommentLabel = 0xFE;
+ ///
+ /// The maximum comment length.
+ ///
+ public const int MaxCommentLength = 1024 * 8;
+
///
/// The image descriptor label ,.
///
@@ -61,8 +76,14 @@ namespace ImageProcessor.Formats
public const byte PlainTextLabel = 0x01;
///
- /// The graphic control label.
+ /// The image label introducer ,.
///
- public const byte GraphicControlLabel = 0xF9;
+ public const byte ImageLabel = 0x2C;
+
+ ///
+ /// The terminator.
+ ///
+ public const byte Terminator = 0;
+
}
}
diff --git a/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs b/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
index 92382d8bc..67d71b2c8 100644
--- a/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
@@ -210,10 +210,10 @@
private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor)
{
int dataSize = this.currentStream.ReadByte();
-
LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream);
byte[] indices = lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize);
+
return indices;
}
@@ -296,7 +296,7 @@
for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++)
{
- offset = writeY * imageWidth + x;
+ offset = (writeY * imageWidth) + x;
index = indices[i];
@@ -337,6 +337,11 @@
currentImage = frame;
currentImage.SetPixels(imageWidth, imageHeight, pixels);
+ if (this.GraphicsControlExtension != null && this.GraphicsControlExtension.DelayTime > 0)
+ {
+ currentImage.FrameDelay = this.GraphicsControlExtension.DelayTime;
+ }
+
this.image.Frames.Add(frame);
}
@@ -348,7 +353,7 @@
{
for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++)
{
- offset = y * imageWidth + x;
+ offset = (y * imageWidth) + x;
this.currentFrame[offset * 4 + 0] = 0;
this.currentFrame[offset * 4 + 1] = 0;
diff --git a/src/ImageProcessor/Formats/Gif/GifEncoder.cs b/src/ImageProcessor/Formats/Gif/GifEncoder.cs
index dcce4ad1a..f671b8427 100644
--- a/src/ImageProcessor/Formats/Gif/GifEncoder.cs
+++ b/src/ImageProcessor/Formats/Gif/GifEncoder.cs
@@ -1,9 +1,16 @@
-
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
namespace ImageProcessor.Formats
{
using System;
using System.IO;
+ ///
+ /// The Gif encoder
+ ///
public class GifEncoder : IImageEncoder
{
///
@@ -11,7 +18,10 @@ namespace ImageProcessor.Formats
///
private int quality = 256;
- private Image image;
+ ///
+ /// The gif decoder if any used to decode the original image.
+ ///
+ private GifDecoder gifDecoder;
///
/// Gets or sets the quality of output for images.
@@ -54,53 +64,77 @@ namespace ImageProcessor.Formats
///
/// Encodes the image to the specified stream from the .
///
- /// The to encode from.
+ /// The to encode from.
/// The to encode the image data to.
- public void Encode(ImageBase image, Stream stream)
+ public void Encode(ImageBase imageBase, Stream stream)
{
- Guard.NotNull(image, nameof(image));
+ Guard.NotNull(imageBase, nameof(imageBase));
Guard.NotNull(stream, nameof(stream));
- this.image = (Image)image;
+ Image image = (Image)imageBase;
+
+ // Try to grab and assign an image decoder.
+ IImageDecoder decoder = image.CurrentDecoder;
+ if (decoder.GetType() == typeof(GifDecoder))
+ {
+ this.gifDecoder = (GifDecoder)decoder;
+ }
// Write the header.
// File Header signature and version.
- this.WriteString(stream, "GIF");
- this.WriteString(stream, "89a");
+ this.WriteString(stream, GifConstants.FileType);
+ this.WriteString(stream, GifConstants.FileVersion);
int bitdepth = this.GetBitsNeededForColorDepth(this.Quality) - 1;
- this.WriteGlobalLogicalScreenDescriptor(stream, bitdepth);
-
- foreach (ImageFrame frame in this.image.Frames)
+ // Write the LSD and check to see if we need a global color table.
+ bool globalColor = this.WriteGlobalLogicalScreenDescriptor(image, stream, bitdepth);
+
+ if (globalColor)
+ {
+ this.WriteColorTable(imageBase, stream, bitdepth);
+ }
+
+ this.WriteGraphicalControlExtension(imageBase, stream);
+
+ // TODO: Write Comments
+ this.WriteApplicationExtension(stream, image.RepeatCount);
+
+ // TODO: Write Image Info
+
+ foreach (ImageFrame frame in image.Frames)
{
- this.WriteColorTable(stream, bitdepth);
- this.WriteGraphicalControlExtension(stream);
+ this.WriteColorTable(frame, stream, bitdepth);
+ this.WriteGraphicalControlExtension(frame, stream);
+ // TODO: Write Image Info
}
throw new System.NotImplementedException();
+
+ // Cleanup
+ this.Quality = 256;
+ this.gifDecoder = null;
}
- private void WriteGlobalLogicalScreenDescriptor(Stream stream, int bitDepth)
+ private bool WriteGlobalLogicalScreenDescriptor(Image image, Stream stream, int bitDepth)
{
- IImageDecoder decoder = ((Image)this.image).Decoder;
GifLogicalScreenDescriptor descriptor;
// Try and grab an existing descriptor.
- if (decoder.GetType() == typeof(GifDecoder))
+ if (this.gifDecoder != null)
{
// Ensure the dimensions etc are up to date.
- descriptor = ((GifDecoder)decoder).CoreDecoder.LogicalScreenDescriptor;
- descriptor.Width = (short)this.image.Width;
- descriptor.Height = (short)this.image.Height;
+ descriptor = this.gifDecoder.CoreDecoder.LogicalScreenDescriptor;
+ descriptor.Width = (short)image.Width;
+ descriptor.Height = (short)image.Height;
descriptor.GlobalColorTableSize = this.Quality;
}
else
{
descriptor = new GifLogicalScreenDescriptor
{
- Width = (short)this.image.Width,
- Height = (short)this.image.Height,
+ Width = (short)image.Width,
+ Height = (short)image.Height,
GlobalColorTableFlag = true,
GlobalColorTableSize = this.Quality
};
@@ -117,14 +151,15 @@ namespace ImageProcessor.Formats
this.WriteByte(stream, packed);
this.WriteByte(stream, descriptor.BackgroundColorIndex); // Background Color Index
this.WriteByte(stream, descriptor.PixelAspectRatio); // Pixel aspect ratio
+
+ return descriptor.GlobalColorTableFlag;
}
- private void WriteColorTable(Stream stream, int bitDepth)
+ private void WriteColorTable(ImageBase image, Stream stream, int bitDepth)
{
// Quantize the image returning a pallete.
IQuantizer quantizer = new OctreeQuantizer(Math.Max(1, this.quality - 1), bitDepth);
- QuantizedImage quantizedImage = quantizer.Quantize(this.image);
- this.image = quantizedImage.ToImage();
+ QuantizedImage quantizedImage = quantizer.Quantize(image);
// Grab the pallete and write it to the stream.
Bgra[] pallete = quantizedImage.Palette;
@@ -144,24 +179,23 @@ namespace ImageProcessor.Formats
stream.Write(colorTable, 0, colorTableLength);
}
- private void WriteGraphicalControlExtension(Stream stream)
+ private void WriteGraphicalControlExtension(ImageBase image, Stream stream)
{
- Image i = ((Image)this.image);
- IImageDecoder decoder = i.Decoder;
GifGraphicsControlExtension extension;
// Try and grab an existing descriptor.
// TODO: Check whether we need to.
- if (decoder.GetType() == typeof(GifDecoder))
+ if (this.gifDecoder != null)
{
// Ensure the dimensions etc are up to date.
- extension = ((GifDecoder)decoder).CoreDecoder.GraphicsControlExtension;
+ extension = this.gifDecoder.CoreDecoder.GraphicsControlExtension;
extension.TransparencyFlag = this.Quality > 1;
- extension.TransparencyIndex = this.Quality - 1;
- extension.DelayTime = i.FrameDelay;
+ extension.TransparencyIndex = this.Quality - 1; // Quantizer set last as transparent.
+ extension.DelayTime = image.FrameDelay;
}
else
{
+ // TODO: Check transparency logic.
bool hasTransparent = this.Quality > 1;
DisposalMethod disposalMethod = hasTransparent
? DisposalMethod.RestoreToBackground
@@ -171,8 +205,8 @@ namespace ImageProcessor.Formats
{
DisposalMethod = disposalMethod,
TransparencyFlag = hasTransparent,
- TransparencyIndex = this.Quality - 1, // Quantizer set last as transparent.
- DelayTime = i.FrameDelay
+ TransparencyIndex = this.Quality - 1,
+ DelayTime = image.FrameDelay
};
}
@@ -190,12 +224,25 @@ namespace ImageProcessor.Formats
this.WriteByte(stream, GifConstants.Terminator);
}
-
-
- private void WriteApplicationExtension(Stream stream)
+ private void WriteApplicationExtension(Stream stream, ushort repeatCount)
{
- // TODO: Implement
- throw new NotImplementedException();
+ // Application Extension Header
+ if (repeatCount != 1)
+ {
+ // 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
+ }
}
///
@@ -235,9 +282,13 @@ namespace ImageProcessor.Formats
}
///
- /// Returns how many bits are required to store the specified number of colors.
+ /// Returns how many bits are required to store the specified number of colors.
/// Performs a Log2() on the value.
///
+ /// The number of colors.
+ ///
+ /// The
+ ///
private int GetBitsNeededForColorDepth(int colors)
{
return (int)Math.Ceiling(Math.Log(colors, 2));
diff --git a/src/ImageProcessor/Formats/Gif/LzwEncoder.cs b/src/ImageProcessor/Formats/Gif/LzwEncoder.cs
index b9244f649..e4e8b08d5 100644
--- a/src/ImageProcessor/Formats/Gif/LzwEncoder.cs
+++ b/src/ImageProcessor/Formats/Gif/LzwEncoder.cs
@@ -1,357 +1,194 @@
-namespace ImageProcessor.Formats
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Encodes an image pixels used on a method based on LZW compression.
+//
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
{
- using System;
+ using System.Collections.Generic;
using System.IO;
+ ///
+ /// Encodes an image pixels used on a method based on LZW compression.
+ ///
+ ///
internal class LzwEncoder
{
- private const int EOF = -1;
-
- private readonly int imgW;
-
- private readonly int imgH;
-
- private readonly byte[] pixAry;
-
- private readonly int initCodeSize;
-
- // output
- //
- // Output the given code.
- // Inputs:
- // code: A n_bits-bit integer. If == -1, then EOF. This assumes
- // that n_bits =< 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 readonly int[] masks =
- {
- 0x0000, // 0
- 0x0001, // 1
- 0x0003, // 3
- 0x0007, // 7
- 0x000F, // 15
- 0x001F, // 31
- 0x003F, // 63
- 0x007F, // 127
- 0x00FF, // 255
- 0x01FF, // 511
- 0x03FF, // 1023
- 0x07FF, // 2047
- 0x0FFF, // 4095
- 0x1FFF, // 8191
- 0x3FFF, // 16383
- 0x7FFF, // 32767
- 0xFFFF // 65535
- };
-
- // Define the storage for the packet accumulator
- private readonly byte[] accumulatorBytes = new byte[256];
-
- private int remaining;
- private int curPixel;
-
- // GIFCOMPR.C - GIF Image compression routines
- //
- // Lempel-Ziv compression based on 'compress'. GIF modifications by
- // David Rowley (mgardi@watdcsu.waterloo.edu)
-
- // General DEFINEs
-
- private const int Bits = 12;
-
- private const int HSIZE = 5003; // 80% occupancy
-
- // 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)
-
- private int numberOfBits; // number of bits/code
- private int maxbits = Bits; // user settable max # bits/code
- private int maxcode; // maximum code, given n_bits
- private int maxmaxcode = 1 << Bits; // should NEVER generate this code
-
- private readonly int[] hashTable = new int[HSIZE];
- private readonly int[] codeTable = new int[HSIZE];
-
- private int hsize = HSIZE; // for dynamic table sizing
-
- private int freeEntry = 0; // first unused entry
-
- // block compression parameters -- after all codes are used up,
- // and compression rate changes, start over.
- 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;
+ ///
+ /// One more than the maximum value 12 bit integer.
+ ///
+ private const int MaxStackSize = 4096;
- private int currentAccumulator;
+ ///
+ /// The initial bit depth.
+ ///
+ private readonly byte initDataSize;
- private int currentBits;
+ ///
+ /// The indexed pixels to encode.
+ ///
+ private readonly byte[] indexedPixels;
///
- /// Number of characters so far in this 'packet'
+ /// The color depth in bits.
///
- private int accumulatorCount;
+ private byte colorDepth;
- public LzwEncoder(int width, int height, byte[] pixels, int colorDepth)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array of indexed pixels.
+ /// The color depth in bits.
+ public LzwEncoder(byte[] indexedPixels, byte colorDepth)
{
- this.imgW = width;
- this.imgH = height;
- this.pixAry = pixels;
- this.initCodeSize = Math.Max(2, colorDepth);
+ this.indexedPixels = indexedPixels;
+ this.colorDepth = colorDepth.Clamp(2, 8);
+ this.initDataSize = this.colorDepth;
}
public void Encode(Stream stream)
{
- stream.WriteByte((byte)this.initCodeSize); // write "initial code size" byte
+ // Whether it is a first step.
+ bool first = true;
- this.remaining = this.imgW * this.imgH; // reset navigation variables
- this.curPixel = 0;
+ // The initial suffix.
+ int suffix = 0;
- this.Compress(this.initCodeSize + 1, stream); // compress and write the pixel data
+ // Indicator to reinitialize the code table.
+ int clearCode = 1 << this.colorDepth;
- stream.WriteByte(0); // write block terminator
- }
+ // End of information code
+ int endOfInformation = clearCode + 1;
- // Add a character to the end of the current packet, and if it is 254
- // characters, flush the packet to disk.
- private void CharOut(byte character, Stream stream)
- {
- this.accumulatorBytes[this.accumulatorCount++] = character;
- if (this.accumulatorCount >= 254)
- {
- this.FlushChar(stream);
- }
- }
+ // The code table for storing encoded colors.
+ Dictionary codeTable = new Dictionary();
- // Clear out the hash table
+ // The current number of index bytes processed.
+ int releaseCount = 0;
- // table clear for block compress
- private void ClearBlock(Stream outs)
- {
- this.ClearHashTable(this.hsize);
- this.freeEntry = this.ClearCode + 2;
- this.clearFlag = true;
+ // Calculate the code available.
+ byte codeSize = (byte)(this.colorDepth + 1);
+ int availableCode = endOfInformation + 1;
- this.Output(this.ClearCode, outs);
- }
+ // Initialise.
+ BitEncoder bitEncoder = new BitEncoder(codeSize);
+ stream.WriteByte(this.colorDepth);
+ bitEncoder.Add(clearCode);
- // reset code table
- private void ClearHashTable(int hsize)
- {
- for (int i = 0; i < hsize; ++i)
+ while (releaseCount < this.indexedPixels.Length)
{
- this.hashTable[i] = -1;
- }
- }
-
- private void Compress(int init_bits, Stream outs)
- {
- int fcode;
- int c;
- int ent;
- int disp;
- int hsize_reg;
- int hshift;
-
- // Set up the globals: globalInitialBits - initial number of bits
- this.globalInitialBits = init_bits;
-
- // Set up the necessary values
- this.clearFlag = false;
- this.numberOfBits = this.globalInitialBits;
- this.maxcode = this.MAXCODE(this.numberOfBits);
-
- this.ClearCode = 1 << (init_bits - 1);
- this.EOFCode = this.ClearCode + 1;
- this.freeEntry = this.ClearCode + 2;
-
- this.accumulatorCount = 0; // clear packet
-
- ent = this.NextPixel();
+ if (first)
+ {
+ // If this is the first byte the suffix is set to the first byte index.
+ suffix = this.indexedPixels[releaseCount++];
- hshift = 0;
- for (fcode = this.hsize; fcode < 65536; fcode *= 2)
- {
- ++hshift;
- }
+ if (releaseCount == this.indexedPixels.Length)
+ {
+ bitEncoder.Add(suffix);
+ bitEncoder.Add(endOfInformation);
+ bitEncoder.End();
+ stream.WriteByte((byte)bitEncoder.Length);
+ stream.Write(bitEncoder.ToArray(), 0, bitEncoder.Length);
+ bitEncoder.Clear();
+ break;
+ }
- hshift = 8 - hshift; // set hash code range bound
+ first = false;
+ continue;
+ }
- hsize_reg = this.hsize;
- this.ClearHashTable(hsize_reg); // clear hash table
+ // Switch
+ int prefix = suffix;
- this.Output(this.ClearCode, outs);
+ // Read the bytes at the index.
+ suffix = this.indexedPixels[releaseCount++];
- // TODO: Refactor this. Goto is baaaaaaad!
- // outer_loop:
- while ((c = this.NextPixel()) != EOF)
- {
- fcode = (c << this.maxbits) + ent;
- int i = c << hshift ^ ent;
+ // Acts as a key for code table entries.
+ string key = $"{prefix},{suffix}";
- if (this.hashTable[i] == fcode)
+ // Is index buffer + the index in our code table?
+ if (!codeTable.ContainsKey(key))
{
- ent = this.codeTable[i];
- continue;
- }
+ // If the current entity is not coded add the prefix.
+ bitEncoder.Add(prefix);
- // non-empty slot
- if (this.hashTable[i] >= 0)
- {
- disp = hsize_reg - i; // secondary hash (after G. Knott)
- if (i == 0)
+ // Add the current bytes
+ codeTable.Add(key, availableCode++);
+
+ if (availableCode > (MaxStackSize - 3))
{
- disp = 1;
+ // 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;
}
- do
+ if (bitEncoder.Length >= 255)
{
- if ((i -= disp) < 0)
+ stream.WriteByte(255);
+ stream.Write(bitEncoder.ToArray(), 0, 255);
+ if (bitEncoder.Length > 255)
{
- i += hsize_reg;
+ byte[] leftBuffer = new byte[bitEncoder.Length - 255];
+ bitEncoder.CopyTo(255, leftBuffer, 0, leftBuffer.Length);
+ bitEncoder.Clear();
+ bitEncoder.AddRange(leftBuffer);
}
-
- if (this.hashTable[i] == fcode)
+ else
{
- ent = this.codeTable[i];
- // goto outer_loop;
- break;
+ bitEncoder.Clear();
}
}
- while (this.hashTable[i] >= 0);
- }
-
- this.Output(ent, outs);
- ent = c;
-
- if (this.freeEntry < this.maxmaxcode)
- {
- this.codeTable[i] = this.freeEntry++; // code -> hashtable
- this.hashTable[i] = fcode;
}
else
{
- this.ClearBlock(outs);
+ // Set the suffix to the current byte.
+ suffix = codeTable[key];
}
- }
-
- // Put out the final code.
- this.Output(ent, outs);
- this.Output(this.EOFCode, outs);
- }
-
- // Flush the packet to disk, and reset the accumulator
- private void FlushChar(Stream stream)
- {
- if (this.accumulatorCount > 0)
- {
- stream.WriteByte((byte)this.accumulatorCount);
- stream.Write(this.accumulatorBytes, 0, this.accumulatorCount);
- this.accumulatorCount = 0;
- }
- }
-
- private int MAXCODE(int bits)
- {
- return (1 << bits) - 1;
- }
-
- //----------------------------------------------------------------------------
- // Return the next pixel from the image
- //----------------------------------------------------------------------------
- private int NextPixel()
- {
- if (this.remaining == 0)
- {
- return EOF;
- }
-
- --this.remaining;
- byte pix = this.pixAry[this.curPixel++];
-
- return pix & 0xff;
- }
-
- 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.numberOfBits;
-
- while (this.currentBits >= 8)
- {
- this.CharOut((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 = this.MAXCODE(this.numberOfBits = this.globalInitialBits);
- this.clearFlag = false;
- }
- else
+ // Output code for contents of index buffer.
+ // Output end-of-information code.
+ if (releaseCount == this.indexedPixels.Length)
{
- ++this.numberOfBits;
- this.maxcode = this.numberOfBits == this.maxbits
- ? this.maxmaxcode
- : this.MAXCODE(this.numberOfBits);
- }
- }
+ 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();
+ }
- if (code == this.EOFCode)
- {
- // At EOF, write the rest of the buffer.
- while (this.currentBits > 0)
- {
- this.CharOut((byte)(this.currentAccumulator & 0xff), outs);
- this.currentAccumulator >>= 8;
- this.currentBits -= 8;
+ break;
}
-
- this.FlushChar(outs);
}
}
}
diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs b/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
index 5738f9e88..d6a0801d4 100644
--- a/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
+++ b/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
@@ -17,29 +17,14 @@ namespace ImageProcessor.Formats
///
public class QuantizedImage
{
- ///
- /// Gets the width of this .
- ///
- public int Width { get; }
-
- ///
- /// Gets the height of this .
- ///
- public int Height { get; }
-
- ///
- /// Gets the color palette of this .
- ///
- public Bgra[] Palette { get; }
-
- ///
- /// Gets the pixels of this .
- ///
- public byte[] Pixels { get; }
///
- /// Initializes a new instance of .
+ /// Initializes a new instance of the class.
///
+ /// The image width.
+ /// The image height.
+ /// The color palette.
+ /// The quantized pixels.
public QuantizedImage(int width, int height, Bgra[] palette, byte[] pixels)
{
Guard.MustBeGreaterThan(width, 0, nameof(width));
@@ -59,10 +44,32 @@ namespace ImageProcessor.Formats
this.Pixels = pixels;
}
+ ///
+ /// Gets the width of this .
+ ///
+ public int Width { get; }
+
+ ///
+ /// Gets the height of this .
+ ///
+ public int Height { get; }
+
+ ///
+ /// Gets the color palette of this .
+ ///
+ public Bgra[] Palette { get; }
+
+ ///
+ /// Gets the pixels of this .
+ ///
+ public byte[] Pixels { get; }
+
///
/// Converts this quantized image to a normal image.
///
- ///
+ ///
+ /// The
+ ///
public Image ToImage()
{
Image image = new Image();
diff --git a/src/ImageProcessor/Formats/Gif/GifGraphicsControlExtension.cs b/src/ImageProcessor/Formats/Gif/Sections/GifGraphicsControlExtension.cs
similarity index 100%
rename from src/ImageProcessor/Formats/Gif/GifGraphicsControlExtension.cs
rename to src/ImageProcessor/Formats/Gif/Sections/GifGraphicsControlExtension.cs
diff --git a/src/ImageProcessor/Formats/Gif/GifImageDescriptor.cs b/src/ImageProcessor/Formats/Gif/Sections/GifImageDescriptor.cs
similarity index 100%
rename from src/ImageProcessor/Formats/Gif/GifImageDescriptor.cs
rename to src/ImageProcessor/Formats/Gif/Sections/GifImageDescriptor.cs
diff --git a/src/ImageProcessor/Formats/Gif/GifLogicalScreenDescriptor.cs b/src/ImageProcessor/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs
similarity index 100%
rename from src/ImageProcessor/Formats/Gif/GifLogicalScreenDescriptor.cs
rename to src/ImageProcessor/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs
diff --git a/src/ImageProcessor/Formats/Jpg/LibJpeg/BitStream.cs b/src/ImageProcessor/Formats/Jpg/LibJpeg/BitStream.cs
index 9c2128bcd..4faa346e9 100644
--- a/src/ImageProcessor/Formats/Jpg/LibJpeg/BitStream.cs
+++ b/src/ImageProcessor/Formats/Jpg/LibJpeg/BitStream.cs
@@ -59,6 +59,25 @@ namespace ImageProcessor.Formats
this.stream = new MemoryStream();
}
+ ///
+ /// Initializes a new instance of the class based on the
+ /// specified byte array.
+ ///
+ ///
+ /// The from which to create the current stream.
+ ///
+ ///
+ /// Thrown if the given stream is null.
+ ///
+ public BitStream(Stream stream)
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ this.stream = new MemoryStream();
+ stream.CopyTo(this.stream);
+ this.size = this.BitsAllocated();
+ }
+
///
/// Initializes a new instance of the class based on the
/// specified byte array.
@@ -71,7 +90,7 @@ namespace ImageProcessor.Formats
///
public BitStream(byte[] buffer)
{
- Guard.NotNull(buffer, "buffer");
+ Guard.NotNull(buffer, nameof(buffer));
this.stream = new MemoryStream(buffer);
this.size = this.BitsAllocated();
@@ -129,7 +148,7 @@ namespace ImageProcessor.Formats
///
public virtual int Read(int bitCount)
{
- Guard.MustBeLessThanOrEqualTo(this.Tell() + bitCount, this.BitsAllocated(), "bitCount");
+ Guard.MustBeLessThanOrEqualTo(this.Tell() + bitCount, this.BitsAllocated(), nameof(bitCount));
return this.ReadBits(bitCount);
}
@@ -150,7 +169,7 @@ namespace ImageProcessor.Formats
const int MaxBitsInStorage = sizeof(int) * BitsInByte;
- Guard.MustBeLessThanOrEqualTo(bitCount, MaxBitsInStorage, "bitCount");
+ Guard.MustBeLessThanOrEqualTo(bitCount, MaxBitsInStorage, nameof(bitCount));
for (int i = 0; i < bitCount; ++i)
{
diff --git a/src/ImageProcessor/Formats/Jpg/LibJpeg/Classic/Internal/phuff_entropy_decoder.cs b/src/ImageProcessor/Formats/Jpg/LibJpeg/Classic/Internal/phuff_entropy_decoder.cs
index 518472c04..7d3a7a123 100644
--- a/src/ImageProcessor/Formats/Jpg/LibJpeg/Classic/Internal/phuff_entropy_decoder.cs
+++ b/src/ImageProcessor/Formats/Jpg/LibJpeg/Classic/Internal/phuff_entropy_decoder.cs
@@ -375,7 +375,7 @@ namespace BitMiracle.LibJpeg.Classic.Internal
s = HUFF_EXTEND(r, s);
/* Scale and output coefficient in natural (dezigzagged) order */
- MCU_data[0][JpegUtils.jpeg_natural_order[k]] = (short) (s << m_cinfo.m_Al);
+ MCU_data[0][JpegUtils.jpeg_natural_order[k]] = (short)(s << m_cinfo.m_Al);
}
else
{
@@ -455,7 +455,7 @@ namespace BitMiracle.LibJpeg.Classic.Internal
if (GET_BITS(1, get_buffer, ref bits_left) != 0)
{
/* 1 in the bit position being coded */
- MCU_data[blkn][0] |= (short)(1 << m_cinfo.m_Al);
+ MCU_data[blkn][0] = (short)((ushort)MCU_data[blkn][0] | (ushort)(1 << m_cinfo.m_Al));
}
/* Note: since we use |=, repeating the assignment later is safe */
@@ -542,7 +542,7 @@ namespace BitMiracle.LibJpeg.Classic.Internal
s = p1;
}
else
- {
+ {
/* newly nonzero coef is negative */
s = m1;
}
@@ -608,9 +608,9 @@ namespace BitMiracle.LibJpeg.Classic.Internal
if (s != 0)
{
int pos = JpegUtils.jpeg_natural_order[k];
-
+
/* Output newly nonzero coefficient */
- MCU_data[0][pos] = (short) s;
+ MCU_data[0][pos] = (short)s;
/* Remember its position in case we have to suspend */
newnz_pos[num_newnz++] = pos;
diff --git a/src/ImageProcessor/Formats/Png/PngEncoder.cs b/src/ImageProcessor/Formats/Png/PngEncoder.cs
index ab0f1ac4f..bc32c95c6 100644
--- a/src/ImageProcessor/Formats/Png/PngEncoder.cs
+++ b/src/ImageProcessor/Formats/Png/PngEncoder.cs
@@ -116,7 +116,7 @@ namespace ImageProcessor.Formats
// Write the png header.
stream.Write(
new byte[]
- {
+ {
0x89, // Set the high bit.
0x50, // P
0x4E, // N
@@ -124,7 +124,7 @@ namespace ImageProcessor.Formats
0x0D, // Line ending CRLF
0x0A, // Line ending CRLF
0x1A, // EOF
- 0x0A // LF
+ 0x0A // LF
},
0,
8);
diff --git a/src/ImageProcessor/Image.cs b/src/ImageProcessor/Image.cs
index d27c77833..0f2a51f6d 100644
--- a/src/ImageProcessor/Image.cs
+++ b/src/ImageProcessor/Image.cs
@@ -52,7 +52,7 @@ namespace ImageProcessor
new BmpDecoder(),
new JpegDecoder(),
new PngDecoder(),
- // new GifDecoder(),
+ new GifDecoder(),
});
///
@@ -150,22 +150,14 @@ namespace ImageProcessor
public static IList Encoders => DefaultEncoders.Value;
///
- /// Gets or sets the frame delay.
- /// If not 0, this field specifies the number of hundredths (1/100) of a second to
- /// wait before continuing with the processing of the Data Stream.
- /// The clock starts ticking immediately after the graphic is rendered.
- ///
- public int FrameDelay { get; set; }
-
- ///
- /// Gets or sets the resolution of the image in x- direction. It is defined as
- /// number of dots per inch and should be an positive value.
+ /// Gets or sets the resolution of the image in x- direction. It is defined as
+ /// number of dots per inch and should be an positive value.
///
/// The density of the image in x- direction.
public double HorizontalResolution { get; set; }
///
- /// Gets or sets the resolution of the image in y- direction. It is defined as
+ /// Gets or sets the resolution of the image in y- direction. It is defined as
/// number of dots per inch and should be an positive value.
///
/// The density of the image in y- direction.
@@ -239,7 +231,10 @@ namespace ImageProcessor
/// A list of image properties.
public IList Properties { get; } = new List();
- internal IImageDecoder Decoder { get; set; }
+ ///
+ /// The current decoder
+ ///
+ internal IImageDecoder CurrentDecoder { get; set; }
///
/// Loads the image from the given stream.
@@ -280,8 +275,8 @@ namespace ImageProcessor
IImageDecoder decoder = decoders.FirstOrDefault(x => x.IsSupportedFileFormat(header));
if (decoder != null)
{
- this.Decoder = decoder;
- this.Decoder.Decode(this, stream);
+ this.CurrentDecoder = decoder;
+ this.CurrentDecoder.Decode(this, stream);
return;
}
}
diff --git a/src/ImageProcessor/ImageBase.cs b/src/ImageProcessor/ImageBase.cs
index a8116b2d2..897e72bc5 100644
--- a/src/ImageProcessor/ImageBase.cs
+++ b/src/ImageProcessor/ImageBase.cs
@@ -110,6 +110,14 @@ namespace ImageProcessor
///
public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height);
+ ///
+ /// Gets or sets the frame delay for animated images.
+ /// If not 0, this field specifies the number of hundredths (1/100) of a second to
+ /// wait before continuing with the processing of the Data Stream.
+ /// The clock starts ticking immediately after the graphic is rendered.
+ ///
+ public int FrameDelay { get; set; }
+
///
/// Gets or sets the color of a pixel at the specified position.
///
diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj
index 5b100ca08..066022679 100644
--- a/src/ImageProcessor/ImageProcessor.csproj
+++ b/src/ImageProcessor/ImageProcessor.csproj
@@ -26,6 +26,7 @@
DEBUG;TRACE
prompt
4
+ bin\Debug\ImageProcessor.XML
pdbonly
@@ -51,7 +52,9 @@
+
+
@@ -153,9 +156,9 @@
-
-
-
+
+
+
@@ -175,11 +178,16 @@
+
+
+
+
+
@@ -193,4 +201,4 @@
-->
-
+
\ No newline at end of file
diff --git a/src/ImageProcessor/ImageProcessor.csproj.DotSettings b/src/ImageProcessor/ImageProcessor.csproj.DotSettings
index 82abcd86d..33397fdd2 100644
--- a/src/ImageProcessor/ImageProcessor.csproj.DotSettings
+++ b/src/ImageProcessor/ImageProcessor.csproj.DotSettings
@@ -8,6 +8,7 @@
True
True
True
+ True
True
True
True
diff --git a/src/ImageProcessor/packages.config b/src/ImageProcessor/packages.config
index f66ba65eb..255cb6b3d 100644
--- a/src/ImageProcessor/packages.config
+++ b/src/ImageProcessor/packages.config
@@ -1,4 +1,5 @@
-
+
+
\ No newline at end of file
diff --git a/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs b/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
index b6162a840..c986b1c75 100644
--- a/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
+++ b/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
@@ -14,6 +14,7 @@
//[InlineData("TestImages/Car.bmp")]
//[InlineData("TestImages/Portrait.png")]
[InlineData("../../TestImages/Formats/Jpg/Backdrop.jpg")]
+ [InlineData("../../TestImages/Formats/Gif/leaf.gif")]
//[InlineData("TestImages/Windmill.gif")]
//[InlineData("../../TestImages/Formats/Bmp/Car.bmp")]
//[InlineData("../../TestImages/Formats/Png/cmyk.png")]
@@ -28,13 +29,35 @@
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
- string encodedFilename = "Encoded/" + Path.GetFileName(filename);
+ OctreeQuantizer quantizer = new OctreeQuantizer();
+ var o = quantizer.Quantize(image);
+
+ using (MemoryStream s2 = new MemoryStream())
+ {
+ LzwEncoder2 enc2 = new LzwEncoder2(image.Width, image.Height, o.Pixels, 8);
+ enc2.Encode(s2);
+ using (MemoryStream s = new MemoryStream())
+ {
+ LzwEncoder enc = new LzwEncoder(o.Pixels, 8);
+ enc.Encode(s);
+
+ var x = s.ToArray();
+ var y = s2.ToArray();
+
+ var a = x.Skip(1080);
+ var b = y.Skip(1080);
+
+ Assert.Equal(s.ToArray(), s2.ToArray());
+ }
+ }
+
+ string encodedFilename = "Encoded/" + Path.GetFileNameWithoutExtension(filename) + ".jpg";
//if (!image.IsAnimated)
//{
using (FileStream output = File.OpenWrite(encodedFilename))
{
- IImageEncoder encoder = Image.Encoders.First(e => e.IsSupportedFileExtension(Path.GetExtension(filename)));
+ IImageEncoder encoder = Image.Encoders.First(e => e.IsSupportedFileExtension(".jpg"));
encoder.Encode(image, output);
}
//}
diff --git a/tests/ImageProcessor.Tests/TestImages/Formats/Gif/leaf.gif.REMOVED.git-id b/tests/ImageProcessor.Tests/TestImages/Formats/Gif/leaf.gif.REMOVED.git-id
new file mode 100644
index 000000000..e62f10ab5
--- /dev/null
+++ b/tests/ImageProcessor.Tests/TestImages/Formats/Gif/leaf.gif.REMOVED.git-id
@@ -0,0 +1 @@
+98e8f0bd9cd631e784bcc356a589d87989d43176
\ No newline at end of file