diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16PlanarTiffColor{TPixel}.cs
new file mode 100644
index 000000000..b422e65ad
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16PlanarTiffColor{TPixel}.cs
@@ -0,0 +1,144 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.ColorProfiles;
+using SixLabors.ImageSharp.ColorProfiles.Icc;
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
+
+///
+/// Implements decoding pixel data with photometric interpretation of type 'CieLab' with the planar configuration.
+/// Each channel is represented with 16 bits.
+///
+/// The type of pixel format.
+internal class CieLab16PlanarTiffColor : TiffBasePlanarColorDecoder
+ where TPixel : unmanaged, IPixel
+{
+ private readonly ColorProfileConverter colorProfileConverter;
+ private readonly Configuration configuration;
+ private readonly bool isBigEndian;
+
+ // libtiff encodes 16-bit Lab as:
+ // L* : unsigned [0, 65535] mapping to [0, 100]
+ // a*, b* : signed [-32768, 32767], values are 256x the 1976 a*, b* values.
+ private const float Inv65535 = 1f / 65535f;
+ private const float Inv256 = 1f / 256f;
+
+ public CieLab16PlanarTiffColor(
+ Configuration configuration,
+ DecoderOptions decoderOptions,
+ ImageFrameMetadata metadata,
+ MemoryAllocator allocator,
+ bool isBigEndian)
+ {
+ this.isBigEndian = isBigEndian;
+ this.configuration = configuration;
+
+ if (decoderOptions.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile))
+ {
+ ColorConversionOptions options = new()
+ {
+ SourceIccProfile = iccProfile,
+ TargetIccProfile = CompactSrgbV4Profile.Profile,
+ MemoryAllocator = allocator
+ };
+
+ this.colorProfileConverter = new ColorProfileConverter(options);
+ }
+ else
+ {
+ ColorConversionOptions options = new()
+ {
+ MemoryAllocator = allocator
+ };
+
+ this.colorProfileConverter = new ColorProfileConverter(options);
+ }
+ }
+
+ ///
+ public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height)
+ {
+ Span lPlane = data[0].GetSpan();
+ Span aPlane = data[1].GetSpan();
+ Span bPlane = data[2].GetSpan();
+
+ // Allocate temporary buffers to hold the LAB -> RGB conversion.
+ // This should be the maximum width of a row.
+ using IMemoryOwner rgbBuffer = this.colorProfileConverter.Options.MemoryAllocator.Allocate(width);
+ using IMemoryOwner vectorBuffer = this.colorProfileConverter.Options.MemoryAllocator.Allocate(width);
+
+ Span rgbRow = rgbBuffer.Memory.Span;
+ Span vectorRow = vectorBuffer.Memory.Span;
+
+ // Reuse the rgbRow span for lab data since both are 3-float structs, avoiding an extra allocation.
+ Span cieLabRow = MemoryMarshal.Cast(rgbRow);
+
+ int stride = width * 2;
+
+ if (this.isBigEndian)
+ {
+ for (int y = 0; y < height; y++)
+ {
+ int rowBase = y * stride;
+ Span pixelRow = pixels.DangerousGetRowSpan(top + y).Slice(left, width);
+
+ for (int x = 0; x < width; x++)
+ {
+ int i = rowBase + (x * 2);
+
+ ushort lRaw = TiffUtilities.ConvertToUShortBigEndian(lPlane.Slice(i, 2));
+ short aRaw = unchecked((short)TiffUtilities.ConvertToUShortBigEndian(aPlane.Slice(i, 2)));
+ short bRaw = unchecked((short)TiffUtilities.ConvertToUShortBigEndian(bPlane.Slice(i, 2)));
+
+ float l = lRaw * 100f * Inv65535;
+ float a = aRaw * Inv256;
+ float b = bRaw * Inv256;
+
+ cieLabRow[x] = new CieLab(l, a, b);
+ }
+
+ // Convert CIE Lab -> Rgb -> Vector4 -> TPixel
+ this.colorProfileConverter.Convert(cieLabRow, rgbRow);
+ Rgb.ToScaledVector4(rgbRow, vectorRow);
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale);
+ }
+
+ return;
+ }
+
+ for (int y = 0; y < height; y++)
+ {
+ int rowBase = y * stride;
+ Span pixelRow = pixels.DangerousGetRowSpan(top + y).Slice(left, width);
+
+ for (int x = 0; x < width; x++)
+ {
+ int i = rowBase + (x * 2);
+
+ ushort lRaw = TiffUtilities.ConvertToUShortLittleEndian(lPlane.Slice(i, 2));
+ short aRaw = unchecked((short)TiffUtilities.ConvertToUShortLittleEndian(aPlane.Slice(i, 2)));
+ short bRaw = unchecked((short)TiffUtilities.ConvertToUShortLittleEndian(bPlane.Slice(i, 2)));
+
+ float l = lRaw * 100f * Inv65535;
+ float a = aRaw * Inv256;
+ float b = bRaw * Inv256;
+
+ cieLabRow[x] = new CieLab(l, a, b);
+ }
+
+ // Convert CIE Lab -> Rgb -> Vector4 -> TPixel
+ this.colorProfileConverter.Convert(cieLabRow, rgbRow);
+ Rgb.ToScaledVector4(rgbRow, vectorRow);
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16TiffColor{TPixel}.cs
new file mode 100644
index 000000000..4455753bf
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab16TiffColor{TPixel}.cs
@@ -0,0 +1,140 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.ColorProfiles;
+using SixLabors.ImageSharp.ColorProfiles.Icc;
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
+
+///
+/// Implements decoding pixel data with photometric interpretation of type 'CieLab'.
+/// Each channel is represented with 16 bits.
+///
+/// The type of pixel format.
+internal class CieLab16TiffColor : TiffBaseColorDecoder
+ where TPixel : unmanaged, IPixel
+{
+ private readonly ColorProfileConverter colorProfileConverter;
+ private readonly Configuration configuration;
+ private readonly bool isBigEndian;
+
+ // libtiff encodes 16-bit Lab as:
+ // L* : unsigned [0, 65535] mapping to [0, 100]
+ // a*, b* : signed [-32768, 32767], values are 256x the 1976 a*, b* values.
+ private const float Inv65535 = 1f / 65535f;
+ private const float Inv256 = 1f / 256f;
+
+ public CieLab16TiffColor(
+ Configuration configuration,
+ DecoderOptions decoderOptions,
+ ImageFrameMetadata metadata,
+ MemoryAllocator allocator,
+ bool isBigEndian)
+ {
+ this.isBigEndian = isBigEndian;
+ this.configuration = configuration;
+
+ if (decoderOptions.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile))
+ {
+ ColorConversionOptions options = new()
+ {
+ SourceIccProfile = iccProfile,
+ TargetIccProfile = CompactSrgbV4Profile.Profile,
+ MemoryAllocator = allocator
+ };
+
+ this.colorProfileConverter = new ColorProfileConverter(options);
+ }
+ else
+ {
+ ColorConversionOptions options = new()
+ {
+ MemoryAllocator = allocator
+ };
+
+ this.colorProfileConverter = new ColorProfileConverter(options);
+ }
+ }
+
+ ///
+ public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height)
+ {
+ int offset = 0;
+
+ // Allocate temporary buffers to hold the LAB -> RGB conversion.
+ // This should be the maximum width of a row.
+ using IMemoryOwner rgbBuffer = this.colorProfileConverter.Options.MemoryAllocator.Allocate(width);
+ using IMemoryOwner vectorBuffer = this.colorProfileConverter.Options.MemoryAllocator.Allocate(width);
+
+ Span rgbRow = rgbBuffer.Memory.Span;
+ Span vectorRow = vectorBuffer.Memory.Span;
+
+ // Reuse the rgbRow span for lab data since both are 3-float structs, avoiding an extra allocation.
+ Span cieLabRow = MemoryMarshal.Cast(rgbRow);
+
+ if (this.isBigEndian)
+ {
+ for (int y = top; y < top + height; y++)
+ {
+ Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
+
+ for (int x = 0; x < pixelRow.Length; x++)
+ {
+ ushort lRaw = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2));
+ offset += 2;
+ short aRaw = unchecked((short)TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)));
+ offset += 2;
+ short bRaw = unchecked((short)TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)));
+ offset += 2;
+
+ float l = lRaw * 100f * Inv65535;
+ float a = aRaw * Inv256;
+ float b = bRaw * Inv256;
+
+ cieLabRow[x] = new CieLab(l, a, b);
+ }
+
+ // Convert CIE Lab -> Rgb -> Vector4 -> TPixel
+ this.colorProfileConverter.Convert(cieLabRow, rgbRow);
+ Rgb.ToScaledVector4(rgbRow, vectorRow);
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale);
+ }
+
+ return;
+ }
+
+ for (int y = top; y < top + height; y++)
+ {
+ Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
+
+ for (int x = 0; x < pixelRow.Length; x++)
+ {
+ ushort lRaw = TiffUtilities.ConvertToUShortLittleEndian(data.Slice(offset, 2));
+ offset += 2;
+ short aRaw = unchecked((short)TiffUtilities.ConvertToUShortLittleEndian(data.Slice(offset, 2)));
+ offset += 2;
+ short bRaw = unchecked((short)TiffUtilities.ConvertToUShortLittleEndian(data.Slice(offset, 2)));
+ offset += 2;
+
+ float l = lRaw * 100f * Inv65535;
+ float a = aRaw * Inv256;
+ float b = bRaw * Inv256;
+
+ cieLabRow[x] = new CieLab(l, a, b);
+ }
+
+ // Convert CIE Lab -> Rgb -> Vector4 -> TPixel
+ this.colorProfileConverter.Convert(cieLabRow, rgbRow);
+ Rgb.ToScaledVector4(rgbRow, vectorRow);
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8PlanarTiffColor{TPixel}.cs
similarity index 94%
rename from src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs
rename to src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8PlanarTiffColor{TPixel}.cs
index d23d1e290..1252f1d3d 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8PlanarTiffColor{TPixel}.cs
@@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// Implements decoding pixel data with photometric interpretation of type 'CieLab' with the planar configuration.
///
/// The type of pixel format.
-internal class CieLabPlanarTiffColor : TiffBasePlanarColorDecoder
+internal class CieLab8PlanarTiffColor : TiffBasePlanarColorDecoder
where TPixel : unmanaged, IPixel
{
private static readonly ColorProfileConverter ColorProfileConverter = new();
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8TiffColor{TPixel}.cs
similarity index 83%
rename from src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs
rename to src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8TiffColor{TPixel}.cs
index b10d27ccd..ce5bed53c 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLab8TiffColor{TPixel}.cs
@@ -10,14 +10,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
///
/// Implements decoding pixel data with photometric interpretation of type 'CieLab'.
+/// Each channel is represented with 8 bits.
///
/// The type of pixel format.
-internal class CieLabTiffColor : TiffBaseColorDecoder
+internal class CieLab8TiffColor : TiffBaseColorDecoder
where TPixel : unmanaged, IPixel
{
private static readonly ColorProfileConverter ColorProfileConverter = new();
private const float Inv255 = 1f / 255f;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CieLab8TiffColor()
+ {
+ }
+
///
public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height)
{
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs
index 2e22fcde0..509056267 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs
@@ -1,10 +1,15 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Buffers;
using System.Numerics;
+using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
+using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
@@ -12,18 +17,48 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
internal class CmykTiffColor : TiffBaseColorDecoder
where TPixel : unmanaged, IPixel
{
- private static readonly ColorProfileConverter ColorProfileConverter = new();
+ private readonly ColorProfileConverter colorProfileConverter;
+ private readonly Configuration configuration;
private const float Inv255 = 1f / 255f;
private readonly TiffDecoderCompressionType compression;
- public CmykTiffColor(TiffDecoderCompressionType compression) => this.compression = compression;
+ public CmykTiffColor(
+ TiffDecoderCompressionType compression,
+ Configuration configuration,
+ DecoderOptions decoderOptions,
+ ImageFrameMetadata metadata,
+ MemoryAllocator allocator)
+ {
+ this.compression = compression;
+ this.configuration = configuration;
+
+ if (decoderOptions.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile))
+ {
+ ColorConversionOptions options = new()
+ {
+ SourceIccProfile = iccProfile,
+ TargetIccProfile = CompactSrgbV4Profile.Profile,
+ MemoryAllocator = allocator
+ };
+
+ this.colorProfileConverter = new ColorProfileConverter(options);
+ }
+ else
+ {
+ ColorConversionOptions options = new()
+ {
+ MemoryAllocator = allocator
+ };
+
+ this.colorProfileConverter = new ColorProfileConverter(options);
+ }
+ }
///
public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height)
{
int offset = 0;
-
if (this.compression == TiffDecoderCompressionType.Jpeg)
{
for (int y = top; y < top + height; y++)
@@ -40,17 +75,31 @@ internal class CmykTiffColor : TiffBaseColorDecoder
return;
}
+ // Allocate temporary buffers to hold the CMYK -> RGB conversion.
+ // This should be the maximum width of a row.
+ using IMemoryOwner rgbBuffer = this.colorProfileConverter.Options.MemoryAllocator.Allocate(width);
+ using IMemoryOwner vectorBuffer = this.colorProfileConverter.Options.MemoryAllocator.Allocate(width);
+
+ Span rgbRow = rgbBuffer.Memory.Span;
+ Span vectorRow = vectorBuffer.Memory.Span;
+
+ // Reuse the Vector4 buffer as CMYK storage since both are 4-float structs, avoiding an extra allocation.
+ Span cmykRow = MemoryMarshal.Cast(vectorRow);
+
for (int y = top; y < top + height; y++)
{
Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
- for (int x = 0; x < pixelRow.Length; x++)
- {
- Cmyk cmyk = new(data[offset] * Inv255, data[offset + 1] * Inv255, data[offset + 2] * Inv255, data[offset + 3] * Inv255);
- Rgb rgb = ColorProfileConverter.Convert(in cmyk);
- pixelRow[x] = TPixel.FromScaledVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f));
- offset += 4;
- }
+ // Collect CMYK pixels.
+ // ByteToNormalizedFloat efficiently converts packed 4-byte component data
+ // to normalized 0-1 floats using SIMD.
+ SimdUtils.ByteToNormalizedFloat(data.Slice(offset, width * 4), MemoryMarshal.Cast(cmykRow));
+ offset += width * 4;
+
+ // Convert CMYK -> RGB -> Vector4 -> TPixel
+ this.colorProfileConverter.Convert(cmykRow, rgbRow);
+ Rgb.ToScaledVector4(rgbRow, vectorRow);
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorRow, pixelRow, PixelConversionModifiers.Scale);
}
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
index e2eb82e3b..363c16445 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
@@ -3,6 +3,7 @@
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
@@ -11,6 +12,8 @@ internal static class TiffColorDecoderFactory
where TPixel : unmanaged, IPixel
{
public static TiffBaseColorDecoder Create(
+ ImageFrameMetadata metadata,
+ DecoderOptions options,
Configuration configuration,
MemoryAllocator memoryAllocator,
TiffColorType colorType,
@@ -396,13 +399,20 @@ internal static class TiffColorDecoderFactory
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();
+
+ DebugGuard.IsTrue(bitsPerSample.Channels == 3, "bitsPerSample");
+
+ if (bitsPerSample.Channel0 == 8)
+ {
+ return new CieLab8TiffColor();
+ }
+
+ return new CieLab16TiffColor(
+ configuration,
+ options,
+ metadata,
+ memoryAllocator,
+ byteOrder == ByteOrder.BigEndian);
case TiffColorType.Cmyk:
DebugGuard.IsTrue(
@@ -412,14 +422,19 @@ internal static class TiffColorDecoderFactory
&& bitsPerSample.Channel1 == 8
&& bitsPerSample.Channel0 == 8,
"bitsPerSample");
- return new CmykTiffColor(compression);
+ return new CmykTiffColor(compression, configuration, options, metadata, memoryAllocator);
default:
throw TiffThrowHelper.InvalidColorType(colorType.ToString());
}
}
+#pragma warning disable IDE0060 // Remove unused parameter
public static TiffBasePlanarColorDecoder CreatePlanar(
+ ImageFrameMetadata metadata,
+ DecoderOptions options,
+ Configuration configuration,
+ MemoryAllocator allocator,
TiffColorType colorType,
TiffBitsPerSample bitsPerSample,
TiffExtraSampleType? extraSampleType,
@@ -443,7 +458,14 @@ internal static class TiffColorDecoderFactory
return new YCbCrPlanarTiffColor(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling);
case TiffColorType.CieLabPlanar:
- return new CieLabPlanarTiffColor();
+ return bitsPerSample.Channel0 == 8
+ ? new CieLab8PlanarTiffColor()
+ : new CieLab16PlanarTiffColor(
+ configuration,
+ options,
+ metadata,
+ allocator,
+ byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb161616Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs
index 744cba35f..9e5e3dba9 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs
@@ -107,7 +107,7 @@ internal class YCbCrConverter
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgba32 Convert(float y, float cb, float cr)
{
- Rgba32 pixel = default(Rgba32);
+ Rgba32 pixel = default;
pixel.R = RoundAndClampTo8Bit((cr * this.cr2R) + y);
pixel.G = RoundAndClampTo8Bit((this.y2G * y) + (this.cr2G * cr) + (this.cb2G * cb));
pixel.B = RoundAndClampTo8Bit((cb * this.cb2B) + y);
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index 8a4a27946..30fa277fc 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -453,7 +453,7 @@ internal class TiffDecoderCore : ImageDecoderCore
}
using TiffBaseDecompressor decompressor = this.CreateDecompressor(width, bitsPerPixel, frame.Metadata);
- TiffBasePlanarColorDecoder colorDecoder = this.CreatePlanarColorDecoder();
+ TiffBasePlanarColorDecoder colorDecoder = this.CreatePlanarColorDecoder(frame.Metadata);
for (int i = 0; i < stripsPerPlane; i++)
{
@@ -518,7 +518,7 @@ internal class TiffDecoderCore : ImageDecoderCore
int bitsPerPixel = this.BitsPerPixel;
using TiffBaseDecompressor decompressor = this.CreateDecompressor(width, bitsPerPixel, frame.Metadata);
- TiffBaseColorDecoder colorDecoder = this.CreateChunkyColorDecoder();
+ TiffBaseColorDecoder colorDecoder = this.CreateChunkyColorDecoder(frame.Metadata);
Buffer2D pixels = frame.PixelBuffer;
// There exists in this world TIFF files with uncompressed strips larger than Int32.MaxValue.
@@ -661,7 +661,7 @@ internal class TiffDecoderCore : ImageDecoderCore
}
using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel, frame.Metadata);
- TiffBasePlanarColorDecoder colorDecoder = this.CreatePlanarColorDecoder();
+ TiffBasePlanarColorDecoder colorDecoder = this.CreatePlanarColorDecoder(frame.Metadata);
int tileIndex = 0;
int remainingPixelsInColumn = height;
@@ -762,7 +762,7 @@ internal class TiffDecoderCore : ImageDecoderCore
Span tileBufferSpan = tileBuffer.GetSpan();
using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel, frame.Metadata, true, tileWidth, tileLength);
- TiffBaseColorDecoder colorDecoder = this.CreateChunkyColorDecoder();
+ TiffBaseColorDecoder colorDecoder = this.CreateChunkyColorDecoder(frame.Metadata);
int tileIndex = 0;
for (int tileY = 0; tileY < tilesDown; tileY++)
@@ -803,9 +803,11 @@ internal class TiffDecoderCore : ImageDecoderCore
}
}
- private TiffBaseColorDecoder CreateChunkyColorDecoder()
+ private TiffBaseColorDecoder CreateChunkyColorDecoder(ImageFrameMetadata metadata)
where TPixel : unmanaged, IPixel =>
TiffColorDecoderFactory.Create(
+ metadata,
+ this.Options,
this.configuration,
this.memoryAllocator,
this.ColorType,
@@ -818,9 +820,13 @@ internal class TiffDecoderCore : ImageDecoderCore
this.CompressionType,
this.byteOrder);
- private TiffBasePlanarColorDecoder CreatePlanarColorDecoder()
+ private TiffBasePlanarColorDecoder CreatePlanarColorDecoder(ImageFrameMetadata metadata)
where TPixel : unmanaged, IPixel =>
TiffColorDecoderFactory.CreatePlanar(
+ metadata,
+ this.Options,
+ this.configuration,
+ this.memoryAllocator,
this.ColorType,
this.BitsPerSample,
this.ExtraSamplesType,
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
index 7519871b7..9445f468c 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
@@ -452,13 +452,9 @@ internal static class TiffDecoderOptionsParser
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;
+ options.ColorType = options.PlanarConfiguration == TiffPlanarConfiguration.Chunky
+ ? TiffColorType.CieLab
+ : TiffColorType.CieLabPlanar;
break;
}
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
index 5096d93bd..c186d4f01 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
@@ -23,7 +23,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
public static readonly string[] MultiframeTestImages = Multiframes;
[Theory]
- [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)]
+ // [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)]
[WithFile(Cmyk64BitDeflate, PixelTypes.Rgba32)]
public void ThrowsNotSupported(TestImageProvider provider)
where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder.Instance));
@@ -352,6 +352,19 @@ public class TiffDecoderTests : TiffDecoderBaseTester
image.CompareToReferenceOutput(ImageComparer.Exact, provider);
}
+ [Theory]
+ [WithFile(Icc.PerceptualCmyk, PixelTypes.Rgba32)]
+ [WithFile(Icc.PerceptualCieLab, PixelTypes.Rgba32)]
+ public void Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage(TiffDecoder.Instance, new DecoderOptions { ColorProfileHandling = ColorProfileHandling.Convert });
+
+ image.DebugSave(provider);
+ image.CompareToReferenceOutput(provider);
+ Assert.Null(image.Metadata.IccProfile);
+ }
+
[Theory]
[WithFile(Issues2454_A, PixelTypes.Rgba32)]
[WithFile(Issues2454_B, PixelTypes.Rgba32)]
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index dc3275999..ee0766107 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -1194,6 +1194,12 @@ public static class TestImages
];
public static readonly string[] Metadata = [SampleMetadata];
+
+ public static class Icc
+ {
+ public const string PerceptualCmyk = "Tiff/icc-profiles/Perceptual_CMYK.tiff";
+ public const string PerceptualCieLab = "Tiff/icc-profiles/Perceptual_CIELAB.tiff";
+ }
}
public static class BigTiff
diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_CIELAB.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_CIELAB.png
new file mode 100644
index 000000000..3863e7202
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_CIELAB.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8dd7d54f362b33c2d2d4b4b3cb3bbece23c14138073403234db7b53012939101
+size 383
diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_CMYK.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_CMYK.png
new file mode 100644
index 000000000..bc97709c1
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_CMYK.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:370af73b800622a671c0718b2c137ead8401adf20c39b45f153d2c9bb09b40ed
+size 385
diff --git a/tests/Images/Input/Tiff/icc-profiles/Perceptual_CIELAB.tiff b/tests/Images/Input/Tiff/icc-profiles/Perceptual_CIELAB.tiff
new file mode 100644
index 000000000..ab2aff820
--- /dev/null
+++ b/tests/Images/Input/Tiff/icc-profiles/Perceptual_CIELAB.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9494eea65b2c5b6ef033fb89b4fee2ef97d07773ae4b3988da972e1ba152b890
+size 64418
diff --git a/tests/Images/Input/Tiff/icc-profiles/Perceptual_CMYK.tiff b/tests/Images/Input/Tiff/icc-profiles/Perceptual_CMYK.tiff
new file mode 100644
index 000000000..7dd685fe3
--- /dev/null
+++ b/tests/Images/Input/Tiff/icc-profiles/Perceptual_CMYK.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cfb7fe9e93362fa121d97fa05f61242733d79fa9fa06bb7153e1773e827567dd
+size 601124