Skip to content

Commit a11752e

Browse files
committed
Add support for decoding tiff's with T.6 fax compression
1 parent d105ab4 commit a11752e

23 files changed

+959
-223
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
6+
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
7+
{
8+
/// <summary>
9+
/// Represents a reference scan line for CCITT 2D decoding.
10+
/// </summary>
11+
internal readonly ref struct CcittReferenceScanline
12+
{
13+
private readonly ReadOnlySpan<byte> scanLine;
14+
private readonly int width;
15+
private readonly byte whiteByte;
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="CcittReferenceScanline"/> struct.
19+
/// </summary>
20+
/// <param name="whiteIsZero">Indicates, if white is zero, otherwise black is zero.</param>
21+
/// <param name="scanLine">The scan line.</param>
22+
public CcittReferenceScanline(bool whiteIsZero, ReadOnlySpan<byte> scanLine)
23+
{
24+
this.scanLine = scanLine;
25+
this.width = scanLine.Length;
26+
this.whiteByte = whiteIsZero ? (byte)0 : (byte)255;
27+
}
28+
29+
/// <summary>
30+
/// Initializes a new instance of the <see cref="CcittReferenceScanline"/> struct.
31+
/// </summary>
32+
/// <param name="whiteIsZero">Indicates, if white is zero, otherwise black is zero.</param>
33+
/// <param name="width">The width of the scanline.</param>
34+
public CcittReferenceScanline(bool whiteIsZero, int width)
35+
{
36+
this.scanLine = default;
37+
this.width = width;
38+
this.whiteByte = whiteIsZero ? (byte)0 : (byte)255;
39+
}
40+
41+
public bool IsEmpty => this.scanLine.IsEmpty;
42+
43+
/// <summary>
44+
/// Finds b1: The first changing element on the reference line to the right of a0 and of opposite color to a0.
45+
/// </summary>
46+
/// <param name="a0">The reference or starting element om the coding line.</param>
47+
/// <param name="a0Byte">Fill byte.</param>
48+
/// <returns>Position of b1.</returns>
49+
public int FindB1(int a0, byte a0Byte)
50+
{
51+
if (this.scanLine.IsEmpty)
52+
{
53+
return this.FindB1ForImaginaryWhiteLine(a0, a0Byte);
54+
}
55+
56+
return this.FindB1ForNormalLine(a0, a0Byte);
57+
}
58+
59+
/// <summary>
60+
/// Finds b2: The next changing element to the right of b1 on the reference line.
61+
/// </summary>
62+
/// <param name="b1">The first changing element on the reference line to the right of a0 and opposite of color to a0.</param>
63+
/// <returns>Position of b1.</returns>
64+
public int FindB2(int b1)
65+
{
66+
if (this.scanLine.IsEmpty)
67+
{
68+
return this.FindB2ForImaginaryWhiteLine();
69+
}
70+
71+
return this.FindB2ForNormalLine(b1);
72+
}
73+
74+
private int FindB1ForImaginaryWhiteLine(int a0, byte a0Byte)
75+
{
76+
if (a0 < 0)
77+
{
78+
if (a0Byte != this.whiteByte)
79+
{
80+
return 0;
81+
}
82+
}
83+
84+
return this.width;
85+
}
86+
87+
private int FindB1ForNormalLine(int a0, byte a0Byte)
88+
{
89+
int offset = 0;
90+
if (a0 < 0)
91+
{
92+
if (a0Byte != this.scanLine[0])
93+
{
94+
return 0;
95+
}
96+
}
97+
else
98+
{
99+
offset = a0;
100+
}
101+
102+
ReadOnlySpan<byte> searchSpace = this.scanLine.Slice(offset);
103+
byte searchByte = (byte)~a0Byte;
104+
int index = searchSpace.IndexOf(searchByte);
105+
if (index < 0)
106+
{
107+
return this.scanLine.Length;
108+
}
109+
110+
if (index != 0)
111+
{
112+
return offset + index;
113+
}
114+
115+
searchByte = (byte)~searchSpace[0];
116+
index = searchSpace.IndexOf(searchByte);
117+
if (index < 0)
118+
{
119+
return this.scanLine.Length;
120+
}
121+
122+
searchSpace = searchSpace.Slice(index);
123+
offset += index;
124+
index = searchSpace.IndexOf((byte)~searchByte);
125+
if (index < 0)
126+
{
127+
return this.scanLine.Length;
128+
}
129+
130+
return index + offset;
131+
}
132+
133+
private int FindB2ForImaginaryWhiteLine() => this.width;
134+
135+
private int FindB2ForNormalLine(int b1)
136+
{
137+
if (b1 >= this.scanLine.Length)
138+
{
139+
return this.scanLine.Length;
140+
}
141+
142+
byte searchByte = (byte)~this.scanLine[b1];
143+
int offset = b1 + 1;
144+
ReadOnlySpan<byte> searchSpace = this.scanLine.Slice(offset);
145+
int index = searchSpace.IndexOf(searchByte);
146+
if (index == -1)
147+
{
148+
return this.scanLine.Length;
149+
}
150+
151+
return offset + index;
152+
}
153+
}
154+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.Diagnostics;
5+
6+
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
7+
{
8+
[DebuggerDisplay("Type = {Type}")]
9+
internal readonly struct CcittTwoDimensionalCode
10+
{
11+
private readonly ushort value;
12+
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="CcittTwoDimensionalCode"/> struct.
15+
/// </summary>
16+
/// <param name="type">The type.</param>
17+
/// <param name="bitsRequired">The bits required.</param>
18+
/// <param name="extensionBits">The extension bits.</param>
19+
public CcittTwoDimensionalCode(CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0)
20+
=> this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11));
21+
22+
/// <summary>
23+
/// Gets the code type.
24+
/// </summary>
25+
public CcittTwoDimensionalCodeType Type => (CcittTwoDimensionalCodeType)(this.value & 0b11111111);
26+
}
27+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
5+
{
6+
internal enum CcittTwoDimensionalCodeType
7+
{
8+
None = 0,
9+
10+
Pass = 1,
11+
12+
Horizontal = 2,
13+
14+
Vertical0 = 3,
15+
16+
VerticalR1 = 4,
17+
18+
VerticalR2 = 5,
19+
20+
VerticalR3 = 6,
21+
22+
VerticalL1 = 7,
23+
24+
VerticalL2 = 8,
25+
26+
VerticalL3 = 9,
27+
28+
Extensions1D = 10,
29+
30+
Extensions2D = 11,
31+
}
32+
}

src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bi
4141
}
4242

4343
/// <inheritdoc/>
44-
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
44+
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
4545
{
4646
long pos = stream.Position;
4747
using (var deframeStream = new ZlibInflateStream(

src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPe
3535
}
3636

3737
/// <inheritdoc/>
38-
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
38+
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
3939
{
4040
var decoder = new TiffLzwDecoder(stream);
4141
decoder.DecodePixels(buffer);
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.IO;
5+
using SixLabors.ImageSharp.Formats.Tiff.Constants;
6+
using SixLabors.ImageSharp.Memory;
7+
8+
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
9+
{
10+
/// <summary>
11+
/// Bit reader for data encoded with the modified huffman rle method.
12+
/// See TIFF 6.0 specification, section 10.
13+
/// </summary>
14+
internal class ModifiedHuffmanBitReader : T4BitReader
15+
{
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="ModifiedHuffmanBitReader"/> class.
18+
/// </summary>
19+
/// <param name="input">The compressed input stream.</param>
20+
/// <param name="fillOrder">The logical order of bits within a byte.</param>
21+
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
22+
/// <param name="allocator">The memory allocator.</param>
23+
public ModifiedHuffmanBitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator)
24+
: base(input, fillOrder, bytesToRead, allocator)
25+
{
26+
}
27+
28+
/// <inheritdoc/>
29+
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (this.BitsRead > 0 && this.BitsRead < 7);
30+
31+
/// <inheritdoc/>
32+
public override bool IsEndOfScanLine
33+
{
34+
get
35+
{
36+
if (this.IsWhiteRun && this.CurValueBitsRead == 12 && this.Value == 1)
37+
{
38+
return true;
39+
}
40+
41+
if (this.CurValueBitsRead == 11 && this.Value == 0)
42+
{
43+
// black run.
44+
return true;
45+
}
46+
47+
return false;
48+
}
49+
}
50+
51+
/// <inheritdoc/>
52+
public override void StartNewRow()
53+
{
54+
base.StartNewRow();
55+
56+
int pad = 8 - (this.BitsRead % 8);
57+
if (pad != 8)
58+
{
59+
// Skip padding bits, move to next byte.
60+
this.Position++;
61+
this.ResetBitsRead();
62+
}
63+
}
64+
65+
/// <summary>
66+
/// No EOL is expected at the start of a run for the modified huffman encoding.
67+
/// </summary>
68+
protected override void ReadEolBeforeFirstData()
69+
{
70+
// Nothing to do here.
71+
}
72+
}
73+
}

src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffFillOrder f
3535
}
3636

3737
/// <inheritdoc/>
38-
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
38+
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
3939
{
40-
using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true);
40+
using var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount, this.Allocator);
4141

4242
buffer.Clear();
4343
uint bitsWritten = 0;
@@ -51,20 +51,20 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa
5151
if (bitReader.IsWhiteRun)
5252
{
5353
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
54-
bitsWritten += bitReader.RunLength;
55-
pixelsWritten += bitReader.RunLength;
5654
}
5755
else
5856
{
5957
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
60-
bitsWritten += bitReader.RunLength;
61-
pixelsWritten += bitReader.RunLength;
6258
}
59+
60+
bitsWritten += bitReader.RunLength;
61+
pixelsWritten += bitReader.RunLength;
6362
}
6463

65-
if (pixelsWritten % this.Width == 0)
64+
if (pixelsWritten == this.Width)
6665
{
6766
bitReader.StartNewRow();
67+
pixelsWritten = 0;
6868

6969
// Write padding bits, if necessary.
7070
uint pad = 8 - (bitsWritten % 8);
@@ -74,6 +74,11 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa
7474
bitsWritten += pad;
7575
}
7676
}
77+
78+
if (pixelsWritten > this.Width)
79+
{
80+
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, decoded more pixels then image width");
81+
}
7782
}
7883
}
7984
}

src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public NoneTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsP
2525
}
2626

2727
/// <inheritdoc/>
28-
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount));
28+
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
29+
=> _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount));
2930

3031
/// <inheritdoc/>
3132
protected override void Dispose(bool disposing)

src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public PackBitsTiffCompression(MemoryAllocator memoryAllocator, int width, int b
2828
}
2929

3030
/// <inheritdoc/>
31-
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
31+
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
3232
{
3333
if (this.compressedDataMemory == null)
3434
{

0 commit comments

Comments
 (0)