From 701d56f7adb837656af52c69ae53bc1e46ac03be Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Thu, 4 Jan 2024 09:21:09 -0500 Subject: [PATCH] LibGfx/ICC: Implement LutBToATagData::evaluate() when there is no CLUT This is enough to make these work: for f in VideoHD VideoNTSC VideoPAL; do Build/lagom/bin/icc --debug-roundtrip \ ~/Downloads/Adobe\ ICC\ Profiles\ \(end-user\)/RGB/$f.icc done It's also possible to convert images to those color spaces: Build/lagom/bin/image -o image-pal.png \ --convert-to-color-profile path/to/RGB/VideoPAL.icc \ image-srgb.png (If there's no png file with an embedded sRGB profile at hand, do first: Build/lagom/bin/icc --reencode-to serenity-sRGB.icc -n sRGB Build/lagom/bin/image -o image-srgb.png \ --assign-color-profile serenity-sRGB.icc image-no-color-space.png ) In color-managed viewers, images-srgb.png and image-pal.png should look identical. But if you either explicitly assign the sRGB profile to the pal image, or strip the embedded color space, they'll look different (since image viewers then assume the image in the VideoPAL space is in sRGB): Build/lagom/bin/image -o image-pal-strip.png --strip-color-profile \ image-pal.png Build/lagom/bin/image -o image-not-actually-srgb.png \ --assign-color-profile serenity-sRGB.icc image-pal.png --- Userland/Libraries/LibGfx/ICC/TagTypes.h | 65 +++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/Userland/Libraries/LibGfx/ICC/TagTypes.h b/Userland/Libraries/LibGfx/ICC/TagTypes.h index 709273a9ca..eaaaf4579c 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.h +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.h @@ -1303,13 +1303,74 @@ inline ErrorOr LutAToBTagData::evaluate(ColorSpace connection_spac return output_color; } -inline ErrorOr LutBToATagData::evaluate(ColorSpace connection_space, FloatVector3 const&, Bytes out_bytes) const +inline ErrorOr LutBToATagData::evaluate(ColorSpace connection_space, FloatVector3 const& in_color, Bytes out_bytes) const { VERIFY(connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB); VERIFY(number_of_input_channels() == 3); VERIFY(number_of_output_channels() == out_bytes.size()); - return Error::from_string_literal("LutBToATagData::evaluate: Not yet implemented"); + // ICC v4, 10.13 lutBToAType + // "Data are processed using these elements via the following sequence: + // (“B” curves) ⇨ (matrix) ⇨ (“M” curves) ⇨ (multi-dimensional lookup table, CLUT) ⇨ (“A” curves)." + + // See comment at start of LutAToBTagData::evaluate() for the clipping flow. + // This function generally is the same as LutAToBTagData::evaluate() upside down. + + auto evaluate_curve = [](LutCurveType const& curve, float f) { + VERIFY(curve->type() == CurveTagData::Type || curve->type() == ParametricCurveTagData::Type); + if (curve->type() == CurveTagData::Type) + return static_cast(*curve).evaluate(f); + return static_cast(*curve).evaluate(f); + }; + + FloatVector3 color; + if (connection_space == ColorSpace::PCSXYZ) { + color = in_color * 32768 / 65535.0f; + } else { + VERIFY(connection_space == ColorSpace::PCSLAB); + color[0] = in_color[0] / 100.0f; + color[1] = (in_color[1] + 128.0f) / 255.0f; + color[2] = (in_color[2] + 128.0f) / 255.0f; + } + + color = FloatVector3 { + evaluate_curve(m_b_curves[0], color[0]), + evaluate_curve(m_b_curves[1], color[1]), + evaluate_curve(m_b_curves[2], color[2]) + }; + + VERIFY(m_e.has_value() == m_m_curves.has_value()); + if (m_e.has_value()) { + // ICC v4, 10.13.3 Matrix + // "The resultant values Y1, Y2 and Y3 shall be clipped to the range 0,0 to 1,0 and used as inputs to the “M” curves." + EMatrix3x4 const& e = m_e.value(); + FloatVector3 new_color = { + (float)e[0] * color[0] + (float)e[1] * color[1] + (float)e[2] * color[2] + (float)e[9], + (float)e[3] * color[0] + (float)e[4] * color[1] + (float)e[5] * color[2] + (float)e[10], + (float)e[6] * color[0] + (float)e[7] * color[1] + (float)e[8] * color[2] + (float)e[11], + }; + color = new_color.clamped(0.f, 1.f); + + auto const& m_curves = m_m_curves.value(); + color = FloatVector3 { + evaluate_curve(m_curves[0], color[0]), + evaluate_curve(m_curves[1], color[1]), + evaluate_curve(m_curves[2], color[2]) + }; + } + + VERIFY(m_clut.has_value() == m_a_curves.has_value()); + if (m_clut.has_value()) { + // FIXME + return Error::from_string_literal("LutBToATagData::evaluate: Not yet implemented when CLUT present"); + } else { + VERIFY(number_of_output_channels() == 3); + out_bytes[0] = round_to(color[0] * 255.0f); + out_bytes[1] = round_to(color[1] * 255.0f); + out_bytes[2] = round_to(color[2] * 255.0f); + } + + return {}; } }