diff --git a/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs new file mode 100644 index 0000000000..c37de8031a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs @@ -0,0 +1,1110 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Linq; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression +{ + /// + /// Bitreader for reading compressed CCITT T4 1D data. + /// + internal class T4BitReader + { + /// + /// Number of bits read. + /// + private int bitsRead; + + /// + /// Current value. + /// + private uint value; + + /// + /// Number of bits read for the current run value. + /// + private int curValueBitsRead; + + /// + /// Byte position in the buffer. + /// + private ulong position; + + /// + /// Indicates, if the current run are white pixels. + /// + private bool isWhiteRun; + + /// + /// Indicates whether its the first line of data which is read from the image. + /// + private bool isFirstScanLine; + + private bool terminationCodeFound; + + /// + /// Number of pixels in the current run. + /// + private uint runLength; + + private const int MinCodeLength = 2; + + private const int MaxCodeLength = 13; + + public T4BitReader(Stream input, int bytesToRead) + { + // TODO: use memory allocator + this.Data = new byte[bytesToRead]; + this.ReadImageDataFromStream(input, bytesToRead); + + this.bitsRead = 0; + this.value = 0; + this.curValueBitsRead = 0; + this.position = 0; + this.isWhiteRun = true; + this.isFirstScanLine = true; + this.terminationCodeFound = false; + this.runLength = 0; + } + + /// + /// Gets the compressed image data. + /// + public byte[] Data { get; } + + /// + /// Gets a value indicating whether there is more data to read left. + /// + public bool HasMoreData + { + get + { + return this.position < (ulong)this.Data.Length - 1; + } + } + + /// + /// Gets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run. + /// + public bool IsWhiteRun + { + get + { + return this.isWhiteRun; + } + } + + /// + /// Gets the number of pixels in the current run. + /// + public uint RunLength + { + get + { + return this.runLength; + } + } + + /// + /// Gets a value indicating whether the end of a pixel row has been reached. + /// + public bool IsEndOfScanLine + { + get + { + return this.curValueBitsRead == 12 && this.value == 1; + } + } + + /// + /// Read the next run of pixels. + /// + public void ReadNextRun() + { + if (this.terminationCodeFound) + { + this.isWhiteRun = !this.IsWhiteRun; + this.terminationCodeFound = false; + } + + this.Reset(); + + if (this.isFirstScanLine) + { + // We expect an EOL before the first data. + this.value = this.ReadValue(12); + if (!this.IsEndOfScanLine) + { + TiffThrowHelper.ThrowImageFormatException("t4 parsing error: expected start of data marker not found"); + } + + this.Reset(); + } + + // A code word must have at least 2 bits. + this.value = this.ReadValue(MinCodeLength); + + do + { + if (this.curValueBitsRead > MaxCodeLength) + { + TiffThrowHelper.ThrowImageFormatException("t4 parsing error: invalid code length read"); + } + + 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) + { + this.isWhiteRun = !this.IsWhiteRun; + this.Reset(); + continue; + } + + if (this.IsWhiteRun) + { + this.runLength += this.WhiteTerminatingCodeRunLength(); + } + else + { + this.runLength += this.BlackTerminatingCodeRunLength(); + } + + this.terminationCodeFound = true; + 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; + + if (this.IsEndOfScanLine) + { + // Each new row starts with a white run. + this.isWhiteRun = true; + } + } + while (!this.IsEndOfScanLine); + + this.isFirstScanLine = false; + } + + private uint WhiteTerminatingCodeRunLength() + { + switch (this.curValueBitsRead) + { + case 4: + { + switch (this.value) + { + case 0x7: + return 2; + case 0x8: + return 3; + case 0xB: + return 4; + case 0xC: + return 5; + case 0xE: + return 6; + case 0xF: + return 7; + } + + break; + } + + case 5: + { + switch (this.value) + { + case 0x13: + return 8; + case 0x14: + return 9; + case 0x7: + return 10; + case 0x8: + return 11; + } + + break; + } + + case 6: + { + switch (this.value) + { + case 0x7: + return 1; + case 0x8: + return 12; + case 0x3: + return 13; + case 0x34: + return 14; + case 0x35: + return 15; + case 0x2A: + return 16; + case 0x2B: + return 17; + } + + break; + } + + case 7: + { + switch (this.value) + { + case 0x27: + return 18; + case 0xC: + return 19; + case 0x8: + return 20; + case 0x17: + return 21; + case 0x3: + return 22; + case 0x4: + return 23; + case 0x28: + return 24; + case 0x2B: + return 25; + case 0x13: + return 26; + case 0x24: + return 27; + case 0x18: + return 28; + } + + break; + } + + case 8: + { + switch (this.value) + { + case 0x35: + return 0; + case 0x2: + return 29; + case 0x3: + return 30; + case 0x1A: + return 31; + case 0x1B: + return 32; + case 0x12: + return 33; + case 0x13: + return 34; + case 0x14: + return 35; + case 0x15: + return 36; + case 0x16: + return 37; + case 0x17: + return 38; + case 0x28: + return 39; + case 0x29: + return 40; + case 0x2A: + return 41; + case 0x2B: + return 42; + case 0x2C: + return 43; + case 0x2D: + return 44; + case 0x4: + return 45; + case 0x5: + return 46; + case 0xA: + return 47; + case 0xB: + return 48; + case 0x52: + return 49; + case 0x53: + return 50; + case 0x54: + return 51; + case 0x55: + return 52; + case 0x24: + return 53; + case 0x25: + return 54; + case 0x58: + return 55; + case 0x59: + return 56; + case 0x5A: + return 57; + case 0x5B: + return 58; + case 0x4A: + return 59; + case 0x4B: + return 60; + case 0x32: + return 61; + case 0x33: + return 62; + case 0x34: + return 63; + } + + break; + } + } + + return 0; + } + + private uint BlackTerminatingCodeRunLength() + { + switch (this.curValueBitsRead) + { + case 2: + { + switch (this.value) + { + case 0x3: + return 2; + case 0x2: + return 3; + } + break; + } + + case 3: + { + switch (this.value) + { + case 0x2: + return 1; + case 0x3: + return 4; + } + + break; + } + + case 4: + { + switch (this.value) + { + case 0x3: + return 5; + case 0x2: + return 6; + } + + break; + } + + case 5: + { + switch (this.value) + { + case 0x3: + return 7; + } + + break; + } + + case 6: + { + switch (this.value) + { + case 0x5: + return 8; + case 0x4: + return 9; + } + + break; + } + + case 7: + { + switch (this.value) + { + case 0x4: + return 10; + case 0x5: + return 11; + case 0x7: + return 12; + } + + break; + } + + case 8: + { + switch (this.value) + { + case 0x4: + return 13; + case 0x7: + return 14; + } + + break; + } + + case 9: + { + switch (this.value) + { + case 0x18: + return 15; + } + + break; + } + + case 10: + { + switch (this.value) + { + case 0x37: + return 0; + case 0x17: + return 16; + case 0x18: + return 17; + case 0x8: + return 18; + } + + break; + } + + case 11: + { + switch (this.value) + { + case 0x67: + return 19; + case 0x68: + return 20; + case 0x6C: + return 21; + case 0x37: + return 22; + case 0x28: + return 23; + case 0x17: + return 24; + case 0x18: + return 25; + } + + break; + } + + case 12: + { + switch (this.value) + { + case 0xCA: + return 26; + case 0xCB: + return 27; + case 0xCC: + return 28; + case 0xCD: + return 29; + case 0x68: + return 30; + case 0x69: + return 31; + case 0x6A: + return 32; + case 0x6B: + return 33; + case 0xD2: + return 34; + case 0xD3: + return 35; + case 0xD4: + return 36; + case 0xD5: + return 37; + case 0xD6: + return 38; + case 0xD7: + return 39; + case 0x6C: + return 40; + case 0x6D: + return 41; + case 0xDA: + return 42; + case 0xDB: + return 43; + case 0x54: + return 44; + case 0x55: + return 45; + case 0x56: + return 46; + case 0x57: + return 47; + case 0x64: + return 48; + case 0x65: + return 49; + case 0x52: + return 50; + case 0x53: + return 51; + case 0x24: + return 52; + case 0x37: + return 53; + case 0x38: + return 54; + case 0x27: + return 55; + case 0x28: + return 56; + case 0x58: + return 57; + case 0x59: + return 58; + case 0x2B: + return 59; + case 0x2C: + return 60; + case 0x5A: + return 61; + case 0x66: + return 62; + case 0x67: + return 63; + } + + break; + } + } + + return 0; + } + + private uint WhiteMakeupCodeRunLength() + { + switch (this.curValueBitsRead) + { + case 5: + { + switch (this.value) + { + case 0x1B: + return 64; + case 0x12: + return 128; + } + + break; + } + + case 6: + { + switch (this.value) + { + case 0x17: + return 192; + case 0x18: + return 1664; + } + + break; + } + + case 7: + { + switch (this.value) + { + case 0x37: + return 256; + } + + break; + } + + case 8: + { + switch (this.value) + { + case 0x36: + return 320; + case 0x37: + return 348; + case 0x64: + return 448; + case 0x65: + return 512; + case 0x68: + return 576; + case 0x67: + return 640; + } + + break; + } + + case 9: + { + switch (this.value) + { + case 0xCC: + return 704; + case 0xCD: + return 768; + case 0xD2: + return 832; + case 0xD3: + return 896; + case 0xD4: + return 960; + case 0xD5: + return 1024; + case 0xD6: + return 1088; + case 0xD7: + return 1152; + case 0xD8: + return 1216; + case 0xD9: + return 1280; + case 0xDA: + return 1344; + case 0xDB: + return 1408; + case 0x98: + return 1472; + case 0x99: + return 1536; + case 0x9A: + return 1600; + case 0x9B: + return 1728; + } + + break; + } + } + + return 0; + } + + private uint BlackMakeupCodeRunLength() + { + switch (this.curValueBitsRead) + { + case 10: + { + switch (this.value) + { + case 0xF: + return 64; + } + } + + break; + + case 12: + { + switch (this.value) + { + case 0xC8: + return 128; + case 0xC9: + return 192; + case 0x5B: + return 256; + case 0x33: + return 320; + case 0x34: + return 384; + case 0x35: + return 448; + } + } + + break; + + case 13: + { + switch (this.value) + { + case 0x6C: + return 512; + case 0x6D: + return 576; + case 0x4A: + return 640; + case 0x4B: + return 704; + case 0x4C: + return 768; + case 0x4D: + return 832; + case 0x72: + return 896; + case 0x73: + return 960; + case 0x74: + return 1024; + case 0x75: + return 1088; + case 0x76: + return 1152; + case 0x77: + return 1216; + case 0x52: + return 1280; + case 0x53: + return 1344; + case 0x54: + return 1408; + case 0x55: + return 1472; + case 0x5A: + return 1536; + case 0x5B: + return 1600; + case 0x64: + return 1664; + case 0x65: + return 1728; + } + + break; + } + } + + return 0; + } + + private bool IsMakeupCode() + { + if (this.IsWhiteRun) + { + return this.IsWhiteMakeupCode(); + } + + return this.IsBlackMakeupCode(); + } + + private bool IsWhiteMakeupCode() + { + switch (this.curValueBitsRead) + { + case 5: + { + uint[] codes = { 0x1B, 0x12 }; + return codes.Contains(this.value); + } + + case 6: + { + uint[] codes = { 0x17, 0x18 }; + return codes.Contains(this.value); + } + + case 7: + { + uint[] codes = { 0x37 }; + return codes.Contains(this.value); + } + + case 8: + { + uint[] codes = { 0x36, 0x37, 0x64, 0x65, 0x68, 0x67 }; + return codes.Contains(this.value); + } + + case 9: + { + uint[] codes = + { + 0xCC, 0xCD, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0x98, + 0x99, 0x9A, 0x9B + }; + return codes.Contains(this.value); + } + } + + return false; + } + + private bool IsBlackMakeupCode() + { + switch (this.curValueBitsRead) + { + case 10: + { + uint[] codes = { 0xF }; + return codes.Contains(this.value); + } + + case 12: + { + uint[] codes = { 0xC8, 0xC9, 0x5B, 0x33, 0x34, 0x35 }; + return codes.Contains(this.value); + } + + case 13: + { + uint[] codes = + { + 0x6C, 0x6D, 0x4A, 0x4B, 0x4C, 0x4D, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x52, + 0x53, 0x54, 0x55, 0x5A, 0x5B, 0x64, 0x65 + }; + return codes.Contains(this.value); + } + } + + return false; + } + + private bool IsTerminatingCode() + { + if (this.IsWhiteRun) + { + return this.IsWhiteTerminatingCode(); + } + + return this.IsBlackTerminatingCode(); + } + + private bool IsWhiteTerminatingCode() + { + switch (this.curValueBitsRead) + { + case 4: + { + uint[] codes = { 0x7, 0x8, 0xB, 0xC, 0xE, 0xF }; + return codes.Contains(this.value); + } + + case 5: + { + uint[] codes = { 0x13, 0x14, 0x7, 0x8 }; + return codes.Contains(this.value); + } + + case 6: + { + uint[] codes = { 0x7, 0x8, 0x3, 0x34, 0x35, 0x2A, 0x2B }; + return codes.Contains(this.value); + } + + case 7: + { + uint[] codes = { 0x27, 0xC, 0x8, 0x17, 0x3, 0x4, 0x28, 0x2B, 0x13, 0x24, 0x18 }; + return codes.Contains(this.value); + } + + case 8: + { + uint[] codes = + { + 0x35, 0x2, 0x3, 0x1A, 0x1B, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x28, 0x29, + 0x2A, 0x2B, 0x2C, 0x2D, 0x4, 0x5, 0xA, 0xB, 0x52, 0x53, 0x54, 0x55, 0x24, 0x25, + 0x58, 0x59, 0x5A, 0x5B, 0x4A, 0x4B, 0x32, 0x33, 0x34 + }; + return codes.Contains(this.value); + } + } + + return false; + } + + private bool IsBlackTerminatingCode() + { + switch (this.curValueBitsRead) + { + case 2: + { + uint[] codes = {0x3, 0x2}; + return codes.Contains(this.value); + } + + case 3: + { + uint[] codes = {0x02, 0x03}; + return codes.Contains(this.value); + } + + case 4: + { + uint[] codes = {0x03, 0x02}; + return codes.Contains(this.value); + } + + case 5: + { + uint[] codes = {0x03}; + return codes.Contains(this.value); + } + + case 6: + { + uint[] codes = {0x5, 0x4}; + return codes.Contains(this.value); + } + + case 7: + { + uint[] codes = { 0x4, 0x5, 0x7 }; + return codes.Contains(this.value); + } + + case 8: + { + uint[] codes = { 0x4, 0x7 }; + return codes.Contains(this.value); + } + + case 9: + { + uint[] codes = { 0x18 }; + return codes.Contains(this.value); + } + + case 10: + { + uint[] codes = { 0x37, 0x17, 0x18, 0x8 }; + return codes.Contains(this.value); + } + + case 11: + { + uint[] codes = { 0x67, 0x68, 0x6C, 0x37, 0x28, 0x17, 0x18 }; + return codes.Contains(this.value); + } + + case 12: + { + uint[] codes = + { + 0xCA, 0xCB, 0xCC, 0xCD, 0x68, 0x69, 0x6A, 0x6B, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, + 0xD7, 0x6C, 0x6D, 0xDA, 0xDB, 0x54, 0x55, 0x56, 0x57, 0x64, 0x65, 0x52, 0x53, + 0x24, 0x37, 0x38, 0x27, 0x28, 0x58, 0x59, 0x2B, 0x2C, 0x5A, 0x66, 0x67 + }; + return codes.Contains(this.value); + } + } + + return false; + } + + private void Reset(bool resetRunLength = true) + { + this.value = 0; + this.curValueBitsRead = 0; + + if (resetRunLength) + { + this.runLength = 0; + } + } + + private uint ReadValue(int nBits) + { + Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + Guard.MustBeLessThanOrEqualTo(nBits, 12, nameof(nBits)); + + uint v = 0; + int shift = nBits; + while (shift-- > 0) + { + uint bit = this.GetBit(); + v |= bit << shift; + this.curValueBitsRead++; + } + + return v; + } + + private uint GetBit() + { + if (this.bitsRead >= 8) + { + this.LoadNewByte(); + } + + int shift = 8 - this.bitsRead - 1; + var bit = (uint)((this.Data[this.position] & (1 << shift)) != 0 ? 1 : 0); + this.bitsRead++; + + return bit; + } + + private void LoadNewByte() + { + this.position++; + this.bitsRead = 0; + + if (this.position >= (ulong)this.Data.Length) + { + TiffThrowHelper.ThrowImageFormatException("tiff image has invalid t4 compressed data"); + } + } + + private void ReadImageDataFromStream(Stream input, int bytesToRead) + { + var buffer = new byte[4096]; + + Span bufferSpan = buffer.AsSpan(); + Span dataSpan = this.Data.AsSpan(); + + int read; + while (bytesToRead > 0 && + (read = input.Read(buffer, 0, Math.Min(bufferSpan.Length, bytesToRead))) > 0) + { + buffer.AsSpan(0, read).CopyTo(dataSpan); + bytesToRead -= read; + dataSpan = dataSpan.Slice(read); + } + + if (bytesToRead > 0) + { + TiffThrowHelper.ThrowImageFormatException("tiff image file has insufficient data"); + } + } +} +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs new file mode 100644 index 0000000000..52b11613b6 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs @@ -0,0 +1,95 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression +{ + /// + /// Class to handle cases where TIFF image data is compressed using CCITT T4 compression. + /// + internal class T4TiffCompression : TiffBaseCompression + { + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + public T4TiffCompression(MemoryAllocator allocator) + : base(allocator) + { + } + + /// + public override void Decompress(Stream stream, int byteCount, Span buffer) + { + // TODO: handle case when white is not zero. + bool isWhiteZero = true; + int whiteValue = isWhiteZero ? 0 : 1; + int blackValue = isWhiteZero ? 1 : 0; + + var bitReader = new T4BitReader(stream, byteCount); + + uint bitsWritten = 0; + uint pixels = 0; + while (bitReader.HasMoreData) + { + bitReader.ReadNextRun(); + + if (bitReader.IsEndOfScanLine) + { + // Write padding bytes, if necessary. + uint pad = 8 - (bitsWritten % 8); + if (pad != 8) + { + this.WriteBits(buffer, (int)bitsWritten, pad, 0); + bitsWritten += pad; + } + + continue; + } + + if (bitReader.IsWhiteRun) + { + this.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, whiteValue); + bitsWritten += bitReader.RunLength; + pixels += bitReader.RunLength; + } + else + { + this.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, blackValue); + bitsWritten += bitReader.RunLength; + pixels += bitReader.RunLength; + } + } + + int foo = 0; + } + + private void WriteBits(Span buffer, int pos, uint count, int value) + { + int bitPos = pos % 8; + int bufferPos = pos / 8; + int startIdx = bufferPos + bitPos; + int endIdx = (int)(startIdx + count); + + for (int i = startIdx; i < endIdx; i++) + { + this.WriteBit(buffer, bufferPos, bitPos, value); + + bitPos++; + if (bitPos >= 8) + { + bitPos = 0; + bufferPos++; + } + } + } + + private void WriteBit(Span buffer, int bufferPos, int bitPos, int value) + { + buffer[bufferPos] |= (byte)(value << (7 - bitPos)); + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs index 0f893448d3..cedbbe35b0 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; +using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Tiff @@ -20,6 +20,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff return new DeflateTiffCompression(allocator); case TiffCompressionType.Lzw: return new LzwTiffCompression(allocator); + case TiffCompressionType.T4: + return new T4TiffCompression(allocator); default: throw TiffThrowHelper.NotSupportedCompression(nameof(compressionType)); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs index b62def1ea3..665e4aca20 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Tiff @@ -27,5 +27,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// Image data is compressed using LZW compression. /// Lzw = 3, + + /// + /// Image data is compressed using T4-encoding: CCITT T.4. + /// + T4 = 4, } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 468989d19c..7eced53bd1 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; @@ -311,7 +309,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip); - using IManagedByteBuffer stripBuffer = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize); + using IManagedByteBuffer stripBuffer = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize, AllocationOptions.Clean); Buffer2D pixels = frame.PixelBuffer; diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs index 86a7560cf4..5348be8ce9 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs @@ -1,14 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; -using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff { @@ -348,6 +346,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff break; } + case TiffCompression.CcittGroup3Fax: + { + options.CompressionType = TiffCompressionType.T4; + break; + } + default: { TiffThrowHelper.ThrowNotSupported("The specified TIFF compression format is not supported: " + compression); diff --git a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs index 3254744bc8..96a3e8dbc7 100644 --- a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs +++ b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs @@ -12,6 +12,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// internal static class TiffThrowHelper { + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowImageFormatException(string errorMessage) + { + throw new ImageFormatException(errorMessage); + } + [MethodImpl(InliningOptions.ColdPath)] public static Exception TagNotFound(string tagName) => new ArgumentException("Required tag is not found.", tagName);