Skip to content

Commit 1aea006

Browse files
committed
Add support for horizontal predictor for 16 bit gray tiff's
1 parent 6a388c8 commit 1aea006

File tree

11 files changed

+95
-20
lines changed

11 files changed

+95
-20
lines changed

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.IO.Compression;
66

77
using SixLabors.ImageSharp.Compression.Zlib;
8-
using SixLabors.ImageSharp.Formats.Tiff.Compression;
98
using SixLabors.ImageSharp.Formats.Tiff.Constants;
109
using SixLabors.ImageSharp.IO;
1110
using SixLabors.ImageSharp.Memory;
@@ -20,17 +19,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
2019
/// </remarks>
2120
internal class DeflateTiffCompression : TiffBaseDecompressor
2221
{
22+
private readonly bool isBigEndian;
23+
2324
/// <summary>
2425
/// Initializes a new instance of the <see cref="DeflateTiffCompression" /> class.
2526
/// </summary>
2627
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
2728
/// <param name="width">The image width.</param>
2829
/// <param name="bitsPerPixel">The bits used per pixel.</param>
2930
/// <param name="predictor">The tiff predictor used.</param>
30-
public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor)
31-
: base(memoryAllocator, width, bitsPerPixel, predictor)
32-
{
33-
}
31+
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
32+
public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor, bool isBigEndian)
33+
: base(memoryAllocator, width, bitsPerPixel, predictor) => this.isBigEndian = isBigEndian;
3434

3535
/// <inheritdoc/>
3636
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
@@ -62,7 +62,7 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa
6262

6363
if (this.Predictor == TiffPredictor.Horizontal)
6464
{
65-
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel);
65+
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel, this.isBigEndian);
6666
}
6767
}
6868

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
1313
/// </summary>
1414
internal class LzwTiffCompression : TiffBaseDecompressor
1515
{
16+
private readonly bool isBigEndian;
17+
1618
/// <summary>
1719
/// Initializes a new instance of the <see cref="LzwTiffCompression" /> class.
1820
/// </summary>
1921
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
2022
/// <param name="width">The image width.</param>
2123
/// <param name="bitsPerPixel">The bits used per pixel.</param>
2224
/// <param name="predictor">The tiff predictor used.</param>
23-
public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor)
24-
: base(memoryAllocator, width, bitsPerPixel, predictor)
25-
{
26-
}
25+
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
26+
public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor, bool isBigEndian)
27+
: base(memoryAllocator, width, bitsPerPixel, predictor) => this.isBigEndian = isBigEndian;
2728

2829
/// <inheritdoc/>
2930
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
@@ -33,7 +34,7 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa
3334

3435
if (this.Predictor == TiffPredictor.Horizontal)
3536
{
36-
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel);
37+
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel, this.isBigEndian);
3738
}
3839
}
3940

src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5+
using System.Buffers.Binary;
56
using System.Runtime.CompilerServices;
67
using System.Runtime.InteropServices;
7-
8+
using SixLabors.ImageSharp.Formats.Tiff.Utils;
89
using SixLabors.ImageSharp.PixelFormats;
910

1011
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
@@ -20,12 +21,17 @@ internal static class HorizontalPredictor
2021
/// <param name="pixelBytes">Buffer with decompressed pixel data.</param>
2122
/// <param name="width">The width of the image or strip.</param>
2223
/// <param name="bitsPerPixel">Bits per pixel.</param>
23-
public static void Undo(Span<byte> pixelBytes, int width, int bitsPerPixel)
24+
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
25+
public static void Undo(Span<byte> pixelBytes, int width, int bitsPerPixel, bool isBigEndian)
2426
{
2527
if (bitsPerPixel == 8)
2628
{
2729
Undo8Bit(pixelBytes, width);
2830
}
31+
else if (bitsPerPixel == 16)
32+
{
33+
Undo16Bit(pixelBytes, width, isBigEndian);
34+
}
2935
else if (bitsPerPixel == 24)
3036
{
3137
Undo24Bit(pixelBytes, width);
@@ -110,6 +116,50 @@ private static void Undo8Bit(Span<byte> pixelBytes, int width)
110116
}
111117
}
112118

119+
private static void Undo16Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
120+
{
121+
int rowBytesCount = width * 2;
122+
int height = pixelBytes.Length / rowBytesCount;
123+
if (isBigEndian)
124+
{
125+
for (int y = 0; y < height; y++)
126+
{
127+
int offset = 0;
128+
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
129+
ushort pixelValue = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
130+
offset += 2;
131+
132+
for (int x = 1; x < width; x++)
133+
{
134+
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
135+
ushort diff = TiffUtils.ConvertToUShortBigEndian(rowSpan);
136+
pixelValue += diff;
137+
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, pixelValue);
138+
offset += 2;
139+
}
140+
}
141+
}
142+
else
143+
{
144+
for (int y = 0; y < height; y++)
145+
{
146+
int offset = 0;
147+
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
148+
ushort pixelValue = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
149+
offset += 2;
150+
151+
for (int x = 1; x < width; x++)
152+
{
153+
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
154+
ushort diff = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
155+
pixelValue += diff;
156+
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, pixelValue);
157+
offset += 2;
158+
}
159+
}
160+
}
161+
}
162+
113163
private static void Undo24Bit(Span<byte> pixelBytes, int width)
114164
{
115165
int rowBytesCount = width * 3;

src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ public static TiffBaseDecompressor Create(
1616
int width,
1717
int bitsPerPixel,
1818
TiffPredictor predictor,
19-
FaxCompressionOptions faxOptions)
19+
FaxCompressionOptions faxOptions,
20+
ByteOrder byteOrder)
2021
{
2122
switch (method)
2223
{
@@ -32,11 +33,11 @@ public static TiffBaseDecompressor Create(
3233

3334
case TiffDecoderCompressionType.Deflate:
3435
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
35-
return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor);
36+
return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor, byteOrder == ByteOrder.BigEndian);
3637

3738
case TiffDecoderCompressionType.Lzw:
3839
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
39-
return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor);
40+
return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor, byteOrder == ByteOrder.BigEndian);
4041

4142
case TiffDecoderCompressionType.T4:
4243
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");

src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,15 @@ private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
264264
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>(uncompressedStripSize);
265265
}
266266

267-
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions);
267+
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
268+
this.CompressionType,
269+
this.memoryAllocator,
270+
this.PhotometricInterpretation,
271+
frame.Width,
272+
bitsPerPixel,
273+
this.Predictor,
274+
this.FaxCompressionOptions,
275+
this.byteOrder);
268276

269277
TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);
270278

@@ -314,7 +322,8 @@ private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
314322
frame.Width,
315323
bitsPerPixel,
316324
this.Predictor,
317-
this.FaxCompressionOptions);
325+
this.FaxCompressionOptions,
326+
this.byteOrder);
318327

319328
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.Configuration, this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);
320329

tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public void Compress_Decompress_Roundtrip_Works(byte[] data)
2626
{
2727
var buffer = new byte[data.Length];
2828

29-
using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None);
29+
using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None, false);
3030

3131
decompressor.Decompress(stream, 0, (uint)stream.Length, buffer);
3232

tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public void Compress_Decompress_Roundtrip_Works(byte[] data)
3838
using BufferedReadStream stream = CreateCompressedStream(data);
3939
var buffer = new byte[data.Length];
4040

41-
using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None);
41+
using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None, false);
4242
decompressor.Decompress(stream, 0, (uint)stream.Length, buffer);
4343

4444
Assert.Equal(data, buffer);

tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ public void TiffDecoder_CanDecode_14Bit_Gray<TPixel>(TestImageProvider<TPixel> p
161161
public void TiffDecoder_CanDecode_16Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
162162
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
163163

164+
[Theory]
165+
[WithFile(Flower16BitGrayPredictorBigEndian, PixelTypes.Rgba32)]
166+
[WithFile(Flower16BitGrayPredictorLittleEndian, PixelTypes.Rgba32)]
167+
public void TiffDecoder_CanDecode_16Bit_Gray_WithPredictor<TPixel>(TestImageProvider<TPixel> provider)
168+
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
169+
164170
[Theory]
165171
[WithFile(Flower24BitGray, PixelTypes.Rgba32)]
166172
[WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)]

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,8 @@ public static class Tiff
599599
public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff";
600600
public const string Flower16BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-16_lsb.tiff";
601601
public const string Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16.tiff";
602+
public const string Flower16BitGrayPredictorBigEndian = "Tiff/flower-minisblack-16_msb_lzw_predictor.tiff";
603+
public const string Flower16BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff";
602604
public const string Flower24BitGray = "Tiff/flower-minisblack-24.tiff";
603605
public const string Flower24BitGrayLittleEndian = "Tiff/flower-minisblack-24_lsb.tiff";
604606
public const string Flower32BitGray = "Tiff/flower-minisblack-32.tiff";
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:263da6b84862af1bc23f2631e648b1a629b5571b253c82f3ea64f44e9b7b1fe6
3+
size 8478

0 commit comments

Comments
 (0)