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 000000000..64da69402
--- /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 000000000..74a17b907
--- /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 000000000..0bd04b4cd
--- /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 bb57853d5..917f83585 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
@@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
///
- 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 2e8939607..77d7b765b 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs
@@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
///
- 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 000000000..90ec0d6ca
--- /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 9b12dc90f..65feaa427 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
@@ -35,9 +35,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
///
- 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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 58a1c9878..c4a952430 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs
@@ -25,7 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
///
- 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 e14736b73..8a001f571 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
@@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
///
- 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 384be1cf2..5e83abf8f 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
///
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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
}
+ ///
+ /// 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
///
/// 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
///
/// 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
// 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
uint currBit = this.ReadValue(1);
- this.value = (this.value << 1) | currBit;
+ this.Value = (this.Value << 1) | currBit;
if (this.IsEndOfScanLine)
{
@@ -396,55 +379,106 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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 a79ef3fe5..e424d5290 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
@@ -31,7 +31,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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 000000000..0ebaccf7e
--- /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 000000000..87095d5ee
--- /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 a289e306a..28459d0c5 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
///
/// 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 80bc0af5a..61b691eb8 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs
@@ -29,10 +29,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
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 b1562223a..735ea1aa2 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
@@ -46,6 +46,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
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 ab3394c56..beac42db7 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 28afe4c6f..ff5f8923e 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -317,7 +317,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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 bb435affc..d8357d945 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
@@ -418,6 +418,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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 c93a2018d..ff7025b50 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
@@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
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 5ea75d9a8..08705738f 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
@@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
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 82ecb315b..d153e1ed2 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
@@ -17,10 +17,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
[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 b67cb8325..b56c1e7c9 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
@@ -26,11 +26,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
[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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
{
// arrange
Span input = inputData.AsSpan();
- var compressed = new byte[expectedResult.Length];
+ byte[] compressed = new byte[expectedResult.Length];
// act
PackBitsWriter.PackBits(input, compressed);