Skip to content

Commit 46b0bf0

Browse files
authored
Merge pull request #1993 from frankracis-work/tiff-fax4-compression
Add TIFF CcittGroup4Fax (T6) compression
2 parents 0f587ab + 22364ef commit 46b0bf0

File tree

9 files changed

+769
-464
lines changed

9 files changed

+769
-464
lines changed

src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs

Lines changed: 9 additions & 459 deletions
Large diffs are not rendered by default.
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.IO;
7+
using SixLabors.ImageSharp.Formats.Tiff.Constants;
8+
using SixLabors.ImageSharp.Memory;
9+
10+
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
11+
{
12+
/// <summary>
13+
/// Bitwriter for writing compressed CCITT T6 2D data.
14+
/// </summary>
15+
internal sealed class T6BitCompressor : TiffCcittCompressor
16+
{
17+
/// <summary>
18+
/// Vertical codes from -3 to +3.
19+
/// </summary>
20+
private static readonly (uint Length, uint Code)[] VerticalCodes =
21+
{
22+
(7u, 3u),
23+
(6u, 3u),
24+
(3u, 3u),
25+
(1u, 1u),
26+
(3u, 2u),
27+
(6u, 2u),
28+
(7u, 2u)
29+
};
30+
31+
private IMemoryOwner<byte> referenceLineBuffer;
32+
33+
/// <summary>
34+
/// Initializes a new instance of the <see cref="T6BitCompressor"/> class.
35+
/// </summary>
36+
/// <param name="output">The output stream to write the compressed data.</param>
37+
/// <param name="allocator">The memory allocator.</param>
38+
/// <param name="width">The width of the image.</param>
39+
/// <param name="bitsPerPixel">The bits per pixel.</param>
40+
public T6BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel)
41+
: base(output, allocator, width, bitsPerPixel)
42+
{
43+
}
44+
45+
/// <inheritdoc />
46+
public override TiffCompression Method => TiffCompression.CcittGroup4Fax;
47+
48+
/// <summary>
49+
/// Writes a image compressed with CCITT T6 to the output buffer.
50+
/// </summary>
51+
/// <param name="pixelsAsGray">The pixels as 8-bit gray array.</param>
52+
/// <param name="height">The strip height.</param>
53+
/// <param name="compressedData">The destination for the compressed data.</param>
54+
protected override void CompressStrip(Span<byte> pixelsAsGray, int height, Span<byte> compressedData)
55+
{
56+
// Initial reference line is all white.
57+
Span<byte> referenceLine = this.referenceLineBuffer.GetSpan();
58+
referenceLine.Fill(0xff);
59+
60+
for (int y = 0; y < height; y++)
61+
{
62+
Span<byte> row = pixelsAsGray.Slice(y * this.Width, this.Width);
63+
uint a0 = 0;
64+
uint a1 = row[0] == 0 ? 0 : this.FindRunEnd(row, 0);
65+
uint b1 = referenceLine[0] == 0 ? 0 : this.FindRunEnd(referenceLine, 0);
66+
67+
while (true)
68+
{
69+
uint b2 = this.FindRunEnd(referenceLine, b1);
70+
if (b2 < a1)
71+
{
72+
// Pass mode.
73+
this.WriteCode(4, 1, compressedData);
74+
a0 = b2;
75+
}
76+
else
77+
{
78+
int d = int.MaxValue;
79+
if ((b1 >= a1) && (b1 - a1 <= 3))
80+
{
81+
d = (int)(b1 - a1);
82+
}
83+
else if ((b1 < a1) && (a1 - b1 <= 3))
84+
{
85+
d = -(int)(a1 - b1);
86+
}
87+
88+
if ((d >= -3) && (d <= 3))
89+
{
90+
// Vertical mode.
91+
(uint length, uint code) = VerticalCodes[d + 3];
92+
this.WriteCode(length, code, compressedData);
93+
a0 = a1;
94+
}
95+
else
96+
{
97+
// Horizontal mode.
98+
this.WriteCode(3, 1, compressedData);
99+
100+
uint a2 = this.FindRunEnd(row, a1);
101+
if ((a0 + a1 == 0) || (row[(int)a0] != 0))
102+
{
103+
this.WriteRun(a1 - a0, true, compressedData);
104+
this.WriteRun(a2 - a1, false, compressedData);
105+
}
106+
else
107+
{
108+
this.WriteRun(a1 - a0, false, compressedData);
109+
this.WriteRun(a2 - a1, true, compressedData);
110+
}
111+
112+
a0 = a2;
113+
}
114+
}
115+
116+
if (a0 >= row.Length)
117+
{
118+
break;
119+
}
120+
121+
byte thisPixel = row[(int)a0];
122+
a1 = this.FindRunEnd(row, a0, thisPixel);
123+
b1 = this.FindRunEnd(referenceLine, a0, (byte)~thisPixel);
124+
b1 = this.FindRunEnd(referenceLine, b1, thisPixel);
125+
}
126+
127+
// This row is now the reference line.
128+
row.CopyTo(referenceLine);
129+
}
130+
131+
this.WriteCode(12, 1, compressedData);
132+
this.WriteCode(12, 1, compressedData);
133+
}
134+
135+
/// <inheritdoc />
136+
protected override void Dispose(bool disposing)
137+
{
138+
this.referenceLineBuffer?.Dispose();
139+
base.Dispose(disposing);
140+
}
141+
142+
/// <summary>
143+
/// Finds the end of a pixel run.
144+
/// </summary>
145+
/// <param name="row">The row of pixels to examine.</param>
146+
/// <param name="startIndex">The index of the first pixel in <paramref name="row"/> to examine.</param>
147+
/// <param name="color">Color of pixels in the run. If not specified, the color at
148+
/// <paramref name="startIndex"/> will be used.</param>
149+
/// <returns>The index of the first pixel at or after <paramref name="startIndex"/>
150+
/// that does not match <paramref name="color"/>, or the length of <paramref name="row"/>,
151+
/// whichever comes first.</returns>
152+
private uint FindRunEnd(Span<byte> row, uint startIndex, byte? color = null)
153+
{
154+
if (startIndex >= row.Length)
155+
{
156+
return (uint)row.Length;
157+
}
158+
159+
byte colorValue = color.GetValueOrDefault(row[(int)startIndex]);
160+
for (int i = (int)startIndex; i < row.Length; i++)
161+
{
162+
if (row[i] != colorValue)
163+
{
164+
return (uint)i;
165+
}
166+
}
167+
168+
return (uint)row.Length;
169+
}
170+
171+
/// <inheritdoc />
172+
public override void Initialize(int rowsPerStrip)
173+
{
174+
base.Initialize(rowsPerStrip);
175+
this.referenceLineBuffer = this.Allocator.Allocate<byte>(this.Width);
176+
}
177+
178+
/// <summary>
179+
/// Writes a run to the output buffer.
180+
/// </summary>
181+
/// <param name="runLength">The length of the run.</param>
182+
/// <param name="isWhiteRun">If <c>true</c> the run is white pixels,
183+
/// if <c>false</c> the run is black pixels.</param>
184+
/// <param name="compressedData">The destination to write the run to.</param>
185+
private void WriteRun(uint runLength, bool isWhiteRun, Span<byte> compressedData)
186+
{
187+
uint code;
188+
uint codeLength;
189+
while (runLength > 63)
190+
{
191+
uint makeupLength = this.GetBestFittingMakeupRunLength(runLength);
192+
code = this.GetMakeupCode(makeupLength, out codeLength, isWhiteRun);
193+
this.WriteCode(codeLength, code, compressedData);
194+
runLength -= makeupLength;
195+
}
196+
197+
code = this.GetTermCode(runLength, out codeLength, isWhiteRun);
198+
this.WriteCode(codeLength, code, compressedData);
199+
}
200+
}
201+
}

0 commit comments

Comments
 (0)