diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
index f6ee06db66..ab8f51844c 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
@@ -41,6 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
UndoGray32Bit(pixelBytes, width, isBigEndian);
break;
case TiffColorType.Rgb888:
+ case TiffColorType.CieLab:
UndoRgb24Bit(pixelBytes, width);
break;
case TiffColorType.Rgba8888:
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs
new file mode 100644
index 0000000000..3baf60e786
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System;
+using System.Buffers;
+using System.Numerics;
+using SixLabors.ImageSharp.ColorSpaces;
+using SixLabors.ImageSharp.ColorSpaces.Conversion;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
+{
+ ///
+ /// Implements decoding pixel data with photometric interpretation of type 'CieLab' with the planar configuration.
+ ///
+ internal class CieLabPlanarTiffColor : TiffBasePlanarColorDecoder
+ where TPixel : unmanaged, IPixel
+ {
+ private static readonly ColorSpaceConverter ColorSpaceConverter = new();
+
+ private const float Inv255 = 1.0f / 255.0f;
+
+ ///
+ public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height)
+ {
+ Span l = data[0].GetSpan();
+ Span a = data[1].GetSpan();
+ Span b = data[2].GetSpan();
+
+ var color = default(TPixel);
+ int offset = 0;
+ for (int y = top; y < top + height; y++)
+ {
+ Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
+ for (int x = 0; x < pixelRow.Length; x++)
+ {
+ var lab = new CieLab((l[offset] & 0xFF) * 100f * Inv255, (sbyte)a[offset], (sbyte)b[offset]);
+ var rgb = ColorSpaceConverter.ToRgb(lab);
+
+ color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f));
+ pixelRow[x] = color;
+
+ offset++;
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs
new file mode 100644
index 0000000000..5b272b2be5
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System;
+using System.Numerics;
+using SixLabors.ImageSharp.ColorSpaces;
+using SixLabors.ImageSharp.ColorSpaces.Conversion;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
+{
+ ///
+ /// Implements decoding pixel data with photometric interpretation of type 'CieLab'.
+ ///
+ internal class CieLabTiffColor : TiffBaseColorDecoder
+ where TPixel : unmanaged, IPixel
+ {
+ private static readonly ColorSpaceConverter ColorSpaceConverter = new();
+
+ private const float Inv255 = 1.0f / 255.0f;
+
+ ///
+ public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height)
+ {
+ var color = default(TPixel);
+ int offset = 0;
+ for (int y = top; y < top + height; y++)
+ {
+ Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
+
+ for (int x = 0; x < pixelRow.Length; x++)
+ {
+ float l = (data[offset] & 0xFF) * 100f * Inv255;
+ var lab = new CieLab(l, (sbyte)data[offset + 1], (sbyte)data[offset + 2]);
+ var rgb = ColorSpaceConverter.ToRgb(lab);
+
+ color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f));
+ pixelRow[x] = color;
+
+ offset += 3;
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
index 5c1fba5ef4..2ef46dd84e 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
@@ -385,8 +385,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
return new PaletteTiffColor(bitsPerSample, colorMap);
case TiffColorType.YCbCr:
+ DebugGuard.IsTrue(
+ bitsPerSample.Channels == 3
+ && bitsPerSample.Channel2 == 8
+ && bitsPerSample.Channel1 == 8
+ && bitsPerSample.Channel0 == 8,
+ "bitsPerSample");
return new YCbCrTiffColor(memoryAllocator, referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling);
+ case TiffColorType.CieLab:
+ DebugGuard.IsTrue(
+ bitsPerSample.Channels == 3
+ && bitsPerSample.Channel2 == 8
+ && bitsPerSample.Channel1 == 8
+ && bitsPerSample.Channel0 == 8,
+ "bitsPerSample");
+ return new CieLabTiffColor();
+
default:
throw TiffThrowHelper.InvalidColorType(colorType.ToString());
}
@@ -415,6 +430,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.YCbCrPlanar:
return new YCbCrPlanarTiffColor(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling);
+ case TiffColorType.CieLabPlanar:
+ return new CieLabPlanarTiffColor();
+
case TiffColorType.Rgb161616Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb16PlanarTiffColor(byteOrder == ByteOrder.BigEndian);
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
index 619bfab4d6..cce5c95ad5 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
@@ -276,6 +276,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
///
/// The pixels are stored in YCbCr format as planar.
///
- YCbCrPlanar
+ YCbCrPlanar,
+
+ ///
+ /// The pixels are stored in CieLab format.
+ ///
+ CieLab,
+
+ ///
+ /// The pixels are stored in CieLab format as planar.
+ ///
+ CieLabPlanar,
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs
index c53b755439..6abec16fa2 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs
@@ -9,6 +9,9 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
+ ///
+ /// Implements decoding pixel data with photometric interpretation of type 'YCbCr' with the planar configuration.
+ ///
internal class YCbCrPlanarTiffColor : TiffBasePlanarColorDecoder
where TPixel : unmanaged, IPixel
{
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs
index a850c67872..f4ff75c15e 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs
@@ -9,6 +9,9 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
+ ///
+ /// Implements decoding pixel data with photometric interpretation of type 'YCbCr'.
+ ///
internal class YCbCrTiffColor : TiffBaseColorDecoder
where TPixel : unmanaged, IPixel
{
diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md
index 8cb327a7b0..865b2f9c21 100644
--- a/src/ImageSharp/Formats/Tiff/README.md
+++ b/src/ImageSharp/Formats/Tiff/README.md
@@ -55,7 +55,7 @@
|TransparencyMask | | | |
|Separated (TIFF Extension) | | | |
|YCbCr (TIFF Extension) | | Y | |
-|CieLab (TIFF Extension) | | | |
+|CieLab (TIFF Extension) | | Y | |
|IccLab (TechNote 1) | | | |
### Baseline TIFF Tags
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
index a5d58c2dd7..2e53acec8f 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
@@ -381,7 +381,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value;
if (options.BitsPerSample.Channels != 3)
{
- TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
+ TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for YCbCr images.");
}
ushort bitsPerChannel = options.BitsPerSample.Channel0;
@@ -395,6 +395,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break;
}
+ case TiffPhotometricInterpretation.CieLab:
+ {
+ if (options.BitsPerSample.Channels != 3)
+ {
+ TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for CieLab images.");
+ }
+
+ ushort bitsPerChannel = options.BitsPerSample.Channel0;
+ if (bitsPerChannel != 8)
+ {
+ TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for CieLab images.");
+ }
+
+ options.ColorType = options.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? TiffColorType.CieLab : TiffColorType.CieLabPlanar;
+
+ break;
+ }
+
default:
{
TiffThrowHelper.ThrowNotSupported($"The specified TIFF photometric interpretation is not supported: {options.PhotometricInterpretation}");
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
index 12e0b819d4..96b48ab309 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
@@ -318,6 +318,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
image.CompareToReferenceOutput(ImageComparer.Exact, provider);
}
+ [Theory]
+ [WithFile(CieLab, PixelTypes.Rgba32)]
+ [WithFile(CieLabPlanar, PixelTypes.Rgba32)]
+ [WithFile(CieLabLzwPredictor, PixelTypes.Rgba32)]
+ public void TiffDecoder_CanDecode_CieLab(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ // Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong
+ // converting the pixel data from Magick.NET to our format with CieLab?
+ using Image image = provider.GetImage();
+ image.DebugSave(provider);
+ image.CompareToReferenceOutput(ImageComparer.Exact, provider);
+ }
+
[Theory]
[WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)]
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 616eb3ecdf..b364b1d2c0 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -914,6 +914,11 @@ namespace SixLabors.ImageSharp.Tests
public const string Rgba32BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff";
public const string Rgba32BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff";
+ // Cie Lab color space.
+ public const string CieLab = "Tiff/CieLab.tiff";
+ public const string CieLabPlanar = "Tiff/CieLabPlanar.tiff";
+ public const string CieLabLzwPredictor = "Tiff/CieLab_lzwcompressed_predictor.tiff";
+
public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
public const string Issues1891 = "Tiff/Issues/Issue1891.tiff";
public const string Issues2123 = "Tiff/Issues/Issue2123.tiff";
diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLab.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLab.png
new file mode 100644
index 0000000000..bdd240663b
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLab.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:13bc9da102f85124855217fad757ca907f5d68442e54e3b7039ac048d7b2ad3f
+size 25791
diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLabPlanar.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLabPlanar.png
new file mode 100644
index 0000000000..bdd240663b
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLabPlanar.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:13bc9da102f85124855217fad757ca907f5d68442e54e3b7039ac048d7b2ad3f
+size 25791
diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLab_lzwcompressed_predictor.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLab_lzwcompressed_predictor.png
new file mode 100644
index 0000000000..1938ebe110
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_CieLab_Rgba32_CieLab_lzwcompressed_predictor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6f9481c91c58ca7bbab9de4b9ae95fe4a2197ae4b6ef6b15b72d4858aba3a1a4
+size 25782
diff --git a/tests/Images/Input/Tiff/CieLab.tiff b/tests/Images/Input/Tiff/CieLab.tiff
new file mode 100644
index 0000000000..59a667dd5e
--- /dev/null
+++ b/tests/Images/Input/Tiff/CieLab.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7542b5b3abe049614f2ddaf78ffe995edac13e768f0b2fc9f324c6ef43b379eb
+size 1312046
diff --git a/tests/Images/Input/Tiff/CieLabPlanar.tiff b/tests/Images/Input/Tiff/CieLabPlanar.tiff
new file mode 100644
index 0000000000..d964a96947
--- /dev/null
+++ b/tests/Images/Input/Tiff/CieLabPlanar.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:28592d9da8d51f60700b7136369d2d6bd40550d5f8c7758e570b5e624c71a3e4
+size 1307488
diff --git a/tests/Images/Input/Tiff/CieLab_lzwcompressed_predictor.tiff b/tests/Images/Input/Tiff/CieLab_lzwcompressed_predictor.tiff
new file mode 100644
index 0000000000..2284e1e17f
--- /dev/null
+++ b/tests/Images/Input/Tiff/CieLab_lzwcompressed_predictor.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6affced5550e51441c4cde7f1770d4e57cfa594bd271a12f9571359733c2185d
+size 55346