Skip to content

Commit 78efd68

Browse files
committed
Add support for horizontal predictor for 32 bit gray tiff's
1 parent 1aea006 commit 78efd68

File tree

12 files changed

+118
-26
lines changed

12 files changed

+118
-26
lines changed

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
using SixLabors.ImageSharp.Compression.Zlib;
88
using SixLabors.ImageSharp.Formats.Tiff.Constants;
9+
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
910
using SixLabors.ImageSharp.IO;
1011
using SixLabors.ImageSharp.Memory;
1112

@@ -21,16 +22,23 @@ internal class DeflateTiffCompression : TiffBaseDecompressor
2122
{
2223
private readonly bool isBigEndian;
2324

25+
private readonly TiffColorType colorType;
26+
2427
/// <summary>
2528
/// Initializes a new instance of the <see cref="DeflateTiffCompression" /> class.
2629
/// </summary>
2730
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
2831
/// <param name="width">The image width.</param>
2932
/// <param name="bitsPerPixel">The bits used per pixel.</param>
33+
/// <param name="colorType">The color type of the pixel data.</param>
3034
/// <param name="predictor">The tiff predictor used.</param>
3135
/// <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;
36+
public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian)
37+
: base(memoryAllocator, width, bitsPerPixel, predictor)
38+
{
39+
this.colorType = colorType;
40+
this.isBigEndian = isBigEndian;
41+
}
3442

3543
/// <inheritdoc/>
3644
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
@@ -62,7 +70,7 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa
6270

6371
if (this.Predictor == TiffPredictor.Horizontal)
6472
{
65-
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel, this.isBigEndian);
73+
HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian);
6674
}
6775
}
6876

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using SixLabors.ImageSharp.Formats.Tiff.Constants;
6+
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
67
using SixLabors.ImageSharp.IO;
78
using SixLabors.ImageSharp.Memory;
89

@@ -15,16 +16,23 @@ internal class LzwTiffCompression : TiffBaseDecompressor
1516
{
1617
private readonly bool isBigEndian;
1718

19+
private readonly TiffColorType colorType;
20+
1821
/// <summary>
1922
/// Initializes a new instance of the <see cref="LzwTiffCompression" /> class.
2023
/// </summary>
2124
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
2225
/// <param name="width">The image width.</param>
2326
/// <param name="bitsPerPixel">The bits used per pixel.</param>
27+
/// <param name="colorType">The color type of the pixel data.</param>
2428
/// <param name="predictor">The tiff predictor used.</param>
2529
/// <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;
30+
public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian)
31+
: base(memoryAllocator, width, bitsPerPixel, predictor)
32+
{
33+
this.colorType = colorType;
34+
this.isBigEndian = isBigEndian;
35+
}
2836

2937
/// <inheritdoc/>
3038
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
@@ -34,7 +42,7 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa
3442

3543
if (this.Predictor == TiffPredictor.Horizontal)
3644
{
37-
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel, this.isBigEndian);
45+
HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian);
3846
}
3947
}
4048

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

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Buffers.Binary;
66
using System.Runtime.CompilerServices;
77
using System.Runtime.InteropServices;
8+
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
89
using SixLabors.ImageSharp.Formats.Tiff.Utils;
910
using SixLabors.ImageSharp.PixelFormats;
1011

@@ -20,21 +21,28 @@ internal static class HorizontalPredictor
2021
/// </summary>
2122
/// <param name="pixelBytes">Buffer with decompressed pixel data.</param>
2223
/// <param name="width">The width of the image or strip.</param>
23-
/// <param name="bitsPerPixel">Bits per pixel.</param>
24+
/// <param name="colorType">The color type of the pixel data.</param>
2425
/// <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)
26+
public static void Undo(Span<byte> pixelBytes, int width, TiffColorType colorType, bool isBigEndian)
2627
{
27-
if (bitsPerPixel == 8)
28-
{
29-
Undo8Bit(pixelBytes, width);
30-
}
31-
else if (bitsPerPixel == 16)
28+
switch (colorType)
3229
{
33-
Undo16Bit(pixelBytes, width, isBigEndian);
34-
}
35-
else if (bitsPerPixel == 24)
36-
{
37-
Undo24Bit(pixelBytes, width);
30+
case TiffColorType.BlackIsZero8:
31+
case TiffColorType.WhiteIsZero8:
32+
case TiffColorType.PaletteColor:
33+
UndoGray8Bit(pixelBytes, width);
34+
break;
35+
case TiffColorType.BlackIsZero16:
36+
case TiffColorType.WhiteIsZero16:
37+
UndoGray16Bit(pixelBytes, width, isBigEndian);
38+
break;
39+
case TiffColorType.BlackIsZero32:
40+
case TiffColorType.WhiteIsZero32:
41+
UndoGray32Bit(pixelBytes, width, isBigEndian);
42+
break;
43+
case TiffColorType.Rgb888:
44+
UndoRgb24Bit(pixelBytes, width);
45+
break;
3846
}
3947
}
4048

@@ -99,7 +107,7 @@ private static void ApplyHorizontalPrediction8Bit(Span<byte> rows, int width)
99107
}
100108
}
101109

102-
private static void Undo8Bit(Span<byte> pixelBytes, int width)
110+
private static void UndoGray8Bit(Span<byte> pixelBytes, int width)
103111
{
104112
int rowBytesCount = width;
105113
int height = pixelBytes.Length / rowBytesCount;
@@ -116,7 +124,7 @@ private static void Undo8Bit(Span<byte> pixelBytes, int width)
116124
}
117125
}
118126

119-
private static void Undo16Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
127+
private static void UndoGray16Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
120128
{
121129
int rowBytesCount = width * 2;
122130
int height = pixelBytes.Length / rowBytesCount;
@@ -160,7 +168,51 @@ private static void Undo16Bit(Span<byte> pixelBytes, int width, bool isBigEndian
160168
}
161169
}
162170

163-
private static void Undo24Bit(Span<byte> pixelBytes, int width)
171+
private static void UndoGray32Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
172+
{
173+
int rowBytesCount = width * 4;
174+
int height = pixelBytes.Length / rowBytesCount;
175+
if (isBigEndian)
176+
{
177+
for (int y = 0; y < height; y++)
178+
{
179+
int offset = 0;
180+
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
181+
uint pixelValue = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
182+
offset += 4;
183+
184+
for (int x = 1; x < width; x++)
185+
{
186+
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
187+
uint diff = TiffUtils.ConvertToUIntBigEndian(rowSpan);
188+
pixelValue += diff;
189+
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, pixelValue);
190+
offset += 4;
191+
}
192+
}
193+
}
194+
else
195+
{
196+
for (int y = 0; y < height; y++)
197+
{
198+
int offset = 0;
199+
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
200+
uint pixelValue = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
201+
offset += 4;
202+
203+
for (int x = 1; x < width; x++)
204+
{
205+
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
206+
uint diff = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
207+
pixelValue += diff;
208+
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, pixelValue);
209+
offset += 4;
210+
}
211+
}
212+
}
213+
}
214+
215+
private static void UndoRgb24Bit(Span<byte> pixelBytes, int width)
164216
{
165217
int rowBytesCount = width * 3;
166218
int height = pixelBytes.Length / rowBytesCount;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
55
using SixLabors.ImageSharp.Formats.Tiff.Constants;
6+
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
67
using SixLabors.ImageSharp.Memory;
78

89
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
@@ -15,6 +16,7 @@ public static TiffBaseDecompressor Create(
1516
TiffPhotometricInterpretation photometricInterpretation,
1617
int width,
1718
int bitsPerPixel,
19+
TiffColorType colorType,
1820
TiffPredictor predictor,
1921
FaxCompressionOptions faxOptions,
2022
ByteOrder byteOrder)
@@ -33,11 +35,11 @@ public static TiffBaseDecompressor Create(
3335

3436
case TiffDecoderCompressionType.Deflate:
3537
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
36-
return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor, byteOrder == ByteOrder.BigEndian);
38+
return new DeflateTiffCompression(allocator, width, bitsPerPixel, colorType, predictor, byteOrder == ByteOrder.BigEndian);
3739

3840
case TiffDecoderCompressionType.Lzw:
3941
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
40-
return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor, byteOrder == ByteOrder.BigEndian);
42+
return new LzwTiffCompression(allocator, width, bitsPerPixel, colorType, predictor, byteOrder == ByteOrder.BigEndian);
4143

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

src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5-
using System.Numerics;
65
using SixLabors.ImageSharp.Formats.Tiff.Utils;
76
using SixLabors.ImageSharp.Memory;
87
using SixLabors.ImageSharp.PixelFormats;

src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
270270
this.PhotometricInterpretation,
271271
frame.Width,
272272
bitsPerPixel,
273+
this.ColorType,
273274
this.Predictor,
274275
this.FaxCompressionOptions,
275276
this.byteOrder);
@@ -321,6 +322,7 @@ private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStr
321322
this.PhotometricInterpretation,
322323
frame.Width,
323324
bitsPerPixel,
325+
this.ColorType,
324326
this.Predictor,
325327
this.FaxCompressionOptions,
326328
this.byteOrder);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using SixLabors.ImageSharp.Compression.Zlib;
66
using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
77
using SixLabors.ImageSharp.Formats.Tiff.Constants;
8+
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
89
using SixLabors.ImageSharp.IO;
910

1011
using Xunit;
@@ -26,7 +27,7 @@ public void Compress_Decompress_Roundtrip_Works(byte[] data)
2627
{
2728
var buffer = new byte[data.Length];
2829

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

3132
decompressor.Decompress(stream, 0, (uint)stream.Length, buffer);
3233

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors;
66
using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
77
using SixLabors.ImageSharp.Formats.Tiff.Constants;
8+
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
89
using SixLabors.ImageSharp.IO;
910
using Xunit;
1011

@@ -38,7 +39,7 @@ public void Compress_Decompress_Roundtrip_Works(byte[] data)
3839
using BufferedReadStream stream = CreateCompressedStream(data);
3940
var buffer = new byte[data.Length];
4041

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

4445
Assert.Equal(data, buffer);

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,17 @@ public void TiffDecoder_CanDecode_32Bit_Gray<TPixel>(TestImageProvider<TPixel> p
197197
image.DebugSave(provider);
198198
}
199199

200+
[Theory]
201+
[WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)]
202+
[WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)]
203+
public void TiffDecoder_CanDecode_32Bit_Gray_WithPredictor<TPixel>(TestImageProvider<TPixel> provider)
204+
where TPixel : unmanaged, IPixel<TPixel>
205+
{
206+
// Note: because the MagickReferenceDecoder fails to load the image, we only debug save them.
207+
using Image<TPixel> image = provider.GetImage();
208+
image.DebugSave(provider);
209+
}
210+
200211
[Theory]
201212
[WithFile(FlowerRgb121212Contiguous, PixelTypes.Rgba32)]
202213
public void TiffDecoder_CanDecode_36Bit<TPixel>(TestImageProvider<TPixel> provider)

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,8 @@ public static class Tiff
607607
public const string Flower32BitGrayLittleEndian = "Tiff/flower-minisblack-32_lsb.tiff";
608608
public const string Flower32BitGrayMinIsWhite = "Tiff/flower-miniswhite-32.tiff";
609609
public const string Flower32BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-32_lsb.tiff";
610+
public const string Flower32BitGrayPredictorBigEndian = "Tiff/flower-minisblack-32_msb_deflate_predictor.tiff";
611+
public const string Flower32BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff";
610612

611613
public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
612614

0 commit comments

Comments
 (0)