mirror of https://github.com/SixLabors/ImageSharp
8 changed files with 867 additions and 3 deletions
@ -0,0 +1,35 @@ |
|||||
|
// <copyright file="LzeTiffCompression.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.IO; |
||||
|
using System.IO.Compression; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Class to handle cases where TIFF image data is compressed using LZW compression.
|
||||
|
/// </summary>
|
||||
|
internal static class LzwTiffCompression |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Decompresses image data into the supplied buffer.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The <see cref="Stream"/> to read image data from.</param>
|
||||
|
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
|
||||
|
/// <param name="buffer">The output buffer for uncompressed data.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Decompress(Stream stream, int byteCount, byte[] buffer) |
||||
|
{ |
||||
|
SubStream subStream = new SubStream(stream, byteCount); |
||||
|
using (var decoder = new TiffLzwDecoder(subStream)) |
||||
|
{ |
||||
|
decoder.DecodePixels(buffer.Length, 8, buffer); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,272 @@ |
|||||
|
// <copyright file="TiffLzwDecoder.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.IO; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decompresses and decodes data using the dynamic LZW algorithms.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// This code is based on the <see cref="LzwDecoder"/> used for GIF decoding. There is potential
|
||||
|
/// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW
|
||||
|
/// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is
|
||||
|
/// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial
|
||||
|
/// byte indicating the length of the sub-block. In TIFF the data is written as a single block
|
||||
|
/// with no length indicator (this can be determined from the 'StripByteCounts' entry).
|
||||
|
/// </remarks>
|
||||
|
internal sealed class TiffLzwDecoder : IDisposable |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The max decoder pixel stack size.
|
||||
|
/// </summary>
|
||||
|
private const int MaxStackSize = 4096; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The null code.
|
||||
|
/// </summary>
|
||||
|
private const int NullCode = -1; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The stream to decode.
|
||||
|
/// </summary>
|
||||
|
private readonly Stream stream; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The prefix buffer.
|
||||
|
/// </summary>
|
||||
|
private readonly int[] prefix; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The suffix buffer.
|
||||
|
/// </summary>
|
||||
|
private readonly int[] suffix; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The pixel stack buffer.
|
||||
|
/// </summary>
|
||||
|
private readonly int[] pixelStack; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A value indicating whether this instance of the given entity has been disposed.
|
||||
|
/// </summary>
|
||||
|
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
|
||||
|
/// <remarks>
|
||||
|
/// If the entity is disposed, it must not be disposed a second
|
||||
|
/// time. The isDisposed field is set the first time the entity
|
||||
|
/// is disposed. If the isDisposed field is true, then the Dispose()
|
||||
|
/// method will not dispose again. This help not to prolong the entity's
|
||||
|
/// life in the Garbage Collector.
|
||||
|
/// </remarks>
|
||||
|
private bool isDisposed; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="TiffLzwDecoder"/> class
|
||||
|
/// and sets the stream, where the compressed data should be read from.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The stream to read from.</param>
|
||||
|
/// <exception cref="System.ArgumentNullException"><paramref name="stream"/> is null.</exception>
|
||||
|
public TiffLzwDecoder(Stream stream) |
||||
|
{ |
||||
|
Guard.NotNull(stream, nameof(stream)); |
||||
|
|
||||
|
this.stream = stream; |
||||
|
|
||||
|
this.prefix = ArrayPool<int>.Shared.Rent(MaxStackSize); |
||||
|
this.suffix = ArrayPool<int>.Shared.Rent(MaxStackSize); |
||||
|
this.pixelStack = ArrayPool<int>.Shared.Rent(MaxStackSize + 1); |
||||
|
|
||||
|
Array.Clear(this.prefix, 0, MaxStackSize); |
||||
|
Array.Clear(this.suffix, 0, MaxStackSize); |
||||
|
Array.Clear(this.pixelStack, 0, MaxStackSize + 1); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decodes and decompresses all pixel indices from the stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="length">The length of the compressed data.</param>
|
||||
|
/// <param name="dataSize">Size of the data.</param>
|
||||
|
/// <param name="pixels">The pixel array to decode to.</param>
|
||||
|
public void DecodePixels(int length, int dataSize, byte[] pixels) |
||||
|
{ |
||||
|
Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); |
||||
|
|
||||
|
// Calculate the clear code. The value of the clear code is 2 ^ dataSize
|
||||
|
int clearCode = 1 << dataSize; |
||||
|
|
||||
|
int codeSize = dataSize + 1; |
||||
|
|
||||
|
// Calculate the end code
|
||||
|
int endCode = clearCode + 1; |
||||
|
|
||||
|
// Calculate the available code.
|
||||
|
int availableCode = clearCode + 2; |
||||
|
|
||||
|
// Jillzhangs Code see: http://giflib.codeplex.com/
|
||||
|
// Adapted from John Cristy's ImageMagick.
|
||||
|
int code; |
||||
|
int oldCode = NullCode; |
||||
|
int codeMask = (1 << codeSize) - 1; |
||||
|
int bits = 0; |
||||
|
|
||||
|
int top = 0; |
||||
|
int count = 0; |
||||
|
int bi = 0; |
||||
|
int xyz = 0; |
||||
|
|
||||
|
int data = 0; |
||||
|
int first = 0; |
||||
|
|
||||
|
for (code = 0; code < clearCode; code++) |
||||
|
{ |
||||
|
this.prefix[code] = 0; |
||||
|
this.suffix[code] = (byte)code; |
||||
|
} |
||||
|
|
||||
|
byte[] buffer = new byte[255]; |
||||
|
while (xyz < length) |
||||
|
{ |
||||
|
if (top == 0) |
||||
|
{ |
||||
|
if (bits < codeSize) |
||||
|
{ |
||||
|
// Load bytes until there are enough bits for a code.
|
||||
|
if (count == 0) |
||||
|
{ |
||||
|
// Read a new data block.
|
||||
|
count = this.ReadBlock(buffer); |
||||
|
if (count == 0) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
bi = 0; |
||||
|
} |
||||
|
|
||||
|
data += buffer[bi] << bits; |
||||
|
|
||||
|
bits += 8; |
||||
|
bi++; |
||||
|
count--; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// Get the next code
|
||||
|
code = data & codeMask; |
||||
|
data >>= codeSize; |
||||
|
bits -= codeSize; |
||||
|
|
||||
|
// Interpret the code
|
||||
|
if (code > availableCode || code == endCode) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (code == clearCode) |
||||
|
{ |
||||
|
// Reset the decoder
|
||||
|
codeSize = dataSize + 1; |
||||
|
codeMask = (1 << codeSize) - 1; |
||||
|
availableCode = clearCode + 2; |
||||
|
oldCode = NullCode; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (oldCode == NullCode) |
||||
|
{ |
||||
|
this.pixelStack[top++] = this.suffix[code]; |
||||
|
oldCode = code; |
||||
|
first = code; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
int inCode = code; |
||||
|
if (code == availableCode) |
||||
|
{ |
||||
|
this.pixelStack[top++] = (byte)first; |
||||
|
|
||||
|
code = oldCode; |
||||
|
} |
||||
|
|
||||
|
while (code > clearCode) |
||||
|
{ |
||||
|
this.pixelStack[top++] = this.suffix[code]; |
||||
|
code = this.prefix[code]; |
||||
|
} |
||||
|
|
||||
|
first = this.suffix[code]; |
||||
|
|
||||
|
this.pixelStack[top++] = this.suffix[code]; |
||||
|
|
||||
|
// Fix for Gifs that have "deferred clear code" as per here :
|
||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918
|
||||
|
if (availableCode < MaxStackSize) |
||||
|
{ |
||||
|
this.prefix[availableCode] = oldCode; |
||||
|
this.suffix[availableCode] = first; |
||||
|
availableCode++; |
||||
|
if (availableCode == codeMask + 1 && availableCode < MaxStackSize) |
||||
|
{ |
||||
|
codeSize++; |
||||
|
codeMask = (1 << codeSize) - 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
oldCode = inCode; |
||||
|
} |
||||
|
|
||||
|
// Pop a pixel off the pixel stack.
|
||||
|
top--; |
||||
|
|
||||
|
// Clear missing pixels
|
||||
|
pixels[xyz++] = (byte)this.pixelStack[top]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
||||
|
this.Dispose(true); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reads the next data block from the stream. For consistency with the GIF decoder,
|
||||
|
/// the image is read in blocks - For TIFF this is always a maximum of 255
|
||||
|
/// </summary>
|
||||
|
/// <param name="buffer">The buffer to store the block in.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="T:byte[]"/>.
|
||||
|
/// </returns>
|
||||
|
private int ReadBlock(byte[] buffer) |
||||
|
{ |
||||
|
return this.stream.Read(buffer, 0, 255); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Disposes the object and frees resources for the Garbage Collector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="disposing">If true, the object gets disposed.</param>
|
||||
|
private void Dispose(bool disposing) |
||||
|
{ |
||||
|
if (this.isDisposed) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (disposing) |
||||
|
{ |
||||
|
ArrayPool<int>.Shared.Return(this.prefix); |
||||
|
ArrayPool<int>.Shared.Return(this.suffix); |
||||
|
ArrayPool<int>.Shared.Return(this.pixelStack); |
||||
|
} |
||||
|
|
||||
|
this.isDisposed = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,496 @@ |
|||||
|
// <copyright file="TiffLzwEncoder.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Formats.Tiff |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.IO; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00
|
||||
|
/// <para>
|
||||
|
/// GIFCOMPR.C - GIF Image compression routines
|
||||
|
/// </para>
|
||||
|
/// <para>
|
||||
|
/// Lempel-Ziv compression based on 'compress'. GIF modifications by
|
||||
|
/// David Rowley (mgardi@watdcsu.waterloo.edu)
|
||||
|
/// </para>
|
||||
|
/// GIF Image compression - modified 'compress'
|
||||
|
/// <para>
|
||||
|
/// Based on: compress.c - File compression ala IEEE Computer, June 1984.
|
||||
|
/// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
|
||||
|
/// Jim McKie (decvax!mcvax!jim)
|
||||
|
/// Steve Davies (decvax!vax135!petsd!peora!srd)
|
||||
|
/// Ken Turkowski (decvax!decwrl!turtlevax!ken)
|
||||
|
/// James A. Woods (decvax!ihnp4!ames!jaw)
|
||||
|
/// Joe Orost (decvax!vax135!petsd!joe)
|
||||
|
/// </para>
|
||||
|
/// <para>
|
||||
|
/// This code is based on the <see cref="LzwEncoder"/> used for GIF encoding. There is potential
|
||||
|
/// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW
|
||||
|
/// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is
|
||||
|
/// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial
|
||||
|
/// byte indicating the length of the sub-block. In TIFF the data is written as a single block
|
||||
|
/// with no length indicator (this can be determined from the 'StripByteCounts' entry).
|
||||
|
/// </para>
|
||||
|
/// </remarks>
|
||||
|
internal sealed class TiffLzwEncoder : IDisposable |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The end-of-file marker
|
||||
|
/// </summary>
|
||||
|
private const int Eof = -1; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The maximum number of bits.
|
||||
|
/// </summary>
|
||||
|
private const int Bits = 12; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 80% occupancy
|
||||
|
/// </summary>
|
||||
|
private const int HashSize = 5003; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Mask used when shifting pixel values
|
||||
|
/// </summary>
|
||||
|
private static readonly int[] Masks = |
||||
|
{ |
||||
|
0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, |
||||
|
0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The working pixel array
|
||||
|
/// </summary>
|
||||
|
private readonly byte[] pixelArray; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The initial code size.
|
||||
|
/// </summary>
|
||||
|
private readonly int initialCodeSize; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The hash table.
|
||||
|
/// </summary>
|
||||
|
private readonly int[] hashTable; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The code table.
|
||||
|
/// </summary>
|
||||
|
private readonly int[] codeTable; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Define the storage for the packet accumulator.
|
||||
|
/// </summary>
|
||||
|
private readonly byte[] accumulators = new byte[256]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A value indicating whether this instance of the given entity has been disposed.
|
||||
|
/// </summary>
|
||||
|
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
|
||||
|
/// <remarks>
|
||||
|
/// If the entity is disposed, it must not be disposed a second
|
||||
|
/// time. The isDisposed field is set the first time the entity
|
||||
|
/// is disposed. If the isDisposed field is true, then the Dispose()
|
||||
|
/// method will not dispose again. This help not to prolong the entity's
|
||||
|
/// life in the Garbage Collector.
|
||||
|
/// </remarks>
|
||||
|
private bool isDisposed; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The current pixel
|
||||
|
/// </summary>
|
||||
|
private int currentPixel; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Number of bits/code
|
||||
|
/// </summary>
|
||||
|
private int bitCount; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// User settable max # bits/code
|
||||
|
/// </summary>
|
||||
|
private int maxbits = Bits; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// maximum code, given bitCount
|
||||
|
/// </summary>
|
||||
|
private int maxcode; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// should NEVER generate this code
|
||||
|
/// </summary>
|
||||
|
private int maxmaxcode = 1 << Bits; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// For dynamic table sizing
|
||||
|
/// </summary>
|
||||
|
private int hsize = HashSize; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// First unused entry
|
||||
|
/// </summary>
|
||||
|
private int freeEntry; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Block compression parameters -- after all codes are used up,
|
||||
|
/// and compression rate changes, start over.
|
||||
|
/// </summary>
|
||||
|
private bool clearFlag; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 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.
|
||||
|
/// </summary>
|
||||
|
private int globalInitialBits; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The clear code.
|
||||
|
/// </summary>
|
||||
|
private int clearCode; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The end-of-file code.
|
||||
|
/// </summary>
|
||||
|
private int eofCode; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Output the given code.
|
||||
|
/// Inputs:
|
||||
|
/// code: A bitCount-bit integer. If == -1, then EOF. This assumes
|
||||
|
/// that bitCount =< wordsize - 1.
|
||||
|
/// Outputs:
|
||||
|
/// Outputs code to the file.
|
||||
|
/// Assumptions:
|
||||
|
/// Chars are 8 bits long.
|
||||
|
/// Algorithm:
|
||||
|
/// Maintain a BITS character long buffer (so that 8 codes will
|
||||
|
/// fit in it exactly). Use the VAX insv instruction to insert each
|
||||
|
/// code in turn. When the buffer fills up empty it and start over.
|
||||
|
/// </summary>
|
||||
|
private int currentAccumulator; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The current bits.
|
||||
|
/// </summary>
|
||||
|
private int currentBits; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Number of characters so far in this 'packet'
|
||||
|
/// </summary>
|
||||
|
private int accumulatorCount; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="TiffLzwEncoder"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="indexedPixels">The array of indexed pixels.</param>
|
||||
|
/// <param name="colorDepth">The color depth in bits.</param>
|
||||
|
public TiffLzwEncoder(byte[] indexedPixels, int colorDepth) |
||||
|
{ |
||||
|
this.pixelArray = indexedPixels; |
||||
|
this.initialCodeSize = Math.Max(2, colorDepth); |
||||
|
|
||||
|
this.hashTable = ArrayPool<int>.Shared.Rent(HashSize); |
||||
|
this.codeTable = ArrayPool<int>.Shared.Rent(HashSize); |
||||
|
Array.Clear(this.hashTable, 0, HashSize); |
||||
|
Array.Clear(this.codeTable, 0, HashSize); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Encodes and compresses the indexed pixels to the stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The stream to write to.</param>
|
||||
|
public void Encode(Stream stream) |
||||
|
{ |
||||
|
this.currentPixel = 0; |
||||
|
|
||||
|
// Compress and write the pixel data
|
||||
|
this.Compress(this.initialCodeSize + 1, stream); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
||||
|
this.Dispose(true); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the maximum code value
|
||||
|
/// </summary>
|
||||
|
/// <param name="bitCount">The number of bits</param>
|
||||
|
/// <returns>See <see cref="int"/></returns>
|
||||
|
private static int GetMaxcode(int bitCount) |
||||
|
{ |
||||
|
return (1 << bitCount) - 1; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Add a character to the end of the current packet, and if it is 254 characters,
|
||||
|
/// flush the packet to disk.
|
||||
|
/// </summary>
|
||||
|
/// <param name="c">The character to add.</param>
|
||||
|
/// <param name="stream">The stream to write to.</param>
|
||||
|
private void AddCharacter(byte c, Stream stream) |
||||
|
{ |
||||
|
this.accumulators[this.accumulatorCount++] = c; |
||||
|
if (this.accumulatorCount >= 254) |
||||
|
{ |
||||
|
this.FlushPacket(stream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Table clear for block compress
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The output stream.</param>
|
||||
|
private void ClearBlock(Stream stream) |
||||
|
{ |
||||
|
this.ResetCodeTable(this.hsize); |
||||
|
this.freeEntry = this.clearCode + 2; |
||||
|
this.clearFlag = true; |
||||
|
|
||||
|
this.Output(this.clearCode, stream); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reset the code table.
|
||||
|
/// </summary>
|
||||
|
/// <param name="size">The hash size.</param>
|
||||
|
private void ResetCodeTable(int size) |
||||
|
{ |
||||
|
for (int i = 0; i < size; ++i) |
||||
|
{ |
||||
|
this.hashTable[i] = -1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compress the packets to the stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="intialBits">The initial bits.</param>
|
||||
|
/// <param name="stream">The stream to write to.</param>
|
||||
|
private void Compress(int intialBits, Stream stream) |
||||
|
{ |
||||
|
int fcode; |
||||
|
int c; |
||||
|
int ent; |
||||
|
int hsizeReg; |
||||
|
int hshift; |
||||
|
|
||||
|
// Set up the globals: globalInitialBits - initial number of bits
|
||||
|
this.globalInitialBits = intialBits; |
||||
|
|
||||
|
// Set up the necessary values
|
||||
|
this.clearFlag = false; |
||||
|
this.bitCount = this.globalInitialBits; |
||||
|
this.maxcode = GetMaxcode(this.bitCount); |
||||
|
|
||||
|
this.clearCode = 1 << (intialBits - 1); |
||||
|
this.eofCode = this.clearCode + 1; |
||||
|
this.freeEntry = this.clearCode + 2; |
||||
|
|
||||
|
this.accumulatorCount = 0; // clear packet
|
||||
|
|
||||
|
ent = this.NextPixel(); |
||||
|
|
||||
|
hshift = 0; |
||||
|
for (fcode = this.hsize; fcode < 65536; fcode *= 2) |
||||
|
{ |
||||
|
++hshift; |
||||
|
} |
||||
|
|
||||
|
hshift = 8 - hshift; // set hash code range bound
|
||||
|
|
||||
|
hsizeReg = this.hsize; |
||||
|
|
||||
|
this.ResetCodeTable(hsizeReg); // clear hash table
|
||||
|
|
||||
|
this.Output(this.clearCode, stream); |
||||
|
|
||||
|
while ((c = this.NextPixel()) != Eof) |
||||
|
{ |
||||
|
fcode = (c << this.maxbits) + ent; |
||||
|
int i = (c << hshift) ^ ent /* = 0 */; |
||||
|
|
||||
|
if (this.hashTable[i] == fcode) |
||||
|
{ |
||||
|
ent = this.codeTable[i]; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// Non-empty slot
|
||||
|
if (this.hashTable[i] >= 0) |
||||
|
{ |
||||
|
int disp = hsizeReg - i; |
||||
|
if (i == 0) |
||||
|
{ |
||||
|
disp = 1; |
||||
|
} |
||||
|
|
||||
|
do |
||||
|
{ |
||||
|
if ((i -= disp) < 0) |
||||
|
{ |
||||
|
i += hsizeReg; |
||||
|
} |
||||
|
|
||||
|
if (this.hashTable[i] == fcode) |
||||
|
{ |
||||
|
ent = this.codeTable[i]; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
while (this.hashTable[i] >= 0); |
||||
|
|
||||
|
if (this.hashTable[i] == fcode) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.Output(ent, stream); |
||||
|
ent = c; |
||||
|
if (this.freeEntry < this.maxmaxcode) |
||||
|
{ |
||||
|
this.codeTable[i] = this.freeEntry++; // code -> hashtable
|
||||
|
this.hashTable[i] = fcode; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.ClearBlock(stream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Put out the final code.
|
||||
|
this.Output(ent, stream); |
||||
|
|
||||
|
this.Output(this.eofCode, stream); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Flush the packet to disk, and reset the accumulator.
|
||||
|
/// </summary>
|
||||
|
/// <param name="outStream">The output stream.</param>
|
||||
|
private void FlushPacket(Stream outStream) |
||||
|
{ |
||||
|
if (this.accumulatorCount > 0) |
||||
|
{ |
||||
|
outStream.Write(this.accumulators, 0, this.accumulatorCount); |
||||
|
this.accumulatorCount = 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Return the next pixel from the image
|
||||
|
/// </summary>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="int"/>
|
||||
|
/// </returns>
|
||||
|
private int NextPixel() |
||||
|
{ |
||||
|
if (this.currentPixel == this.pixelArray.Length) |
||||
|
{ |
||||
|
return Eof; |
||||
|
} |
||||
|
|
||||
|
this.currentPixel++; |
||||
|
return this.pixelArray[this.currentPixel - 1] & 0xff; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Output the current code to the stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="code">The code.</param>
|
||||
|
/// <param name="outs">The stream to write to.</param>
|
||||
|
private void Output(int code, Stream outs) |
||||
|
{ |
||||
|
this.currentAccumulator &= Masks[this.currentBits]; |
||||
|
|
||||
|
if (this.currentBits > 0) |
||||
|
{ |
||||
|
this.currentAccumulator |= code << this.currentBits; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.currentAccumulator = code; |
||||
|
} |
||||
|
|
||||
|
this.currentBits += this.bitCount; |
||||
|
|
||||
|
while (this.currentBits >= 8) |
||||
|
{ |
||||
|
this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); |
||||
|
this.currentAccumulator >>= 8; |
||||
|
this.currentBits -= 8; |
||||
|
} |
||||
|
|
||||
|
// If the next entry is going to be too big for the code size,
|
||||
|
// then increase it, if possible.
|
||||
|
if (this.freeEntry > this.maxcode || this.clearFlag) |
||||
|
{ |
||||
|
if (this.clearFlag) |
||||
|
{ |
||||
|
this.maxcode = GetMaxcode(this.bitCount = this.globalInitialBits); |
||||
|
this.clearFlag = false; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++this.bitCount; |
||||
|
this.maxcode = this.bitCount == this.maxbits |
||||
|
? this.maxmaxcode |
||||
|
: GetMaxcode(this.bitCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (code == this.eofCode) |
||||
|
{ |
||||
|
// At EOF, write the rest of the buffer.
|
||||
|
while (this.currentBits > 0) |
||||
|
{ |
||||
|
this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); |
||||
|
this.currentAccumulator >>= 8; |
||||
|
this.currentBits -= 8; |
||||
|
} |
||||
|
|
||||
|
this.FlushPacket(outs); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Disposes the object and frees resources for the Garbage Collector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="disposing">If true, the object gets disposed.</param>
|
||||
|
private void Dispose(bool disposing) |
||||
|
{ |
||||
|
if (this.isDisposed) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (disposing) |
||||
|
{ |
||||
|
ArrayPool<int>.Shared.Return(this.hashTable); |
||||
|
ArrayPool<int>.Shared.Return(this.codeTable); |
||||
|
} |
||||
|
|
||||
|
this.isDisposed = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// <copyright file="LzwTiffCompressionTests.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Tests |
||||
|
{ |
||||
|
using System.IO; |
||||
|
using Xunit; |
||||
|
|
||||
|
using ImageSharp.Formats; |
||||
|
using ImageSharp.Formats.Tiff; |
||||
|
|
||||
|
public class LzwTiffCompressionTests |
||||
|
{ |
||||
|
[Theory] |
||||
|
[InlineData(new byte[] { })] |
||||
|
[InlineData(new byte[] { 42 })] // One byte
|
||||
|
[InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes
|
||||
|
[InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes
|
||||
|
[InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence
|
||||
|
public void Decompress_ReadsData(byte[] data) |
||||
|
{ |
||||
|
using (Stream stream = CreateCompressedStream(data)) |
||||
|
{ |
||||
|
byte[] buffer = new byte[data.Length]; |
||||
|
|
||||
|
LzwTiffCompression.Decompress(stream, (int)stream.Length, buffer); |
||||
|
|
||||
|
Assert.Equal(data, buffer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static Stream CreateCompressedStream(byte[] data) |
||||
|
{ |
||||
|
Stream compressedStream = new MemoryStream(); |
||||
|
|
||||
|
using (var encoder = new TiffLzwEncoder(data, 8)) |
||||
|
{ |
||||
|
encoder.Encode(compressedStream); |
||||
|
} |
||||
|
|
||||
|
compressedStream.Seek(0, SeekOrigin.Begin); |
||||
|
return compressedStream; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue