diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs index ef99832f70..bb57853d53 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs @@ -6,6 +6,7 @@ using System.IO.Compression; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -21,16 +22,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { private readonly bool isBigEndian; + private readonly TiffColorType colorType; + /// /// Initializes a new instance of the class. /// /// The memoryAllocator to use for buffer allocations. /// The image width. /// The bits used per pixel. + /// The color type of the pixel data. /// The tiff predictor used. /// 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; + public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian) + : base(memoryAllocator, width, bitsPerPixel, predictor) + { + this.colorType = colorType; + this.isBigEndian = isBigEndian; + } /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) @@ -62,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors if (this.Predictor == TiffPredictor.Horizontal) { - HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel, this.isBigEndian); + HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs index 0fe1b60ecf..2e89396075 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs @@ -3,6 +3,7 @@ using System; using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -15,16 +16,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { private readonly bool isBigEndian; + private readonly TiffColorType colorType; + /// /// Initializes a new instance of the class. /// /// The memoryAllocator to use for buffer allocations. /// The image width. /// The bits used per pixel. + /// The color type of the pixel data. /// The tiff predictor used. /// 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; + public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian) + : base(memoryAllocator, width, bitsPerPixel, predictor) + { + this.colorType = colorType; + this.isBigEndian = isBigEndian; + } /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) @@ -34,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors if (this.Predictor == TiffPredictor.Horizontal) { - HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel, this.isBigEndian); + HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs index da84b51b39..3685167d53 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs @@ -5,6 +5,7 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.PixelFormats; @@ -20,21 +21,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression /// /// Buffer with decompressed pixel data. /// The width of the image or strip. - /// Bits per pixel. + /// The color type of the pixel data. /// 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) + public static void Undo(Span pixelBytes, int width, TiffColorType colorType, bool isBigEndian) { - if (bitsPerPixel == 8) - { - Undo8Bit(pixelBytes, width); - } - else if (bitsPerPixel == 16) + switch (colorType) { - Undo16Bit(pixelBytes, width, isBigEndian); - } - else if (bitsPerPixel == 24) - { - Undo24Bit(pixelBytes, width); + case TiffColorType.BlackIsZero8: + case TiffColorType.WhiteIsZero8: + case TiffColorType.PaletteColor: + UndoGray8Bit(pixelBytes, width); + break; + case TiffColorType.BlackIsZero16: + case TiffColorType.WhiteIsZero16: + UndoGray16Bit(pixelBytes, width, isBigEndian); + break; + case TiffColorType.BlackIsZero32: + case TiffColorType.WhiteIsZero32: + UndoGray32Bit(pixelBytes, width, isBigEndian); + break; + case TiffColorType.Rgb888: + UndoRgb24Bit(pixelBytes, width); + break; } } @@ -99,7 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression } } - private static void Undo8Bit(Span pixelBytes, int width) + private static void UndoGray8Bit(Span pixelBytes, int width) { int rowBytesCount = width; int height = pixelBytes.Length / rowBytesCount; @@ -116,7 +124,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression } } - private static void Undo16Bit(Span pixelBytes, int width, bool isBigEndian) + private static void UndoGray16Bit(Span pixelBytes, int width, bool isBigEndian) { int rowBytesCount = width * 2; int height = pixelBytes.Length / rowBytesCount; @@ -160,7 +168,51 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression } } - private static void Undo24Bit(Span pixelBytes, int width) + private static void UndoGray32Bit(Span pixelBytes, int width, bool isBigEndian) + { + int rowBytesCount = width * 4; + int height = pixelBytes.Length / rowBytesCount; + if (isBigEndian) + { + for (int y = 0; y < height; y++) + { + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + uint pixelValue = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + + for (int x = 1; x < width; x++) + { + Span rowSpan = rowBytes.Slice(offset, 4); + uint diff = TiffUtils.ConvertToUIntBigEndian(rowSpan); + pixelValue += diff; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, pixelValue); + offset += 4; + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + uint pixelValue = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + + for (int x = 1; x < width; x++) + { + Span rowSpan = rowBytes.Slice(offset, 4); + uint diff = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + pixelValue += diff; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, pixelValue); + offset += 4; + } + } + } + } + + private static void UndoRgb24Bit(Span pixelBytes, int width) { int rowBytesCount = width * 3; int height = pixelBytes.Length / rowBytesCount; diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs index 964f0bf127..083e53950a 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Tiff.Compression @@ -15,6 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression TiffPhotometricInterpretation photometricInterpretation, int width, int bitsPerPixel, + TiffColorType colorType, TiffPredictor predictor, FaxCompressionOptions faxOptions, ByteOrder byteOrder) @@ -33,11 +35,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, byteOrder == ByteOrder.BigEndian); + return new DeflateTiffCompression(allocator, width, bitsPerPixel, colorType, 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, byteOrder == ByteOrder.BigEndian); + return new LzwTiffCompression(allocator, width, bitsPerPixel, colorType, 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/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs index 862756bc42..f54a794840 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 2c7e8d0c8f..37bc01ad18 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -270,6 +270,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.PhotometricInterpretation, frame.Width, bitsPerPixel, + this.ColorType, this.Predictor, this.FaxCompressionOptions, this.byteOrder); @@ -321,6 +322,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.PhotometricInterpretation, frame.Width, bitsPerPixel, + this.ColorType, this.Predictor, this.FaxCompressionOptions, this.byteOrder); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs index e9399e23b5..c93a2018df 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs @@ -5,6 +5,7 @@ using System.IO; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.IO; using Xunit; @@ -26,7 +27,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, false); + using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, 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 834d206cc7..5ea75d9a84 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs @@ -5,6 +5,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.IO; using Xunit; @@ -38,7 +39,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, false); + using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, 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 bba27039dd..a8460e6c93 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -197,6 +197,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff image.DebugSave(provider); } + [Theory] + [WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)] + [WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_32Bit_Gray_WithPredictor(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + [Theory] [WithFile(FlowerRgb121212Contiguous, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_36Bit(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 08b0b2901f..ff6c198c4d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -607,6 +607,8 @@ namespace SixLabors.ImageSharp.Tests public const string Flower32BitGrayLittleEndian = "Tiff/flower-minisblack-32_lsb.tiff"; public const string Flower32BitGrayMinIsWhite = "Tiff/flower-miniswhite-32.tiff"; public const string Flower32BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-32_lsb.tiff"; + public const string Flower32BitGrayPredictorBigEndian = "Tiff/flower-minisblack-32_msb_deflate_predictor.tiff"; + public const string Flower32BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff"; public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; diff --git a/tests/Images/Input/Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff b/tests/Images/Input/Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff new file mode 100644 index 0000000000..2dc9e8092a --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5e7998cc985ef11ab9da410f18dcfb6b9a3169fb1ec01f9e61aa38d8ee4cfb6 +size 12704 diff --git a/tests/Images/Input/Tiff/flower-minisblack-32_msb_deflate_predictor.tiff b/tests/Images/Input/Tiff/flower-minisblack-32_msb_deflate_predictor.tiff new file mode 100644 index 0000000000..f87c74c721 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-32_msb_deflate_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:630e7f46655b6e61c4de7d56946a3a9225db68f776f9062ff2d5372547cc7c02 +size 12704