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