Skip to content

Commit 5d888be

Browse files
committed
Tiff decoder now respects byte order for 16 bit gray images
1 parent 0701021 commit 5d888be

File tree

9 files changed

+91
-7
lines changed

9 files changed

+91
-7
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using SixLabors.ImageSharp.Formats.Tiff.Utils;
6+
using SixLabors.ImageSharp.Memory;
7+
using SixLabors.ImageSharp.PixelFormats;
8+
9+
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
10+
{
11+
/// <summary>
12+
/// Implements the 'BlackIsZero' photometric interpretation for 16-bit grayscale images.
13+
/// </summary>
14+
internal class BlackIsZero16TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
15+
where TPixel : unmanaged, IPixel<TPixel>
16+
{
17+
private readonly bool isBigEndian;
18+
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="BlackIsZero16TiffColor{TPixel}" /> class.
21+
/// </summary>
22+
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
23+
public BlackIsZero16TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
24+
25+
/// <inheritdoc/>
26+
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
27+
{
28+
var color = default(TPixel);
29+
30+
int offset = 0;
31+
32+
var l16 = default(L16);
33+
for (int y = top; y < top + height; y++)
34+
{
35+
for (int x = left; x < left + width; x++)
36+
{
37+
ushort intensity = TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian);
38+
offset += 2;
39+
40+
l16.PackedValue = intensity;
41+
color.FromL16(l16);
42+
43+
pixels[x, y] = color;
44+
}
45+
}
46+
}
47+
}
48+
}

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

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

44
using System;
55
using System.Buffers.Binary;
6+
using SixLabors.ImageSharp.Formats.Tiff.Utils;
67
using SixLabors.ImageSharp.Memory;
78
using SixLabors.ImageSharp.PixelFormats;
89

@@ -36,11 +37,11 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
3637

3738
for (int x = left; x < left + width; x++)
3839
{
39-
ulong r = this.ConvertToShort(data.Slice(offset, 2));
40+
ulong r = TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian);
4041
offset += 2;
41-
ulong g = this.ConvertToShort(data.Slice(offset, 2));
42+
ulong g = TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian);
4243
offset += 2;
43-
ulong b = this.ConvertToShort(data.Slice(offset, 2));
44+
ulong b = TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian);
4445
offset += 2;
4546

4647
rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48);
@@ -50,9 +51,5 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
5051
}
5152
}
5253
}
53-
54-
private ushort ConvertToShort(ReadOnlySpan<byte> buffer) => this.isBigEndian
55-
? BinaryPrimitives.ReadUInt16BigEndian(buffer)
56-
: BinaryPrimitives.ReadUInt16LittleEndian(buffer);
5754
}
5855
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ public static TiffBaseColorDecoder<TPixel> Create(TiffColorType colorType, TiffB
5252
DebugGuard.IsTrue(colorMap == null, "colorMap");
5353
return new BlackIsZero8TiffColor<TPixel>();
5454

55+
case TiffColorType.BlackIsZero16:
56+
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample");
57+
DebugGuard.IsTrue(colorMap == null, "colorMap");
58+
return new BlackIsZero16TiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
59+
5560
case TiffColorType.Rgb:
5661
DebugGuard.IsTrue(colorMap == null, "colorMap");
5762
return new RgbTiffColor<TPixel>(bitsPerSample);

src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ internal enum TiffColorType
2828
/// </summary>
2929
BlackIsZero8,
3030

31+
/// <summary>
32+
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 16-bit images.
33+
/// </summary>
34+
BlackIsZero16,
35+
3136
/// <summary>
3237
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black.
3338
/// </summary>

src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi
154154

155155
switch (bitsPerChannel)
156156
{
157+
case 16:
158+
{
159+
options.ColorType = TiffColorType.BlackIsZero16;
160+
break;
161+
}
162+
157163
case 8:
158164
{
159165
options.ColorType = TiffColorType.BlackIsZero8;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Buffers.Binary;
6+
7+
namespace SixLabors.ImageSharp.Formats.Tiff.Utils
8+
{
9+
/// <summary>
10+
/// Helper methods for TIFF decoding.
11+
/// </summary>
12+
internal static class TiffUtils
13+
{
14+
public static ushort ConvertToShort(ReadOnlySpan<byte> buffer, bool isBigEndian) => isBigEndian
15+
? BinaryPrimitives.ReadUInt16BigEndian(buffer)
16+
: BinaryPrimitives.ReadUInt16LittleEndian(buffer);
17+
}
18+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ public void TiffDecoder_CanDecode_14Bit<TPixel>(TestImageProvider<TPixel> provid
140140
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
141141

142142
[Theory]
143+
[WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)]
143144
[WithFile(Flower16BitGray, PixelTypes.Rgba32)]
144145
public void TiffDecoder_CanDecode_16Bit<TPixel>(TestImageProvider<TPixel> provider)
145146
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,7 @@ public static class Tiff
582582
public const string Flower12BitGray = "Tiff/flower-minisblack-12.tiff";
583583
public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff";
584584
public const string Flower16BitGray = "Tiff/flower-minisblack-16.tiff";
585+
public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff";
585586
public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
586587

587588
public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.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:c3806304a5453a6ec8a6795bc77b967b9aa8593288af36bbf9802f22ee27869e
3+
size 6588

0 commit comments

Comments
 (0)