Skip to content

Commit 6a388c8

Browse files
authored
Merge pull request #1724 from SixLabors/bp/tiff24bit
Add support for decoding 24 and 32 bit tiff images
2 parents 566babf + 51e1870 commit 6a388c8

32 files changed

+900
-39
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
4646
{
4747
for (int x = 0; x < pixelRow.Length; x++)
4848
{
49-
ushort intensity = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2));
49+
ushort intensity = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
5050
offset += 2;
5151

5252
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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 24-bit grayscale images.
13+
/// </summary>
14+
internal class BlackIsZero24TiffColor<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="BlackIsZero24TiffColor{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 BlackIsZero24TiffColor(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+
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
29+
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
30+
var color = default(TPixel);
31+
color.FromVector4(TiffUtils.Vector4Default);
32+
byte[] buffer = new byte[4];
33+
int bufferStartIdx = this.isBigEndian ? 1 : 0;
34+
35+
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
36+
int offset = 0;
37+
for (int y = top; y < top + height; y++)
38+
{
39+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
40+
if (this.isBigEndian)
41+
{
42+
for (int x = 0; x < pixelRow.Length; x++)
43+
{
44+
data.Slice(offset, 3).CopyTo(bufferSpan);
45+
ulong intensity = TiffUtils.ConvertToUIntBigEndian(buffer);
46+
offset += 3;
47+
48+
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color);
49+
}
50+
}
51+
else
52+
{
53+
for (int x = 0; x < pixelRow.Length; x++)
54+
{
55+
data.Slice(offset, 3).CopyTo(bufferSpan);
56+
ulong intensity = TiffUtils.ConvertToUIntLittleEndian(buffer);
57+
offset += 3;
58+
59+
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color);
60+
}
61+
}
62+
}
63+
}
64+
}
65+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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 grayscale images.
14+
/// </summary>
15+
internal class BlackIsZero32TiffColor<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="BlackIsZero32TiffColor{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 BlackIsZero32TiffColor(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+
34+
int offset = 0;
35+
for (int y = top; y < top + height; y++)
36+
{
37+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
38+
if (this.isBigEndian)
39+
{
40+
for (int x = 0; x < pixelRow.Length; x++)
41+
{
42+
ulong intensity = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
43+
offset += 4;
44+
45+
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color);
46+
}
47+
}
48+
else
49+
{
50+
for (int x = 0; x < pixelRow.Length; x++)
51+
{
52+
ulong intensity = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
53+
offset += 4;
54+
55+
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color);
56+
}
57+
}
58+
}
59+
}
60+
}
61+
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
4848
{
4949
for (int x = 0; x < pixelRow.Length; x++)
5050
{
51-
ulong r = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2));
51+
ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
5252
offset += 2;
53-
ulong g = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2));
53+
ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
5454
offset += 2;
55-
ulong b = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2));
55+
ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
5656
offset += 2;
5757

5858
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
1111
{
1212
/// <summary>
13-
/// Implements the 'RGB' photometric interpretation with 'Planar' layout for all 16 bit.
13+
/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 16 bit.
1414
/// </summary>
1515
internal class Rgb16PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
1616
where TPixel : unmanaged, IPixel<TPixel>
@@ -44,9 +44,9 @@ public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels,
4444
{
4545
for (int x = 0; x < pixelRow.Length; x++)
4646
{
47-
ulong r = TiffUtils.ConvertToShortBigEndian(redData.Slice(offset, 2));
48-
ulong g = TiffUtils.ConvertToShortBigEndian(greenData.Slice(offset, 2));
49-
ulong b = TiffUtils.ConvertToShortBigEndian(blueData.Slice(offset, 2));
47+
ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2));
48+
ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2));
49+
ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2));
5050

5151
offset += 2;
5252

@@ -57,9 +57,9 @@ public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels,
5757
{
5858
for (int x = 0; x < pixelRow.Length; x++)
5959
{
60-
ulong r = TiffUtils.ConvertToShortLittleEndian(redData.Slice(offset, 2));
61-
ulong g = TiffUtils.ConvertToShortLittleEndian(greenData.Slice(offset, 2));
62-
ulong b = TiffUtils.ConvertToShortLittleEndian(blueData.Slice(offset, 2));
60+
ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2));
61+
ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2));
62+
ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2));
6363

6464
offset += 2;
6565

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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 'RGB' photometric interpretation with 24 bits for each channel.
13+
/// </summary>
14+
internal class Rgb242424TiffColor<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="Rgb242424TiffColor{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 Rgb242424TiffColor(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+
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
29+
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
30+
var color = default(TPixel);
31+
color.FromVector4(TiffUtils.Vector4Default);
32+
int offset = 0;
33+
byte[] buffer = new byte[4];
34+
int bufferStartIdx = this.isBigEndian ? 1 : 0;
35+
36+
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
37+
for (int y = top; y < top + height; y++)
38+
{
39+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
40+
41+
if (this.isBigEndian)
42+
{
43+
for (int x = 0; x < pixelRow.Length; x++)
44+
{
45+
data.Slice(offset, 3).CopyTo(bufferSpan);
46+
ulong r = TiffUtils.ConvertToUIntBigEndian(buffer);
47+
offset += 3;
48+
49+
data.Slice(offset, 3).CopyTo(bufferSpan);
50+
ulong g = TiffUtils.ConvertToUIntBigEndian(buffer);
51+
offset += 3;
52+
53+
data.Slice(offset, 3).CopyTo(bufferSpan);
54+
ulong b = TiffUtils.ConvertToUIntBigEndian(buffer);
55+
offset += 3;
56+
57+
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color);
58+
}
59+
}
60+
else
61+
{
62+
for (int x = 0; x < pixelRow.Length; x++)
63+
{
64+
data.Slice(offset, 3).CopyTo(bufferSpan);
65+
ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer);
66+
offset += 3;
67+
68+
data.Slice(offset, 3).CopyTo(bufferSpan);
69+
ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer);
70+
offset += 3;
71+
72+
data.Slice(offset, 3).CopyTo(bufferSpan);
73+
ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer);
74+
offset += 3;
75+
76+
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color);
77+
}
78+
}
79+
}
80+
}
81+
}
82+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Buffers;
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 'Planar' layout for each color channel with 24 bit.
14+
/// </summary>
15+
internal class Rgb24PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<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="Rgb24PlanarTiffColor{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 Rgb24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
25+
26+
/// <inheritdoc/>
27+
public override void Decode(IMemoryOwner<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+
int bufferStartIdx = this.isBigEndian ? 1 : 0;
35+
36+
Span<byte> redData = data[0].GetSpan();
37+
Span<byte> greenData = data[1].GetSpan();
38+
Span<byte> blueData = data[2].GetSpan();
39+
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
40+
41+
int offset = 0;
42+
for (int y = top; y < top + height; y++)
43+
{
44+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
45+
if (this.isBigEndian)
46+
{
47+
for (int x = 0; x < pixelRow.Length; x++)
48+
{
49+
redData.Slice(offset, 3).CopyTo(bufferSpan);
50+
ulong r = TiffUtils.ConvertToUIntBigEndian(buffer);
51+
greenData.Slice(offset, 3).CopyTo(bufferSpan);
52+
ulong g = TiffUtils.ConvertToUIntBigEndian(buffer);
53+
blueData.Slice(offset, 3).CopyTo(bufferSpan);
54+
ulong b = TiffUtils.ConvertToUIntBigEndian(buffer);
55+
56+
offset += 3;
57+
58+
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color);
59+
}
60+
}
61+
else
62+
{
63+
for (int x = 0; x < pixelRow.Length; x++)
64+
{
65+
redData.Slice(offset, 3).CopyTo(bufferSpan);
66+
ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer);
67+
greenData.Slice(offset, 3).CopyTo(bufferSpan);
68+
ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer);
69+
blueData.Slice(offset, 3).CopyTo(bufferSpan);
70+
ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer);
71+
72+
offset += 3;
73+
74+
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color);
75+
}
76+
}
77+
}
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)