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)