diff --git a/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs
new file mode 100644
index 0000000000..ae4d22a715
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Tiff
+{
+ using System;
+ using System.Buffers;
+ using System.IO;
+ using System.IO.Compression;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Class to handle cases where TIFF image data is compressed using LZW compression.
+ ///
+ internal static class LzwTiffCompression
+ {
+ ///
+ /// Decompresses image data into the supplied buffer.
+ ///
+ /// The to read image data from.
+ /// The number of bytes to read from the input stream.
+ /// The output buffer for uncompressed data.
+ [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);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
index 6108194c41..3ea9270c87 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
@@ -24,5 +24,10 @@ namespace ImageSharp.Formats.Tiff
/// Image data is compressed using Deflate compression.
///
Deflate = 2,
+
+ ///
+ /// Image data is compressed using LZW compression.
+ ///
+ Lzw = 3,
}
}
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index f3a55412bf..942e510d34 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/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();
}
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs
new file mode 100644
index 0000000000..f6ad7b3a43
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs
@@ -0,0 +1,272 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Tiff
+{
+ using System;
+ using System.Buffers;
+ using System.IO;
+
+ ///
+ /// Decompresses and decodes data using the dynamic LZW algorithms.
+ ///
+ ///
+ /// This code is based on the 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).
+ ///
+ internal sealed class TiffLzwDecoder : IDisposable
+ {
+ ///
+ /// The max decoder pixel stack size.
+ ///
+ private const int MaxStackSize = 4096;
+
+ ///
+ /// The null code.
+ ///
+ private const int NullCode = -1;
+
+ ///
+ /// The stream to decode.
+ ///
+ private readonly Stream stream;
+
+ ///
+ /// The prefix buffer.
+ ///
+ private readonly int[] prefix;
+
+ ///
+ /// The suffix buffer.
+ ///
+ private readonly int[] suffix;
+
+ ///
+ /// The pixel stack buffer.
+ ///
+ private readonly int[] pixelStack;
+
+ ///
+ /// A value indicating whether this instance of the given entity has been disposed.
+ ///
+ /// if this instance has been disposed; otherwise, .
+ ///
+ /// 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.
+ ///
+ private bool isDisposed;
+
+ ///
+ /// Initializes a new instance of the class
+ /// and sets the stream, where the compressed data should be read from.
+ ///
+ /// The stream to read from.
+ /// is null.
+ public TiffLzwDecoder(Stream stream)
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ this.stream = stream;
+
+ this.prefix = ArrayPool.Shared.Rent(MaxStackSize);
+ this.suffix = ArrayPool.Shared.Rent(MaxStackSize);
+ this.pixelStack = ArrayPool.Shared.Rent(MaxStackSize + 1);
+
+ Array.Clear(this.prefix, 0, MaxStackSize);
+ Array.Clear(this.suffix, 0, MaxStackSize);
+ Array.Clear(this.pixelStack, 0, MaxStackSize + 1);
+ }
+
+ ///
+ /// Decodes and decompresses all pixel indices from the stream.
+ ///
+ /// The length of the compressed data.
+ /// Size of the data.
+ /// The pixel array to decode to.
+ 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];
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ this.Dispose(true);
+ }
+
+ ///
+ /// 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
+ ///
+ /// The buffer to store the block in.
+ ///
+ /// The .
+ ///
+ private int ReadBlock(byte[] buffer)
+ {
+ return this.stream.Read(buffer, 0, 255);
+ }
+
+ ///
+ /// Disposes the object and frees resources for the Garbage Collector.
+ ///
+ /// If true, the object gets disposed.
+ private void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ ArrayPool.Shared.Return(this.prefix);
+ ArrayPool.Shared.Return(this.suffix);
+ ArrayPool.Shared.Return(this.pixelStack);
+ }
+
+ this.isDisposed = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
new file mode 100644
index 0000000000..1ad7c3128c
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
@@ -0,0 +1,496 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Tiff
+{
+ using System;
+ using System.Buffers;
+ using System.IO;
+
+ ///
+ /// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
+ ///
+ ///
+ /// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00
+ ///
+ /// GIFCOMPR.C - GIF Image compression routines
+ ///
+ ///
+ /// Lempel-Ziv compression based on 'compress'. GIF modifications by
+ /// David Rowley (mgardi@watdcsu.waterloo.edu)
+ ///
+ /// 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)
+ ///
+ ///
+ /// This code is based on the 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).
+ ///
+ ///
+ internal sealed class TiffLzwEncoder : IDisposable
+ {
+ ///
+ /// The end-of-file marker
+ ///
+ private const int Eof = -1;
+
+ ///
+ /// The maximum number of bits.
+ ///
+ private const int Bits = 12;
+
+ ///
+ /// 80% occupancy
+ ///
+ private const int HashSize = 5003;
+
+ ///
+ /// Mask used when shifting pixel values
+ ///
+ private static readonly int[] Masks =
+ {
+ 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,
+ 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
+ };
+
+ ///
+ /// The working pixel array
+ ///
+ private readonly byte[] pixelArray;
+
+ ///
+ /// The initial code size.
+ ///
+ private readonly int initialCodeSize;
+
+ ///
+ /// The hash table.
+ ///
+ private readonly int[] hashTable;
+
+ ///
+ /// The code table.
+ ///
+ private readonly int[] codeTable;
+
+ ///
+ /// Define the storage for the packet accumulator.
+ ///
+ private readonly byte[] accumulators = new byte[256];
+
+ ///
+ /// A value indicating whether this instance of the given entity has been disposed.
+ ///
+ /// if this instance has been disposed; otherwise, .
+ ///
+ /// 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.
+ ///
+ private bool isDisposed;
+
+ ///
+ /// The current pixel
+ ///
+ private int currentPixel;
+
+ ///
+ /// Number of bits/code
+ ///
+ private int bitCount;
+
+ ///
+ /// User settable max # bits/code
+ ///
+ private int maxbits = Bits;
+
+ ///
+ /// maximum code, given bitCount
+ ///
+ private int maxcode;
+
+ ///
+ /// should NEVER generate this code
+ ///
+ private int maxmaxcode = 1 << Bits;
+
+ ///
+ /// For dynamic table sizing
+ ///
+ private int hsize = HashSize;
+
+ ///
+ /// First unused entry
+ ///
+ private int freeEntry;
+
+ ///
+ /// 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;
+
+ ///
+ /// The clear code.
+ ///
+ private int clearCode;
+
+ ///
+ /// The end-of-file code.
+ ///
+ private int eofCode;
+
+ ///
+ /// 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.
+ ///
+ private int currentAccumulator;
+
+ ///
+ /// The current bits.
+ ///
+ private int currentBits;
+
+ ///
+ /// Number of characters so far in this 'packet'
+ ///
+ private int accumulatorCount;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array of indexed pixels.
+ /// The color depth in bits.
+ public TiffLzwEncoder(byte[] indexedPixels, int colorDepth)
+ {
+ this.pixelArray = indexedPixels;
+ this.initialCodeSize = Math.Max(2, colorDepth);
+
+ this.hashTable = ArrayPool.Shared.Rent(HashSize);
+ this.codeTable = ArrayPool.Shared.Rent(HashSize);
+ Array.Clear(this.hashTable, 0, HashSize);
+ Array.Clear(this.codeTable, 0, HashSize);
+ }
+
+ ///
+ /// Encodes and compresses the indexed pixels to the stream.
+ ///
+ /// The stream to write to.
+ public void Encode(Stream stream)
+ {
+ this.currentPixel = 0;
+
+ // Compress and write the pixel data
+ this.Compress(this.initialCodeSize + 1, stream);
+ }
+
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ this.Dispose(true);
+ }
+
+ ///
+ /// Gets the maximum code value
+ ///
+ /// The number of bits
+ /// See
+ private static int GetMaxcode(int bitCount)
+ {
+ return (1 << bitCount) - 1;
+ }
+
+ ///
+ /// Add a character to the end of the current packet, and if it is 254 characters,
+ /// flush the packet to disk.
+ ///
+ /// The character to add.
+ /// The stream to write to.
+ private void AddCharacter(byte c, Stream stream)
+ {
+ this.accumulators[this.accumulatorCount++] = c;
+ if (this.accumulatorCount >= 254)
+ {
+ this.FlushPacket(stream);
+ }
+ }
+
+ ///
+ /// Table clear for block compress
+ ///
+ /// The output stream.
+ private void ClearBlock(Stream stream)
+ {
+ this.ResetCodeTable(this.hsize);
+ this.freeEntry = this.clearCode + 2;
+ this.clearFlag = true;
+
+ this.Output(this.clearCode, stream);
+ }
+
+ ///
+ /// Reset the code table.
+ ///
+ /// The hash size.
+ private void ResetCodeTable(int size)
+ {
+ for (int i = 0; i < size; ++i)
+ {
+ this.hashTable[i] = -1;
+ }
+ }
+
+ ///
+ /// Compress the packets to the stream.
+ ///
+ /// The initial bits.
+ /// The stream to write to.
+ 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);
+ }
+
+ ///
+ /// Flush the packet to disk, and reset the accumulator.
+ ///
+ /// The output stream.
+ private void FlushPacket(Stream outStream)
+ {
+ if (this.accumulatorCount > 0)
+ {
+ outStream.Write(this.accumulators, 0, this.accumulatorCount);
+ this.accumulatorCount = 0;
+ }
+ }
+
+ ///
+ /// Return the next pixel from the image
+ ///
+ ///
+ /// The
+ ///
+ private int NextPixel()
+ {
+ if (this.currentPixel == this.pixelArray.Length)
+ {
+ return Eof;
+ }
+
+ this.currentPixel++;
+ return this.pixelArray[this.currentPixel - 1] & 0xff;
+ }
+
+ ///
+ /// Output the current code to the stream.
+ ///
+ /// The code.
+ /// The stream to write to.
+ 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);
+ }
+ }
+
+ ///
+ /// Disposes the object and frees resources for the Garbage Collector.
+ ///
+ /// If true, the object gets disposed.
+ private void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ ArrayPool.Shared.Return(this.hashTable);
+ ArrayPool.Shared.Return(this.codeTable);
+ }
+
+ this.isDisposed = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
index e637008806..7021684d56 100644
--- a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
+++ b/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);
}
diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
new file mode 100644
index 0000000000..e54d0dd5d1
--- /dev/null
+++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
@@ -0,0 +1,47 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs
index c779a631e0..9f90e691d2 100644
--- a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs
+++ b/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)