From 369bf5ff827f48c7809ec9507db0ce13eb54568c Mon Sep 17 00:00:00 2001 From: Wacton Date: Sat, 14 Dec 2024 18:59:29 +0000 Subject: [PATCH] Fix CMYK to RGB using TRCs --- .../ColorProfiles/ColorProfileConverterExtensionsIcc.cs | 6 ++++++ .../ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs | 7 +++++-- .../ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs | 9 ++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs index f6a5af89c..6ed7a3532 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs @@ -189,6 +189,9 @@ internal static class ColorProfileConverterExtensionsIcc sourcePcs = sourceParams.Is16BitLutEntry ? LabV2ToLab(sourcePcs) : sourcePcs; CieLab lab = CieLab.FromScaledVector4(sourcePcs); CieXyz xyz = pcsConverter.Convert(in lab); + + // DemoMaxICC clips negatives as part of IccUtil.cpp : icLabToXYZ > icICubeth + xyz = new CieXyz(Vector3.Clamp(xyz.ToVector3(), Vector3.Zero, new Vector3(float.MaxValue, float.MaxValue, float.MaxValue))); return xyz.ToScaledVector4(); } @@ -258,6 +261,9 @@ internal static class ColorProfileConverterExtensionsIcc sourcePcs = sourceParams.Is16BitLutEntry ? LabV2ToLab(sourcePcs) : sourcePcs; CieLab lab = CieLab.FromScaledVector4(sourcePcs); xyz = pcsConverter.Convert(in lab); + + // DemoMaxICC clips negatives as part of IccUtil.cpp : icLabToXYZ > icICubeth + xyz = new CieXyz(Vector3.Clamp(xyz.ToVector3(), Vector3.Zero, new Vector3(float.MaxValue, float.MaxValue, float.MaxValue))); break; case IccColorSpaceType.CieXyz: xyz = CieXyz.FromScaledVector4(sourcePcs); diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs index 865deae42..362940103 100644 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs +++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs @@ -39,13 +39,16 @@ internal class ColorTrcCalculator : IVector4Calculator [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 Calculate(Vector4 value) { + // uses the descaled XYZ as DemoMaxICC IccCmm.cpp : CIccXformMatrixTRC::Apply() + Vector4 xyz = new(CieXyz.FromScaledVector4(value).ToVector3(), 1); + if (this.toPcs) { - value = this.curveCalculator.Calculate(value); + value = this.curveCalculator.Calculate(xyz); return Vector4.Transform(value, this.matrix); } - value = Vector4.Transform(value, this.matrix); + value = Vector4.Transform(xyz, this.matrix); return this.curveCalculator.Calculate(value); } } diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs index 015153d7f..9ed51b519 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs @@ -80,9 +80,12 @@ public class ColorProfileConverterTests Rgb actualRgb = converter.Convert(new Cmyk(new Vector4(input))); - Assert.Equal(expectedR, actualRgb.R); - Assert.Equal(expectedG, actualRgb.G); - Assert.Equal(expectedB, actualRgb.B); + // TODO: investigate lower tolerance than CanConvertCmykIccProfiles() + // currently assuming it's a rounding error in the process of gathering test data manually + const double tolerance = 0.0005; + Assert.Equal(expectedR, actualRgb.R, tolerance); + Assert.Equal(expectedG, actualRgb.G, tolerance); + Assert.Equal(expectedB, actualRgb.B, tolerance); } private static double[] GetExpectedTargetValues(string sourceProfile, string targetProfile, float[] input)