Skip to content

Commit c084b5f

Browse files
authored
Merge pull request #1720 from SixLabors/bp/tiff16bitgray
Respect byte order with 16 bit tiff images
2 parents 0701021 + f868b7a commit c084b5f

25 files changed

+402
-47
lines changed
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 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+
// 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+
L16 l16 = TiffUtils.L16Default;
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+
ushort intensity = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2));
43+
offset += 2;
44+
45+
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
46+
}
47+
}
48+
else
49+
{
50+
for (int x = 0; x < pixelRow.Length; x++)
51+
{
52+
ushort intensity = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2));
53+
offset += 2;
54+
55+
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
56+
}
57+
}
58+
}
59+
}
60+
}
61+
}

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5-
5+
using SixLabors.ImageSharp.Formats.Tiff.Utils;
66
using SixLabors.ImageSharp.Memory;
77
using SixLabors.ImageSharp.PixelFormats;
88

@@ -24,14 +24,11 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
2424
var l8 = default(L8);
2525
for (int y = top; y < top + height; y++)
2626
{
27-
for (int x = left; x < left + width; x++)
27+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
28+
for (int x = 0; x < pixelRow.Length; x++)
2829
{
2930
byte intensity = data[offset++];
30-
31-
l8.PackedValue = intensity;
32-
color.FromL8(l8);
33-
34-
pixels[x, y] = color;
31+
pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color);
3532
}
3633
}
3734
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
3434

3535
for (int y = top; y < top + height; y++)
3636
{
37-
for (int x = left; x < left + width; x++)
37+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
38+
for (int x = 0; x < pixelRow.Length; x++)
3839
{
3940
int value = bitReader.ReadBits(this.bitsPerSample0);
4041
float intensity = value / this.factor;
4142

4243
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f));
43-
pixels[x, y] = color;
44+
pixelRow[x] = color;
4445
}
4546

4647
bitReader.NextRow();

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
3535

3636
for (int y = top; y < top + height; y++)
3737
{
38-
for (int x = left; x < left + width; x++)
38+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
39+
for (int x = 0; x < pixelRow.Length; x++)
3940
{
4041
int index = bitReader.ReadBits(this.bitsPerSample0);
41-
pixels[x, y] = this.palette[index];
42+
pixelRow[x] = this.palette[index];
4243
}
4344

4445
bitReader.NextRow();

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

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5-
using System.Buffers.Binary;
5+
using SixLabors.ImageSharp.Formats.Tiff.Utils;
66
using SixLabors.ImageSharp.Memory;
77
using SixLabors.ImageSharp.PixelFormats;
88

@@ -25,34 +25,47 @@ internal class Rgb161616TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
2525
/// <inheritdoc/>
2626
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
2727
{
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+
Rgba64 rgba = TiffUtils.Rgba64Default;
2831
var color = default(TPixel);
32+
color.FromVector4(TiffUtils.Vector4Default);
2933

3034
int offset = 0;
3135

32-
var rgba = default(Rgba64);
3336
for (int y = top; y < top + height; y++)
3437
{
35-
Span<TPixel> pixelRow = pixels.GetRowSpan(y);
38+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
3639

37-
for (int x = left; x < left + width; x++)
40+
if (this.isBigEndian)
3841
{
39-
ulong r = this.ConvertToShort(data.Slice(offset, 2));
40-
offset += 2;
41-
ulong g = this.ConvertToShort(data.Slice(offset, 2));
42-
offset += 2;
43-
ulong b = this.ConvertToShort(data.Slice(offset, 2));
44-
offset += 2;
42+
for (int x = 0; x < pixelRow.Length; x++)
43+
{
44+
ulong r = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2));
45+
offset += 2;
46+
ulong g = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2));
47+
offset += 2;
48+
ulong b = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2));
49+
offset += 2;
4550

46-
rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48);
47-
color.FromRgba64(rgba);
51+
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
52+
}
53+
}
54+
else
55+
{
56+
for (int x = 0; x < pixelRow.Length; x++)
57+
{
58+
ulong r = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2));
59+
offset += 2;
60+
ulong g = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2));
61+
offset += 2;
62+
ulong b = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2));
63+
offset += 2;
4864

49-
pixelRow[x] = color;
65+
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
66+
}
5067
}
5168
}
5269
}
53-
54-
private ushort ConvertToShort(ReadOnlySpan<byte> buffer) => this.isBigEndian
55-
? BinaryPrimitives.ReadUInt16BigEndian(buffer)
56-
: BinaryPrimitives.ReadUInt16LittleEndian(buffer);
5770
}
5871
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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 all 16 bit.
14+
/// </summary>
15+
internal class Rgb16PlanarTiffColor<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="Rgb16PlanarTiffColor{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 Rgb16PlanarTiffColor(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+
Rgba64 rgba = TiffUtils.Rgba64Default;
32+
var color = default(TPixel);
33+
color.FromVector4(TiffUtils.Vector4Default);
34+
35+
Span<byte> redData = data[0].GetSpan();
36+
Span<byte> greenData = data[1].GetSpan();
37+
Span<byte> blueData = data[2].GetSpan();
38+
39+
int offset = 0;
40+
for (int y = top; y < top + height; y++)
41+
{
42+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
43+
if (this.isBigEndian)
44+
{
45+
for (int x = 0; x < pixelRow.Length; x++)
46+
{
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));
50+
51+
offset += 2;
52+
53+
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
54+
}
55+
}
56+
else
57+
{
58+
for (int x = 0; x < pixelRow.Length; x++)
59+
{
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));
63+
64+
offset += 2;
65+
66+
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
67+
}
68+
}
69+
}
70+
}
71+
}
72+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
2424
var rgba = default(Rgba32);
2525
for (int y = top; y < top + height; y++)
2626
{
27-
Span<TPixel> pixelRow = pixels.GetRowSpan(y);
27+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
2828

29-
for (int x = left; x < left + width; x++)
29+
for (int x = 0; x < pixelRow.Length; x++)
3030
{
3131
byte r = data[offset++];
3232
byte g = data[offset++];

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using System;
45
using System.Buffers;
56
using System.Numerics;
67
using SixLabors.ImageSharp.Formats.Tiff.Utils;
@@ -12,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
1213
/// <summary>
1314
/// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths).
1415
/// </summary>
15-
internal class RgbPlanarTiffColor<TPixel>
16+
internal class RgbPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
1617
where TPixel : unmanaged, IPixel<TPixel>
1718
{
1819
private readonly float rFactor;
@@ -47,7 +48,7 @@ public RgbPlanarTiffColor(TiffBitsPerSample bitsPerSample)
4748
/// <param name="top">The y-coordinate of the top of the image block.</param>
4849
/// <param name="width">The width of the image block.</param>
4950
/// <param name="height">The height of the image block.</param>
50-
public void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
51+
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
5152
{
5253
var color = default(TPixel);
5354

@@ -57,14 +58,15 @@ public void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left,
5758

5859
for (int y = top; y < top + height; y++)
5960
{
60-
for (int x = left; x < left + width; x++)
61+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
62+
for (int x = 0; x < pixelRow.Length; x++)
6163
{
6264
float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor;
6365
float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
6466
float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
6567

6668
color.FromVector4(new Vector4(r, g, b, 1.0f));
67-
pixels[x, y] = color;
69+
pixelRow[x] = color;
6870
}
6971

7072
rBitReader.NextRow();

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@ public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, in
4747

4848
for (int y = top; y < top + height; y++)
4949
{
50-
for (int x = left; x < left + width; x++)
50+
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
51+
for (int x = 0; x < pixelRow.Length; x++)
5152
{
5253
float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor;
5354
float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
5455
float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
5556

5657
color.FromVector4(new Vector4(r, g, b, 1.0f));
57-
pixels[x, y] = color;
58+
pixelRow[x] = color;
5859
}
5960

6061
bitReader.NextRow();
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.Buffers;
5+
using SixLabors.ImageSharp.Memory;
6+
using SixLabors.ImageSharp.PixelFormats;
7+
8+
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
9+
{
10+
/// <summary>
11+
/// The base class for planar color decoders.
12+
/// </summary>
13+
/// <typeparam name="TPixel">The pixel format.</typeparam>
14+
internal abstract class TiffBasePlanarColorDecoder<TPixel>
15+
where TPixel : unmanaged, IPixel<TPixel>
16+
{
17+
/// <summary>
18+
/// Decodes source raw pixel data using the current photometric interpretation.
19+
/// </summary>
20+
/// <param name="data">The buffers to read image data from.</param>
21+
/// <param name="pixels">The image buffer to write pixels to.</param>
22+
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
23+
/// <param name="top">The y-coordinate of the top of the image block.</param>
24+
/// <param name="width">The width of the image block.</param>
25+
/// <param name="height">The height of the image block.</param>
26+
public abstract void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height);
27+
}
28+
}

0 commit comments

Comments
 (0)