diff --git a/Userland/Libraries/LibGfx/ICC/Profile.cpp b/Userland/Libraries/LibGfx/ICC/Profile.cpp index 702ff44901..2a520d9419 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.cpp +++ b/Userland/Libraries/LibGfx/ICC/Profile.cpp @@ -1374,8 +1374,11 @@ static TagSignature forward_transform_tag_for_rendering_intent(RenderingIntent r VERIFY_NOT_REACHED(); } -ErrorOr Profile::to_pcs_a_to_b(TagData const& tag_data, ReadonlyBytes) const +ErrorOr Profile::to_pcs_a_to_b(TagData const& tag_data, ReadonlyBytes color) const { + // Assumes a "normal" device_class() (i.e. not DeviceLink). + VERIFY(number_of_components_in_color_space(connection_space()) == 3); + switch (tag_data.type()) { case Lut16TagData::Type: // FIXME @@ -1383,9 +1386,16 @@ ErrorOr Profile::to_pcs_a_to_b(TagData const& tag_data, ReadonlyBy case Lut8TagData::Type: // FIXME return Error::from_string_literal("ICC::Profile::to_pcs: AToB*Tag handling for mft1 tags not yet implemented"); - case LutAToBTagData::Type: - // FIXME - return Error::from_string_literal("ICC::Profile::to_pcs: AToB*Tag handling for mAB tags not yet implemented"); + case LutAToBTagData::Type: { + auto const& a_to_b = static_cast(tag_data); + if (a_to_b.number_of_input_channels() != number_of_components_in_color_space(data_color_space())) + return Error::from_string_literal("ICC::Profile::to_pcs_a_to_b: mAB input channel count does not match color space size"); + + if (a_to_b.number_of_output_channels() != number_of_components_in_color_space(connection_space())) + return Error::from_string_literal("ICC::Profile::to_pcs_a_to_b: mAB output channel count does not match profile connection space size"); + + return a_to_b.evaluate(color); + } } VERIFY_NOT_REACHED(); } diff --git a/Userland/Libraries/LibGfx/ICC/TagTypes.h b/Userland/Libraries/LibGfx/ICC/TagTypes.h index 107a66e5a1..f81dfe8cf8 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.h +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.h @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace Gfx::ICC { @@ -411,6 +412,9 @@ public: Optional const& e_matrix() const { return m_e; } Vector const& b_curves() const { return m_b_curves; } + // Returns the result of the LUT pipeline for u8 inputs. + ErrorOr evaluate(ReadonlyBytes) const; + private: u8 m_number_of_input_channels; u8 m_number_of_output_channels; @@ -984,6 +988,70 @@ private: Vector m_xyzs; }; +inline ErrorOr LutAToBTagData::evaluate(ReadonlyBytes color_u8) const +{ + VERIFY(number_of_input_channels() == color_u8.size()); + VERIFY(number_of_output_channels() == 3); + + // ICC v4, 10.12 lutAToBType + // "Data are processed using these elements via the following sequence: + // (“A” curves) ⇨ (multi-dimensional lookup table, CLUT) ⇨ (“M” curves) ⇨ (matrix) ⇨ (“B” curves). + + 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); + }; + + VERIFY(m_a_curves.has_value() == m_clut.has_value()); + if (m_a_curves.has_value()) { + // FIXME + return Error::from_string_literal("mAB evaluation not yet implemented for A curve and CLUT"); + } + + FloatVector3 color { color_u8[0] / 255.f, color_u8[1] / 255.f, color_u8[2] / 255.f }; + + VERIFY(m_m_curves.has_value() == m_e.has_value()); + if (m_m_curves.has_value()) { + 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]) + }; + + // ICC v4, 10.12.5 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 “B” 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] + }; + + // Mystery conversion factor! + // skcms, littlecms, and argyll all do this somewhere, but I don't understand why! + // skcms has a "TODO: understand" comment as well. + // littlecms and argyll have both comments which don't make sense to me. + // littlecms does this to the matrix profile matrices (i.e. it considers the lut pcs scale canonical). + // skcms does it to the mAB matrix (...which means it'll do something different if the matrix is missing, + // and it'll also do something different if the b curve isn't the identity). + // argyll does it in Lut_Lut2XYZ(), but I'm not clear on when that's called. + // SampleICC does it in IccCmm.cpp, XYZScale() and in IccUtil.cpp, icXyzFromPcs(). + // Without this, colors are too bright. So let's do it too, and maybe I'll understand it one day. + new_color *= 65535 / 32768.f; // ??? + + color = new_color.clamped(0.f, 1.f); + } + + return 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]) + }; +} + } template<>