From a11752e98c8e5ede6eed22672ddb389e8387b368 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 22 Aug 2021 20:21:29 +0200 Subject: [PATCH 1/5] Add support for decoding tiff's with T.6 fax compression --- .../Decompressors/CcittReferenceScanline.cs | 154 +++++++ .../Decompressors/CcittTwoDimensionalCode.cs | 27 ++ .../CcittTwoDimensionalCodeType.cs | 32 ++ .../Decompressors/DeflateTiffCompression.cs | 2 +- .../Decompressors/LzwTiffCompression.cs | 2 +- .../Decompressors/ModifiedHuffmanBitReader.cs | 73 ++++ .../ModifiedHuffmanTiffCompression.cs | 19 +- .../Decompressors/NoneTiffCompression.cs | 3 +- .../Decompressors/PackBitsTiffCompression.cs | 2 +- .../Compression/Decompressors/T4BitReader.cs | 375 +++++++++--------- .../Decompressors/T4TiffCompression.cs | 10 +- .../Compression/Decompressors/T6BitReader.cs | 160 ++++++++ .../Decompressors/T6TiffCompression.cs | 253 ++++++++++++ .../Tiff/Compression/TiffBaseDecompressor.cs | 19 +- .../Compression/TiffDecoderCompressionType.cs | 7 +- .../Compression/TiffDecompressorsFactory.cs | 4 + src/ImageSharp/Formats/Tiff/README.md | 2 +- .../Formats/Tiff/TiffDecoderCore.cs | 9 +- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 8 + .../DeflateTiffCompressionTests.cs | 2 +- .../Compression/LzwTiffCompressionTests.cs | 2 +- .../Compression/NoneTiffCompressionTests.cs | 8 +- .../PackBitsTiffCompressionTests.cs | 9 +- 23 files changed, 959 insertions(+), 223 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs create mode 100644 src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs create mode 100644 src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs create mode 100644 src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs create mode 100644 src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs create mode 100644 src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs new file mode 100644 index 0000000000..64da694021 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs @@ -0,0 +1,154 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Represents a reference scan line for CCITT 2D decoding. + /// + internal readonly ref struct CcittReferenceScanline + { + private readonly ReadOnlySpan scanLine; + private readonly int width; + private readonly byte whiteByte; + + /// + /// Initializes a new instance of the struct. + /// + /// Indicates, if white is zero, otherwise black is zero. + /// The scan line. + public CcittReferenceScanline(bool whiteIsZero, ReadOnlySpan scanLine) + { + this.scanLine = scanLine; + this.width = scanLine.Length; + this.whiteByte = whiteIsZero ? (byte)0 : (byte)255; + } + + /// + /// Initializes a new instance of the struct. + /// + /// Indicates, if white is zero, otherwise black is zero. + /// The width of the scanline. + public CcittReferenceScanline(bool whiteIsZero, int width) + { + this.scanLine = default; + this.width = width; + this.whiteByte = whiteIsZero ? (byte)0 : (byte)255; + } + + public bool IsEmpty => this.scanLine.IsEmpty; + + /// + /// Finds b1: The first changing element on the reference line to the right of a0 and of opposite color to a0. + /// + /// The reference or starting element om the coding line. + /// Fill byte. + /// Position of b1. + public int FindB1(int a0, byte a0Byte) + { + if (this.scanLine.IsEmpty) + { + return this.FindB1ForImaginaryWhiteLine(a0, a0Byte); + } + + return this.FindB1ForNormalLine(a0, a0Byte); + } + + /// + /// Finds b2: The next changing element to the right of b1 on the reference line. + /// + /// The first changing element on the reference line to the right of a0 and opposite of color to a0. + /// Position of b1. + public int FindB2(int b1) + { + if (this.scanLine.IsEmpty) + { + return this.FindB2ForImaginaryWhiteLine(); + } + + return this.FindB2ForNormalLine(b1); + } + + private int FindB1ForImaginaryWhiteLine(int a0, byte a0Byte) + { + if (a0 < 0) + { + if (a0Byte != this.whiteByte) + { + return 0; + } + } + + return this.width; + } + + private int FindB1ForNormalLine(int a0, byte a0Byte) + { + int offset = 0; + if (a0 < 0) + { + if (a0Byte != this.scanLine[0]) + { + return 0; + } + } + else + { + offset = a0; + } + + ReadOnlySpan searchSpace = this.scanLine.Slice(offset); + byte searchByte = (byte)~a0Byte; + int index = searchSpace.IndexOf(searchByte); + if (index < 0) + { + return this.scanLine.Length; + } + + if (index != 0) + { + return offset + index; + } + + searchByte = (byte)~searchSpace[0]; + index = searchSpace.IndexOf(searchByte); + if (index < 0) + { + return this.scanLine.Length; + } + + searchSpace = searchSpace.Slice(index); + offset += index; + index = searchSpace.IndexOf((byte)~searchByte); + if (index < 0) + { + return this.scanLine.Length; + } + + return index + offset; + } + + private int FindB2ForImaginaryWhiteLine() => this.width; + + private int FindB2ForNormalLine(int b1) + { + if (b1 >= this.scanLine.Length) + { + return this.scanLine.Length; + } + + byte searchByte = (byte)~this.scanLine[b1]; + int offset = b1 + 1; + ReadOnlySpan searchSpace = this.scanLine.Slice(offset); + int index = searchSpace.IndexOf(searchByte); + if (index == -1) + { + return this.scanLine.Length; + } + + return offset + index; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs new file mode 100644 index 0000000000..74a17b9075 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Diagnostics; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + [DebuggerDisplay("Type = {Type}")] + internal readonly struct CcittTwoDimensionalCode + { + private readonly ushort value; + + /// + /// Initializes a new instance of the struct. + /// + /// The type. + /// The bits required. + /// The extension bits. + public CcittTwoDimensionalCode(CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0) + => this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11)); + + /// + /// Gets the code type. + /// + public CcittTwoDimensionalCodeType Type => (CcittTwoDimensionalCodeType)(this.value & 0b11111111); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs new file mode 100644 index 0000000000..0bd04b4cde --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + internal enum CcittTwoDimensionalCodeType + { + None = 0, + + Pass = 1, + + Horizontal = 2, + + Vertical0 = 3, + + VerticalR1 = 4, + + VerticalR2 = 5, + + VerticalR3 = 6, + + VerticalL1 = 7, + + VerticalL2 = 8, + + VerticalL3 = 9, + + Extensions1D = 10, + + Extensions2D = 11, + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs index bb57853d53..917f83585f 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs @@ -41,7 +41,7 @@ public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bi } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) { long pos = stream.Position; using (var deframeStream = new ZlibInflateStream( diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs index 2e89396075..77d7b765b7 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs @@ -35,7 +35,7 @@ public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPe } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) { var decoder = new TiffLzwDecoder(stream); decoder.DecodePixels(buffer); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs new file mode 100644 index 0000000000..90ec0d6caf --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Bit reader for data encoded with the modified huffman rle method. + /// See TIFF 6.0 specification, section 10. + /// + internal class ModifiedHuffmanBitReader : T4BitReader + { + /// + /// 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. + public ModifiedHuffmanBitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator) + : base(input, fillOrder, bytesToRead, allocator) + { + } + + /// + public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (this.BitsRead > 0 && this.BitsRead < 7); + + /// + public override bool IsEndOfScanLine + { + get + { + if (this.IsWhiteRun && this.CurValueBitsRead == 12 && this.Value == 1) + { + return true; + } + + if (this.CurValueBitsRead == 11 && this.Value == 0) + { + // black run. + return true; + } + + return false; + } + } + + /// + public override void StartNewRow() + { + base.StartNewRow(); + + int pad = 8 - (this.BitsRead % 8); + if (pad != 8) + { + // Skip padding bits, move to next byte. + this.Position++; + this.ResetBitsRead(); + } + } + + /// + /// No EOL is expected at the start of a run for the modified huffman encoding. + /// + protected override void ReadEolBeforeFirstData() + { + // Nothing to do here. + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs index 9b12dc90ff..65feaa427a 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs @@ -35,9 +35,9 @@ public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffFillOrder f } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) { - using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true); + using var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount, this.Allocator); buffer.Clear(); uint bitsWritten = 0; @@ -51,20 +51,20 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa if (bitReader.IsWhiteRun) { BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); - bitsWritten += bitReader.RunLength; - pixelsWritten += bitReader.RunLength; } else { BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue); - bitsWritten += bitReader.RunLength; - pixelsWritten += bitReader.RunLength; } + + bitsWritten += bitReader.RunLength; + pixelsWritten += bitReader.RunLength; } - if (pixelsWritten % this.Width == 0) + if (pixelsWritten == this.Width) { bitReader.StartNewRow(); + pixelsWritten = 0; // Write padding bits, if necessary. uint pad = 8 - (bitsWritten % 8); @@ -74,6 +74,11 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa bitsWritten += pad; } } + + if (pixelsWritten > this.Width) + { + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, decoded more pixels then image width"); + } } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs index 58a1c98781..c4a9524306 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs @@ -25,7 +25,8 @@ public NoneTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsP } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount)); + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) + => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount)); /// protected override void Dispose(bool disposing) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs index e14736b734..8a001f5714 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs @@ -28,7 +28,7 @@ public PackBitsTiffCompression(MemoryAllocator memoryAllocator, int width, int b } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) { if (this.compressedDataMemory == null) { diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs index 384be1cf24..5e83abf8f6 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs @@ -16,31 +16,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// internal class T4BitReader : IDisposable { - /// - /// Number of bits read. - /// - private int bitsRead; - /// /// The logical order of bits within a byte. /// private readonly TiffFillOrder fillOrder; - /// - /// 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 whether its the first line of data which is read from the image. /// @@ -57,20 +37,19 @@ internal class T4BitReader : IDisposable /// private bool isStartOfRow; - /// - /// Indicates whether the modified huffman compression, as specified in the TIFF spec in section 10, is used. - /// - private readonly bool isModifiedHuffmanRle; - /// /// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false. /// private readonly bool eolPadding; - private readonly int dataLength; - + /// + /// The minimum code length in bits. + /// private const int MinCodeLength = 2; + /// + /// The maximum code length in bits. + /// private readonly int maxCodeLength = 13; private static readonly Dictionary WhiteLen4TermCodes = new Dictionary() @@ -231,19 +210,17 @@ internal class T4BitReader : IDisposable /// 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, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false) + public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false) { this.fillOrder = fillOrder; this.Data = allocator.Allocate(bytesToRead); this.ReadImageDataFromStream(input, bytesToRead); - this.isModifiedHuffmanRle = isModifiedHuffman; - this.dataLength = bytesToRead; - this.bitsRead = 0; - this.value = 0; - this.curValueBitsRead = 0; - this.position = 0; + this.DataLength = bytesToRead; + this.BitsRead = 0; + this.Value = 0; + this.CurValueBitsRead = 0; + this.Position = 0; this.IsWhiteRun = true; this.isFirstScanLine = true; this.isStartOfRow = true; @@ -257,6 +234,31 @@ public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, Memor } } + /// + /// Gets the current value. + /// + protected uint Value { get; private set; } + + /// + /// Gets the number of bits read for the current run value. + /// + protected int CurValueBitsRead { get; private set; } + + /// + /// Gets the number of bits read. + /// + protected int BitsRead { get; private set; } + + /// + /// Gets the available data in bytes. + /// + protected int DataLength { get; } + + /// + /// Gets or sets the byte position in the buffer. + /// + protected ulong Position { get; set; } + /// /// Gets the compressed image data. /// @@ -265,23 +267,12 @@ public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, Memor /// /// Gets a value indicating whether there is more data to read left. /// - public bool HasMoreData - { - get - { - if (this.isModifiedHuffmanRle) - { - return this.position < (ulong)this.dataLength - 1 || (this.bitsRead > 0 && this.bitsRead < 7); - } - - return this.position < (ulong)this.dataLength - 1; - } - } + public virtual bool HasMoreData => this.Position < (ulong)this.DataLength - 1; /// - /// Gets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run. + /// Gets or sets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run. /// - public bool IsWhiteRun { get; private set; } + public bool IsWhiteRun { get; protected set; } /// /// Gets the number of pixels in the current run. @@ -291,16 +282,16 @@ public bool HasMoreData /// /// Gets a value indicating whether the end of a pixel row has been reached. /// - public bool IsEndOfScanLine + public virtual bool IsEndOfScanLine { get { if (this.eolPadding) { - return this.curValueBitsRead >= 12 && this.value == 1; + return this.CurValueBitsRead >= 12 && this.Value == 1; } - return this.curValueBitsRead == 12 && this.value == 1; + return this.CurValueBitsRead == 12 && this.Value == 1; } } @@ -315,29 +306,20 @@ public void ReadNextRun() this.terminationCodeFound = false; } + // Initialize for next run. this.Reset(); - if (this.isFirstScanLine && !this.isModifiedHuffmanRle) - { - // We expect an EOL before the first data. - this.value = this.ReadValue(this.eolPadding ? 16 : 12); - - if (!this.IsEndOfScanLine) - { - TiffThrowHelper.ThrowImageFormatException("t4 parsing error: expected start of data marker not found"); - } - - this.Reset(); - } + // We expect an EOL before the first data. + this.ReadEolBeforeFirstData(); // A code word must have at least 2 bits. - this.value = this.ReadValue(MinCodeLength); + this.Value = this.ReadValue(MinCodeLength); do { - if (this.curValueBitsRead > this.maxCodeLength) + if (this.CurValueBitsRead > this.maxCodeLength) { - TiffThrowHelper.ThrowImageFormatException("t4 parsing error: invalid code length read"); + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read"); } bool isMakeupCode = this.IsMakeupCode(); @@ -363,10 +345,11 @@ public void ReadNextRun() // Each line starts with a white run. If the image starts with black, a white run with length zero is written. if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0) { - this.IsWhiteRun = !this.IsWhiteRun; this.Reset(); this.isStartOfRow = false; - continue; + this.terminationCodeFound = true; + this.RunLength = 0; + break; } if (this.IsWhiteRun) @@ -384,7 +367,7 @@ public void ReadNextRun() } uint currBit = this.ReadValue(1); - this.value = (this.value << 1) | currBit; + this.Value = (this.Value << 1) | currBit; if (this.IsEndOfScanLine) { @@ -396,55 +379,106 @@ public void ReadNextRun() this.isFirstScanLine = false; } - public void StartNewRow() + /// + /// Initialization for a new row. + /// + public virtual void StartNewRow() { // Each new row starts with a white run. this.IsWhiteRun = true; this.isStartOfRow = true; this.terminationCodeFound = false; + } - if (this.isModifiedHuffmanRle) + /// + public void Dispose() => this.Data.Dispose(); + + /// + /// An EOL is expected before the first data. + /// + protected virtual void ReadEolBeforeFirstData() + { + if (this.isFirstScanLine) { - int pad = 8 - (this.bitsRead % 8); - if (pad != 8) + this.Value = this.ReadValue(this.eolPadding ? 16 : 12); + + if (!this.IsEndOfScanLine) { - // Skip padding bits, move to next byte. - this.position++; - this.bitsRead = 0; + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: expected start of data marker not found"); } + + this.Reset(); } } - /// - public void Dispose() => this.Data.Dispose(); + /// + /// Resets the current value read and the number of bits read. + /// + /// if set to true resets also the run length. + protected void Reset(bool resetRunLength = true) + { + this.Value = 0; + this.CurValueBitsRead = 0; + + if (resetRunLength) + { + this.RunLength = 0; + } + } + + /// + /// Resets the bits read to 0. + /// + protected void ResetBitsRead() => this.BitsRead = 0; + + /// + /// Reads the next value. + /// + /// The number of bits to read. + /// The value read. + protected uint ReadValue(int nBits) + { + Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + + uint v = 0; + int shift = nBits; + while (shift-- > 0) + { + uint bit = this.GetBit(); + v |= bit << shift; + this.CurValueBitsRead++; + } + + return v; + } private uint WhiteTerminatingCodeRunLength() { - switch (this.curValueBitsRead) + switch (this.CurValueBitsRead) { case 4: { - return WhiteLen4TermCodes[this.value]; + return WhiteLen4TermCodes[this.Value]; } case 5: { - return WhiteLen5TermCodes[this.value]; + return WhiteLen5TermCodes[this.Value]; } case 6: { - return WhiteLen6TermCodes[this.value]; + return WhiteLen6TermCodes[this.Value]; } case 7: { - return WhiteLen7TermCodes[this.value]; + return WhiteLen7TermCodes[this.Value]; } case 8: { - return WhiteLen8TermCodes[this.value]; + return WhiteLen8TermCodes[this.Value]; } } @@ -453,61 +487,61 @@ private uint WhiteTerminatingCodeRunLength() private uint BlackTerminatingCodeRunLength() { - switch (this.curValueBitsRead) + switch (this.CurValueBitsRead) { case 2: { - return BlackLen2TermCodes[this.value]; + return BlackLen2TermCodes[this.Value]; } case 3: { - return BlackLen3TermCodes[this.value]; + return BlackLen3TermCodes[this.Value]; } case 4: { - return BlackLen4TermCodes[this.value]; + return BlackLen4TermCodes[this.Value]; } case 5: { - return BlackLen5TermCodes[this.value]; + return BlackLen5TermCodes[this.Value]; } case 6: { - return BlackLen6TermCodes[this.value]; + return BlackLen6TermCodes[this.Value]; } case 7: { - return BlackLen7TermCodes[this.value]; + return BlackLen7TermCodes[this.Value]; } case 8: { - return BlackLen8TermCodes[this.value]; + return BlackLen8TermCodes[this.Value]; } case 9: { - return BlackLen9TermCodes[this.value]; + return BlackLen9TermCodes[this.Value]; } case 10: { - return BlackLen10TermCodes[this.value]; + return BlackLen10TermCodes[this.Value]; } case 11: { - return BlackLen11TermCodes[this.value]; + return BlackLen11TermCodes[this.Value]; } case 12: { - return BlackLen12TermCodes[this.value]; + return BlackLen12TermCodes[this.Value]; } } @@ -516,41 +550,41 @@ private uint BlackTerminatingCodeRunLength() private uint WhiteMakeupCodeRunLength() { - switch (this.curValueBitsRead) + switch (this.CurValueBitsRead) { case 5: { - return WhiteLen5MakeupCodes[this.value]; + return WhiteLen5MakeupCodes[this.Value]; } case 6: { - return WhiteLen6MakeupCodes[this.value]; + return WhiteLen6MakeupCodes[this.Value]; } case 7: { - return WhiteLen7MakeupCodes[this.value]; + return WhiteLen7MakeupCodes[this.Value]; } case 8: { - return WhiteLen8MakeupCodes[this.value]; + return WhiteLen8MakeupCodes[this.Value]; } case 9: { - return WhiteLen9MakeupCodes[this.value]; + return WhiteLen9MakeupCodes[this.Value]; } case 11: { - return WhiteLen11MakeupCodes[this.value]; + return WhiteLen11MakeupCodes[this.Value]; } case 12: { - return WhiteLen12MakeupCodes[this.value]; + return WhiteLen12MakeupCodes[this.Value]; } } @@ -559,26 +593,26 @@ private uint WhiteMakeupCodeRunLength() private uint BlackMakeupCodeRunLength() { - switch (this.curValueBitsRead) + switch (this.CurValueBitsRead) { case 10: { - return BlackLen10MakeupCodes[this.value]; + return BlackLen10MakeupCodes[this.Value]; } case 11: { - return BlackLen11MakeupCodes[this.value]; + return BlackLen11MakeupCodes[this.Value]; } case 12: { - return BlackLen12MakeupCodes[this.value]; + return BlackLen12MakeupCodes[this.Value]; } case 13: { - return BlackLen13MakeupCodes[this.value]; + return BlackLen13MakeupCodes[this.Value]; } } @@ -597,49 +631,41 @@ private bool IsMakeupCode() private bool IsWhiteMakeupCode() { - switch (this.curValueBitsRead) + switch (this.CurValueBitsRead) { case 5: { - return WhiteLen5MakeupCodes.ContainsKey(this.value); + return WhiteLen5MakeupCodes.ContainsKey(this.Value); } case 6: { - return WhiteLen6MakeupCodes.ContainsKey(this.value); + return WhiteLen6MakeupCodes.ContainsKey(this.Value); } case 7: { - return WhiteLen7MakeupCodes.ContainsKey(this.value); + return WhiteLen7MakeupCodes.ContainsKey(this.Value); } case 8: { - return WhiteLen8MakeupCodes.ContainsKey(this.value); + return WhiteLen8MakeupCodes.ContainsKey(this.Value); } case 9: { - return WhiteLen9MakeupCodes.ContainsKey(this.value); + return WhiteLen9MakeupCodes.ContainsKey(this.Value); } case 11: { - return WhiteLen11MakeupCodes.ContainsKey(this.value); + return WhiteLen11MakeupCodes.ContainsKey(this.Value); } case 12: { - if (this.isModifiedHuffmanRle) - { - if (this.value == 1) - { - return true; - } - } - - return WhiteLen12MakeupCodes.ContainsKey(this.value); + return WhiteLen12MakeupCodes.ContainsKey(this.Value); } } @@ -648,34 +674,26 @@ private bool IsWhiteMakeupCode() private bool IsBlackMakeupCode() { - switch (this.curValueBitsRead) + switch (this.CurValueBitsRead) { case 10: { - return BlackLen10MakeupCodes.ContainsKey(this.value); + return BlackLen10MakeupCodes.ContainsKey(this.Value); } case 11: { - if (this.isModifiedHuffmanRle) - { - if (this.value == 0) - { - return true; - } - } - - return BlackLen11MakeupCodes.ContainsKey(this.value); + return BlackLen11MakeupCodes.ContainsKey(this.Value); } case 12: { - return BlackLen12MakeupCodes.ContainsKey(this.value); + return BlackLen12MakeupCodes.ContainsKey(this.Value); } case 13: { - return BlackLen13MakeupCodes.ContainsKey(this.value); + return BlackLen13MakeupCodes.ContainsKey(this.Value); } } @@ -694,31 +712,31 @@ private bool IsTerminatingCode() private bool IsWhiteTerminatingCode() { - switch (this.curValueBitsRead) + switch (this.CurValueBitsRead) { case 4: { - return WhiteLen4TermCodes.ContainsKey(this.value); + return WhiteLen4TermCodes.ContainsKey(this.Value); } case 5: { - return WhiteLen5TermCodes.ContainsKey(this.value); + return WhiteLen5TermCodes.ContainsKey(this.Value); } case 6: { - return WhiteLen6TermCodes.ContainsKey(this.value); + return WhiteLen6TermCodes.ContainsKey(this.Value); } case 7: { - return WhiteLen7TermCodes.ContainsKey(this.value); + return WhiteLen7TermCodes.ContainsKey(this.Value); } case 8: { - return WhiteLen8TermCodes.ContainsKey(this.value); + return WhiteLen8TermCodes.ContainsKey(this.Value); } } @@ -727,117 +745,90 @@ private bool IsWhiteTerminatingCode() private bool IsBlackTerminatingCode() { - switch (this.curValueBitsRead) + switch (this.CurValueBitsRead) { case 2: { - return BlackLen2TermCodes.ContainsKey(this.value); + return BlackLen2TermCodes.ContainsKey(this.Value); } case 3: { - return BlackLen3TermCodes.ContainsKey(this.value); + return BlackLen3TermCodes.ContainsKey(this.Value); } case 4: { - return BlackLen4TermCodes.ContainsKey(this.value); + return BlackLen4TermCodes.ContainsKey(this.Value); } case 5: { - return BlackLen5TermCodes.ContainsKey(this.value); + return BlackLen5TermCodes.ContainsKey(this.Value); } case 6: { - return BlackLen6TermCodes.ContainsKey(this.value); + return BlackLen6TermCodes.ContainsKey(this.Value); } case 7: { - return BlackLen7TermCodes.ContainsKey(this.value); + return BlackLen7TermCodes.ContainsKey(this.Value); } case 8: { - return BlackLen8TermCodes.ContainsKey(this.value); + return BlackLen8TermCodes.ContainsKey(this.Value); } case 9: { - return BlackLen9TermCodes.ContainsKey(this.value); + return BlackLen9TermCodes.ContainsKey(this.Value); } case 10: { - return BlackLen10TermCodes.ContainsKey(this.value); + return BlackLen10TermCodes.ContainsKey(this.Value); } case 11: { - return BlackLen11TermCodes.ContainsKey(this.value); + return BlackLen11TermCodes.ContainsKey(this.Value); } case 12: { - return BlackLen12TermCodes.ContainsKey(this.value); + return BlackLen12TermCodes.ContainsKey(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)); - - 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) + if (this.BitsRead >= 8) { this.LoadNewByte(); } Span dataSpan = this.Data.GetSpan(); - int shift = 8 - this.bitsRead - 1; - uint bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0); - this.bitsRead++; + int shift = 8 - this.BitsRead - 1; + uint bit = (uint)((dataSpan[(int)this.Position] & (1 << shift)) != 0 ? 1 : 0); + this.BitsRead++; return bit; } private void LoadNewByte() { - this.position++; - this.bitsRead = 0; + this.Position++; + this.ResetBitsRead(); - if (this.position >= (ulong)this.dataLength) + if (this.Position >= (ulong)this.DataLength) { - TiffThrowHelper.ThrowImageFormatException("tiff image has invalid t4 compressed data"); + TiffThrowHelper.ThrowImageFormatException("tiff image has invalid ccitt compressed data"); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs index a79ef3fe5e..e424d52904 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs @@ -31,7 +31,13 @@ internal class T4TiffCompression : TiffBaseDecompressor /// The number of bits per pixel. /// Fax compression options. /// The photometric interpretation. - public T4TiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, 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; @@ -48,7 +54,7 @@ public T4TiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int protected TiffFillOrder FillOrder { get; } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) { if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding)) { diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs new file mode 100644 index 0000000000..0ebaccf7eb --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs @@ -0,0 +1,160 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.IO; + +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Bit reader for reading CCITT T6 compressed fax data. + /// See: Facsimile Coding Schemes and Coding Control Functions for Group 4 Facsimile Apparatus, itu-t recommendation t.6 + /// + internal class T6BitReader : T4BitReader + { + private readonly int maxCodeLength = 12; + + private static readonly CcittTwoDimensionalCode None = new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.None, 0); + + private static readonly Dictionary Len1Codes = new Dictionary() + { + { 0b1, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Vertical0, 1) } + }; + + private static readonly Dictionary Len3Codes = new Dictionary() + { + { 0b001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Horizontal, 3) }, + { 0b010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL1, 3) }, + { 0b011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR1, 3) } + }; + + private static readonly Dictionary Len4Codes = new Dictionary() + { + { 0b0001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Pass, 4) } + }; + + private static readonly Dictionary Len6Codes = new Dictionary() + { + { 0b000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR2, 6) }, + { 0b000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL2, 6) } + }; + + private static readonly Dictionary Len7Codes = new Dictionary() + { + { 0b0000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR3, 7) }, + { 0b0000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL3, 7) }, + { 0b0000001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions2D, 7) }, + { 0b0000000, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions1D, 7) } + }; + + /// + /// 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. + public T6BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator) + : base(input, fillOrder, bytesToRead, allocator) + { + } + + /// + public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (this.BitsRead > 0 && this.BitsRead < 7); + + /// + /// Gets or sets the two dimensional code. + /// + public CcittTwoDimensionalCode Code { get; internal set; } + + public bool ReadNextCodeWord() + { + this.Code = None; + this.Reset(); + uint value = this.ReadValue(1); + + do + { + if (this.CurValueBitsRead > this.maxCodeLength) + { + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read"); + } + + switch (this.CurValueBitsRead) + { + case 1: + if (Len1Codes.ContainsKey(value)) + { + this.Code = Len1Codes[value]; + return false; + } + + break; + + case 3: + if (Len3Codes.ContainsKey(value)) + { + this.Code = Len3Codes[value]; + return false; + } + + break; + + case 4: + if (Len4Codes.ContainsKey(value)) + { + this.Code = Len4Codes[value]; + return false; + } + + break; + + case 6: + if (Len6Codes.ContainsKey(value)) + { + this.Code = Len6Codes[value]; + return false; + } + + break; + + case 7: + if (Len7Codes.ContainsKey(value)) + { + this.Code = Len7Codes[value]; + return false; + } + + break; + } + + uint currBit = this.ReadValue(1); + value = (value << 1) | currBit; + } + while (!this.IsEndOfScanLine); + + if (this.IsEndOfScanLine) + { + return true; + } + + return false; + } + + /// + /// No EOL is expected at the start of a run. + /// + protected override void ReadEolBeforeFirstData() + { + // Nothing to do here. + } + + /// + /// Swaps the white run to black run an vise versa. + /// + public void SwapColor() => this.IsWhiteRun = !this.IsWhiteRun; + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs new file mode 100644 index 0000000000..87095d5ee4 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs @@ -0,0 +1,253 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Class to handle cases where TIFF image data is compressed using CCITT T6 compression. + /// + internal class T6TiffCompression : TiffBaseDecompressor + { + private readonly bool isWhiteZero; + + private readonly byte whiteValue; + + private readonly byte blackValue; + + private readonly int width; + + /// + /// 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 T6TiffCompression( + MemoryAllocator allocator, + TiffFillOrder fillOrder, + int width, + int bitsPerPixel, + TiffPhotometricInterpretation photometricInterpretation) + : base(allocator, width, bitsPerPixel) + { + this.FillOrder = fillOrder; + this.width = width; + this.isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; + this.whiteValue = (byte)(this.isWhiteZero ? 0 : 1); + this.blackValue = (byte)(this.isWhiteZero ? 1 : 0); + } + + /// + /// Gets the logical order of bits within a byte. + /// + private TiffFillOrder FillOrder { get; } + + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) + { + int height = stripHeight; + + using System.Buffers.IMemoryOwner scanLineBuffer = this.Allocator.Allocate(this.width * 2); + Span scanLine = scanLineBuffer.GetSpan().Slice(0, this.width); + Span referenceScanLineSpan = scanLineBuffer.GetSpan().Slice(this.width, this.width); + + using var bitReader = new T6BitReader(stream, this.FillOrder, byteCount, this.Allocator); + + var referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, this.width); + uint bitsWritten = 0; + for (int y = 0; y < height; y++) + { + scanLine.Fill(0); + Decode2DScanline(bitReader, this.isWhiteZero, referenceScanLine, scanLine); + + bitsWritten = this.WriteScanLine(buffer, scanLine, bitsWritten); + + scanLine.CopyTo(referenceScanLineSpan); + referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, referenceScanLineSpan); + } + } + + private uint WriteScanLine(Span buffer, Span scanLine, uint bitsWritten) + { + byte white = (byte)(this.isWhiteZero ? 0 : 255); + for (int i = 0; i < scanLine.Length; i++) + { + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, 1, scanLine[i] == white ? this.whiteValue : this.blackValue); + bitsWritten++; + } + + // Write padding bytes, if necessary. + uint pad = 8 - (bitsWritten % 8); + if (pad != 8) + { + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); + bitsWritten += pad; + } + + return bitsWritten; + } + + private static void Decode2DScanline(T6BitReader bitReader, bool whiteIsZero, CcittReferenceScanline referenceScanline, Span scanline) + { + int width = scanline.Length; + bitReader.StartNewRow(); + + // 2D Encoding variables. + int a0 = -1; + byte fillByte = whiteIsZero ? (byte)0 : (byte)255; + + // Process every code word in this scanline. + int unpacked = 0; + while (true) + { + // Read next code word and advance pass it. + bool isEol = bitReader.ReadNextCodeWord(); + + // Special case handling for EOL. + if (isEol) + { + // If a TIFF reader encounters EOFB before the expected number of lines has been extracted, + // it is appropriate to assume that the missing rows consist entirely of white pixels. + scanline.Fill(whiteIsZero ? (byte)0 : (byte)255); + break; + } + + // Update 2D Encoding variables. + int b1 = referenceScanline.FindB1(a0, fillByte); + + // Switch on the code word. + int a1; + switch (bitReader.Code.Type) + { + case CcittTwoDimensionalCodeType.None: + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, could not read a valid code word."); + break; + + case CcittTwoDimensionalCodeType.Pass: + int b2 = referenceScanline.FindB2(b1); + scanline.Slice(unpacked, b2 - unpacked).Fill(fillByte); + unpacked = b2; + a0 = b2; + break; + case CcittTwoDimensionalCodeType.Horizontal: + // Decode M(a0a1) + bitReader.ReadNextRun(); + int runLength = (int)bitReader.RunLength; + if (runLength > (uint)(scanline.Length - unpacked)) + { + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error"); + } + + scanline.Slice(unpacked, runLength).Fill(fillByte); + unpacked += runLength; + fillByte = (byte)~fillByte; + + // Decode M(a1a2) + bitReader.ReadNextRun(); + runLength = (int)bitReader.RunLength; + if (runLength > (uint)(scanline.Length - unpacked)) + { + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error"); + } + + scanline.Slice(unpacked, runLength).Fill(fillByte); + unpacked += runLength; + fillByte = (byte)~fillByte; + + // Prepare next a0 + a0 = unpacked; + break; + + case CcittTwoDimensionalCodeType.Vertical0: + a1 = b1; + scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; + + case CcittTwoDimensionalCodeType.VerticalR1: + a1 = b1 + 1; + scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; + + case CcittTwoDimensionalCodeType.VerticalR2: + a1 = b1 + 2; + scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; + + case CcittTwoDimensionalCodeType.VerticalR3: + a1 = b1 + 3; + scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; + + case CcittTwoDimensionalCodeType.VerticalL1: + a1 = b1 - 1; + scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; + + case CcittTwoDimensionalCodeType.VerticalL2: + a1 = b1 - 2; + scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; + + case CcittTwoDimensionalCodeType.VerticalL3: + a1 = b1 - 3; + scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; + + default: + throw new NotSupportedException("ccitt extensions are not supported."); + } + + // This line is fully unpacked. Should exit and process next line. + if (unpacked == width) + { + break; + } + + if (unpacked > width) + { + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, unpacked data > width"); + } + } + } + + /// + protected override void Dispose(bool disposing) + { + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs index a289e306a0..28459d0c5c 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs @@ -15,8 +15,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression /// internal abstract class TiffBaseDecompressor : TiffBaseCompression { - protected TiffBaseDecompressor(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) - : base(allocator, width, bitsPerPixel, predictor) + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The width of the image. + /// The bits per pixel. + /// The predictor. + protected TiffBaseDecompressor(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + : base(memoryAllocator, width, bitsPerPixel, predictor) { } @@ -26,8 +33,9 @@ protected TiffBaseDecompressor(MemoryAllocator allocator, int width, int bitsPer /// The to read image data from. /// The strip offset of stream. /// The number of bytes to read from the input stream. + /// The height of the strip. /// The output buffer for uncompressed data. - public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripByteCount, Span buffer) + public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripByteCount, int stripHeight, Span buffer) { if (stripByteCount > int.MaxValue) { @@ -35,7 +43,7 @@ public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripBy } stream.Seek(stripOffset, SeekOrigin.Begin); - this.Decompress(stream, (int)stripByteCount, buffer); + this.Decompress(stream, (int)stripByteCount, stripHeight, buffer); if (stripOffset + stripByteCount < stream.Position) { @@ -48,7 +56,8 @@ public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripBy /// /// The to read image data from. /// The number of bytes to read from the input stream. + /// The height of the strip. /// The output buffer for uncompressed data. - protected abstract void Decompress(BufferedReadStream stream, int byteCount, Span buffer); + protected abstract void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs index 80bc0af5ab..61b691eb84 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs @@ -29,10 +29,15 @@ internal enum TiffDecoderCompressionType Lzw = 3, /// - /// Image data is compressed using T4-encoding: CCITT T.4. + /// Image data is compressed using CCITT T.4 fax compression. /// T4 = 4, + /// + /// Image data is compressed using CCITT T.6 fax compression. + /// + T6 = 6, + /// /// Image data is compressed using modified huffman compression. /// diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs index b1562223aa..735ea1aa2a 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs @@ -46,6 +46,10 @@ public static TiffBaseDecompressor Create( DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); return new T4TiffCompression(allocator, fillOrder, width, bitsPerPixel, faxOptions, photometricInterpretation); + case TiffDecoderCompressionType.T6: + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new T6TiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation); + case TiffDecoderCompressionType.HuffmanRle: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation); diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index ab3394c565..beac42db7c 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -45,7 +45,7 @@ |Ccitt1D | Y | Y | | |PackBits | Y | Y | | |CcittGroup3Fax | Y | Y | | -|CcittGroup4Fax | | | | +|CcittGroup4Fax | | Y | | |Lzw | Y | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case | |Old Jpeg | | | We should not even try to support this | |Jpeg (Technote 2) | | | | diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 28afe4c6f2..ff5f8923e3 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -317,7 +317,12 @@ private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStr int stripIndex = i; for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) { - decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan()); + decompressor.Decompress( + this.inputStream, + (uint)stripOffsets[stripIndex], + (uint)stripByteCounts[stripIndex], + stripHeight, + stripBuffers[planeIndex].GetSpan()); stripIndex += stripsPerPlane; } @@ -385,7 +390,7 @@ private void DecodeStripsChunky(ImageFrame frame, int rowsPerStr break; } - decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBufferSpan); + decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripHeight, stripBufferSpan); colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight); } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index bb435affcc..d8357d945f 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -418,6 +418,14 @@ private static void ParseCompression(this TiffDecoderCore options, TiffCompressi break; } + case TiffCompression.CcittGroup4Fax: + { + options.CompressionType = TiffDecoderCompressionType.T6; + options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None; + + break; + } + case TiffCompression.Ccitt1D: { options.CompressionType = TiffDecoderCompressionType.HuffmanRle; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs index c93a2018df..ff7025b506 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs @@ -29,7 +29,7 @@ public void Compress_Decompress_Roundtrip_Works(byte[] data) using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false); - decompressor.Decompress(stream, 0, (uint)stream.Length, buffer); + decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer); Assert.Equal(data, buffer); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs index 5ea75d9a84..08705738f4 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs @@ -40,7 +40,7 @@ public void Compress_Decompress_Roundtrip_Works(byte[] data) var buffer = new byte[data.Length]; using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false); - decompressor.Decompress(stream, 0, (uint)stream.Length, buffer); + decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer); Assert.Equal(data, buffer); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs index 82ecb315b0..d153e1ed22 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs @@ -17,10 +17,12 @@ public class NoneTiffCompressionTests [InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 5, new byte[] { 10, 15, 20, 25, 30 })] public void Decompress_ReadsData(byte[] inputData, uint byteCount, byte[] expectedResult) { - var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData)); - var buffer = new byte[expectedResult.Length]; + using var memoryStream = new MemoryStream(inputData); + using var stream = new BufferedReadStream(Configuration.Default, memoryStream); + byte[] buffer = new byte[expectedResult.Length]; - new NoneTiffCompression(default, default, default).Decompress(stream, 0, byteCount, buffer); + using var decompressor = new NoneTiffCompression(default, default, default); + decompressor.Decompress(stream, 0, byteCount, 1, buffer); Assert.Equal(expectedResult, buffer); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs index b67cb83254..b56c1e7c92 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs @@ -26,11 +26,12 @@ public class PackBitsTiffCompressionTests [InlineData(new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA }, new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA })] // Apple PackBits sample public void Decompress_ReadsData(byte[] inputData, byte[] expectedResult) { - var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData)); - var buffer = new byte[expectedResult.Length]; + using var memoryStream = new MemoryStream(inputData); + using var stream = new BufferedReadStream(Configuration.Default, memoryStream); + byte[] buffer = new byte[expectedResult.Length]; using var decompressor = new PackBitsTiffCompression(new ArrayPoolMemoryAllocator(), default, default); - decompressor.Decompress(stream, 0, (uint)inputData.Length, buffer); + decompressor.Decompress(stream, 0, (uint)inputData.Length, 1, buffer); Assert.Equal(expectedResult, buffer); } @@ -41,7 +42,7 @@ public void Compress_Works(byte[] inputData, byte[] expectedResult) { // arrange Span input = inputData.AsSpan(); - var compressed = new byte[expectedResult.Length]; + byte[] compressed = new byte[expectedResult.Length]; // act PackBitsWriter.PackBits(input, compressed); From 3f4f07835c5a4163a54a890e56f645ffa088bbe8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 22 Aug 2021 20:51:13 +0200 Subject: [PATCH 2/5] Add tests for fax4 compression --- .../Formats/Tiff/TiffDecoderTests.cs | 15 ++++++++++++--- tests/ImageSharp.Tests/TestImages.cs | 4 ++-- .../Images/Input/Tiff/Calliphora_ccitt_fax4.tiff | 4 ++-- tests/Images/Input/Tiff/basi3p02_fax4.tiff | 3 +++ .../Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff | 3 +++ .../Input/Tiff/basi3p02_fax4_minisblack.tiff | 3 +++ 6 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 tests/Images/Input/Tiff/basi3p02_fax4.tiff create mode 100644 tests/Images/Input/Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff create mode 100644 tests/Images/Input/Tiff/basi3p02_fax4_minisblack.tiff diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 5a0495e0a2..290b992c25 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -23,14 +23,16 @@ public class TiffDecoderTests { public static readonly string[] MultiframeTestImages = Multiframes; - public static readonly string[] NotSupportedImages = NotSupported; - private static TiffDecoder TiffDecoder => new TiffDecoder(); private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); [Theory] - [WithFileCollection(nameof(NotSupportedImages), PixelTypes.Rgba32)] + [WithFile(Calliphora_RgbJpeg, PixelTypes.Rgba32)] + [WithFile(RgbJpeg, PixelTypes.Rgba32)] + [WithFile(RgbUncompressedTiled, PixelTypes.Rgba32)] + [WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)] + [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] public void ThrowsNotSupported(TestImageProvider provider) where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); @@ -356,6 +358,13 @@ public void TiffDecoder_CanDecode_HuffmanCompressed(TestImageProvider(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Fax4Compressed, PixelTypes.Rgba32)] + [WithFile(Fax4CompressedLowerOrderBitsFirst, PixelTypes.Rgba32)] + [WithFile(Calliphora_Fax4Compressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Fax4Compressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(CcittFax3LowerOrderBitsFirst, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_Compressed_LowerOrderBitsFirst(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 0e892baec6..6f16a0c1d4 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -534,6 +534,8 @@ public static class Tiff public const string Calliphora_Fax4Compressed = "Tiff/Calliphora_ccitt_fax4.tiff"; public const string Calliphora_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.tiff"; public const string Calliphora_BiColorUncompressed = "Tiff/Calliphora_bicolor_uncompressed.tiff"; + public const string Fax4Compressed = "Tiff/basi3p02_fax4.tiff"; + public const string Fax4CompressedLowerOrderBitsFirst = "Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff"; public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tiff"; public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff"; @@ -658,8 +660,6 @@ public static class Tiff public static readonly string[] Multiframes = { MultiframeDeflateWithPreview, MultiframeLzwPredictor /*, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ }; public static readonly string[] Metadata = { SampleMetadata }; - - public static readonly string[] NotSupported = { Calliphora_RgbJpeg, RgbJpeg, RgbUncompressedTiled, MultiframeDifferentSize, MultiframeDifferentVariants, Calliphora_Fax4Compressed, Fax4_Motorola }; } } } diff --git a/tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff b/tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff index 384d00eaa9..8bdbdcddc6 100644 --- a/tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff +++ b/tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a2c95aec08b96bca30af344f7d9952a603a951802ce534a5f2c5f563795cbd2 -size 117704 +oid sha256:b8c7f712f9e7d1feeeb55e7743f6ce7d66bc5292f4786ea8526d95057d73145e +size 4534 diff --git a/tests/Images/Input/Tiff/basi3p02_fax4.tiff b/tests/Images/Input/Tiff/basi3p02_fax4.tiff new file mode 100644 index 0000000000..a53f8f36f5 --- /dev/null +++ b/tests/Images/Input/Tiff/basi3p02_fax4.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:185ae3c4174b323adcf811d125cd77b71768406845923f50395c0baebff57b7c +size 282 diff --git a/tests/Images/Input/Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff b/tests/Images/Input/Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff new file mode 100644 index 0000000000..12d10ffa73 --- /dev/null +++ b/tests/Images/Input/Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a19eb117f194718575681a81a4fbe7fe4a1b82b99113707295194090fb935784 +size 282 diff --git a/tests/Images/Input/Tiff/basi3p02_fax4_minisblack.tiff b/tests/Images/Input/Tiff/basi3p02_fax4_minisblack.tiff new file mode 100644 index 0000000000..9c76237b58 --- /dev/null +++ b/tests/Images/Input/Tiff/basi3p02_fax4_minisblack.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af0d8f3c18f96228aa369bc295201a1bfe1b044c23991ff168401adc5402ebb6 +size 308 From 37033cded685c8c643bce18ea1b20ac30828f9eb Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Tue, 24 Aug 2021 12:20:20 +0200 Subject: [PATCH 3/5] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- .../Compression/Decompressors/ModifiedHuffmanBitReader.cs | 6 +++--- .../Formats/Tiff/Compression/Decompressors/T6BitReader.cs | 2 +- .../Tiff/Compression/Decompressors/T6TiffCompression.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs index 90ec0d6caf..40f6dae9bb 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs @@ -26,7 +26,7 @@ public ModifiedHuffmanBitReader(Stream input, TiffFillOrder fillOrder, int bytes } /// - public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (this.BitsRead > 0 && this.BitsRead < 7); + public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || ((uint)(this.BitsRead - 1) < (7 - 1)); /// public override bool IsEndOfScanLine @@ -53,8 +53,8 @@ public override void StartNewRow() { base.StartNewRow(); - int pad = 8 - (this.BitsRead % 8); - if (pad != 8) + int remainder = this.BitsRead & 7; // bit-hack for % 8 + if (remainder != 0) { // Skip padding bits, move to next byte. this.Position++; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs index 0ebaccf7eb..05bbd26ab7 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs @@ -63,7 +63,7 @@ public T6BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, Memor } /// - public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (this.BitsRead > 0 && this.BitsRead < 7); + public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || ((uint)(this.BitsRead - 1) < (7 - 1)); /// /// Gets or sets the two dimensional code. diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs index 87095d5ee4..255e861b34 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs @@ -84,8 +84,8 @@ private uint WriteScanLine(Span buffer, Span scanLine, uint bitsWrit } // Write padding bytes, if necessary. - uint pad = 8 - (bitsWritten % 8); - if (pad != 8) + uint remainder = bitsWritten % 8; + if (remainder != 0) { BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); bitsWritten += pad; From 31fa1eac3512cf1dbf050f253696509b9d759cc3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 24 Aug 2021 12:11:46 +0200 Subject: [PATCH 4/5] Review changes --- .../Decompressors/CcittReferenceScanline.cs | 4 +- .../CcittTwoDimensionalCodeType.cs | 41 +++++++++++++++++++ .../Decompressors/DeflateTiffCompression.cs | 2 +- .../Decompressors/LzwTiffCompression.cs | 2 +- .../Decompressors/ModifiedHuffmanBitReader.cs | 2 +- .../ModifiedHuffmanTiffCompression.cs | 15 ++++++- .../Decompressors/NoneTiffCompression.cs | 2 +- .../Decompressors/PackBitsTiffCompression.cs | 2 +- .../Compression/Decompressors/T4BitReader.cs | 2 +- .../Decompressors/T4TiffCompression.cs | 4 +- .../Compression/Decompressors/T6BitReader.cs | 2 +- .../Decompressors/T6TiffCompression.cs | 2 +- 12 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs index 64da694021..0aec2361c3 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs @@ -48,7 +48,7 @@ public CcittReferenceScanline(bool whiteIsZero, int width) /// Position of b1. public int FindB1(int a0, byte a0Byte) { - if (this.scanLine.IsEmpty) + if (this.IsEmpty) { return this.FindB1ForImaginaryWhiteLine(a0, a0Byte); } @@ -63,7 +63,7 @@ public int FindB1(int a0, byte a0Byte) /// Position of b1. public int FindB2(int b1) { - if (this.scanLine.IsEmpty) + if (this.IsEmpty) { return this.FindB2ForImaginaryWhiteLine(); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs index 0bd04b4cde..6d5427d638 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs @@ -3,30 +3,71 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { + /// + /// Enum for the different two dimensional code words for the ccitt fax compression. + /// internal enum CcittTwoDimensionalCodeType { + /// + /// No valid code word was read. + /// None = 0, + /// + /// Pass mode: This mode is identified when the position of b2 lies to the left of a1. + /// Pass = 1, + /// + /// Indicates horizontal mode. + /// Horizontal = 2, + /// + /// Vertical 0 code word: relative distance between a1 and b1 is 0. + /// Vertical0 = 3, + /// + /// Vertical r1 code word: relative distance between a1 and b1 is 1, a1 is to the right of b1. + /// VerticalR1 = 4, + /// + /// Vertical r2 code word: relative distance between a1 and b1 is 2, a1 is to the right of b1. + /// VerticalR2 = 5, + /// + /// Vertical r3 code word: relative distance between a1 and b1 is 3, a1 is to the right of b1. + /// VerticalR3 = 6, + /// + /// Vertical l1 code word: relative distance between a1 and b1 is 1, a1 is to the left of b1. + /// VerticalL1 = 7, + /// + /// Vertical l2 code word: relative distance between a1 and b1 is 2, a1 is to the left of b1. + /// VerticalL2 = 8, + /// + /// Vertical l3 code word: relative distance between a1 and b1 is 3, a1 is to the left of b1. + /// VerticalL3 = 9, + /// + /// 1d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme. + /// Not supported. + /// Extensions1D = 10, + /// + /// 2d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme. + /// Not supported. + /// Extensions2D = 11, } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs index 917f83585f..4c85116003 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// /// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type. /// - internal class DeflateTiffCompression : TiffBaseDecompressor + internal sealed class DeflateTiffCompression : TiffBaseDecompressor { private readonly bool isBigEndian; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs index 77d7b765b7..b5bf7370e7 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// /// Class to handle cases where TIFF image data is compressed using LZW compression. /// - internal class LzwTiffCompression : TiffBaseDecompressor + internal sealed class LzwTiffCompression : TiffBaseDecompressor { private readonly bool isBigEndian; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs index 40f6dae9bb..89cdf7ea2b 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// Bit reader for data encoded with the modified huffman rle method. /// See TIFF 6.0 specification, section 10. /// - internal class ModifiedHuffmanBitReader : T4BitReader + internal sealed class ModifiedHuffmanBitReader : T4BitReader { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs index 65feaa427a..06911f7f72 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// /// Class to handle cases where TIFF image data is compressed using Modified Huffman Compression. /// - internal class ModifiedHuffmanTiffCompression : T4TiffCompression + internal sealed class ModifiedHuffmanTiffCompression : TiffBaseDecompressor { private readonly byte whiteValue; @@ -27,13 +27,19 @@ internal class ModifiedHuffmanTiffCompression : T4TiffCompression /// The number of bits per pixel. /// The photometric interpretation. public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) - : base(allocator, fillOrder, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation) + : base(allocator, width, bitsPerPixel) { + 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. + /// + private TiffFillOrder FillOrder { get; } + /// protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) { @@ -81,5 +87,10 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, int } } } + + /// + protected override void Dispose(bool disposing) + { + } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs index c4a9524306..2f49247e16 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// /// Class to handle cases where TIFF image data is not compressed. /// - internal class NoneTiffCompression : TiffBaseDecompressor + internal sealed class NoneTiffCompression : TiffBaseDecompressor { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs index 8a001f5714..bd014ef446 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// /// Class to handle cases where TIFF image data is compressed using PackBits compression. /// - internal class PackBitsTiffCompression : TiffBaseDecompressor + internal sealed class PackBitsTiffCompression : TiffBaseDecompressor { private IMemoryOwner compressedDataMemory; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs index 5e83abf8f6..9925d5a194 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs @@ -438,7 +438,7 @@ protected void Reset(bool resetRunLength = true) /// The value read. protected uint ReadValue(int nBits) { - Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); uint v = 0; int shift = nBits; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs index e424d52904..df822326b5 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// /// Class to handle cases where TIFF image data is compressed using CCITT T4 compression. /// - internal class T4TiffCompression : TiffBaseDecompressor + internal sealed class T4TiffCompression : TiffBaseDecompressor { private readonly FaxCompressionOptions faxCompressionOptions; @@ -51,7 +51,7 @@ public T4TiffCompression( /// /// Gets the logical order of bits within a byte. /// - protected TiffFillOrder FillOrder { get; } + private TiffFillOrder FillOrder { get; } /// protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs index 05bbd26ab7..bae3aa4221 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// Bit reader for reading CCITT T6 compressed fax data. /// See: Facsimile Coding Schemes and Coding Control Functions for Group 4 Facsimile Apparatus, itu-t recommendation t.6 /// - internal class T6BitReader : T4BitReader + internal sealed class T6BitReader : T4BitReader { private readonly int maxCodeLength = 12; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs index 255e861b34..9e0495465c 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// /// Class to handle cases where TIFF image data is compressed using CCITT T6 compression. /// - internal class T6TiffCompression : TiffBaseDecompressor + internal sealed class T6TiffCompression : TiffBaseDecompressor { private readonly bool isWhiteZero; From 89548fe2df11add978812295edb7b87c429020cf Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 24 Aug 2021 12:34:10 +0200 Subject: [PATCH 5/5] Fix build error --- .../Tiff/Compression/Decompressors/T6TiffCompression.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs index 9e0495465c..e86418741d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs @@ -87,8 +87,9 @@ private uint WriteScanLine(Span buffer, Span scanLine, uint bitsWrit uint remainder = bitsWritten % 8; if (remainder != 0) { - BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); - bitsWritten += pad; + uint padding = 8 - remainder; + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, padding, 0); + bitsWritten += padding; } return bitsWritten;