From 1aea0061ebd9e3fb34359b18321678d0b236999c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 7 Aug 2021 19:23:32 +0200 Subject: [PATCH 1/4] Add support for horizontal predictor for 16 bit gray tiff's --- .../Decompressors/DeflateTiffCompression.cs | 12 ++--- .../Decompressors/LzwTiffCompression.cs | 11 ++-- .../Tiff/Compression/HorizontalPredictor.cs | 54 ++++++++++++++++++- .../Compression/TiffDecompressorsFactory.cs | 7 +-- .../Formats/Tiff/TiffDecoderCore.cs | 13 ++++- .../DeflateTiffCompressionTests.cs | 2 +- .../Compression/LzwTiffCompressionTests.cs | 2 +- .../Formats/Tiff/TiffDecoderTests.cs | 6 +++ tests/ImageSharp.Tests/TestImages.cs | 2 + ...lower-minisblack-16_lsb_lzw_predictor.tiff | 3 ++ ...lower-minisblack-16_msb_lzw_predictor.tiff | 3 ++ 11 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 tests/Images/Input/Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff create mode 100644 tests/Images/Input/Tiff/flower-minisblack-16_msb_lzw_predictor.tiff 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.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 @@ internal class DeflateTiffCompression : TiffBaseDecompressor /// 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 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa 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 @@ internal class LzwTiffCompression : TiffBaseDecompressor /// 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 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa 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 @@ internal static class HorizontalPredictor /// 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 @@ private static void Undo8Bit(Span pixelBytes, int width) } } + 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 @@ public static TiffBaseDecompressor Create( int width, int bitsPerPixel, TiffPredictor predictor, - FaxCompressionOptions faxOptions) + FaxCompressionOptions faxOptions, + ByteOrder byteOrder) { switch (method) { @@ -32,11 +33,11 @@ public static TiffBaseDecompressor Create( 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 @@ private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStr 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 @@ private void DecodeStripsChunky(ImageFrame frame, int rowsPerStr 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 @@ public void Compress_Decompress_Roundtrip_Works(byte[] data) { 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 @@ public void Compress_Decompress_Roundtrip_Works(byte[] data) 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 @@ public void TiffDecoder_CanDecode_14Bit_Gray(TestImageProvider p 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 @@ public static class Tiff 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 From 78efd684d78443fb612d6cc1cfd6d9a9a8cd9ab4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 7 Aug 2021 20:40:32 +0200 Subject: [PATCH 2/4] Add support for horizontal predictor for 32 bit gray tiff's --- .../Decompressors/DeflateTiffCompression.cs | 14 +++- .../Decompressors/LzwTiffCompression.cs | 14 +++- .../Tiff/Compression/HorizontalPredictor.cs | 82 +++++++++++++++---- .../Compression/TiffDecompressorsFactory.cs | 6 +- .../BlackIsZero32TiffColor{TPixel}.cs | 1 - .../Formats/Tiff/TiffDecoderCore.cs | 2 + .../DeflateTiffCompressionTests.cs | 3 +- .../Compression/LzwTiffCompressionTests.cs | 3 +- .../Formats/Tiff/TiffDecoderTests.cs | 11 +++ tests/ImageSharp.Tests/TestImages.cs | 2 + ...r-minisblack-32_lsb_deflate_predictor.tiff | 3 + ...r-minisblack-32_msb_deflate_predictor.tiff | 3 + 12 files changed, 118 insertions(+), 26 deletions(-) create mode 100644 tests/Images/Input/Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff create mode 100644 tests/Images/Input/Tiff/flower-minisblack-32_msb_deflate_predictor.tiff 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 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 @@ internal class DeflateTiffCompression : TiffBaseDecompressor { 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 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa 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 @@ internal class LzwTiffCompression : TiffBaseDecompressor { 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 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa 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.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 @@ internal static class HorizontalPredictor /// /// 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 @@ private static void ApplyHorizontalPrediction8Bit(Span rows, int width) } } - 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 @@ private static void Undo8Bit(Span pixelBytes, int width) } } - 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 @@ private static void Undo16Bit(Span pixelBytes, int width, bool isBigEndian } } - 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 @@ public static TiffBaseDecompressor Create( TiffPhotometricInterpretation photometricInterpretation, int width, int bitsPerPixel, + TiffColorType colorType, TiffPredictor predictor, FaxCompressionOptions faxOptions, ByteOrder byteOrder) @@ -33,11 +35,11 @@ public static TiffBaseDecompressor Create( 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 @@ private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStr this.PhotometricInterpretation, frame.Width, bitsPerPixel, + this.ColorType, this.Predictor, this.FaxCompressionOptions, this.byteOrder); @@ -321,6 +322,7 @@ private void DecodeStripsChunky(ImageFrame frame, int rowsPerStr 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 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 @@ public void Compress_Decompress_Roundtrip_Works(byte[] data) { 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 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 @@ public void Compress_Decompress_Roundtrip_Works(byte[] data) 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 @@ public void TiffDecoder_CanDecode_32Bit_Gray(TestImageProvider p 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 @@ public static class Tiff 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 From 484ec85ba7fec5a38bbc4dc82ae1ed17a741f97f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 7 Aug 2021 21:14:30 +0200 Subject: [PATCH 3/4] Add support for horizontal predictor with 16 bit color tiff's --- .../Tiff/Compression/HorizontalPredictor.cs | 79 +++++++++++++++++++ .../Formats/Tiff/TiffDecoderTests.cs | 6 ++ tests/ImageSharp.Tests/TestImages.cs | 2 + ...lower-rgb-contig-16_lsb_zip_predictor.tiff | 3 + ...lower-rgb-contig-16_msb_zip_predictor.tiff | 3 + 5 files changed, 93 insertions(+) create mode 100644 tests/Images/Input/Tiff/flower-rgb-contig-16_lsb_zip_predictor.tiff create mode 100644 tests/Images/Input/Tiff/flower-rgb-contig-16_msb_zip_predictor.tiff diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs index 3685167d53..b4941c5396 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs @@ -43,6 +43,9 @@ public static void Undo(Span pixelBytes, int width, TiffColorType colorTyp case TiffColorType.Rgb888: UndoRgb24Bit(pixelBytes, width); break; + case TiffColorType.Rgb161616: + UndoRgb48Bit(pixelBytes, width, isBigEndian); + break; } } @@ -236,5 +239,81 @@ private static void UndoRgb24Bit(Span pixelBytes, int width) } } } + + private static void UndoRgb48Bit(Span pixelBytes, int width, bool isBigEndian) + { + int rowBytesCount = width * 6; + 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 r = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort g = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort b = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + + for (int x = 1; x < width; x++) + { + Span rowSpan = rowBytes.Slice(offset, 2); + ushort deltaR = TiffUtils.ConvertToUShortBigEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r); + offset += 2; + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaG = TiffUtils.ConvertToUShortBigEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g); + offset += 2; + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaB = TiffUtils.ConvertToUShortBigEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b); + offset += 2; + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + ushort r = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort g = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort b = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + + for (int x = 1; x < width; x++) + { + Span rowSpan = rowBytes.Slice(offset, 2); + ushort deltaR = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r); + offset += 2; + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaG = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g); + offset += 2; + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaB = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b); + offset += 2; + } + } + } + } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index a8460e6c93..f07924791e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -228,6 +228,12 @@ public void TiffDecoder_CanDecode_42Bit(TestImageProvider provid public void TiffDecoder_CanDecode_48Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)] + [WithFile(FlowerRgb161616PredictorLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_48Bit_WithPredictor(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index ff6c198c4d..1215574c57 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -573,6 +573,8 @@ public static class Tiff public const string FlowerRgb242424ContiguousLittleEndian = "Tiff/flower-rgb-contig-24_lsb.tiff"; public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff"; public const string FlowerRgb161616ContiguousLittleEndian = "Tiff/flower-rgb-contig-16_lsb.tiff"; + public const string FlowerRgb161616PredictorBigEndian = "Tiff/flower-rgb-contig-16_msb_zip_predictor.tiff"; + public const string FlowerRgb161616PredictorLittleEndian = "Tiff/flower-rgb-contig-16_lsb_zip_predictor.tiff"; public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff"; public const string FlowerRgb161616PlanarLittleEndian = "Tiff/flower-rgb-planar-16_lsb.tiff"; public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff"; diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-16_lsb_zip_predictor.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-16_lsb_zip_predictor.tiff new file mode 100644 index 0000000000..69fe133f8c --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-16_lsb_zip_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb465c21009def345d192319ba13ba2e1e537310eec3ab2cce680f0d111a4f18 +size 18256 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-16_msb_zip_predictor.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-16_msb_zip_predictor.tiff new file mode 100644 index 0000000000..231de2eaad --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-16_msb_zip_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c098beb9c8f250e9d4f6eb66a3a42f3852ad3ca86aabbdbacfa897d93ec8bc0d +size 18254 From f8f0c101c87dc76ed63ee77ce9ce410da234fbfe Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 7 Aug 2021 21:28:39 +0200 Subject: [PATCH 4/4] Add support for horizontal predictor with 32 bit color tiff's --- .../Tiff/Compression/HorizontalPredictor.cs | 79 +++++++++++++++++++ .../Formats/Tiff/TiffDecoderTests.cs | 11 +++ tests/ImageSharp.Tests/TestImages.cs | 2 + ...lower-rgb-contig-32_lsb_zip_predictor.tiff | 3 + ...lower-rgb-contig-32_msb_zip_predictor.tiff | 3 + 5 files changed, 98 insertions(+) create mode 100644 tests/Images/Input/Tiff/flower-rgb-contig-32_lsb_zip_predictor.tiff create mode 100644 tests/Images/Input/Tiff/flower-rgb-contig-32_msb_zip_predictor.tiff diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs index b4941c5396..e2dbc6ca99 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs @@ -46,6 +46,9 @@ public static void Undo(Span pixelBytes, int width, TiffColorType colorTyp case TiffColorType.Rgb161616: UndoRgb48Bit(pixelBytes, width, isBigEndian); break; + case TiffColorType.Rgb323232: + UndoRgb96Bit(pixelBytes, width, isBigEndian); + break; } } @@ -315,5 +318,81 @@ private static void UndoRgb48Bit(Span pixelBytes, int width, bool isBigEnd } } } + + private static void UndoRgb96Bit(Span pixelBytes, int width, bool isBigEndian) + { + int rowBytesCount = width * 12; + 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 r = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint g = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint b = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + + for (int x = 1; x < width; x++) + { + Span rowSpan = rowBytes.Slice(offset, 4); + uint deltaR = TiffUtils.ConvertToUIntBigEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r); + offset += 4; + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaG = TiffUtils.ConvertToUIntBigEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g); + offset += 4; + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaB = TiffUtils.ConvertToUIntBigEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b); + offset += 4; + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + uint r = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint g = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint b = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + + for (int x = 1; x < width; x++) + { + Span rowSpan = rowBytes.Slice(offset, 4); + uint deltaR = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r); + offset += 4; + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaG = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g); + offset += 4; + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaB = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b); + offset += 4; + } + } + } + } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index f07924791e..5c235dfcf8 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -260,6 +260,17 @@ public void TiffDecoder_CanDecode_96Bit(TestImageProvider provid image.DebugSave(provider); } + [Theory] + [WithFile(FlowerRgb323232PredictorBigEndian, PixelTypes.Rgba32)] + [WithFile(FlowerRgb323232PredictorLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_96Bit_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(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)] [WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 1215574c57..bde2de5b88 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -567,6 +567,8 @@ public static class Tiff public const string FlowerRgb323232ContiguousLittleEndian = "Tiff/flower-rgb-contig-32_lsb.tiff"; public const string FlowerRgb323232Planar = "Tiff/flower-rgb-planar-32.tiff"; public const string FlowerRgb323232PlanarLittleEndian = "Tiff/flower-rgb-planar-32_lsb.tiff"; + public const string FlowerRgb323232PredictorBigEndian = "Tiff/flower-rgb-contig-32_msb_zip_predictor.tiff"; + public const string FlowerRgb323232PredictorLittleEndian = "Tiff/flower-rgb-contig-32_lsb_zip_predictor.tiff"; public const string FlowerRgb242424Planar = "Tiff/flower-rgb-planar-24.tiff"; public const string FlowerRgb242424PlanarLittleEndian = "Tiff/flower-rgb-planar-24_lsb.tiff"; public const string FlowerRgb242424Contiguous = "Tiff/flower-rgb-contig-24.tiff"; diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-32_lsb_zip_predictor.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-32_lsb_zip_predictor.tiff new file mode 100644 index 0000000000..ca3fee9da5 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-32_lsb_zip_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ed60ad91ea70db01789f9dd37745d8a5ab86f72b98637cf2007b4e28a71976f +size 37632 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-32_msb_zip_predictor.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-32_msb_zip_predictor.tiff new file mode 100644 index 0000000000..07a1ae74f7 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-32_msb_zip_predictor.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1086c2ec568c5c3c2b08fcc66691fab017eb6fad109373a1dadd2e12fae86bc8 +size 37630