diff --git a/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs index c31eb8793f..043e5b313e 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs @@ -55,6 +55,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression /// private uint runLength; + /// + /// We keep track if its the start of the row, because each run is expected to start with a white run. + /// If the image row itself starts with black, a white run of zero is expected. + /// + private bool isStartOfRow; + private readonly int dataLength; private const int MinCodeLength = 2; @@ -78,14 +84,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression private static readonly Dictionary WhiteLen7TermCodes = new Dictionary() { - { 0x27, 18 }, { 0xC, 19 }, { 0x8, 20 }, { 0x17, 21 }, { 0x3, 22 }, { 0x4, 23 }, { 0x28, 24 }, { 0x2B, 25 }, { 0x13, 26 }, { 0x24, 27 }, { 0x18, 28 } + { 0x27, 18 }, { 0xC, 19 }, { 0x8, 20 }, { 0x17, 21 }, { 0x3, 22 }, { 0x4, 23 }, { 0x28, 24 }, { 0x2B, 25 }, { 0x13, 26 }, + { 0x24, 27 }, { 0x18, 28 } }; private static readonly Dictionary WhiteLen8TermCodes = new Dictionary() { - { 0x35, 0 }, { 0x2, 29 }, { 0x3, 30 }, { 0x1A, 31 }, { 0x1B, 32 }, { 0x12, 33 }, { 0x13, 34 }, { 0x14, 35 }, { 0x15, 36 }, { 0x16, 37 }, { 0x17, 38 }, { 0x28, 39 }, { 0x29, 40 }, { 0x2A, 41 }, - { 0x2B, 42 }, { 0x2C, 43 }, { 0x2D, 44 }, { 0x4, 45 }, { 0x5, 46 }, { 0xA, 47 }, { 0xB, 48 }, { 0x52, 49 }, { 0x53, 50 }, { 0x54, 51 }, { 0x55, 52 }, { 0x24, 53 }, { 0x25, 54 }, { 0x58, 55 }, - { 0x59, 56 }, { 0x5A, 57 }, { 0x5B, 58 }, { 0x4A, 59 }, { 0x4B, 60 }, { 0x32, 61 }, { 0x33, 62 }, { 0x34, 63 } + { 0x35, 0 }, { 0x2, 29 }, { 0x3, 30 }, { 0x1A, 31 }, { 0x1B, 32 }, { 0x12, 33 }, { 0x13, 34 }, { 0x14, 35 }, { 0x15, 36 }, + { 0x16, 37 }, { 0x17, 38 }, { 0x28, 39 }, { 0x29, 40 }, { 0x2A, 41 }, { 0x2B, 42 }, { 0x2C, 43 }, { 0x2D, 44 }, { 0x4, 45 }, + { 0x5, 46 }, { 0xA, 47 }, { 0xB, 48 }, { 0x52, 49 }, { 0x53, 50 }, { 0x54, 51 }, { 0x55, 52 }, { 0x24, 53 }, { 0x25, 54 }, + { 0x58, 55 }, { 0x59, 56 }, { 0x5A, 57 }, { 0x5B, 58 }, { 0x4A, 59 }, { 0x4B, 60 }, { 0x32, 61 }, { 0x33, 62 }, { 0x34, 63 } }; private static readonly Dictionary BlackLen2TermCodes = new Dictionary() @@ -159,7 +167,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression private static readonly Dictionary WhiteLen8MakeupCodes = new Dictionary() { - { 0x36, 320 }, { 0x37, 348 }, { 0x64, 448 }, { 0x65, 512 }, { 0x68, 576 }, { 0x67, 640 } + { 0x36, 320 }, { 0x37, 384 }, { 0x64, 448 }, { 0x65, 512 }, { 0x68, 576 }, { 0x67, 640 } }; private static readonly Dictionary WhiteLen7MakeupCodes = new Dictionary() @@ -181,7 +189,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression private static readonly Dictionary WhiteLen12MakeupCodes = new Dictionary() { - { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304}, { 0x1C, 2368 }, { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } + { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 }, + { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } }; private static readonly Dictionary BlackLen10MakeupCodes = new Dictionary() @@ -197,7 +206,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression private static readonly Dictionary BlackLen12MakeupCodes = new Dictionary() { { 0xC8, 128 }, { 0xC9, 192 }, { 0x5B, 256 }, { 0x33, 320 }, { 0x34, 384 }, { 0x35, 448 }, - { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304}, { 0x1C, 2368 }, { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } + { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 }, + { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } }; private static readonly Dictionary BlackLen13MakeupCodes = new Dictionary() @@ -225,6 +235,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression this.position = 0; this.isWhiteRun = true; this.isFirstScanLine = true; + this.isStartOfRow = true; this.terminationCodeFound = false; this.runLength = 0; } @@ -313,14 +324,32 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression TiffThrowHelper.ThrowImageFormatException("t4 parsing error: invalid code length read"); } + bool isMakeupCode = this.IsMakeupCode(); + if (isMakeupCode) + { + if (this.IsWhiteRun) + { + this.runLength += this.WhiteMakeupCodeRunLength(); + } + else + { + this.runLength += this.BlackMakeupCodeRunLength(); + } + + this.isStartOfRow = false; + this.Reset(resetRunLength: false); + continue; + } + bool isTerminatingCode = this.IsTerminatingCode(); if (isTerminatingCode) { // Each line starts with a white run. If the image starts with black, a white run with length zero is written. - if (this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0) + if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0) { this.isWhiteRun = !this.IsWhiteRun; this.Reset(); + this.isStartOfRow = false; continue; } @@ -334,25 +363,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression } this.terminationCodeFound = true; + this.isStartOfRow = false; break; } - bool isMakeupCode = this.IsMakeupCode(); - if (isMakeupCode) - { - if (this.IsWhiteRun) - { - this.runLength += this.WhiteMakeupCodeRunLength(); - } - else - { - this.runLength += this.BlackMakeupCodeRunLength(); - } - - this.Reset(false); - continue; - } - var currBit = this.ReadValue(1); this.value = (this.value << 1) | currBit; @@ -360,6 +374,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression { // Each new row starts with a white run. this.isWhiteRun = true; + this.isStartOfRow = true; } } while (!this.IsEndOfScanLine); diff --git a/src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs index 13f7eb7945..ae15a8b614 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs @@ -36,6 +36,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression { bitReader.ReadNextRun(); + if (bitReader.RunLength > 0) + { + if (bitReader.IsWhiteRun) + { + this.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, whiteValue); + bitsWritten += bitReader.RunLength; + } + else + { + this.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, blackValue); + bitsWritten += bitReader.RunLength; + } + } + if (bitReader.IsEndOfScanLine) { // Write padding bytes, if necessary. @@ -45,19 +59,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression this.WriteBits(buffer, (int)bitsWritten, pad, 0); bitsWritten += pad; } - - continue; - } - - if (bitReader.IsWhiteRun) - { - this.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, whiteValue); - bitsWritten += bitReader.RunLength; - } - else - { - this.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, blackValue); - bitsWritten += bitReader.RunLength; } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 3a40d5ce2b..5e04906bb5 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -18,6 +18,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Trait("Category", "Tiff")] public class TiffDecoderTests { + private static TiffDecoder TiffDecoder => new TiffDecoder(); + + private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); + public static readonly string[] SingleTestImages = TestImages.Tiff.All; public static readonly string[] MultiframeTestImages = TestImages.Tiff.Multiframes; @@ -29,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void ThrowsNotSupported(TestImageProvider provider) where TPixel : unmanaged, IPixel { - Assert.Throws(() => provider.GetImage(new TiffDecoder())); + Assert.Throws(() => provider.GetImage(TiffDecoder)); } [Theory] @@ -79,10 +83,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void Decode(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TiffDecoder())) + using (Image image = provider.GetImage(TiffDecoder)) { image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact, new MagickReferenceDecoder()); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); } } @@ -91,15 +95,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void DecodeMultiframe(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TiffDecoder())) + using (Image image = provider.GetImage(TiffDecoder)) { Assert.True(image.Frames.Count > 1); image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact, new MagickReferenceDecoder()); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); image.DebugSaveMultiFrame(provider); - image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, new MagickReferenceDecoder()); + image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, ReferenceDecoder); } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index abdde50b19..fa9e4e290a 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -514,7 +514,7 @@ namespace SixLabors.ImageSharp.Tests public const string Calliphora_Fax3Compressed = "Tiff/Calliphora_ccitt_fax3.tif"; public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tif"; - public const string CcittFax3AllMakupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tif"; + public const string CcittFax3AllMakupCodes = "Tiff/ccitt_fax3_all_makeupcodes_codes.tif"; public const string GrayscaleDeflateMultistrip = "Tiff/grayscale_deflate_multistrip.tiff"; public const string GrayscaleUncompressed = "Tiff/grayscale_uncompressed.tiff"; diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index d411a6fb7e..17ad3e9903 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs MemoryGroup framePixels = frame.PixelBuffer.FastMemoryGroup; using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); - if (magicFrame.Depth == 8) + if (magicFrame.Depth == 8 || magicFrame.Depth == 1) { byte[] data = pixels.ToByteArray(PixelMapping.RGBA); diff --git a/tests/Images/Input/Tiff/ccitt_fax3_all_makeupcodes_codes.tif b/tests/Images/Input/Tiff/ccitt_fax3_all_makeupcodes_codes.tif index 09be316550..6a1153bac7 100644 --- a/tests/Images/Input/Tiff/ccitt_fax3_all_makeupcodes_codes.tif +++ b/tests/Images/Input/Tiff/ccitt_fax3_all_makeupcodes_codes.tif @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b1a0af9ace55d5d4b86225cb569a8632b6a6bb621fa1dc56a7d3d6404eba7bb -size 360 +oid sha256:60b64bd3c24437eb90c0a17a4328e997702d7e4c0889ec90abde092ab9b490e8 +size 546