Skip to content

Commit 7f402b8

Browse files
Allow conversion for CIE Lab and CMYK
1 parent da6d56b commit 7f402b8

15 files changed

Lines changed: 432 additions & 36 deletions
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Buffers;
5+
using System.Numerics;
6+
using System.Runtime.InteropServices;
7+
using SixLabors.ImageSharp.ColorProfiles;
8+
using SixLabors.ImageSharp.ColorProfiles.Icc;
9+
using SixLabors.ImageSharp.Formats.Tiff.Utils;
10+
using SixLabors.ImageSharp.Memory;
11+
using SixLabors.ImageSharp.Metadata;
12+
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
13+
using SixLabors.ImageSharp.PixelFormats;
14+
15+
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
16+
17+
/// <summary>
18+
/// Implements decoding pixel data with photometric interpretation of type 'CieLab' with the planar configuration.
19+
/// Each channel is represented with 16 bits.
20+
/// </summary>
21+
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
22+
internal class CieLab16PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
23+
where TPixel : unmanaged, IPixel<TPixel>
24+
{
25+
private readonly ColorProfileConverter colorProfileConverter;
26+
private readonly Configuration configuration;
27+
private readonly bool isBigEndian;
28+
29+
// libtiff encodes 16-bit Lab as:
30+
// L* : unsigned [0, 65535] mapping to [0, 100]
31+
// a*, b* : signed [-32768, 32767], values are 256x the 1976 a*, b* values.
32+
private const float Inv65535 = 1f / 65535f;
33+
private const float Inv256 = 1f / 256f;
34+
35+
public CieLab16PlanarTiffColor(
36+
Configuration configuration,
37+
DecoderOptions decoderOptions,
38+
ImageFrameMetadata metadata,
39+
MemoryAllocator allocator,
40+
bool isBigEndian)
41+
{
42+
this.isBigEndian = isBigEndian;
43+
this.configuration = configuration;
44+
45+
if (decoderOptions.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile))
46+
{
47+
ColorConversionOptions options = new()
48+
{
49+
SourceIccProfile = iccProfile,
50+
TargetIccProfile = CompactSrgbV4Profile.Profile,
51+
MemoryAllocator = allocator
52+
};
53+
54+
this.colorProfileConverter = new ColorProfileConverter(options);
55+
}
56+
else
57+
{
58+
ColorConversionOptions options = new()
59+
{
60+
MemoryAllocator = allocator
61+
};
62+
63+
this.colorProfileConverter = new ColorProfileConverter(options);
64+
}
65+
}
66+
67+
/// <inheritdoc/>
68+
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
69+
{
70+
Span<byte> lPlane = data[0].GetSpan();
71+
Span<byte> aPlane = data[1].GetSpan();
72+
Span<byte> bPlane = data[2].GetSpan();
73+
74+
// Allocate temporary buffers to hold the LAB -> RGB conversion.
75+
// This should be the maximum width of a row.
76+
using IMemoryOwner<Rgb> rgbBuffer = this.colorProfileConverter.Options.MemoryAllocator.Allocate<Rgb>(width);
77+
using IMemoryOwner<Vector4> vectorBuffer = this.colorProfileConverter.Options.MemoryAllocator.Allocate<Vector4>(width);
78+
79+
Span<Rgb> rgbRow = rgbBuffer.Memory.Span;
80+
Span<Vector4> vectorRow = vectorBuffer.Memory.Span;
81+
82+
// Reuse the rgbRow span for lab data since both are 3-float structs, avoiding an extra allocation.
83+
Span<CieLab> cieLabRow = MemoryMarshal.Cast<Rgb, CieLab>(rgbRow);
84+
85+
int stride = width * 2;
86+
87+
if (this.isBigEndian)
88+
{
89+
for (int y = 0; y < height; y++)
90+
{
91+
int rowBase = y * stride;
92+
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(top + y).Slice(left, width);
93+
94+
for (int x = 0; x < width; x++)
95+
{
96+
int i = rowBase + (x * 2);
97+
98+
ushort lRaw = TiffUtilities.ConvertToUShortBigEndian(lPlane.Slice(i, 2));
99+
short aRaw = unchecked((short)TiffUtilities.ConvertToUShortBigEndian(aPlane.Slice(i, 2)));
100+
short bRaw = unchecked((short)TiffUtilities.ConvertToUShortBigEndian(bPlane.Slice(i, 2)));
101+
102+
float l = lRaw * 100f * Inv65535;
103+
float a = aRaw * Inv256;
104+
float b = bRaw * Inv256;
105+
106+
cieLabRow[x] = new CieLab(l, a, b);
107+
}
108+
109+
// Convert CIE Lab -> Rgb -> Vector4 -> TPixel
110+
this.colorProfileConverter.Convert<CieLab, Rgb>(cieLabRow, rgbRow);
111+
Rgb.ToScaledVector4(rgbRow, vectorRow);
112+
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale);
113+
}
114+
115+
return;
116+
}
117+
118+
for (int y = 0; y < height; y++)
119+
{
120+
int rowBase = y * stride;
121+
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(top + y).Slice(left, width);
122+
123+
for (int x = 0; x < width; x++)
124+
{
125+
int i = rowBase + (x * 2);
126+
127+
ushort lRaw = TiffUtilities.ConvertToUShortLittleEndian(lPlane.Slice(i, 2));
128+
short aRaw = unchecked((short)TiffUtilities.ConvertToUShortLittleEndian(aPlane.Slice(i, 2)));
129+
short bRaw = unchecked((short)TiffUtilities.ConvertToUShortLittleEndian(bPlane.Slice(i, 2)));
130+
131+
float l = lRaw * 100f * Inv65535;
132+
float a = aRaw * Inv256;
133+
float b = bRaw * Inv256;
134+
135+
cieLabRow[x] = new CieLab(l, a, b);
136+
}
137+
138+
// Convert CIE Lab -> Rgb -> Vector4 -> TPixel
139+
this.colorProfileConverter.Convert<CieLab, Rgb>(cieLabRow, rgbRow);
140+
Rgb.ToScaledVector4(rgbRow, vectorRow);
141+
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale);
142+
}
143+
}
144+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Buffers;
5+
using System.Numerics;
6+
using System.Runtime.InteropServices;
7+
using SixLabors.ImageSharp.ColorProfiles;
8+
using SixLabors.ImageSharp.ColorProfiles.Icc;
9+
using SixLabors.ImageSharp.Formats.Tiff.Utils;
10+
using SixLabors.ImageSharp.Memory;
11+
using SixLabors.ImageSharp.Metadata;
12+
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
13+
using SixLabors.ImageSharp.PixelFormats;
14+
15+
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
16+
17+
/// <summary>
18+
/// Implements decoding pixel data with photometric interpretation of type 'CieLab'.
19+
/// Each channel is represented with 16 bits.
20+
/// </summary>
21+
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
22+
internal class CieLab16TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
23+
where TPixel : unmanaged, IPixel<TPixel>
24+
{
25+
private readonly ColorProfileConverter colorProfileConverter;
26+
private readonly Configuration configuration;
27+
private readonly bool isBigEndian;
28+
29+
// libtiff encodes 16-bit Lab as:
30+
// L* : unsigned [0, 65535] mapping to [0, 100]
31+
// a*, b* : signed [-32768, 32767], values are 256x the 1976 a*, b* values.
32+
private const float Inv65535 = 1f / 65535f;
33+
private const float Inv256 = 1f / 256f;
34+
35+
public CieLab16TiffColor(
36+
Configuration configuration,
37+
DecoderOptions decoderOptions,
38+
ImageFrameMetadata metadata,
39+
MemoryAllocator allocator,
40+
bool isBigEndian)
41+
{
42+
this.isBigEndian = isBigEndian;
43+
this.configuration = configuration;
44+
45+
if (decoderOptions.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile))
46+
{
47+
ColorConversionOptions options = new()
48+
{
49+
SourceIccProfile = iccProfile,
50+
TargetIccProfile = CompactSrgbV4Profile.Profile,
51+
MemoryAllocator = allocator
52+
};
53+
54+
this.colorProfileConverter = new ColorProfileConverter(options);
55+
}
56+
else
57+
{
58+
ColorConversionOptions options = new()
59+
{
60+
MemoryAllocator = allocator
61+
};
62+
63+
this.colorProfileConverter = new ColorProfileConverter(options);
64+
}
65+
}
66+
67+
/// <inheritdoc/>
68+
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
69+
{
70+
int offset = 0;
71+
72+
// Allocate temporary buffers to hold the LAB -> RGB conversion.
73+
// This should be the maximum width of a row.
74+
using IMemoryOwner<Rgb> rgbBuffer = this.colorProfileConverter.Options.MemoryAllocator.Allocate<Rgb>(width);
75+
using IMemoryOwner<Vector4> vectorBuffer = this.colorProfileConverter.Options.MemoryAllocator.Allocate<Vector4>(width);
76+
77+
Span<Rgb> rgbRow = rgbBuffer.Memory.Span;
78+
Span<Vector4> vectorRow = vectorBuffer.Memory.Span;
79+
80+
// Reuse the rgbRow span for lab data since both are 3-float structs, avoiding an extra allocation.
81+
Span<CieLab> cieLabRow = MemoryMarshal.Cast<Rgb, CieLab>(rgbRow);
82+
83+
if (this.isBigEndian)
84+
{
85+
for (int y = top; y < top + height; y++)
86+
{
87+
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
88+
89+
for (int x = 0; x < pixelRow.Length; x++)
90+
{
91+
ushort lRaw = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2));
92+
offset += 2;
93+
short aRaw = unchecked((short)TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)));
94+
offset += 2;
95+
short bRaw = unchecked((short)TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)));
96+
offset += 2;
97+
98+
float l = lRaw * 100f * Inv65535;
99+
float a = aRaw * Inv256;
100+
float b = bRaw * Inv256;
101+
102+
cieLabRow[x] = new CieLab(l, a, b);
103+
}
104+
105+
// Convert CIE Lab -> Rgb -> Vector4 -> TPixel
106+
this.colorProfileConverter.Convert<CieLab, Rgb>(cieLabRow, rgbRow);
107+
Rgb.ToScaledVector4(rgbRow, vectorRow);
108+
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale);
109+
}
110+
111+
return;
112+
}
113+
114+
for (int y = top; y < top + height; y++)
115+
{
116+
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
117+
118+
for (int x = 0; x < pixelRow.Length; x++)
119+
{
120+
ushort lRaw = TiffUtilities.ConvertToUShortLittleEndian(data.Slice(offset, 2));
121+
offset += 2;
122+
short aRaw = unchecked((short)TiffUtilities.ConvertToUShortLittleEndian(data.Slice(offset, 2)));
123+
offset += 2;
124+
short bRaw = unchecked((short)TiffUtilities.ConvertToUShortLittleEndian(data.Slice(offset, 2)));
125+
offset += 2;
126+
127+
float l = lRaw * 100f * Inv65535;
128+
float a = aRaw * Inv256;
129+
float b = bRaw * Inv256;
130+
131+
cieLabRow[x] = new CieLab(l, a, b);
132+
}
133+
134+
// Convert CIE Lab -> Rgb -> Vector4 -> TPixel
135+
this.colorProfileConverter.Convert<CieLab, Rgb>(cieLabRow, rgbRow);
136+
Rgb.ToScaledVector4(rgbRow, vectorRow);
137+
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale);
138+
}
139+
}
140+
}

src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs renamed to src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8PlanarTiffColor{TPixel}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
1313
/// Implements decoding pixel data with photometric interpretation of type 'CieLab' with the planar configuration.
1414
/// </summary>
1515
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
16-
internal class CieLabPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
16+
internal class CieLab8PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
1717
where TPixel : unmanaged, IPixel<TPixel>
1818
{
1919
private static readonly ColorProfileConverter ColorProfileConverter = new();

src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs renamed to src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8TiffColor{TPixel}.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
1010

1111
/// <summary>
1212
/// Implements decoding pixel data with photometric interpretation of type 'CieLab'.
13+
/// Each channel is represented with 8 bits.
1314
/// </summary>
1415
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
15-
internal class CieLabTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
16+
internal class CieLab8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
1617
where TPixel : unmanaged, IPixel<TPixel>
1718
{
1819
private static readonly ColorProfileConverter ColorProfileConverter = new();
1920
private const float Inv255 = 1f / 255f;
2021

22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="CieLab8TiffColor{TPixel}" /> class.
24+
/// </summary>
25+
public CieLab8TiffColor()
26+
{
27+
}
28+
2129
/// <inheritdoc/>
2230
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
2331
{

0 commit comments

Comments
 (0)