Browse Source

Cleanup lzw encoder add more encoding functions

Former-commit-id: 906a47f0606c8831f526bab4d0408651e9052a9e
Former-commit-id: 4c760c7eb123df7ea1d2dd5d15a5b6b4a6d8383c
Former-commit-id: 4c431f1e32cd5aa5304c7da88a68fc4a850d860b
af/merge-core
James Jackson-South 11 years ago
parent
commit
016d26c158
  1. 3
      .gitignore
  2. 137
      src/ImageProcessor/Formats/Gif/BitEncoder.cs
  3. 45
      src/ImageProcessor/Formats/Gif/GifConstants.cs
  4. 11
      src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
  5. 129
      src/ImageProcessor/Formats/Gif/GifEncoder.cs
  6. 445
      src/ImageProcessor/Formats/Gif/LzwEncoder.cs
  7. 49
      src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
  8. 0
      src/ImageProcessor/Formats/Gif/Sections/GifGraphicsControlExtension.cs
  9. 0
      src/ImageProcessor/Formats/Gif/Sections/GifImageDescriptor.cs
  10. 0
      src/ImageProcessor/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs
  11. 25
      src/ImageProcessor/Formats/Jpg/LibJpeg/BitStream.cs
  12. 10
      src/ImageProcessor/Formats/Jpg/LibJpeg/Classic/Internal/phuff_entropy_decoder.cs
  13. 4
      src/ImageProcessor/Formats/Png/PngEncoder.cs
  14. 25
      src/ImageProcessor/Image.cs
  15. 8
      src/ImageProcessor/ImageBase.cs
  16. 16
      src/ImageProcessor/ImageProcessor.csproj
  17. 1
      src/ImageProcessor/ImageProcessor.csproj.DotSettings
  18. 3
      src/ImageProcessor/packages.config
  19. 27
      tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
  20. 1
      tests/ImageProcessor.Tests/TestImages/Formats/Gif/leaf.gif.REMOVED.git-id

3
.gitignore

@ -170,4 +170,5 @@ build/*.nupkg
build/TestResult.xml
*.db
_site/
_site/
.vs/config/applicationhost.config

137
src/ImageProcessor/Formats/Gif/BitEncoder.cs

@ -0,0 +1,137 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="BitEncoder.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Handles the encoding of bits for compression.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
using System.Collections.Generic;
/// <summary>
/// Handles the encoding of bits for compression.
/// </summary>
internal class BitEncoder
{
/// <summary>
/// The current working bit.
/// </summary>
private int currentBit;
/// <summary>
/// The current value.
/// </summary>
private int currentValue;
/// <summary>
/// The inner list for collecting the bits.
/// </summary>
private readonly List<byte> list = new List<byte>();
/// <summary>
/// The number of bytes in the encoder.
/// </summary>
internal int Length => this.list.Count;
/// <summary>
/// Gets or sets the intitial bit.
/// </summary>
public int IntitialBit { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="BitEncoder"/> class.
/// </summary>
/// <param name="initial">
/// The initial bits.
/// </param>
public BitEncoder(int initial)
{
this.IntitialBit = initial;
}
/// <summary>
/// Adds the current byte to the end of the encoder.
/// </summary>
/// <param name="item">
/// The byte to add.
/// </param>
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);
}
}
/// <summary>
/// Adds the collection of bytes to the end of the encoder.
/// </summary>
/// <param name="collection">
/// The collection of bytes to add.
/// The collection itself cannot be null but can contain elements that are null.</param>
public void AddRange(byte[] collection)
{
this.list.AddRange(collection);
}
/// <summary>
/// The end.
/// </summary>
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);
}
}
/// <summary>
/// Copies a range of elements from the encoder to a compatible one-dimensional array,
/// starting at the specified index of the target array.
/// </summary>
/// <param name="index">
/// The zero-based index in the source <see cref="BitEncoder"/> at which copying begins.
/// </param>
/// <param name="array">
/// The one-dimensional Array that is the destination of the elements copied
/// from <see cref="BitEncoder"/>. The Array must have zero-based indexing
/// </param>
/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
/// <param name="count">The number of bytes to copy.</param>
public void CopyTo(int index, byte[] array, int arrayIndex, int count)
{
this.list.CopyTo(index, array, arrayIndex, count);
}
/// <summary>
/// Removes all the bytes from the encoder.
/// </summary>
public void Clear()
{
this.list.Clear();
}
/// <summary>
/// Copies the bytes into a new array.
/// </summary>
/// <returns><see cref="T:byte[]"/></returns>
public byte[] ToArray()
{
return this.list.ToArray();
}
}
}

45
src/ImageProcessor/Formats/Gif/GifConstants.cs

@ -16,40 +16,55 @@ namespace ImageProcessor.Formats
internal sealed class GifConstants
{
/// <summary>
/// The maximum comment length.
/// The file type.
/// </summary>
public const int MaxCommentLength = 1024 * 8;
public const string FileType = "GIF";
/// <summary>
/// The extension block introducer <value>!</value>.
/// The file version.
/// </summary>
public const byte ExtensionIntroducer = 0x21;
public const string FileVersion = "89a";
/// <summary>
/// The terminator.
/// The extension block introducer <value>!</value>.
/// </summary>
public const byte Terminator = 0;
public const byte ExtensionIntroducer = 0x21;
/// <summary>
/// The image label introducer <value>,</value>.
/// The end introducer trailer <value>;</value>.
/// </summary>
public const byte ImageLabel = 0x2C;
public const byte EndIntroducer = 0x3B;
/// <summary>
/// The end introducer trailer <value>;</value>.
/// The graphic control label.
/// </summary>
public const byte EndIntroducer = 0x3B;
public const byte GraphicControlLabel = 0xF9;
/// <summary>
/// The application extension label.
/// </summary>
public const byte ApplicationExtensionLabel = 0xFF;
/// <summary>
/// The application identification.
/// </summary>
public const string ApplicationIdentification = "NETSCAPE2.0";
/// <summary>
/// The application block size.
/// </summary>
public const byte ApplicationBlockSize = 0x0b;
/// <summary>
/// The comment label.
/// </summary>
public const byte CommentLabel = 0xFE;
/// <summary>
/// The maximum comment length.
/// </summary>
public const int MaxCommentLength = 1024 * 8;
/// <summary>
/// The image descriptor label <value>,</value>.
/// </summary>
@ -61,8 +76,14 @@ namespace ImageProcessor.Formats
public const byte PlainTextLabel = 0x01;
/// <summary>
/// The graphic control label.
/// The image label introducer <value>,</value>.
/// </summary>
public const byte GraphicControlLabel = 0xF9;
public const byte ImageLabel = 0x2C;
/// <summary>
/// The terminator.
/// </summary>
public const byte Terminator = 0;
}
}

11
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;

129
src/ImageProcessor/Formats/Gif/GifEncoder.cs

@ -1,9 +1,16 @@

// <copyright file="GifEncoder.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor.Formats
{
using System;
using System.IO;
/// <summary>
/// The Gif encoder
/// </summary>
public class GifEncoder : IImageEncoder
{
/// <summary>
@ -11,7 +18,10 @@ namespace ImageProcessor.Formats
/// </summary>
private int quality = 256;
private Image image;
/// <summary>
/// The gif decoder if any used to decode the original image.
/// </summary>
private GifDecoder gifDecoder;
/// <summary>
/// Gets or sets the quality of output for images.
@ -54,53 +64,77 @@ namespace ImageProcessor.Formats
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageBase"/>.
/// </summary>
/// <param name="image">The <see cref="ImageBase"/> to encode from.</param>
/// <param name="imageBase">The <see cref="ImageBase"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
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
}
}
/// <summary>
@ -235,9 +282,13 @@ namespace ImageProcessor.Formats
}
/// <summary>
/// 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.
/// </summary>
/// <para>The number of colors.</para>
/// <returns>
/// The <see cref="int"/>
/// </returns>
private int GetBitsNeededForColorDepth(int colors)
{
return (int)Math.Ceiling(Math.Log(colors, 2));

445
src/ImageProcessor/Formats/Gif/LzwEncoder.cs

@ -1,357 +1,194 @@
namespace ImageProcessor.Formats
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LzwEncoder.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encodes an image pixels used on a method based on LZW compression.
// <see href="http://matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp"/>
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
/// <summary>
/// Encodes an image pixels used on a method based on LZW compression.
/// <see href="http://matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp"/>
/// </summary>
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;
/// <summary>
/// One more than the maximum value 12 bit integer.
/// </summary>
private const int MaxStackSize = 4096;
private int currentAccumulator;
/// <summary>
/// The initial bit depth.
/// </summary>
private readonly byte initDataSize;
private int currentBits;
/// <summary>
/// The indexed pixels to encode.
/// </summary>
private readonly byte[] indexedPixels;
/// <summary>
/// Number of characters so far in this 'packet'
/// The color depth in bits.
/// </summary>
private int accumulatorCount;
private byte colorDepth;
public LzwEncoder(int width, int height, byte[] pixels, int colorDepth)
/// <summary>
/// Initializes a new instance of the <see cref="LzwEncoder"/> class.
/// </summary>
/// <param name="indexedPixels">The array of indexed pixels.</param>
/// <param name="colorDepth">The color depth in bits.</param>
public LzwEncoder(byte[] indexedPixels, byte colorDepth)
{
this.imgW = width;
this.imgH = height;
this.pixAry = pixels;
this.initCodeSize = Math.Max(2, colorDepth);
this.indexedPixels = indexedPixels;
this.colorDepth = colorDepth.Clamp<byte>(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<string, int> codeTable = new Dictionary<string, int>();
// 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);
}
}
}

49
src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs

@ -17,29 +17,14 @@ namespace ImageProcessor.Formats
/// </summary>
public class QuantizedImage
{
/// <summary>
/// Gets the width of this <see cref="T:QuantizedImage"/>.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the height of this <see cref="T:QuantizedImage"/>.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the color palette of this <see cref="T:QuantizedImage"/>.
/// </summary>
public Bgra[] Palette { get; }
/// <summary>
/// Gets the pixels of this <see cref="T:QuantizedImage"/>.
/// </summary>
public byte[] Pixels { get; }
/// <summary>
/// Initializes a new instance of <see cref="T:QuantizedImage"/>.
/// Initializes a new instance of the <see cref="QuantizedImage"/> class.
/// </summary>
/// <param name="width">The image width.</param>
/// <param name="height">The image height.</param>
/// <param name="palette">The color palette.</param>
/// <param name="pixels">The quantized pixels.</param>
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;
}
/// <summary>
/// Gets the width of this <see cref="T:QuantizedImage"/>.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the height of this <see cref="T:QuantizedImage"/>.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the color palette of this <see cref="T:QuantizedImage"/>.
/// </summary>
public Bgra[] Palette { get; }
/// <summary>
/// Gets the pixels of this <see cref="T:QuantizedImage"/>.
/// </summary>
public byte[] Pixels { get; }
/// <summary>
/// Converts this quantized image to a normal image.
/// </summary>
/// <returns></returns>
/// <returns>
/// The <see cref="Image"/>
/// </returns>
public Image ToImage()
{
Image image = new Image();

0
src/ImageProcessor/Formats/Gif/GifGraphicsControlExtension.cs → src/ImageProcessor/Formats/Gif/Sections/GifGraphicsControlExtension.cs

0
src/ImageProcessor/Formats/Gif/GifImageDescriptor.cs → src/ImageProcessor/Formats/Gif/Sections/GifImageDescriptor.cs

0
src/ImageProcessor/Formats/Gif/GifLogicalScreenDescriptor.cs → src/ImageProcessor/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs

25
src/ImageProcessor/Formats/Jpg/LibJpeg/BitStream.cs

@ -59,6 +59,25 @@ namespace ImageProcessor.Formats
this.stream = new MemoryStream();
}
/// <summary>
/// Initializes a new instance of the <see cref="BitStream"/> class based on the
/// specified byte array.
/// </summary>
/// <param name="stream">
/// The <see cref="T:Stream"/> from which to create the current stream.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown if the given stream is null.
/// </exception>
public BitStream(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
this.stream = new MemoryStream();
stream.CopyTo(this.stream);
this.size = this.BitsAllocated();
}
/// <summary>
/// Initializes a new instance of the <see cref="BitStream"/> class based on the
/// specified byte array.
@ -71,7 +90,7 @@ namespace ImageProcessor.Formats
/// </exception>
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
/// </returns>
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)
{

10
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;

4
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);

25
src/ImageProcessor/Image.cs

@ -52,7 +52,7 @@ namespace ImageProcessor
new BmpDecoder(),
new JpegDecoder(),
new PngDecoder(),
// new GifDecoder(),
new GifDecoder(),
});
/// <summary>
@ -150,22 +150,14 @@ namespace ImageProcessor
public static IList<IImageEncoder> Encoders => DefaultEncoders.Value;
/// <summary>
/// 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.
/// </summary>
public int FrameDelay { get; set; }
/// <summary>
/// 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.
/// </summary>
/// <value>The density of the image in x- direction.</value>
public double HorizontalResolution { get; set; }
/// <summary>
/// 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.
/// </summary>
/// <value>The density of the image in y- direction.</value>
@ -239,7 +231,10 @@ namespace ImageProcessor
/// <value>A list of image properties.</value>
public IList<ImageProperty> Properties { get; } = new List<ImageProperty>();
internal IImageDecoder Decoder { get; set; }
/// <summary>
/// The current decoder
/// </summary>
internal IImageDecoder CurrentDecoder { get; set; }
/// <summary>
/// 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;
}
}

8
src/ImageProcessor/ImageBase.cs

@ -110,6 +110,14 @@ namespace ImageProcessor
/// </summary>
public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height);
/// <summary>
/// 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.
/// </summary>
public int FrameDelay { get; set; }
/// <summary>
/// Gets or sets the color of a pixel at the specified position.
/// </summary>

16
src/ImageProcessor/ImageProcessor.csproj

@ -26,6 +26,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\ImageProcessor.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -51,7 +52,9 @@
<Compile Include="Formats\Bmp\BmpDecoder.cs" />
<Compile Include="Formats\Bmp\BmpDecoderCore.cs" />
<Compile Include="Formats\Bmp\BmpEncoder.cs" />
<Compile Include="Formats\Gif\BitEncoder.cs" />
<Compile Include="Formats\Gif\LzwEncoder.cs" />
<Compile Include="Formats\Gif\GifConstants.cs" />
<Compile Include="Formats\Gif\GifDecoderCore.cs" />
<Compile Include="Formats\Gif\GifEncoder.cs" />
<Compile Include="Formats\Gif\Quantizer\QuantizedImage.cs" />
@ -153,9 +156,9 @@
<Compile Include="Common\Helpers\Guard.cs" />
<Compile Include="Formats\Gif\GifDecoder.cs" />
<Compile Include="Formats\Gif\LzwDecoder.cs" />
<Compile Include="Formats\Gif\GifLogicalScreenDescriptor.cs" />
<Compile Include="Formats\Gif\GifImageDescriptor.cs" />
<Compile Include="Formats\Gif\GifGraphicsControlExtension.cs" />
<Compile Include="Formats\Gif\Sections\GifLogicalScreenDescriptor.cs" />
<Compile Include="Formats\Gif\Sections\GifImageDescriptor.cs" />
<Compile Include="Formats\Gif\Sections\GifGraphicsControlExtension.cs" />
<Compile Include="Formats\Gif\DisposalMethod.cs" />
<Compile Include="Formats\IImageDecoder.cs" />
<Compile Include="Formats\IImageEncoder.cs" />
@ -175,11 +178,16 @@
<ItemGroup>
<None Include="Formats\Jpg\README.md" />
<None Include="packages.config" />
<AdditionalFiles Include="stylecop.json" />
</ItemGroup>
<ItemGroup>
<None Include="Formats\Bmp\README.md" />
<None Include="Formats\Png\README.md" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\..\packages\StyleCop.Analyzers.1.0.0-beta013\analyzers\dotnet\cs\Newtonsoft.Json.dll" />
<Analyzer Include="..\..\packages\StyleCop.Analyzers.1.0.0-beta013\analyzers\dotnet\cs\StyleCop.Analyzers.dll" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@ -193,4 +201,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

1
src/ImageProcessor/ImageProcessor.csproj.DotSettings

@ -8,6 +8,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats_005Cbmp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats_005Cgif/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats_005Cgif_005Cquantizer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats_005Cgif_005Csections/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats_005Cjpg/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats_005Cjpg_005Clibjpeg/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=formats_005Cpng/@EntryIndexedValue">True</s:Boolean>

3
src/ImageProcessor/packages.config

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="SharpZipLib.Portable" version="0.86.0.0002" targetFramework="portable-net45+win+wp80" />
<package id="SharpZipLib.Portable" version="0.86.0.0002" targetFramework="portable-net45+win8+wp8" />
<package id="StyleCop.Analyzers" version="1.0.0-beta013" targetFramework="portable45-net45+win8+wp8" developmentDependency="true" />
</packages>

27
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);
}
//}

1
tests/ImageProcessor.Tests/TestImages/Formats/Gif/leaf.gif.REMOVED.git-id

@ -0,0 +1 @@
98e8f0bd9cd631e784bcc356a589d87989d43176
Loading…
Cancel
Save