Skip to content

Commit a6c708f

Browse files
authored
Merge pull request #1727 from SixLabors/bp/tiff32float
Add support for decoding tiff's with float pixel data
2 parents 25dadda + 556fbf8 commit a6c708f

25 files changed

+355
-19
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Numerics;
6+
using SixLabors.ImageSharp.Formats.Tiff.Utils;
7+
using SixLabors.ImageSharp.Memory;
8+
using SixLabors.ImageSharp.PixelFormats;
9+
10+
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
11+
{
12+
/// <summary>
13+
/// Implements the 'BlackIsZero' photometric interpretation for 32-bit float grayscale images.
14+
/// </summary>
15+
internal class BlackIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
16+
where TPixel : unmanaged, IPixel<TPixel>
17+
{
18+
private readonly bool isBigEndian;
19+
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="BlackIsZero32FloatTiffColor{TPixel}" /> class.
22+
/// </summary>
23+
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
24+
public BlackIsZero32FloatTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
25+
26+
/// <inheritdoc/>
27+
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
28+
{
29+
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
30+
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
31+
var color = default(TPixel);
32+
color.FromVector4(TiffUtils.Vector4Default);
33+
byte[] buffer = new byte[4];
34+
35+
int offset = 0;
36+
for (int y = top; y < top + height; y++)
37+
{
38+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
39+
if (this.isBigEndian)
40+
{
41+
for (int x = 0; x < pixelRow.Length; x++)
42+
{
43+
data.Slice(offset, 4).CopyTo(buffer);
44+
Array.Reverse(buffer);
45+
float intensity = BitConverter.ToSingle(buffer, 0);
46+
offset += 4;
47+
48+
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
49+
color.FromVector4(colorVector);
50+
pixelRow[x] = color;
51+
}
52+
}
53+
else
54+
{
55+
for (int x = 0; x < pixelRow.Length; x++)
56+
{
57+
data.Slice(offset, 4).CopyTo(buffer);
58+
float intensity = BitConverter.ToSingle(buffer, 0);
59+
offset += 4;
60+
61+
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
62+
color.FromVector4(colorVector);
63+
pixelRow[x] = color;
64+
}
65+
}
66+
}
67+
}
68+
}
69+
}

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;
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Numerics;
6+
using SixLabors.ImageSharp.Formats.Tiff.Utils;
7+
using SixLabors.ImageSharp.Memory;
8+
using SixLabors.ImageSharp.PixelFormats;
9+
10+
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
11+
{
12+
/// <summary>
13+
/// Implements the 'RGB' photometric interpretation with 32 bits for each channel.
14+
/// </summary>
15+
internal class RgbFloat323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
16+
where TPixel : unmanaged, IPixel<TPixel>
17+
{
18+
private readonly bool isBigEndian;
19+
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="RgbFloat323232TiffColor{TPixel}" /> class.
22+
/// </summary>
23+
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
24+
public RgbFloat323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
25+
26+
/// <inheritdoc/>
27+
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
28+
{
29+
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
30+
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
31+
var color = default(TPixel);
32+
color.FromVector4(TiffUtils.Vector4Default);
33+
int offset = 0;
34+
byte[] buffer = new byte[4];
35+
36+
for (int y = top; y < top + height; y++)
37+
{
38+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
39+
40+
if (this.isBigEndian)
41+
{
42+
for (int x = 0; x < pixelRow.Length; x++)
43+
{
44+
data.Slice(offset, 4).CopyTo(buffer);
45+
Array.Reverse(buffer);
46+
float r = BitConverter.ToSingle(buffer, 0);
47+
offset += 4;
48+
49+
data.Slice(offset, 4).CopyTo(buffer);
50+
Array.Reverse(buffer);
51+
float g = BitConverter.ToSingle(buffer, 0);
52+
offset += 4;
53+
54+
data.Slice(offset, 4).CopyTo(buffer);
55+
Array.Reverse(buffer);
56+
float b = BitConverter.ToSingle(buffer, 0);
57+
offset += 4;
58+
59+
var colorVector = new Vector4(r, g, b, 1.0f);
60+
color.FromVector4(colorVector);
61+
pixelRow[x] = color;
62+
}
63+
}
64+
else
65+
{
66+
for (int x = 0; x < pixelRow.Length; x++)
67+
{
68+
data.Slice(offset, 4).CopyTo(buffer);
69+
float r = BitConverter.ToSingle(buffer, 0);
70+
offset += 4;
71+
72+
data.Slice(offset, 4).CopyTo(buffer);
73+
float g = BitConverter.ToSingle(buffer, 0);
74+
offset += 4;
75+
76+
data.Slice(offset, 4).CopyTo(buffer);
77+
float b = BitConverter.ToSingle(buffer, 0);
78+
offset += 4;
79+
80+
var colorVector = new Vector4(r, g, b, 1.0f);
81+
color.FromVector4(colorVector);
82+
pixelRow[x] = color;
83+
}
84+
}
85+
}
86+
}
87+
}
88+
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public static TiffBaseColorDecoder<TPixel> Create(Configuration configuration, T
4747
DebugGuard.IsTrue(colorMap == null, "colorMap");
4848
return new WhiteIsZero32TiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
4949

50+
case TiffColorType.WhiteIsZero32Float:
51+
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample");
52+
DebugGuard.IsTrue(colorMap == null, "colorMap");
53+
return new WhiteIsZero32FloatTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
54+
5055
case TiffColorType.BlackIsZero:
5156
DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample");
5257
DebugGuard.IsTrue(colorMap == null, "colorMap");
@@ -82,6 +87,11 @@ public static TiffBaseColorDecoder<TPixel> Create(Configuration configuration, T
8287
DebugGuard.IsTrue(colorMap == null, "colorMap");
8388
return new BlackIsZero32TiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
8489

90+
case TiffColorType.BlackIsZero32Float:
91+
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample");
92+
DebugGuard.IsTrue(colorMap == null, "colorMap");
93+
return new BlackIsZero32FloatTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
94+
8595
case TiffColorType.Rgb:
8696
DebugGuard.IsTrue(colorMap == null, "colorMap");
8797
return new RgbTiffColor<TPixel>(bitsPerSample);
@@ -176,6 +186,16 @@ public static TiffBaseColorDecoder<TPixel> Create(Configuration configuration, T
176186
DebugGuard.IsTrue(colorMap == null, "colorMap");
177187
return new Rgb323232TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
178188

189+
case TiffColorType.RgbFloat323232:
190+
DebugGuard.IsTrue(
191+
bitsPerSample.Channels == 3
192+
&& bitsPerSample.Channel2 == 32
193+
&& bitsPerSample.Channel1 == 32
194+
&& bitsPerSample.Channel0 == 32,
195+
"bitsPerSample");
196+
DebugGuard.IsTrue(colorMap == null, "colorMap");
197+
return new RgbFloat323232TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
198+
179199
case TiffColorType.PaletteColor:
180200
DebugGuard.NotNull(colorMap, "colorMap");
181201
return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap);

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ internal enum TiffColorType
4343
/// </summary>
4444
BlackIsZero32,
4545

46+
/// <summary>
47+
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Pixel data is 32-bit float.
48+
/// </summary>
49+
BlackIsZero32Float,
50+
4651
/// <summary>
4752
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black.
4853
/// </summary>
@@ -78,6 +83,11 @@ internal enum TiffColorType
7883
/// </summary>
7984
WhiteIsZero32,
8085

86+
/// <summary>
87+
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Pixel data is 32-bit float.
88+
/// </summary>
89+
WhiteIsZero32Float,
90+
8191
/// <summary>
8292
/// Palette-color.
8393
/// </summary>
@@ -133,6 +143,11 @@ internal enum TiffColorType
133143
/// </summary>
134144
Rgb323232,
135145

146+
/// <summary>
147+
/// RGB color image with 32 bits floats for each channel.
148+
/// </summary>
149+
RgbFloat323232,
150+
136151
/// <summary>
137152
/// RGB Full Color. Planar configuration of data. 8 Bit per color channel.
138153
/// </summary>

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
3131
color.FromVector4(TiffUtils.Vector4Default);
3232
byte[] buffer = new byte[4];
3333
int bufferStartIdx = this.isBigEndian ? 1 : 0;
34+
const uint maxValue = 0xFFFFFF;
3435

3536
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
3637
int offset = 0;
@@ -42,7 +43,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
4243
for (int x = 0; x < pixelRow.Length; x++)
4344
{
4445
data.Slice(offset, 3).CopyTo(bufferSpan);
45-
ulong intensity = TiffUtils.ConvertToUIntBigEndian(buffer);
46+
ulong intensity = maxValue - TiffUtils.ConvertToUIntBigEndian(buffer);
4647
offset += 3;
4748

4849
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color);
@@ -53,7 +54,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
5354
for (int x = 0; x < pixelRow.Length; x++)
5455
{
5556
data.Slice(offset, 3).CopyTo(bufferSpan);
56-
ulong intensity = TiffUtils.ConvertToUIntLittleEndian(buffer);
57+
ulong intensity = maxValue - TiffUtils.ConvertToUIntLittleEndian(buffer);
5758
offset += 3;
5859

5960
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Numerics;
6+
using SixLabors.ImageSharp.Formats.Tiff.Utils;
7+
using SixLabors.ImageSharp.Memory;
8+
using SixLabors.ImageSharp.PixelFormats;
9+
10+
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
11+
{
12+
/// <summary>
13+
/// Implements the 'WhiteIsZero' photometric interpretation for 32-bit float grayscale images.
14+
/// </summary>
15+
internal class WhiteIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
16+
where TPixel : unmanaged, IPixel<TPixel>
17+
{
18+
private readonly bool isBigEndian;
19+
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="WhiteIsZero32FloatTiffColor{TPixel}" /> class.
22+
/// </summary>
23+
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
24+
public WhiteIsZero32FloatTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
25+
26+
/// <inheritdoc/>
27+
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
28+
{
29+
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
30+
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
31+
var color = default(TPixel);
32+
color.FromVector4(TiffUtils.Vector4Default);
33+
byte[] buffer = new byte[4];
34+
35+
int offset = 0;
36+
for (int y = top; y < top + height; y++)
37+
{
38+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
39+
if (this.isBigEndian)
40+
{
41+
for (int x = 0; x < pixelRow.Length; x++)
42+
{
43+
data.Slice(offset, 4).CopyTo(buffer);
44+
Array.Reverse(buffer);
45+
float intensity = 1.0f - BitConverter.ToSingle(buffer, 0);
46+
offset += 4;
47+
48+
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
49+
color.FromVector4(colorVector);
50+
pixelRow[x] = color;
51+
}
52+
}
53+
else
54+
{
55+
for (int x = 0; x < pixelRow.Length; x++)
56+
{
57+
data.Slice(offset, 4).CopyTo(buffer);
58+
float intensity = 1.0f - BitConverter.ToSingle(buffer, 0);
59+
offset += 4;
60+
61+
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
62+
color.FromVector4(colorVector);
63+
pixelRow[x] = color;
64+
}
65+
}
66+
}
67+
}
68+
}
69+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
2929
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
3030
var color = default(TPixel);
3131
color.FromVector4(TiffUtils.Vector4Default);
32+
const uint maxValue = 0xFFFFFFFF;
3233

3334
int offset = 0;
3435
for (int y = top; y < top + height; y++)
@@ -38,7 +39,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
3839
{
3940
for (int x = 0; x < pixelRow.Length; x++)
4041
{
41-
ulong intensity = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
42+
ulong intensity = maxValue - TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
4243
offset += 4;
4344

4445
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color);
@@ -48,7 +49,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
4849
{
4950
for (int x = 0; x < pixelRow.Length; x++)
5051
{
51-
ulong intensity = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
52+
ulong intensity = maxValue - TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
5253
offset += 4;
5354

5455
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
2727
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
2828
for (int x = 0; x < pixelRow.Length; x++)
2929
{
30-
byte intensity = (byte)(255 - data[offset++]);
30+
byte intensity = (byte)(byte.MaxValue - data[offset++]);
3131
pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color);
3232
}
3333
}

src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options)
100100
/// </summary>
101101
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; }
102102

103+
/// <summary>
104+
/// Gets or sets the sample format.
105+
/// </summary>
106+
public TiffSampleFormat SampleFormat { get; set; }
107+
103108
/// <summary>
104109
/// Gets or sets the horizontal predictor.
105110
/// </summary>

0 commit comments

Comments
 (0)