diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs
index bb6c2a282..d4d80fc12 100644
--- a/src/ImageSharp/Formats/DecoderOptions.cs
+++ b/src/ImageSharp/Formats/DecoderOptions.cs
@@ -78,12 +78,12 @@ public sealed class DecoderOptions
return false;
}
- if (profile.IsCanonicalSrgbMatrixTrc())
+ if (this.ColorProfileHandling == ColorProfileHandling.Preserve)
{
return false;
}
- if (this.ColorProfileHandling == ColorProfileHandling.Preserve)
+ if (profile.IsCanonicalSrgbMatrixTrc())
{
return false;
}
@@ -99,11 +99,11 @@ public sealed class DecoderOptions
return false;
}
- if (this.ColorProfileHandling == ColorProfileHandling.Compact && profile.IsCanonicalSrgbMatrixTrc())
+ if (this.ColorProfileHandling == ColorProfileHandling.Convert)
{
return true;
}
- return this.ColorProfileHandling == ColorProfileHandling.Convert;
+ return this.ColorProfileHandling == ColorProfileHandling.Compact && profile.IsCanonicalSrgbMatrixTrc();
}
}
diff --git a/src/ImageSharp/Formats/ImageDecoder.cs b/src/ImageSharp/Formats/ImageDecoder.cs
index c18fc663b..2a5d44a35 100644
--- a/src/ImageSharp/Formats/ImageDecoder.cs
+++ b/src/ImageSharp/Formats/ImageDecoder.cs
@@ -328,6 +328,14 @@ public abstract class ImageDecoder : IImageDecoder
{
image.Metadata.IccProfile = null;
}
+
+ foreach (ImageFrame frame in image.Frames)
+ {
+ if (options.CanRemoveIccProfile(frame.Metadata.IccProfile))
+ {
+ frame.Metadata.IccProfile = null;
+ }
+ }
}
private static void HandleIccProfile(DecoderOptions options, ImageInfo image)
@@ -336,5 +344,13 @@ public abstract class ImageDecoder : IImageDecoder
{
image.Metadata.IccProfile = null;
}
+
+ foreach (ImageFrameMetadata frame in image.FrameMetadataCollection)
+ {
+ if (options.CanRemoveIccProfile(frame.IccProfile))
+ {
+ frame.IccProfile = null;
+ }
+ }
}
}
diff --git a/src/ImageSharp/Formats/ImageDecoderCore.cs b/src/ImageSharp/Formats/ImageDecoderCore.cs
index da50a1abe..f6b1a92a0 100644
--- a/src/ImageSharp/Formats/ImageDecoderCore.cs
+++ b/src/ImageSharp/Formats/ImageDecoderCore.cs
@@ -5,6 +5,7 @@ using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
@@ -164,4 +165,56 @@ internal abstract class ImageDecoderCore
converter.Convert(image);
return true;
}
+
+ ///
+ /// Converts the ICC color profile of the specified image frame to the compact sRGB v4 profile if a source profile is
+ /// available.
+ ///
+ ///
+ /// This method should only be used by decoders that gurantee that the encoded image data is in a color space
+ /// compatible with sRGB (e.g. standard RGB, Adobe RGB, ProPhoto RGB).
+ ///
+ /// If the image does not have a valid ICC profile for color conversion, no changes are made.
+ /// This operation may affect the color appearance of the image to ensure consistency with the sRGB color
+ /// space.
+ ///
+ /// The pixel format.
+ /// The image frame whose ICC profile will be converted to the compact sRGB v4 profile.
+ ///
+ /// if the conversion was performed; otherwise, .
+ ///
+ protected bool TryConvertIccProfile(ImageFrame frame)
+ where TPixel : unmanaged, IPixel
+ {
+ if (!this.Options.TryGetIccProfileForColorConversion(frame.Metadata.IccProfile, out IccProfile? profile))
+ {
+ return false;
+ }
+
+ ColorConversionOptions options = new()
+ {
+ SourceIccProfile = profile,
+ TargetIccProfile = CompactSrgbV4Profile.Profile,
+ MemoryAllocator = frame.Configuration.MemoryAllocator,
+ };
+
+ ColorProfileConverter converter = new(options);
+
+ ImageMetadata metadata = new()
+ {
+ IccProfile = frame.Metadata.IccProfile
+ };
+
+ IMemoryGroup m = frame.PixelBuffer.MemoryGroup;
+
+ // Safe: ToArray only materializes the Memory segment list, not the underlying pixel buffers,
+ // and Wrap(Memory[]) creates a Consumed MemoryGroup that does not own the buffers (Dispose just
+ // invalidates the view). This means no pixel data is cloned and disposing the temporary image will
+ // not dispose or leak the frame's pixel buffer.
+ MemoryGroup memorySource = MemoryGroup.Wrap(m.ToArray());
+
+ using Image image = new(frame.Configuration, memorySource, frame.Width, frame.Height, metadata);
+ converter.Convert(image);
+ return true;
+ }
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
index 363c16445..270c7f356 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
@@ -429,7 +429,6 @@ internal static class TiffColorDecoderFactory
}
}
-#pragma warning disable IDE0060 // Remove unused parameter
public static TiffBasePlanarColorDecoder CreatePlanar(
ImageFrameMetadata metadata,
DecoderOptions options,
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index 30fa277fc..e3a51aa8d 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -52,11 +52,6 @@ internal class TiffDecoderCore : ImageDecoderCore
///
private ByteOrder byteOrder;
- ///
- /// Indicating whether is BigTiff format.
- ///
- private bool isBigTiff;
-
///
/// Initializes a new instance of the class.
///
@@ -167,7 +162,6 @@ internal class TiffDecoderCore : ImageDecoderCore
IList directories = reader.Read();
this.byteOrder = reader.ByteOrder;
- this.isBigTiff = reader.IsBigTiff;
Size? size = null;
uint frameCount = 0;
@@ -273,6 +267,15 @@ internal class TiffDecoderCore : ImageDecoderCore
this.DecodeImageWithStrips(tags, frame, width, height, cancellationToken);
}
+ // Only RGB-compatible color types can be converted here because the TPixel-based ICC profile conversion
+ // expects RGB-like pixel data; other photometric interpretations (YCbCr, CMYK, Lab, etc.) would require
+ // dedicated transforms. We do this once at the frame level to avoid duplicating conversion logic
+ // across all color decoders and to keep their decode paths focused on raw pixel unpacking.
+ if (this.ColorType is >= TiffColorType.PaletteColor and <= TiffColorType.Rgba32323232Planar)
+ {
+ _ = this.TryConvertIccProfile(frame);
+ }
+
return frame;
}
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
index c186d4f01..e432a7251 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));
@@ -355,6 +355,8 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[Theory]
[WithFile(Icc.PerceptualCmyk, PixelTypes.Rgba32)]
[WithFile(Icc.PerceptualCieLab, PixelTypes.Rgba32)]
+ [WithFile(Icc.PerceptualRgb8, PixelTypes.Rgba32)]
+ [WithFile(Icc.PerceptualRgb16, PixelTypes.Rgba32)]
public void Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index ee0766107..764954cae 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -1199,6 +1199,8 @@ public static class TestImages
{
public const string PerceptualCmyk = "Tiff/icc-profiles/Perceptual_CMYK.tiff";
public const string PerceptualCieLab = "Tiff/icc-profiles/Perceptual_CIELAB.tiff";
+ public const string PerceptualRgb8 = "Tiff/icc-profiles/Perceptual_RGB8.tiff";
+ public const string PerceptualRgb16 = "Tiff/icc-profiles/Perceptual_RGB16.tiff";
}
}
diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB16.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB16.png
new file mode 100644
index 000000000..3fcea773a
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB16.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:de9cfa53a82b169c533999908c7ace43aa35a8b456d5f0378b539669c7857d1c
+size 386
diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB8.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB8.png
new file mode 100644
index 000000000..3fcea773a
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB8.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:de9cfa53a82b169c533999908c7ace43aa35a8b456d5f0378b539669c7857d1c
+size 386
diff --git a/tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB16.tiff b/tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB16.tiff
new file mode 100644
index 000000000..369284819
--- /dev/null
+++ b/tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB16.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3b0e89ac971c23c12c9edff6f9e9dd066fe99f36e28e89972343d50562dd7dbd
+size 65260
diff --git a/tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB8.tiff b/tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB8.tiff
new file mode 100644
index 000000000..e2c1ff54c
--- /dev/null
+++ b/tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB8.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:18b0e5ee58c9f53bf5414fc8244e98d65aa0bc6c3397e201399baff0cd378b23
+size 35236