Browse Source

Add support for LZW compression

pull/1570/head
Andrew Wilkinson 9 years ago
parent
commit
63218369f6
  1. 35
      src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs
  2. 5
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
  3. 9
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  4. 272
      src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs
  5. 496
      src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
  6. 2
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
  7. 47
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
  8. 4
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs

35
src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs

@ -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);
}
}
}
}

5
src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs

@ -24,5 +24,10 @@ namespace ImageSharp.Formats.Tiff
/// Image data is compressed using Deflate compression.
/// </summary>
Deflate = 2,
/// <summary>
/// Image data is compressed using LZW compression.
/// </summary>
Lzw = 3,
}
}

9
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -271,6 +271,12 @@ namespace ImageSharp.Formats
break;
}
case TiffCompression.Lzw:
{
this.CompressionType = TiffCompressionType.Lzw;
break;
}
default:
{
throw new NotSupportedException("The specified TIFF compression format is not supported.");
@ -505,6 +511,9 @@ namespace ImageSharp.Formats
case TiffCompressionType.Deflate:
DeflateTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer);
break;
case TiffCompressionType.Lzw:
LzwTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer);
break;
default:
throw new InvalidOperationException();
}

272
src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs

@ -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;
}
}
}

496
src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs

@ -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 =&lt; 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;
}
}
}

2
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs

@ -25,7 +25,7 @@ namespace ImageSharp.Tests
{
byte[] buffer = new byte[data.Length];
DeflateTiffCompression.Decompress(stream, data.Length, buffer);
DeflateTiffCompression.Decompress(stream, (int)stream.Length, buffer);
Assert.Equal(data, buffer);
}

47
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs

@ -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;
}
}
}

4
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs

@ -129,6 +129,8 @@ namespace ImageSharp.Tests
[InlineData(true, TiffCompression.Deflate, TiffCompressionType.Deflate)]
[InlineData(false, TiffCompression.OldDeflate, TiffCompressionType.Deflate)]
[InlineData(true, TiffCompression.OldDeflate, TiffCompressionType.Deflate)]
[InlineData(false, TiffCompression.Lzw, TiffCompressionType.Lzw)]
[InlineData(true, TiffCompression.Lzw, TiffCompressionType.Lzw)]
public void ReadImageFormat_DeterminesCorrectCompressionImplementation(bool isLittleEndian, ushort compression, int compressionType)
{
Stream stream = CreateTiffGenIfd()
@ -149,7 +151,6 @@ namespace ImageSharp.Tests
[InlineData(false, TiffCompression.ItuTRecT43)]
[InlineData(false, TiffCompression.ItuTRecT82)]
[InlineData(false, TiffCompression.Jpeg)]
[InlineData(false, TiffCompression.Lzw)]
[InlineData(false, TiffCompression.OldJpeg)]
[InlineData(false, 999)]
[InlineData(true, TiffCompression.Ccitt1D)]
@ -158,7 +159,6 @@ namespace ImageSharp.Tests
[InlineData(true, TiffCompression.ItuTRecT43)]
[InlineData(true, TiffCompression.ItuTRecT82)]
[InlineData(true, TiffCompression.Jpeg)]
[InlineData(true, TiffCompression.Lzw)]
[InlineData(true, TiffCompression.OldJpeg)]
[InlineData(true, 999)]
public void ReadImageFormat_ThrowsExceptionForUnsupportedCompression(bool isLittleEndian, ushort compression)

Loading…
Cancel
Save