From 4704e6aa5f4524c4b88c3fbe21821f6dd16ae822 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Jan 2024 18:53:40 -0500 Subject: [PATCH] LibGfx/ICC: Refactor matrix/matrix conversion code a bit No behavior change; the goal is to make it usable by LibPDF too. --- Userland/Libraries/LibGfx/ICC/Profile.cpp | 129 +++++++++++++--------- Userland/Libraries/LibGfx/ICC/Profile.h | 28 ++++- 2 files changed, 104 insertions(+), 53 deletions(-) diff --git a/Userland/Libraries/LibGfx/ICC/Profile.cpp b/Userland/Libraries/LibGfx/ICC/Profile.cpp index fe75b87bf7..04902a1255 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.cpp +++ b/Userland/Libraries/LibGfx/ICC/Profile.cpp @@ -1571,33 +1571,25 @@ ErrorOr Profile::to_lab(ReadonlyBytes color) const return CIELAB { lab[0], lab[1], lab[2] }; } -bool Profile::is_matrix_matrix_conversion(Profile const& source_profile) const +MatrixMatrixConversion::MatrixMatrixConversion(LutCurveType source_red_TRC, + LutCurveType source_green_TRC, + LutCurveType source_blue_TRC, + FloatMatrix3x3 matrix, + LutCurveType destination_red_TRC, + LutCurveType destination_green_TRC, + LutCurveType destination_blue_TRC) + : m_source_red_TRC(move(source_red_TRC)) + , m_source_green_TRC(move(source_green_TRC)) + , m_source_blue_TRC(move(source_blue_TRC)) + , m_matrix(matrix) + , m_destination_red_TRC(move(destination_red_TRC)) + , m_destination_green_TRC(move(destination_green_TRC)) + , m_destination_blue_TRC(move(destination_blue_TRC)) { - auto has_normal_device_class = [](DeviceClass device) { - return device == DeviceClass::InputDevice - || device == DeviceClass::DisplayDevice - || device == DeviceClass::OutputDevice - || device == DeviceClass::ColorSpace; - }; - - return has_normal_device_class(device_class()) - && has_normal_device_class(source_profile.device_class()) - && connection_space() == ColorSpace::PCSXYZ - && source_profile.connection_space() == ColorSpace::PCSXYZ - && data_color_space() == ColorSpace::RGB - && source_profile.data_color_space() == ColorSpace::RGB - && !m_cached_has_any_a_to_b_tag - && !source_profile.m_cached_has_any_a_to_b_tag - && m_cached_has_all_rgb_matrix_tags - && source_profile.m_cached_has_all_rgb_matrix_tags; } -ErrorOr Profile::convert_image_matrix_matrix(Gfx::Bitmap& bitmap, Profile const& source_profile) const +Color MatrixMatrixConversion::map(FloatVector3 in_rgb) const { - auto const& sourceRedTRC = *source_profile.m_tag_table.get(redTRCTag).value(); - auto const& sourceGreenTRC = *source_profile.m_tag_table.get(greenTRCTag).value(); - auto const& sourceBlueTRC = *source_profile.m_tag_table.get(blueTRCTag).value(); - auto evaluate_curve = [](TagData const& trc, float f) { VERIFY(trc.type() == CurveTagData::Type || trc.type() == ParametricCurveTagData::Type); if (trc.type() == CurveTagData::Type) @@ -1605,12 +1597,6 @@ ErrorOr Profile::convert_image_matrix_matrix(Gfx::Bitmap& bitmap, Profile return static_cast(trc).evaluate(f); }; - FloatMatrix3x3 matrix = source_profile.rgb_to_xyz_matrix() * TRY(xyz_to_rgb_matrix()); - - auto const& destinationRedTRC = *m_tag_table.get(redTRCTag).value(); - auto const& destinationGreenTRC = *m_tag_table.get(greenTRCTag).value(); - auto const& destinationBlueTRC = *m_tag_table.get(blueTRCTag).value(); - auto evaluate_curve_inverse = [](TagData const& trc, float f) { VERIFY(trc.type() == CurveTagData::Type || trc.type() == ParametricCurveTagData::Type); if (trc.type() == CurveTagData::Type) @@ -1618,36 +1604,77 @@ ErrorOr Profile::convert_image_matrix_matrix(Gfx::Bitmap& bitmap, Profile return static_cast(trc).evaluate_inverse(f); }; + FloatVector3 linear_rgb = { + evaluate_curve(m_source_red_TRC, in_rgb[0]), + evaluate_curve(m_source_green_TRC, in_rgb[1]), + evaluate_curve(m_source_blue_TRC, in_rgb[2]), + }; + linear_rgb = m_matrix * linear_rgb; + + linear_rgb.clamp(0.f, 1.f); + float device_r = evaluate_curve_inverse(m_destination_red_TRC, linear_rgb[0]); + float device_g = evaluate_curve_inverse(m_destination_green_TRC, linear_rgb[1]); + float device_b = evaluate_curve_inverse(m_destination_blue_TRC, linear_rgb[2]); + + u8 out_r = round(255 * device_r); + u8 out_g = round(255 * device_g); + u8 out_b = round(255 * device_b); + + return Color(out_r, out_g, out_b); +} + +Optional Profile::matrix_matrix_conversion(Profile const& source_profile) const +{ + auto has_normal_device_class = [](DeviceClass device) { + return device == DeviceClass::InputDevice + || device == DeviceClass::DisplayDevice + || device == DeviceClass::OutputDevice + || device == DeviceClass::ColorSpace; + }; + + bool is_matrix_matrix_conversion = has_normal_device_class(device_class()) + && has_normal_device_class(source_profile.device_class()) + && connection_space() == ColorSpace::PCSXYZ + && source_profile.connection_space() == ColorSpace::PCSXYZ + && data_color_space() == ColorSpace::RGB + && source_profile.data_color_space() == ColorSpace::RGB + && !m_cached_has_any_a_to_b_tag + && !source_profile.m_cached_has_any_a_to_b_tag + && m_cached_has_all_rgb_matrix_tags + && source_profile.m_cached_has_all_rgb_matrix_tags + && rgb_to_xyz_matrix().is_invertible(); + + if (!is_matrix_matrix_conversion) + return OptionalNone {}; + + LutCurveType sourceRedTRC = *source_profile.m_tag_table.get(redTRCTag).value(); + LutCurveType sourceGreenTRC = *source_profile.m_tag_table.get(greenTRCTag).value(); + LutCurveType sourceBlueTRC = *source_profile.m_tag_table.get(blueTRCTag).value(); + + FloatMatrix3x3 matrix = source_profile.rgb_to_xyz_matrix() * MUST(xyz_to_rgb_matrix()); + + LutCurveType destinationRedTRC = *m_tag_table.get(redTRCTag).value(); + LutCurveType destinationGreenTRC = *m_tag_table.get(greenTRCTag).value(); + LutCurveType destinationBlueTRC = *m_tag_table.get(blueTRCTag).value(); + + return MatrixMatrixConversion(sourceRedTRC, sourceGreenTRC, sourceBlueTRC, matrix, destinationRedTRC, destinationGreenTRC, destinationBlueTRC); +} + +ErrorOr Profile::convert_image_matrix_matrix(Gfx::Bitmap& bitmap, MatrixMatrixConversion const& map) const +{ for (auto& pixel : bitmap) { FloatVector3 rgb { (float)Color::from_argb(pixel).red(), (float)Color::from_argb(pixel).green(), (float)Color::from_argb(pixel).blue() }; - rgb = rgb / 255.0f; - - FloatVector3 linear_rgb = { - evaluate_curve(sourceRedTRC, rgb[0]), - evaluate_curve(sourceGreenTRC, rgb[1]), - evaluate_curve(sourceBlueTRC, rgb[2]), - }; - linear_rgb = matrix * linear_rgb; - - linear_rgb.clamp(0.f, 1.f); - float device_r = evaluate_curve_inverse(destinationRedTRC, linear_rgb[0]); - float device_g = evaluate_curve_inverse(destinationGreenTRC, linear_rgb[1]); - float device_b = evaluate_curve_inverse(destinationBlueTRC, linear_rgb[2]); - - u8 out_r = round(255 * device_r); - u8 out_g = round(255 * device_g); - u8 out_b = round(255 * device_b); - - pixel = Color(out_r, out_g, out_b, Color::from_argb(pixel).alpha()).value(); + auto out = map.map(rgb / 255.0f); + out.set_alpha(Color::from_argb(pixel).alpha()); + pixel = out.value(); } - return {}; } ErrorOr Profile::convert_image(Gfx::Bitmap& bitmap, Profile const& source_profile) const { - if (is_matrix_matrix_conversion(source_profile)) - return convert_image_matrix_matrix(bitmap, source_profile); + if (auto map = matrix_matrix_conversion(source_profile); map.has_value()) + return convert_image_matrix_matrix(bitmap, map.value()); for (auto& pixel : bitmap) { u8 rgb[] = { Color::from_argb(pixel).red(), Color::from_argb(pixel).green(), Color::from_argb(pixel).blue() }; diff --git a/Userland/Libraries/LibGfx/ICC/Profile.h b/Userland/Libraries/LibGfx/ICC/Profile.h index ae349e598f..66e35a7469 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.h +++ b/Userland/Libraries/LibGfx/ICC/Profile.h @@ -149,6 +149,29 @@ struct ProfileHeader { Optional id; }; +// FIXME: This doesn't belong here. +class MatrixMatrixConversion { +public: + MatrixMatrixConversion(LutCurveType source_red_TRC, + LutCurveType source_green_TRC, + LutCurveType source_blue_TRC, + FloatMatrix3x3 matrix, + LutCurveType destination_red_TRC, + LutCurveType destination_green_TRC, + LutCurveType destination_blue_TRC); + + Color map(FloatVector3) const; + +private: + LutCurveType m_source_red_TRC; + LutCurveType m_source_green_TRC; + LutCurveType m_source_blue_TRC; + FloatMatrix3x3 m_matrix; + LutCurveType m_destination_red_TRC; + LutCurveType m_destination_green_TRC; + LutCurveType m_destination_blue_TRC; +}; + class Profile : public RefCounted { public: static ErrorOr> try_load_from_externally_owned_memory(ReadonlyBytes); @@ -225,6 +248,8 @@ public: XYZ const& green_matrix_column() const; XYZ const& blue_matrix_column() const; + Optional matrix_matrix_conversion(Profile const& source_profile) const; + private: Profile(ProfileHeader const& header, OrderedHashMap> tag_table) : m_header(header) @@ -248,8 +273,7 @@ private: // FIXME: The color conversion stuff should be in some other class. ErrorOr to_pcs_a_to_b(TagData const& tag_data, ReadonlyBytes) const; ErrorOr from_pcs_b_to_a(TagData const& tag_data, FloatVector3 const&, Bytes) const; - bool is_matrix_matrix_conversion(Profile const& source_profile) const; - ErrorOr convert_image_matrix_matrix(Gfx::Bitmap& bitmap, Profile const& source_profile) const; + ErrorOr convert_image_matrix_matrix(Gfx::Bitmap&, MatrixMatrixConversion const&) const; // Cached values. bool m_cached_has_any_a_to_b_tag { false };