From 4eb4e540125f64a1fd7747b4af3598c8897e8dd7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 4 Aug 2021 20:38:43 +0200 Subject: [PATCH 1/8] Add support for decoding tiff's encoded with LeastSignificantBitFirst compression --- .../ModifiedHuffmanTiffCompression.cs | 7 +++-- .../Compression/Decompressors/T4BitReader.cs | 29 ++++++++++++++++--- .../Decompressors/T4TiffCompression.cs | 13 +++++++-- .../Compression/TiffDecompressorsFactory.cs | 7 +++-- .../Formats/Tiff/TiffDecoderCore.cs | 18 ++++++++++-- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 5 ++-- .../Formats/Tiff/TiffDecoderTests.cs | 6 ++++ tests/ImageSharp.Tests/TestImages.cs | 3 ++ ...i3p02_huffman_rle_lowerOrderBitsFirst.tiff | 3 ++ .../f8179f8f5e566349cf3583a1ff3ea95c.tiff | 3 ++ tests/Images/Input/Tiff/g3test.tiff | 3 ++ 11 files changed, 80 insertions(+), 17 deletions(-) create mode 100644 tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff create mode 100644 tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff create mode 100644 tests/Images/Input/Tiff/g3test.tiff diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs index 017591e53f..9b12dc90ff 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs @@ -22,11 +22,12 @@ internal class ModifiedHuffmanTiffCompression : T4TiffCompression /// Initializes a new instance of the class. /// /// The memory allocator. + /// The logical order of bits within a byte. /// The image width. /// The number of bits per pixel. /// The photometric interpretation. - public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) - : base(allocator, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation) + public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) + : base(allocator, fillOrder, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation) { bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; this.whiteValue = (byte)(isWhiteZero ? 0 : 1); @@ -36,7 +37,7 @@ public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, int width, int /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) { - using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true); + using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true); buffer.Clear(); uint bitsWritten = 0; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs index 09f8c71f72..384be1cf24 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs @@ -5,7 +5,8 @@ using System.Buffers; using System.Collections.Generic; using System.IO; - +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors @@ -20,6 +21,11 @@ internal class T4BitReader : IDisposable /// private int bitsRead; + /// + /// The logical order of bits within a byte. + /// + private readonly TiffFillOrder fillOrder; + /// /// Current value. /// @@ -221,12 +227,14 @@ internal class T4BitReader : IDisposable /// Initializes a new instance of the class. /// /// The compressed input stream. + /// The logical order of bits within a byte. /// The number of bytes to read from the stream. /// The memory allocator. /// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false. /// Indicates, if its the modified huffman code variation. Defaults to false. - public T4BitReader(Stream input, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false) + public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false) { + this.fillOrder = fillOrder; this.Data = allocator.Allocate(bytesToRead); this.ReadImageDataFromStream(input, bytesToRead); @@ -375,7 +383,7 @@ public void ReadNextRun() break; } - var currBit = this.ReadValue(1); + uint currBit = this.ReadValue(1); this.value = (this.value << 1) | currBit; if (this.IsEndOfScanLine) @@ -816,7 +824,7 @@ private uint GetBit() Span dataSpan = this.Data.GetSpan(); int shift = 8 - this.bitsRead - 1; - var bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0); + uint bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0); this.bitsRead++; return bit; @@ -837,6 +845,19 @@ private void ReadImageDataFromStream(Stream input, int bytesToRead) { Span dataSpan = this.Data.GetSpan(); input.Read(dataSpan, 0, bytesToRead); + + if (this.fillOrder == TiffFillOrder.LeastSignificantBitFirst) + { + for (int i = 0; i < dataSpan.Length; i++) + { + dataSpan[i] = ReverseBits(dataSpan[i]); + } + } } + + // http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte ReverseBits(byte b) => + (byte)((((b * 0x80200802UL) & 0x0884422110UL) * 0x0101010101UL) >> 32); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs index 76f0883643..d95fea29bf 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs @@ -24,20 +24,27 @@ internal class T4TiffCompression : TiffBaseDecompressor /// Initializes a new instance of the class. /// /// The memory allocator. + /// The logical order of bits within a byte. /// The image width. /// The number of bits per pixel. /// Fax compression options. /// The photometric interpretation. - public T4TiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation) + public T4TiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int width, int bitsPerPixel, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation) : base(allocator, width, bitsPerPixel) { this.faxCompressionOptions = faxOptions; + this.FillOrder = fillOrder; bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; this.whiteValue = (byte)(isWhiteZero ? 0 : 1); this.blackValue = (byte)(isWhiteZero ? 1 : 0); } + /// + /// Gets the logical order of bits within a byte. + /// + protected TiffFillOrder FillOrder { get; } + /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) { @@ -46,8 +53,8 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported"); } - var eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding); - using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding); + bool eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding); + using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding); buffer.Clear(); uint bitsWritten = 0; diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs index a6d44f4d3c..ff04edab70 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, + TiffFillOrder fillOrder) { switch (method) { @@ -40,11 +41,11 @@ public static TiffBaseDecompressor Create( case TiffDecoderCompressionType.T4: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T4TiffCompression(allocator, width, bitsPerPixel, faxOptions, photometricInterpretation); + return new T4TiffCompression(allocator, fillOrder, width, bitsPerPixel, faxOptions, photometricInterpretation); case TiffDecoderCompressionType.HuffmanRle: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new ModifiedHuffmanTiffCompression(allocator, width, bitsPerPixel, photometricInterpretation); + return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation); default: throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 011d037796..9fb8c6cebd 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -85,6 +85,11 @@ public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options) /// public FaxCompressionOptions FaxCompressionOptions { get; set; } + /// + /// Gets or sets the the logical order of bits within a byte. + /// + public TiffFillOrder FillOrder { get; set; } + /// /// Gets or sets the planar configuration type to use when decoding the image. /// @@ -264,7 +269,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.FillOrder); TiffBasePlanarColorDecoder colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); @@ -314,7 +327,8 @@ private void DecodeStripsChunky(ImageFrame frame, int rowsPerStr frame.Width, bitsPerPixel, this.Predictor, - this.FaxCompressionOptions); + this.FaxCompressionOptions, + this.FillOrder); TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 14c527a34c..5496b32bfd 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -35,9 +35,9 @@ public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exif } TiffFillOrder fillOrder = (TiffFillOrder?)exifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst; - if (fillOrder != TiffFillOrder.MostSignificantBitFirst) + if (fillOrder == TiffFillOrder.LeastSignificantBitFirst && frameMetadata.BitsPerPixel != TiffBitsPerPixel.Bit1) { - TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is not supported."); + TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is only supported in combination with 1bit per pixel bicolor tiff's."); } if (frameMetadata.Predictor == TiffPredictor.FloatingPoint) @@ -69,6 +69,7 @@ public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exif options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0); + options.FillOrder = fillOrder; options.ParseColorType(exifProfile); options.ParseCompression(frameMetadata.Compression, exifProfile); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 67892b14b1..cae1597a50 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -221,6 +221,12 @@ public void TiffDecoder_CanDecode_HuffmanCompressed(TestImageProvider(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(CcittFax3LowerOrderBitsFirst01, PixelTypes.Rgba32)] + [WithFile(CcittFax3LowerOrderBitsFirst02, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Compressed_LowerOrderBitsFirst(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(Calliphora_RgbPackbits, PixelTypes.Rgba32)] [WithFile(RgbPackbits, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 929e375246..d24c32b55e 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -539,6 +539,9 @@ public static class Tiff public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff"; public const string HuffmanRleAllTermCodes = "Tiff/huffman_rle_all_terminating_codes.tiff"; public const string HuffmanRleAllMakeupCodes = "Tiff/huffman_rle_all_makeup_codes.tiff"; + public const string CcittFax3LowerOrderBitsFirst01 = "Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff"; + public const string CcittFax3LowerOrderBitsFirst02 = "Tiff/g3test.tiff"; + public const string HuffmanRleLowerOrderBitsFirst = "Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff"; // Test case for an issue, that the last bits in a row got ignored. public const string HuffmanRle_basi3p02 = "Tiff/basi3p02_huffman_rle.tiff"; diff --git a/tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff b/tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff new file mode 100644 index 0000000000..6ab0603246 --- /dev/null +++ b/tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ac3e56a93996464a579ae19cf5f8d9531e2f08db36879aaba176731c24951a5 +size 352 diff --git a/tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff b/tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff new file mode 100644 index 0000000000..9dc10018e4 --- /dev/null +++ b/tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf75c4b679d2449e239f228cdee6a25adc7d7b16dde3fb9061a07b2fb0699db1 +size 735412 diff --git a/tests/Images/Input/Tiff/g3test.tiff b/tests/Images/Input/Tiff/g3test.tiff new file mode 100644 index 0000000000..62207de3ad --- /dev/null +++ b/tests/Images/Input/Tiff/g3test.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5b2e1a17338133aa95cb8a16d82a171f5b50f7b9ae1a51ab06227dc3daa81d5 +size 50401 From 4439c3804229f534e0d416cede087484719c18f7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 4 Aug 2021 21:26:39 +0200 Subject: [PATCH 2/8] Update readme --- src/ImageSharp/Formats/Tiff/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index 5b116b819a..7b7c0efd53 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -25,7 +25,7 @@ ## Implementation Status -- The Decoder and Encoder currently only supports a single frame per image. +- The Decoder currently only supports a single frame per image. - Some compression formats are not yet supported. See the list below. ### Deviations from the TIFF spec (to be fixed) @@ -81,7 +81,7 @@ |Thresholding | | | | |CellWidth | | | | |CellLength | | | | -|FillOrder | | - | Ignore. In practice is very uncommon, and is not recommended. | +|FillOrder | | Y | | |ImageDescription | Y | Y | | |Make | Y | Y | | |Model | Y | Y | | From af439841b8b37c1f7df772ad8199c4a646194b27 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Aug 2021 10:33:34 +0200 Subject: [PATCH 3/8] Run ArrayPoolMemoryAllocatorTests serial --- .../Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs index 50ec09ce3f..7620d63deb 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs @@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators { + [Collection("RunSerial")] public class ArrayPoolMemoryAllocatorTests { private const int MaxPooledBufferSizeInBytes = 2048; @@ -56,19 +57,14 @@ public void WhenPassedOnly_MaxPooledBufferSizeInBytes_SmallerThresholdValueIsAut [Fact] public void When_PoolSelectorThresholdInBytes_IsGreaterThan_MaxPooledBufferSizeInBytes_ExceptionIsThrown() - { - Assert.ThrowsAny(() => new ArrayPoolMemoryAllocator(100, 200)); - } + => Assert.ThrowsAny(() => new ArrayPoolMemoryAllocator(100, 200)); } [Theory] [InlineData(32)] [InlineData(512)] [InlineData(MaxPooledBufferSizeInBytes - 1)] - public void SmallBuffersArePooled_OfByte(int size) - { - Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(size)); - } + public void SmallBuffersArePooled_OfByte(int size) => Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(size)); [Theory] [InlineData(128 * 1024 * 1024)] From 928db20b1fee312103117a71383aee029b712280 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Aug 2021 18:46:04 +0200 Subject: [PATCH 4/8] Use fax3 test image with multiple strips --- tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff b/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff index 39852d5345..d2761d2919 100644 --- a/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff +++ b/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b9b105857723bca5f478a9ab23c0aeca93abe863781019bbd2da47f18c46f24 -size 125778 +oid sha256:bba35f1e43c8425f3bcfab682efae4d2c00c62f0d8a4b411e646d32047469526 +size 125802 From eb63882d4e77574cdbc63962d7b2fe9b84ee1ebf Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Aug 2021 20:19:05 +0200 Subject: [PATCH 5/8] Handle edge case when we are at the last byte position, but not all pixels have been written --- .../Decompressors/T4TiffCompression.cs | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs index d95fea29bf..a79ef3fe5e 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs @@ -20,6 +20,8 @@ internal class T4TiffCompression : TiffBaseDecompressor private readonly byte blackValue; + private readonly int width; + /// /// Initializes a new instance of the class. /// @@ -34,7 +36,7 @@ public T4TiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int { this.faxCompressionOptions = faxOptions; this.FillOrder = fillOrder; - + this.width = width; bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; this.whiteValue = (byte)(isWhiteZero ? 0 : 1); this.blackValue = (byte)(isWhiteZero ? 1 : 0); @@ -58,22 +60,17 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa buffer.Clear(); uint bitsWritten = 0; + uint pixelWritten = 0; while (bitReader.HasMoreData) { bitReader.ReadNextRun(); if (bitReader.RunLength > 0) { - if (bitReader.IsWhiteRun) - { - BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); - bitsWritten += bitReader.RunLength; - } - else - { - BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue); - bitsWritten += bitReader.RunLength; - } + this.WritePixelRun(buffer, bitReader, bitsWritten); + + bitsWritten += bitReader.RunLength; + pixelWritten += bitReader.RunLength; } if (bitReader.IsEndOfScanLine) @@ -85,8 +82,29 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); bitsWritten += pad; } + + pixelWritten = 0; } } + + // Edge case for when we are at the last byte, but there are still some unwritten pixels left. + if (pixelWritten > 0 && pixelWritten < this.width) + { + bitReader.ReadNextRun(); + this.WritePixelRun(buffer, bitReader, bitsWritten); + } + } + + private void WritePixelRun(Span buffer, T4BitReader bitReader, uint bitsWritten) + { + if (bitReader.IsWhiteRun) + { + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); + } + else + { + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue); + } } /// From ebfb1b5148b4a60175107b2535e1d813a4972532 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Aug 2021 20:19:36 +0200 Subject: [PATCH 6/8] Use smaller test images --- tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs | 3 +-- tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs | 6 +++++- tests/ImageSharp.Tests/TestImages.cs | 6 ++---- tests/Images/Input/Tiff/b0350_fillorder2.tiff | 3 --- .../Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff | 3 +++ 5 files changed, 11 insertions(+), 10 deletions(-) delete mode 100644 tests/Images/Input/Tiff/b0350_fillorder2.tiff create mode 100644 tests/Images/Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index cae1597a50..6d0f65168e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -222,8 +222,7 @@ public void TiffDecoder_CanDecode_Fax3Compressed(TestImageProvider => TestTiffDecoder(provider); [Theory] - [WithFile(CcittFax3LowerOrderBitsFirst01, PixelTypes.Rgba32)] - [WithFile(CcittFax3LowerOrderBitsFirst02, PixelTypes.Rgba32)] + [WithFile(CcittFax3LowerOrderBitsFirst, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_Compressed_LowerOrderBitsFirst(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 47b6fcf727..712c8502ab 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -117,7 +117,11 @@ public void EncoderOptions_UnsupportedBitPerPixel_DefaultTo24Bits(TiffBitsPerPix [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldDeflate, TiffBitsPerPixel.Bit24, TiffCompression.None)] [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] - public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffBitsPerPixel expectedBitsPerPixel, TiffCompression expectedCompression) + public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works( + TiffPhotometricInterpretation? photometricInterpretation, + TiffCompression compression, + TiffBitsPerPixel expectedBitsPerPixel, + TiffCompression expectedCompression) { // arrange var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation, Compression = compression }; diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index d24c32b55e..4233c69e05 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -539,8 +539,7 @@ public static class Tiff public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff"; public const string HuffmanRleAllTermCodes = "Tiff/huffman_rle_all_terminating_codes.tiff"; public const string HuffmanRleAllMakeupCodes = "Tiff/huffman_rle_all_makeup_codes.tiff"; - public const string CcittFax3LowerOrderBitsFirst01 = "Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff"; - public const string CcittFax3LowerOrderBitsFirst02 = "Tiff/g3test.tiff"; + public const string CcittFax3LowerOrderBitsFirst = "Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff"; public const string HuffmanRleLowerOrderBitsFirst = "Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff"; // Test case for an issue, that the last bits in a row got ignored. @@ -607,7 +606,6 @@ public static class Tiff public const string MultiframeDifferentSize = "Tiff/multipage_differentSize.tiff"; public const string MultiframeDifferentVariants = "Tiff/multipage_differentVariants.tiff"; - public const string FillOrder2 = "Tiff/b0350_fillorder2.tiff"; public const string LittleEndianByteOrder = "Tiff/little_endian.tiff"; public const string Fax4_Motorola = "Tiff/moy.tiff"; @@ -622,7 +620,7 @@ public static class Tiff public static readonly string[] Metadata = { SampleMetadata }; - public static readonly string[] NotSupported = { Calliphora_RgbJpeg, RgbJpeg, RgbUncompressedTiled, MultiframeDifferentSize, MultiframeDifferentVariants, FillOrder2, Calliphora_Fax4Compressed, Fax4_Motorola }; + public static readonly string[] NotSupported = { Calliphora_RgbJpeg, RgbJpeg, RgbUncompressedTiled, MultiframeDifferentSize, MultiframeDifferentVariants, Calliphora_Fax4Compressed, Fax4_Motorola }; } } } diff --git a/tests/Images/Input/Tiff/b0350_fillorder2.tiff b/tests/Images/Input/Tiff/b0350_fillorder2.tiff deleted file mode 100644 index 3b7ee6ac3a..0000000000 --- a/tests/Images/Input/Tiff/b0350_fillorder2.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:37c6a28f460d8781fdc3bcf0cc9bd23f633b03899563546bfc6234a8478f67f0 -size 68637 diff --git a/tests/Images/Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff b/tests/Images/Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff new file mode 100644 index 0000000000..d9b5a5bfb7 --- /dev/null +++ b/tests/Images/Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb56b3582c5c7d91d712e68181110ab0bf74d21992030629f05803c420b7b483 +size 388 From 0559f464dd60b19515dd764fff1f950da36b41da Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Aug 2021 22:31:13 +0200 Subject: [PATCH 7/8] Use smaller test image --- tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff index 5574bd58ed..6f929f3e1d 100644 --- a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff +++ b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:622d69dba0a8a67aa3b87e384a2b9ea8d29689eaa5cb5d0eee857f98ed660517 -size 15154924 +oid sha256:e4a6c925dd6d293c5d97aac01cdb0ab3f2fb4bbfa4bb1cbe6463545295f5c5fb +size 207979 From 1d8b9101f8ac5403e548335576a3d9e5519b715d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 6 Aug 2021 11:55:47 +0200 Subject: [PATCH 8/8] Restore broken IPTC data for testimage (which got lost during resize) --- tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff index 6f929f3e1d..24a4141f56 100644 --- a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff +++ b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4a6c925dd6d293c5d97aac01cdb0ab3f2fb4bbfa4bb1cbe6463545295f5c5fb -size 207979 +oid sha256:579db6b2bd34566846de992f255c6b341d0f88d957a0eb02b01caad3f20c5b44 +size 78794