Skip to content

Commit 9d6b7a6

Browse files
committed
Add support for decoding tiff's with 32bit float gray pixel data with min is black
1 parent 3d02cfd commit 9d6b7a6

File tree

10 files changed

+110
-8
lines changed

10 files changed

+110
-8
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;

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
5757
offset += 4;
5858

5959
var colorVector = new Vector4(r, g, b, 1.0f);
60-
Array.Reverse(buffer);
6160
color.FromVector4(colorVector);
6261
pixelRow[x] = color;
6362
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ public static TiffBaseColorDecoder<TPixel> Create(Configuration configuration, T
8282
DebugGuard.IsTrue(colorMap == null, "colorMap");
8383
return new BlackIsZero32TiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
8484

85+
case TiffColorType.BlackIsZero32Float:
86+
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample");
87+
DebugGuard.IsTrue(colorMap == null, "colorMap");
88+
return new BlackIsZero32FloatTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
89+
8590
case TiffColorType.Rgb:
8691
DebugGuard.IsTrue(colorMap == null, "colorMap");
8792
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
@@ -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>

src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,12 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi
177177
{
178178
case 32:
179179
{
180+
if (options.SampleFormat == TiffSampleFormat.Float)
181+
{
182+
options.ColorType = TiffColorType.BlackIsZero32Float;
183+
return;
184+
}
185+
180186
options.ColorType = TiffColorType.BlackIsZero32;
181187
break;
182188
}
@@ -234,18 +240,18 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi
234240
TiffThrowHelper.ThrowNotSupported("Only BitsPerSample with equal bits per channel are supported.");
235241
}
236242

237-
if (options.SampleFormat == TiffSampleFormat.Float)
238-
{
239-
options.ColorType = TiffColorType.RgbFloat323232;
240-
return;
241-
}
242-
243243
if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
244244
{
245245
ushort bitsPerChannel = options.BitsPerSample.Channel0;
246246
switch (bitsPerChannel)
247247
{
248248
case 32:
249+
if (options.SampleFormat == TiffSampleFormat.Float)
250+
{
251+
options.ColorType = TiffColorType.RgbFloat323232;
252+
return;
253+
}
254+
249255
options.ColorType = TiffColorType.Rgb323232;
250256
break;
251257

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,17 @@ public void TiffDecoder_CanDecode_Float_96Bit<TPixel>(TestImageProvider<TPixel>
248248
image.DebugSave(provider);
249249
}
250250

251+
[Theory]
252+
[WithFile(Flower32BitFloatGray, PixelTypes.Rgba32)]
253+
[WithFile(Flower32BitFloatGrayLittleEndian, PixelTypes.Rgba32)]
254+
public void TiffDecoder_CanDecode_Float_96Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
255+
where TPixel : unmanaged, IPixel<TPixel>
256+
{
257+
// Note: because the MagickReferenceDecoder fails to load the image, we only debug save them.
258+
using Image<TPixel> image = provider.GetImage();
259+
image.DebugSave(provider);
260+
}
261+
251262
[Theory]
252263
[WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)]
253264
[WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)]

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,8 @@ public static class Tiff
605605
public const string Flower24BitGrayLittleEndian = "Tiff/flower-minisblack-24_lsb.tiff";
606606
public const string Flower32BitGray = "Tiff/flower-minisblack-32.tiff";
607607
public const string Flower32BitGrayLittleEndian = "Tiff/flower-minisblack-32_lsb.tiff";
608+
public const string Flower32BitFloatGray = "Tiff/flower-minisblack-float32_msb.tiff";
609+
public const string Flower32BitFloatGrayLittleEndian = "Tiff/flower-minisblack-float32_lsb.tiff";
608610
public const string Flower32BitGrayMinIsWhite = "Tiff/flower-miniswhite-32.tiff";
609611
public const string Flower32BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-32_lsb.tiff";
610612

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:af82e07e6298082c91d60a97acb29b67ecabf386bc14371fcb698b248cfd3b40
3+
size 12814
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:5c918b8aa9968c03c12d85b3bcacd35ae57663a19f5490fc1c351521ed835f30
3+
size 12814

0 commit comments

Comments
 (0)