Skip to content

Commit 29320a6

Browse files
Merge pull request #2127 from SixLabors/bp/cielab
Add support for decoding tiff images with CieLab color space
2 parents e29a9e8 + a258c47 commit 29320a6

17 files changed

Lines changed: 188 additions & 3 deletions

src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public static void Undo(Span<byte> pixelBytes, int width, TiffColorType colorTyp
4141
UndoGray32Bit(pixelBytes, width, isBigEndian);
4242
break;
4343
case TiffColorType.Rgb888:
44+
case TiffColorType.CieLab:
4445
UndoRgb24Bit(pixelBytes, width);
4546
break;
4647
case TiffColorType.Rgba8888:
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Numerics;
7+
using SixLabors.ImageSharp.ColorSpaces;
8+
using SixLabors.ImageSharp.ColorSpaces.Conversion;
9+
using SixLabors.ImageSharp.Memory;
10+
using SixLabors.ImageSharp.PixelFormats;
11+
12+
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
13+
{
14+
/// <summary>
15+
/// Implements decoding pixel data with photometric interpretation of type 'CieLab' with the planar configuration.
16+
/// </summary>
17+
internal class CieLabPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
18+
where TPixel : unmanaged, IPixel<TPixel>
19+
{
20+
private static readonly ColorSpaceConverter ColorSpaceConverter = new();
21+
22+
private const float Inv255 = 1.0f / 255.0f;
23+
24+
/// <inheritdoc/>
25+
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
26+
{
27+
Span<byte> l = data[0].GetSpan();
28+
Span<byte> a = data[1].GetSpan();
29+
Span<byte> b = data[2].GetSpan();
30+
31+
var color = default(TPixel);
32+
int offset = 0;
33+
for (int y = top; y < top + height; y++)
34+
{
35+
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
36+
for (int x = 0; x < pixelRow.Length; x++)
37+
{
38+
var lab = new CieLab((l[offset] & 0xFF) * 100f * Inv255, (sbyte)a[offset], (sbyte)b[offset]);
39+
var rgb = ColorSpaceConverter.ToRgb(lab);
40+
41+
color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f));
42+
pixelRow[x] = color;
43+
44+
offset++;
45+
}
46+
}
47+
}
48+
}
49+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System;
5+
using System.Numerics;
6+
using SixLabors.ImageSharp.ColorSpaces;
7+
using SixLabors.ImageSharp.ColorSpaces.Conversion;
8+
using SixLabors.ImageSharp.Memory;
9+
using SixLabors.ImageSharp.PixelFormats;
10+
11+
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
12+
{
13+
/// <summary>
14+
/// Implements decoding pixel data with photometric interpretation of type 'CieLab'.
15+
/// </summary>
16+
internal class CieLabTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
17+
where TPixel : unmanaged, IPixel<TPixel>
18+
{
19+
private static readonly ColorSpaceConverter ColorSpaceConverter = new();
20+
21+
private const float Inv255 = 1.0f / 255.0f;
22+
23+
/// <inheritdoc/>
24+
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
25+
{
26+
var color = default(TPixel);
27+
int offset = 0;
28+
for (int y = top; y < top + height; y++)
29+
{
30+
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
31+
32+
for (int x = 0; x < pixelRow.Length; x++)
33+
{
34+
float l = (data[offset] & 0xFF) * 100f * Inv255;
35+
var lab = new CieLab(l, (sbyte)data[offset + 1], (sbyte)data[offset + 2]);
36+
var rgb = ColorSpaceConverter.ToRgb(lab);
37+
38+
color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f));
39+
pixelRow[x] = color;
40+
41+
offset += 3;
42+
}
43+
}
44+
}
45+
}
46+
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,23 @@ public static TiffBaseColorDecoder<TPixel> Create(
385385
return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap);
386386

387387
case TiffColorType.YCbCr:
388+
DebugGuard.IsTrue(
389+
bitsPerSample.Channels == 3
390+
&& bitsPerSample.Channel2 == 8
391+
&& bitsPerSample.Channel1 == 8
392+
&& bitsPerSample.Channel0 == 8,
393+
"bitsPerSample");
388394
return new YCbCrTiffColor<TPixel>(memoryAllocator, referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling);
389395

396+
case TiffColorType.CieLab:
397+
DebugGuard.IsTrue(
398+
bitsPerSample.Channels == 3
399+
&& bitsPerSample.Channel2 == 8
400+
&& bitsPerSample.Channel1 == 8
401+
&& bitsPerSample.Channel0 == 8,
402+
"bitsPerSample");
403+
return new CieLabTiffColor<TPixel>();
404+
390405
default:
391406
throw TiffThrowHelper.InvalidColorType(colorType.ToString());
392407
}
@@ -415,6 +430,9 @@ public static TiffBasePlanarColorDecoder<TPixel> CreatePlanar(
415430
case TiffColorType.YCbCrPlanar:
416431
return new YCbCrPlanarTiffColor<TPixel>(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling);
417432

433+
case TiffColorType.CieLabPlanar:
434+
return new CieLabPlanarTiffColor<TPixel>();
435+
418436
case TiffColorType.Rgb161616Planar:
419437
DebugGuard.IsTrue(colorMap == null, "colorMap");
420438
return new Rgb16PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,16 @@ internal enum TiffColorType
276276
/// <summary>
277277
/// The pixels are stored in YCbCr format as planar.
278278
/// </summary>
279-
YCbCrPlanar
279+
YCbCrPlanar,
280+
281+
/// <summary>
282+
/// The pixels are stored in CieLab format.
283+
/// </summary>
284+
CieLab,
285+
286+
/// <summary>
287+
/// The pixels are stored in CieLab format as planar.
288+
/// </summary>
289+
CieLabPlanar,
280290
}
281291
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
1111
{
12+
/// <summary>
13+
/// Implements decoding pixel data with photometric interpretation of type 'YCbCr' with the planar configuration.
14+
/// </summary>
1215
internal class YCbCrPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
1316
where TPixel : unmanaged, IPixel<TPixel>
1417
{

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
1111
{
12+
/// <summary>
13+
/// Implements decoding pixel data with photometric interpretation of type 'YCbCr'.
14+
/// </summary>
1215
internal class YCbCrTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
1316
where TPixel : unmanaged, IPixel<TPixel>
1417
{

src/ImageSharp/Formats/Tiff/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
|TransparencyMask | | | |
5656
|Separated (TIFF Extension) | | | |
5757
|YCbCr (TIFF Extension) | | Y | |
58-
|CieLab (TIFF Extension) | | | |
58+
|CieLab (TIFF Extension) | | Y | |
5959
|IccLab (TechNote 1) | | | |
6060

6161
### Baseline TIFF Tags

src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi
381381
options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value;
382382
if (options.BitsPerSample.Channels != 3)
383383
{
384-
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
384+
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for YCbCr images.");
385385
}
386386

387387
ushort bitsPerChannel = options.BitsPerSample.Channel0;
@@ -395,6 +395,24 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi
395395
break;
396396
}
397397

398+
case TiffPhotometricInterpretation.CieLab:
399+
{
400+
if (options.BitsPerSample.Channels != 3)
401+
{
402+
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for CieLab images.");
403+
}
404+
405+
ushort bitsPerChannel = options.BitsPerSample.Channel0;
406+
if (bitsPerChannel != 8)
407+
{
408+
TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for CieLab images.");
409+
}
410+
411+
options.ColorType = options.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? TiffColorType.CieLab : TiffColorType.CieLabPlanar;
412+
413+
break;
414+
}
415+
398416
default:
399417
{
400418
TiffThrowHelper.ThrowNotSupported($"The specified TIFF photometric interpretation is not supported: {options.PhotometricInterpretation}");

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,20 @@ public void TiffDecoder_CanDecode_YCbCr_24Bit<TPixel>(TestImageProvider<TPixel>
318318
image.CompareToReferenceOutput(ImageComparer.Exact, provider);
319319
}
320320

321+
[Theory]
322+
[WithFile(CieLab, PixelTypes.Rgba32)]
323+
[WithFile(CieLabPlanar, PixelTypes.Rgba32)]
324+
[WithFile(CieLabLzwPredictor, PixelTypes.Rgba32)]
325+
public void TiffDecoder_CanDecode_CieLab<TPixel>(TestImageProvider<TPixel> provider)
326+
where TPixel : unmanaged, IPixel<TPixel>
327+
{
328+
// Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong
329+
// converting the pixel data from Magick.NET to our format with CieLab?
330+
using Image<TPixel> image = provider.GetImage();
331+
image.DebugSave(provider);
332+
image.CompareToReferenceOutput(ImageComparer.Exact, provider);
333+
}
334+
321335
[Theory]
322336
[WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)]
323337
[WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)]

0 commit comments

Comments
 (0)