diff --git a/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs index bba9739e22..f4caeb3c27 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs @@ -21,10 +21,8 @@ public LzwTiffCompression(MemoryAllocator allocator) public override void Decompress(Stream stream, int byteCount, Span buffer) { var subStream = new SubStream(stream, byteCount); - using (var decoder = new TiffLzwDecoder(subStream)) - { - decoder.DecodePixels(buffer.Length, 8, buffer); - } + var decoder = new TiffLzwDecoder(subStream, this.Allocator); + decoder.DecodePixels(buffer.Length, 8, buffer); } } } diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs index a490e7c0d9..2e95d7e5ab 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.IO; using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Tiff { @@ -19,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// 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 + internal sealed class TiffLzwDecoder { /// /// The max decoder pixel stack size. @@ -37,52 +38,23 @@ internal sealed class TiffLzwDecoder : IDisposable private readonly Stream stream; /// - /// The prefix buffer. + /// The memory allocator. /// - private readonly int[] prefix; + private readonly MemoryAllocator allocator; /// - /// 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 + /// 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) + /// The memory allocator. + /// is null. + public TiffLzwDecoder(Stream stream, MemoryAllocator allocator) { 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); + this.allocator = allocator; } /// @@ -95,6 +67,15 @@ public void DecodePixels(int length, int dataSize, Span pixels) { Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); + // Initialize buffers + using IMemoryOwner prefixMemory = this.allocator.Allocate(MaxStackSize, AllocationOptions.Clean); + using IMemoryOwner suffixMemory = this.allocator.Allocate(MaxStackSize, AllocationOptions.Clean); + using IMemoryOwner pixelStackMemory = this.allocator.Allocate(MaxStackSize + 1, AllocationOptions.Clean); + + Span prefix = prefixMemory.GetSpan(); + Span suffix = suffixMemory.GetSpan(); + Span pixelStack = pixelStackMemory.GetSpan(); + // Calculate the clear code. The value of the clear code is 2 ^ dataSize int clearCode = 1 << dataSize; @@ -111,54 +92,39 @@ public void DecodePixels(int length, int dataSize, Span pixels) int code; int oldCode = NullCode; int codeMask = (1 << codeSize) - 1; + + int inputByte = 0; 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; + prefix[code] = 0; + suffix[code] = (byte)code; } - byte[] buffer = new byte[255]; + // Decoding process 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; + // Get the next code + int data = inputByte & ((1 << bits) - 1); + while (bits < codeSize) + { + inputByte = this.stream.ReadByte(); + data = (data << 8) | inputByte; bits += 8; - bi++; - count--; - continue; } - // Get the next code - code = data & codeMask; - data >>= codeSize; + data >>= bits - codeSize; bits -= codeSize; + code = data & codeMask; // Interpret the code if (code > availableCode || code == endCode) @@ -178,7 +144,7 @@ public void DecodePixels(int length, int dataSize, Span pixels) if (oldCode == NullCode) { - this.pixelStack[top++] = this.suffix[code]; + pixelStack[top++] = suffix[code]; oldCode = code; first = code; continue; @@ -187,27 +153,27 @@ public void DecodePixels(int length, int dataSize, Span pixels) int inCode = code; if (code == availableCode) { - this.pixelStack[top++] = (byte)first; + pixelStack[top++] = (byte)first; code = oldCode; } while (code > clearCode) { - this.pixelStack[top++] = this.suffix[code]; - code = this.prefix[code]; + pixelStack[top++] = suffix[code]; + code = prefix[code]; } - first = this.suffix[code]; + first = suffix[code]; - this.pixelStack[top++] = this.suffix[code]; + pixelStack[top++] = 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; + prefix[availableCode] = oldCode; + suffix[availableCode] = first; availableCode++; if (availableCode == codeMask + 1 && availableCode < MaxStackSize) { @@ -223,49 +189,8 @@ public void DecodePixels(int length, int dataSize, Span pixels) top--; // Clear missing pixels - pixels[xyz++] = (byte)this.pixelStack[top]; + pixels[xyz++] = (byte)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; - } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index d9abc163a6..3a40d5ce2b 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -53,6 +53,27 @@ public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, } } + [Theory] + [InlineData(TestImages.Tiff.RgbLzw_NoPredictor_Multistrip, TiffByteOrder.LittleEndian)] + [InlineData(TestImages.Tiff.RgbLzw_NoPredictor_Multistrip_Motorola, TiffByteOrder.BigEndian)] + public void ByteOrder(string imagePath, TiffByteOrder expectedByteOrder) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo info = Image.Identify(stream); + + Assert.NotNull(info.Metadata); + Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder); + + // todo: it's not a mistake? + stream.Seek(0, SeekOrigin.Begin); + + using var img = Image.Load(stream); + Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder); + } + } + [Theory] [WithFileCollection(nameof(SingleTestImages), PixelTypes.Rgba32)] public void Decode(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f90324d64f..d66f1a5c71 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -504,7 +504,7 @@ public static class Tiff public const string Calliphora_PaletteUncompressed = "Tiff/Calliphora_palette_uncompressed.tiff"; public const string Calliphora_RgbDeflate_Predictor = "Tiff/Calliphora_rgb_deflate.tiff"; public const string Calliphora_RgbJpeg = "Tiff/Calliphora_rgb_jpeg.tiff"; - public const string Calliphora_RgbLzwe_Predictor = "Tiff/Calliphora_rgb_lzw.tiff"; + public const string Calliphora_RgbLzw_Predictor = "Tiff/Calliphora_rgb_lzw.tiff"; public const string Calliphora_RgbPackbits = "Tiff/Calliphora_rgb_packbits.tiff"; public const string Calliphora_RgbUncompressed = "Tiff/Calliphora_rgb_uncompressed.tiff"; @@ -516,6 +516,9 @@ public static class Tiff public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff"; public const string RgbJpeg = "Tiff/rgb_jpeg.tiff"; public const string RgbLzw_Predictor = "Tiff/rgb_lzw.tiff"; + public const string RgbLzw_NoPredictor_Multistrip = "Tiff/rgb_lzw_noPredictor_multistrip.tiff"; + public const string RgbLzw_NoPredictor_Multistrip_Motorola = "Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff"; + public const string RgbLzw_NoPredictor_Singlestrip_Motorola = "Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff"; public const string RgbLzwMultistrip_Predictor = "Tiff/rgb_lzw_multistrip.tiff"; public const string RgbPackbits = "Tiff/rgb_packbits.tiff"; public const string RgbPackbitsMultistrip = "Tiff/rgb_packbits_multistrip.tiff"; @@ -534,13 +537,13 @@ public static class Tiff public const string SampleMetadata = "Tiff/metadata_sample.tiff"; - public static readonly string[] All = { Calliphora_GrayscaleUncompressed, Calliphora_PaletteUncompressed, /*Calliphora_RgbDeflate_Predictor, Calliphora_RgbLzwe_Predictor, */ Calliphora_RgbPackbits, Calliphora_RgbUncompressed, GrayscaleDeflateMultistrip, GrayscaleUncompressed, PaletteDeflateMultistrip, PaletteUncompressed, /*RgbDeflate_Predictor,*/ RgbDeflateMultistrip, /*RgbJpeg,*/ /*RgbLzw_Predictor, RgbLzwMultistrip_Predictor,*/ RgbPackbits, RgbPackbitsMultistrip, RgbUncompressed, /* MultiframeLzw_Predictor, MultiFrameDifferentVariants, SampleMetadata,*/ SmallRgbDeflate, SmallRgbLzw }; + public static readonly string[] All = { Calliphora_GrayscaleUncompressed, Calliphora_PaletteUncompressed, /*Calliphora_RgbDeflate_Predictor, Calliphora_RgbLzwe_Predictor, */ Calliphora_RgbPackbits, Calliphora_RgbUncompressed, GrayscaleDeflateMultistrip, GrayscaleUncompressed, PaletteDeflateMultistrip, PaletteUncompressed, /*RgbDeflate_Predictor,*/ RgbDeflateMultistrip, /*RgbJpeg,*/ /*RgbLzw_Predictor, RgbLzwMultistrip_Predictor,*/ RgbLzw_NoPredictor_Multistrip, RgbLzw_NoPredictor_Multistrip_Motorola, RgbLzw_NoPredictor_Singlestrip_Motorola, RgbPackbits, RgbPackbitsMultistrip, RgbUncompressed, /* MultiframeLzw_Predictor, MultiFrameDifferentVariants, SampleMetadata,*/ SmallRgbDeflate, SmallRgbLzw, }; public static readonly string[] Multiframes = { MultiframeDeflateWithPreview /*MultiframeLzw_Predictor, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ }; public static readonly string[] Metadata = { SampleMetadata }; - public static readonly string[] NotSupported = { Calliphora_RgbJpeg, Calliphora_RgbDeflate_Predictor, Calliphora_RgbLzwe_Predictor, RgbDeflate_Predictor, RgbLzw_Predictor, RgbLzwMultistrip_Predictor, RgbJpeg, RgbUncompressedTiled, MultiframeLzw_Predictor, MultiframeDifferentSize, MultiframeDifferentVariants }; + public static readonly string[] NotSupported = { Calliphora_RgbJpeg, Calliphora_RgbDeflate_Predictor, Calliphora_RgbLzw_Predictor, RgbDeflate_Predictor, RgbLzw_Predictor, RgbLzwMultistrip_Predictor, RgbJpeg, RgbUncompressedTiled, MultiframeLzw_Predictor, MultiframeDifferentSize, MultiframeDifferentVariants }; } } } diff --git a/tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_rgb_small_lzw.png b/tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_rgb_small_lzw.png deleted file mode 100644 index 27837a6149..0000000000 Binary files a/tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_rgb_small_lzw.png and /dev/null differ diff --git a/tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_rgb_small_deflate.png b/tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_rgb_small_deflate.png deleted file mode 100644 index 11432e8465..0000000000 Binary files a/tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_rgb_small_deflate.png and /dev/null differ diff --git a/tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_rgb_small_lzw.png b/tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_rgb_small_lzw.png deleted file mode 100644 index 27837a6149..0000000000 Binary files a/tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_rgb_small_lzw.png and /dev/null differ diff --git a/tests/Images/Input/Tiff/issues/readme.md b/tests/Images/Input/Tiff/issues/readme.md deleted file mode 100644 index 1616a432cc..0000000000 --- a/tests/Images/Input/Tiff/issues/readme.md +++ /dev/null @@ -1,2 +0,0 @@ -SixLabors.ImageSharp.Tests.Formats.Tiff.TiffDecoderTests.Decode -damaged output files \ No newline at end of file diff --git a/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip.tiff b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip.tiff new file mode 100644 index 0000000000..6314e89896 Binary files /dev/null and b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip.tiff differ diff --git a/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff new file mode 100644 index 0000000000..497dfe9f9b Binary files /dev/null and b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff differ diff --git a/tests/Images/Input/Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff new file mode 100644 index 0000000000..0219c1751f Binary files /dev/null and b/tests/Images/Input/Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff differ