Skip to content

Commit ce97da5

Browse files
authored
Merge pull request #1747 from SixLabors/bp/tiff-fax4
Add support for decoding tiff's with T.6 fax compression
2 parents 5d82124 + 89548fe commit ce97da5

29 files changed

+1045
-238
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.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.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: 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+
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
5+
{
6+
/// <summary>
7+
/// Enum for the different two dimensional code words for the ccitt fax compression.
8+
/// </summary>
9+
internal enum CcittTwoDimensionalCodeType
10+
{
11+
/// <summary>
12+
/// No valid code word was read.
13+
/// </summary>
14+
None = 0,
15+
16+
/// <summary>
17+
/// Pass mode: This mode is identified when the position of b2 lies to the left of a1.
18+
/// </summary>
19+
Pass = 1,
20+
21+
/// <summary>
22+
/// Indicates horizontal mode.
23+
/// </summary>
24+
Horizontal = 2,
25+
26+
/// <summary>
27+
/// Vertical 0 code word: relative distance between a1 and b1 is 0.
28+
/// </summary>
29+
Vertical0 = 3,
30+
31+
/// <summary>
32+
/// Vertical r1 code word: relative distance between a1 and b1 is 1, a1 is to the right of b1.
33+
/// </summary>
34+
VerticalR1 = 4,
35+
36+
/// <summary>
37+
/// Vertical r2 code word: relative distance between a1 and b1 is 2, a1 is to the right of b1.
38+
/// </summary>
39+
VerticalR2 = 5,
40+
41+
/// <summary>
42+
/// Vertical r3 code word: relative distance between a1 and b1 is 3, a1 is to the right of b1.
43+
/// </summary>
44+
VerticalR3 = 6,
45+
46+
/// <summary>
47+
/// Vertical l1 code word: relative distance between a1 and b1 is 1, a1 is to the left of b1.
48+
/// </summary>
49+
VerticalL1 = 7,
50+
51+
/// <summary>
52+
/// Vertical l2 code word: relative distance between a1 and b1 is 2, a1 is to the left of b1.
53+
/// </summary>
54+
VerticalL2 = 8,
55+
56+
/// <summary>
57+
/// Vertical l3 code word: relative distance between a1 and b1 is 3, a1 is to the left of b1.
58+
/// </summary>
59+
VerticalL3 = 9,
60+
61+
/// <summary>
62+
/// 1d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme.
63+
/// Not supported.
64+
/// </summary>
65+
Extensions1D = 10,
66+
67+
/// <summary>
68+
/// 2d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme.
69+
/// Not supported.
70+
/// </summary>
71+
Extensions2D = 11,
72+
}
73+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
1818
/// <remarks>
1919
/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
2020
/// </remarks>
21-
internal class DeflateTiffCompression : TiffBaseDecompressor
21+
internal sealed class DeflateTiffCompression : TiffBaseDecompressor
2222
{
2323
private readonly bool isBigEndian;
2424

@@ -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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
1212
/// <summary>
1313
/// Class to handle cases where TIFF image data is compressed using LZW compression.
1414
/// </summary>
15-
internal class LzwTiffCompression : TiffBaseDecompressor
15+
internal sealed class LzwTiffCompression : TiffBaseDecompressor
1616
{
1717
private readonly bool isBigEndian;
1818

@@ -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 sealed 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 || ((uint)(this.BitsRead - 1) < (7 - 1));
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 remainder = this.BitsRead & 7; // bit-hack for % 8
57+
if (remainder != 0)
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+
}

0 commit comments

Comments
 (0)