Skip to content

Commit 3d02cfd

Browse files
committed
Add support for decoding tiff's with 32bit float rgb pixel data
1 parent 6a388c8 commit 3d02cfd

File tree

9 files changed

+142
-5
lines changed

9 files changed

+142
-5
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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+
Array.Reverse(buffer);
61+
color.FromVector4(colorVector);
62+
pixelRow[x] = color;
63+
}
64+
}
65+
else
66+
{
67+
for (int x = 0; x < pixelRow.Length; x++)
68+
{
69+
data.Slice(offset, 4).CopyTo(buffer);
70+
float r = BitConverter.ToSingle(buffer, 0);
71+
offset += 4;
72+
73+
data.Slice(offset, 4).CopyTo(buffer);
74+
float g = BitConverter.ToSingle(buffer, 0);
75+
offset += 4;
76+
77+
data.Slice(offset, 4).CopyTo(buffer);
78+
float b = BitConverter.ToSingle(buffer, 0);
79+
offset += 4;
80+
81+
var colorVector = new Vector4(r, g, b, 1.0f);
82+
color.FromVector4(colorVector);
83+
pixelRow[x] = color;
84+
}
85+
}
86+
}
87+
}
88+
}
89+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,16 @@ public static TiffBaseColorDecoder<TPixel> Create(Configuration configuration, T
176176
DebugGuard.IsTrue(colorMap == null, "colorMap");
177177
return new Rgb323232TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
178178

179+
case TiffColorType.RgbFloat323232:
180+
DebugGuard.IsTrue(
181+
bitsPerSample.Channels == 3
182+
&& bitsPerSample.Channel2 == 32
183+
&& bitsPerSample.Channel1 == 32
184+
&& bitsPerSample.Channel0 == 32,
185+
"bitsPerSample");
186+
DebugGuard.IsTrue(colorMap == null, "colorMap");
187+
return new RgbFloat323232TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
188+
179189
case TiffColorType.PaletteColor:
180190
DebugGuard.NotNull(colorMap, "colorMap");
181191
return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ internal enum TiffColorType
133133
/// </summary>
134134
Rgb323232,
135135

136+
/// <summary>
137+
/// RGB color image with 32 bits floats for each channel.
138+
/// </summary>
139+
RgbFloat323232,
140+
136141
/// <summary>
137142
/// RGB Full Color. Planar configuration of data. 8 Bit per color channel.
138143
/// </summary>

src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

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

98+
/// <summary>
99+
/// Gets or sets the sample format.
100+
/// </summary>
101+
public TiffSampleFormat SampleFormat { get; set; }
102+
98103
/// <summary>
99104
/// Gets or sets the horizontal predictor.
100105
/// </summary>

src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,16 @@ public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exif
4545
TiffThrowHelper.ThrowNotSupported("TIFF images with FloatingPoint horizontal predictor are not supported.");
4646
}
4747

48-
TiffSampleFormat[] sampleFormat = exifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray();
49-
if (sampleFormat != null)
48+
TiffSampleFormat[] sampleFormats = exifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray();
49+
TiffSampleFormat? sampleFormat = null;
50+
if (sampleFormats != null)
5051
{
51-
foreach (TiffSampleFormat format in sampleFormat)
52+
sampleFormat = sampleFormats[0];
53+
foreach (TiffSampleFormat format in sampleFormats)
5254
{
53-
if (format != TiffSampleFormat.UnsignedInteger)
55+
if (format != TiffSampleFormat.UnsignedInteger && format != TiffSampleFormat.Float)
5456
{
55-
TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger SampleFormat.");
57+
TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger and Float SampleFormat.");
5658
}
5759
}
5860
}
@@ -67,6 +69,7 @@ public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exif
6769
options.PlanarConfiguration = (TiffPlanarConfiguration?)exifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration;
6870
options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None;
6971
options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb;
72+
options.SampleFormat = sampleFormat ?? TiffSampleFormat.UnsignedInteger;
7073
options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24;
7174
options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0);
7275

@@ -231,6 +234,12 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi
231234
TiffThrowHelper.ThrowNotSupported("Only BitsPerSample with equal bits per channel are supported.");
232235
}
233236

237+
if (options.SampleFormat == TiffSampleFormat.Float)
238+
{
239+
options.ColorType = TiffColorType.RgbFloat323232;
240+
return;
241+
}
242+
234243
if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
235244
{
236245
ushort bitsPerChannel = options.BitsPerSample.Channel0;

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,17 @@ public void TiffDecoder_CanDecode_96Bit<TPixel>(TestImageProvider<TPixel> provid
237237
image.DebugSave(provider);
238238
}
239239

240+
[Theory]
241+
[WithFile(FlowerRgbFloat323232, PixelTypes.Rgba32)]
242+
[WithFile(FlowerRgbFloat323232LittleEndian, PixelTypes.Rgba32)]
243+
public void TiffDecoder_CanDecode_Float_96Bit<TPixel>(TestImageProvider<TPixel> provider)
244+
where TPixel : unmanaged, IPixel<TPixel>
245+
{
246+
// Note: because the MagickReferenceDecoder fails to load the image, we only debug save them.
247+
using Image<TPixel> image = provider.GetImage();
248+
image.DebugSave(provider);
249+
}
250+
240251
[Theory]
241252
[WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)]
242253
[WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)]

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,8 @@ public static class Tiff
563563
public const string RgbPalette = "Tiff/rgb_palette.tiff";
564564
public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff";
565565
public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff";
566+
public const string FlowerRgbFloat323232 = "Tiff/flower-rgb-float32_msb.tiff";
567+
public const string FlowerRgbFloat323232LittleEndian = "Tiff/flower-rgb-float32_lsb.tiff";
566568
public const string FlowerRgb323232Contiguous = "Tiff/flower-rgb-contig-32.tiff";
567569
public const string FlowerRgb323232ContiguousLittleEndian = "Tiff/flower-rgb-contig-32_lsb.tiff";
568570
public const string FlowerRgb323232Planar = "Tiff/flower-rgb-planar-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:a1f889baa6f3bb99f15609848cdd47d548d3e2ed1b7b558d428550dfa3bd4bf9
3+
size 38050
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:2c02be5dff2bcd5d60afbf379ba9095b0c8fd3a7a0063f684ac9ac9119f967a5
3+
size 38050

0 commit comments

Comments
 (0)