diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs index 2188913bc3..ef99832f70 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs @@ -5,7 +5,6 @@ using System; using System.IO.Compression; using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -20,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// internal class DeflateTiffCompression : TiffBaseDecompressor { + private readonly bool isBigEndian; + /// /// Initializes a new instance of the class. /// @@ -27,10 +28,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// The image width. /// The bits used per pixel. /// The tiff predictor used. - public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor) - : base(memoryAllocator, width, bitsPerPixel, predictor) - { - } + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor, bool isBigEndian) + : base(memoryAllocator, width, bitsPerPixel, predictor) => this.isBigEndian = isBigEndian; /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors if (this.Predictor == TiffPredictor.Horizontal) { - HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel); + HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel, this.isBigEndian); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs index 7e75dd4f05..0fe1b60ecf 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs @@ -13,6 +13,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// internal class LzwTiffCompression : TiffBaseDecompressor { + private readonly bool isBigEndian; + /// /// Initializes a new instance of the class. /// @@ -20,10 +22,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// The image width. /// The bits used per pixel. /// The tiff predictor used. - public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor) - : base(memoryAllocator, width, bitsPerPixel, predictor) - { - } + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor, bool isBigEndian) + : base(memoryAllocator, width, bitsPerPixel, predictor) => this.isBigEndian = isBigEndian; /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) @@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors if (this.Predictor == TiffPredictor.Horizontal) { - HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel); + HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel, this.isBigEndian); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs index ae2f17dbb5..da84b51b39 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs @@ -2,9 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - +using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff.Compression @@ -20,12 +21,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression /// Buffer with decompressed pixel data. /// The width of the image or strip. /// Bits per pixel. - public static void Undo(Span pixelBytes, int width, int bitsPerPixel) + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public static void Undo(Span pixelBytes, int width, int bitsPerPixel, bool isBigEndian) { if (bitsPerPixel == 8) { Undo8Bit(pixelBytes, width); } + else if (bitsPerPixel == 16) + { + Undo16Bit(pixelBytes, width, isBigEndian); + } else if (bitsPerPixel == 24) { Undo24Bit(pixelBytes, width); @@ -110,6 +116,50 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression } } + private static void Undo16Bit(Span pixelBytes, int width, bool isBigEndian) + { + int rowBytesCount = width * 2; + int height = pixelBytes.Length / rowBytesCount; + if (isBigEndian) + { + for (int y = 0; y < height; y++) + { + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + ushort pixelValue = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + + for (int x = 1; x < width; x++) + { + Span rowSpan = rowBytes.Slice(offset, 2); + ushort diff = TiffUtils.ConvertToUShortBigEndian(rowSpan); + pixelValue += diff; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, pixelValue); + offset += 2; + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + ushort pixelValue = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + + for (int x = 1; x < width; x++) + { + Span rowSpan = rowBytes.Slice(offset, 2); + ushort diff = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + pixelValue += diff; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, pixelValue); + offset += 2; + } + } + } + } + private static void Undo24Bit(Span pixelBytes, int width) { int rowBytesCount = width * 3; diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs index a6d44f4d3c..964f0bf127 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs @@ -16,7 +16,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression int width, int bitsPerPixel, TiffPredictor predictor, - FaxCompressionOptions faxOptions) + FaxCompressionOptions faxOptions, + ByteOrder byteOrder) { switch (method) { @@ -32,11 +33,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression case TiffDecoderCompressionType.Deflate: DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); - return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor); + return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor, byteOrder == ByteOrder.BigEndian); case TiffDecoderCompressionType.Lzw: DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); - return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor); + return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor, byteOrder == ByteOrder.BigEndian); case TiffDecoderCompressionType.T4: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 63185619d4..2c7e8d0c8f 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -264,7 +264,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff stripBuffers[stripIndex] = this.memoryAllocator.Allocate(uncompressedStripSize); } - using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); + using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( + this.CompressionType, + this.memoryAllocator, + this.PhotometricInterpretation, + frame.Width, + bitsPerPixel, + this.Predictor, + this.FaxCompressionOptions, + this.byteOrder); TiffBasePlanarColorDecoder colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); @@ -314,7 +322,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff frame.Width, bitsPerPixel, this.Predictor, - this.FaxCompressionOptions); + this.FaxCompressionOptions, + this.byteOrder); TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.Configuration, this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs index 782a504d58..e9399e23b5 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression { var buffer = new byte[data.Length]; - using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None); + using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None, false); decompressor.Decompress(stream, 0, (uint)stream.Length, buffer); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs index bf585e9c8f..834d206cc7 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression using BufferedReadStream stream = CreateCompressedStream(data); var buffer = new byte[data.Length]; - using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None); + using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None, false); decompressor.Decompress(stream, 0, (uint)stream.Length, buffer); Assert.Equal(data, buffer); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 182fafa33e..bba27039dd 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -161,6 +161,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_16Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Flower16BitGrayPredictorBigEndian, PixelTypes.Rgba32)] + [WithFile(Flower16BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_16Bit_Gray_WithPredictor(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(Flower24BitGray, PixelTypes.Rgba32)] [WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 8eaaec4935..08b0b2901f 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -599,6 +599,8 @@ namespace SixLabors.ImageSharp.Tests public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff"; public const string Flower16BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-16_lsb.tiff"; public const string Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16.tiff"; + public const string Flower16BitGrayPredictorBigEndian = "Tiff/flower-minisblack-16_msb_lzw_predictor.tiff"; + public const string Flower16BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff"; public const string Flower24BitGray = "Tiff/flower-minisblack-24.tiff"; public const string Flower24BitGrayLittleEndian = "Tiff/flower-minisblack-24_lsb.tiff"; public const string Flower32BitGray = "Tiff/flower-minisblack-32.tiff"; diff --git a/tests/Images/Input/Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff b/tests/Images/Input/Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff new file mode 100644 index 0000000000..e28ccda483 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:263da6b84862af1bc23f2631e648b1a629b5571b253c82f3ea64f44e9b7b1fe6 +size 8478 diff --git a/tests/Images/Input/Tiff/flower-minisblack-16_msb_lzw_predictor.tiff b/tests/Images/Input/Tiff/flower-minisblack-16_msb_lzw_predictor.tiff new file mode 100644 index 0000000000..144f96887b --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-16_msb_lzw_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e67ac19cbdeb8585b204ee958edc7679a92c2b415a1a2c6051f14fe2966f933c +size 8504