diff --git a/src/ImageSharp/Formats/Tiff/Compression/T4BitWriter.cs b/src/ImageSharp/Formats/Tiff/Compression/T4BitWriter.cs index f302a3d4f..0dd79410f 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/T4BitWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/T4BitWriter.cs @@ -15,6 +15,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression /// internal class T4BitWriter { + private const uint WhiteZeroRunTermCode = 0x35; + + private const uint BlackZeroRunTermCode = 0x37; + + private static readonly List MakeupRunLength = new List() + { + 64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560 + }; + private static readonly Dictionary WhiteLen4TermCodes = new Dictionary() { { 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF } @@ -38,10 +47,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression private static readonly Dictionary WhiteLen8TermCodes = new Dictionary() { - { 0, 0x35 }, { 29, 0x2 }, { 30, 0x3 }, { 31, 0x1A }, { 32, 0x1B }, { 33, 0x12 }, { 34, 0x13 }, { 35, 0x14 }, { 36, 0x15 }, - { 37, 0x16 }, { 38, 0x17 }, { 39, 0x28 }, { 40, 0x29 }, { 41, 0x2A }, { 42, 0x2B }, { 43, 0x2C }, { 44, 0x2D }, { 45, 0x4 }, - { 46, 0x5 }, { 47, 0xA }, { 48, 0xB }, { 49, 0x52 }, { 50, 0x53 }, { 51, 0x54 }, { 52, 0x55 }, { 53, 0x24 }, { 54, 0x25 }, - { 55, 0x58 }, { 56, 0x59 }, { 57, 0x5A }, { 58, 0x5B }, { 59, 0x4A }, { 60, 0x4B }, { 61, 0x32 }, { 62, 0x33 }, { 63, 0x34 } + { 0, WhiteZeroRunTermCode }, { 29, 0x2 }, { 30, 0x3 }, { 31, 0x1A }, { 32, 0x1B }, { 33, 0x12 }, { 34, 0x13 }, { 35, 0x14 }, + { 36, 0x15 }, { 37, 0x16 }, { 38, 0x17 }, { 39, 0x28 }, { 40, 0x29 }, { 41, 0x2A }, { 42, 0x2B }, { 43, 0x2C }, { 44, 0x2D }, + { 45, 0x4 }, { 46, 0x5 }, { 47, 0xA }, { 48, 0xB }, { 49, 0x52 }, { 50, 0x53 }, { 51, 0x54 }, { 52, 0x55 }, { 53, 0x24 }, + { 54, 0x25 }, { 55, 0x58 }, { 56, 0x59 }, { 57, 0x5A }, { 58, 0x5B }, { 59, 0x4A }, { 60, 0x4B }, { 61, 0x32 }, { 62, 0x33 }, + { 63, 0x34 } }; private static readonly Dictionary BlackLen2TermCodes = new Dictionary() @@ -86,7 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression private static readonly Dictionary BlackLen10TermCodes = new Dictionary() { - { 0, 0x37 }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 } + { 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 } }; private static readonly Dictionary BlackLen11TermCodes = new Dictionary() @@ -103,13 +113,75 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression { 62, 0x66 }, { 63, 0x67 } }; + private static readonly Dictionary WhiteLen5MakeupCodes = new Dictionary() + { + { 64, 0x1B }, { 128, 0x12 } + }; + + private static readonly Dictionary WhiteLen6MakeupCodes = new Dictionary() + { + { 192, 0x17 }, { 1664, 0x18 } + }; + + private static readonly Dictionary WhiteLen8MakeupCodes = new Dictionary() + { + { 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 } + }; + + private static readonly Dictionary WhiteLen7MakeupCodes = new Dictionary() + { + { 256, 0x37 } + }; + + private static readonly Dictionary WhiteLen9MakeupCodes = new Dictionary() + { + { 704, 0xCC }, { 768, 0xCD }, { 832, 0xD2 }, { 896, 0xD3 }, { 960, 0xD4 }, { 1024, 0xD5 }, { 1088, 0xD6 }, + { 1152, 0xD7 }, { 1216, 0xD8 }, { 1280, 0xD9 }, { 1344, 0xDA }, { 1408, 0xDB }, { 1472, 0x98 }, { 1536, 0x99 }, + { 1600, 0x9A }, { 1728, 0x9B } + }; + + private static readonly Dictionary WhiteLen11MakeupCodes = new Dictionary() + { + { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } + }; + + private static readonly Dictionary WhiteLen12MakeupCodes = new Dictionary() + { + { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, + { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } + }; + + private static readonly Dictionary BlackLen10MakeupCodes = new Dictionary() + { + { 64, 0xF } + }; + + private static readonly Dictionary BlackLen11MakeupCodes = new Dictionary() + { + { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } + }; + + private static readonly Dictionary BlackLen12MakeupCodes = new Dictionary() + { + { 128, 0xC8 }, { 192, 0xC9 }, { 256, 0x5B }, { 320, 0x33 }, { 384, 0x34 }, { 448, 0x35 }, + { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, + { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } + }; + + private static readonly Dictionary BlackLen13MakeupCodes = new Dictionary() + { + { 512, 0x6C }, { 576, 0x6D }, { 640, 0x4A }, { 704, 0x4B }, { 768, 0x4C }, { 832, 0x4D }, { 896, 0x72 }, + { 960, 0x73 }, { 1024, 0x74 }, { 1088, 0x75 }, { 1152, 0x76 }, { 1216, 0x77 }, { 1280, 0x52 }, { 1344, 0x53 }, + { 1408, 0x54 }, { 1472, 0x55 }, { 1536, 0x5A }, { 1600, 0x5B }, { 1664, 0x64 }, { 1728, 0x65 } + }; + private readonly MemoryAllocator memoryAllocator; private readonly Configuration configuration; - private int bytePosition = 0; + private int bytePosition; - private byte bitPosition = 0; + private byte bitPosition; /// /// Initializes a new instance of the class. @@ -149,13 +221,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression for (int y = 0; y < image.Height; y++) { bool isWhiteRun = true; + bool isStartOrRow = true; Span pixelRow = image.GetPixelRowSpan(y); PixelOperations.Instance.ToL8(this.configuration, pixelRow, pixelRowAsGray); int x = 0; while (x < image.Width) { uint runLength = 0; - for (int i = x; i < pixelRow.Length; i++) + for (int i = x; i < image.Width; i++) { if (isWhiteRun && pixelRowAsGray[i].PackedValue != 255) { @@ -179,16 +252,51 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression } } - bool gotTermCode = this.GetTermCode(runLength, out var code, out var codeLength, isWhiteRun); + if (isStartOrRow && runLength == 0) + { + this.WriteCode(8, WhiteZeroRunTermCode, compressedData); - this.WriteCode(codeLength, code, compressedData); + isWhiteRun = false; + isStartOrRow = false; + continue; + } - x += (int)runLength; + uint code; + uint codeLength; + if (runLength <= 63) + { + code = this.GetTermCode(runLength, out codeLength, isWhiteRun); + this.WriteCode(codeLength, code, compressedData); + x += (int)runLength; + } + else + { + runLength = this.GetBestFittingMakeupRunLength(runLength); + code = this.GetMakeupCode(runLength, out codeLength, isWhiteRun); + this.WriteCode(codeLength, code, compressedData); + x += (int)runLength; + // If we are at the end of the line with a makeup code, we need to write a final term code with a length of zero. + if (x == image.Width) + { + if (isWhiteRun) + { + this.WriteCode(8, WhiteZeroRunTermCode, compressedData); + } + else + { + this.WriteCode(10, BlackZeroRunTermCode, compressedData); + } + } + + continue; + } + + isStartOrRow = false; isWhiteRun = !isWhiteRun; } - // Write EOL + // Write EOL. this.WriteCode(12, 1, compressedData); } @@ -202,7 +310,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression { while (codeLength > 0) { - var bitNumber = (int) codeLength; + var bitNumber = (int)codeLength; var bit = (code & (1 << (bitNumber - 1))) != 0; if (bit) { @@ -224,142 +332,227 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression } } - private bool GetTermCode(uint runLength, out uint code, out uint codeLength, bool isWhiteRun) + private uint GetBestFittingMakeupRunLength(uint runLength) + { + for (int i = 0; i < MakeupRunLength.Count - 1; i++) + { + if (MakeupRunLength[i] <= runLength && MakeupRunLength[i + 1] > runLength) + { + return MakeupRunLength[i]; + } + } + + return MakeupRunLength[MakeupRunLength.Count - 1]; + } + + private uint GetTermCode(uint runLength, out uint codeLength, bool isWhiteRun) + { + if (isWhiteRun) + { + return this.GetWhiteTermCode(runLength, out codeLength); + } + + return this.GetBlackTermCode(runLength, out codeLength); + } + + private uint GetMakeupCode(uint runLength, out uint codeLength, bool isWhiteRun) { if (isWhiteRun) { - return this.GetWhiteTermCode(runLength, out code, out codeLength); + return this.GetWhiteMakeupCode(runLength, out codeLength); + } + + return this.GetBlackMakeupCode(runLength, out codeLength); + } + + private uint GetWhiteMakeupCode(uint runLength, out uint codeLength) + { + codeLength = 0; + + if (WhiteLen5MakeupCodes.ContainsKey(runLength)) + { + codeLength = 5; + return WhiteLen5MakeupCodes[runLength]; + } + + if (WhiteLen6MakeupCodes.ContainsKey(runLength)) + { + codeLength = 6; + return WhiteLen6MakeupCodes[runLength]; + } + + if (WhiteLen7MakeupCodes.ContainsKey(runLength)) + { + codeLength = 7; + return WhiteLen7MakeupCodes[runLength]; + } + + if (WhiteLen8MakeupCodes.ContainsKey(runLength)) + { + codeLength = 8; + return WhiteLen8MakeupCodes[runLength]; + } + + if (WhiteLen9MakeupCodes.ContainsKey(runLength)) + { + codeLength = 9; + return WhiteLen9MakeupCodes[runLength]; + } + + if (WhiteLen11MakeupCodes.ContainsKey(runLength)) + { + codeLength = 11; + return WhiteLen11MakeupCodes[runLength]; + } + + if (WhiteLen12MakeupCodes.ContainsKey(runLength)) + { + codeLength = 12; + return WhiteLen12MakeupCodes[runLength]; + } + + return 0; + } + + private uint GetBlackMakeupCode(uint runLength, out uint codeLength) + { + codeLength = 0; + + if (BlackLen10MakeupCodes.ContainsKey(runLength)) + { + codeLength = 10; + return BlackLen10MakeupCodes[runLength]; + } + + if (BlackLen11MakeupCodes.ContainsKey(runLength)) + { + codeLength = 11; + return BlackLen11MakeupCodes[runLength]; + } + + if (BlackLen12MakeupCodes.ContainsKey(runLength)) + { + codeLength = 12; + return BlackLen12MakeupCodes[runLength]; + } + + if (BlackLen13MakeupCodes.ContainsKey(runLength)) + { + codeLength = 13; + return BlackLen13MakeupCodes[runLength]; } - return this.GetBlackTermCode(runLength, out code, out codeLength); + return 0; } - private bool GetWhiteTermCode(uint runLength, out uint code, out uint codeLength) + private uint GetWhiteTermCode(uint runLength, out uint codeLength) { - code = 0; codeLength = 0; if (WhiteLen4TermCodes.ContainsKey(runLength)) { - code = WhiteLen4TermCodes[runLength]; codeLength = 4; - return true; + return WhiteLen4TermCodes[runLength]; } if (WhiteLen5TermCodes.ContainsKey(runLength)) { - code = WhiteLen5TermCodes[runLength]; codeLength = 5; - return true; + return WhiteLen5TermCodes[runLength]; } if (WhiteLen6TermCodes.ContainsKey(runLength)) { - code = WhiteLen6TermCodes[runLength]; codeLength = 6; - return true; + return WhiteLen6TermCodes[runLength]; } if (WhiteLen7TermCodes.ContainsKey(runLength)) { - code = WhiteLen7TermCodes[runLength]; codeLength = 7; - return true; + return WhiteLen7TermCodes[runLength]; } if (WhiteLen8TermCodes.ContainsKey(runLength)) { - code = WhiteLen8TermCodes[runLength]; codeLength = 8; - return true; + return WhiteLen8TermCodes[runLength]; } - return false; + return 0; } - private bool GetBlackTermCode(uint runLength, out uint code, out uint codeLength) + private uint GetBlackTermCode(uint runLength, out uint codeLength) { - code = 0; codeLength = 0; if (BlackLen2TermCodes.ContainsKey(runLength)) { - code = BlackLen2TermCodes[runLength]; codeLength = 2; - return true; + return BlackLen2TermCodes[runLength]; } if (BlackLen3TermCodes.ContainsKey(runLength)) { - code = BlackLen3TermCodes[runLength]; codeLength = 3; - return true; + return BlackLen3TermCodes[runLength]; } if (BlackLen4TermCodes.ContainsKey(runLength)) { - code = BlackLen4TermCodes[runLength]; codeLength = 4; - return true; + return BlackLen4TermCodes[runLength]; } if (BlackLen5TermCodes.ContainsKey(runLength)) { - code = BlackLen5TermCodes[runLength]; codeLength = 5; - return true; + return BlackLen5TermCodes[runLength]; } if (BlackLen6TermCodes.ContainsKey(runLength)) { - code = BlackLen6TermCodes[runLength]; codeLength = 6; - return true; + return BlackLen6TermCodes[runLength]; } if (BlackLen7TermCodes.ContainsKey(runLength)) { - code = BlackLen7TermCodes[runLength]; codeLength = 7; - return true; + return BlackLen7TermCodes[runLength]; } if (BlackLen8TermCodes.ContainsKey(runLength)) { - code = BlackLen8TermCodes[runLength]; codeLength = 8; - return true; + return BlackLen8TermCodes[runLength]; } if (BlackLen9TermCodes.ContainsKey(runLength)) { - code = BlackLen9TermCodes[runLength]; codeLength = 9; - return true; + return BlackLen9TermCodes[runLength]; } if (BlackLen10TermCodes.ContainsKey(runLength)) { - code = BlackLen10TermCodes[runLength]; codeLength = 10; - return true; + return BlackLen10TermCodes[runLength]; } if (BlackLen11TermCodes.ContainsKey(runLength)) { - code = BlackLen11TermCodes[runLength]; codeLength = 11; - return true; + return BlackLen11TermCodes[runLength]; } if (BlackLen12TermCodes.ContainsKey(runLength)) { - code = BlackLen12TermCodes[runLength]; codeLength = 12; - return true; + return BlackLen12TermCodes[runLength]; } - return false; + return 0; } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 0d5bacc34..742c2da42 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -341,6 +341,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor; break; case TiffEncodingMode.BiColor: + if (this.CompressionType == TiffEncoderCompression.CcittGroup3Fax) + { + // The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero. + this.PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero; + } + else + { + this.PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero; + } + + break; + case TiffEncodingMode.Gray: this.PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero; break; @@ -358,6 +370,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff return 3; case TiffPhotometricInterpretation.PaletteColor: case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.WhiteIsZero: return 1; default: return 3; @@ -372,6 +385,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff return new ushort[] { 8 }; case TiffPhotometricInterpretation.Rgb: return new ushort[] { 8, 8, 8 }; + case TiffPhotometricInterpretation.WhiteIsZero: + if (this.Mode == TiffEncodingMode.BiColor) + { + return new ushort[] { 1 }; + } + + return new ushort[] { 8 }; case TiffPhotometricInterpretation.BlackIsZero: if (this.Mode == TiffEncodingMode.BiColor) {