diff --git a/Userland/Libraries/LibGfx/ICC/Profile.cpp b/Userland/Libraries/LibGfx/ICC/Profile.cpp index d7708e61ec..5fa6b66621 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.cpp +++ b/Userland/Libraries/LibGfx/ICC/Profile.cpp @@ -1664,6 +1664,23 @@ ErrorOr Profile::to_lab(ReadonlyBytes color) const return CIELAB { L, a, b }; } +ErrorOr Profile::convert_image(Gfx::Bitmap& bitmap, Profile const& source_profile) const +{ + // FIXME: Convert XYZ<->Lab conversion when needed. + // Currently, to_pcs() and from_pcs() are only implemented for matrix profiles, which are always XYZ anyways. + if (connection_space() != source_profile.connection_space()) + return Error::from_string_literal("ICC::Profile::convert_image: mismatching profile connection spaces not yet implemented"); + + for (auto& pixel : bitmap) { + u8 rgb[] = { Color::from_argb(pixel).red(), Color::from_argb(pixel).green(), Color::from_argb(pixel).blue() }; + auto pcs = TRY(source_profile.to_pcs(rgb)); + TRY(from_pcs(pcs, rgb)); + pixel = Color(rgb[0], rgb[1], rgb[2], Color::from_argb(pixel).alpha()).value(); + } + + return {}; +} + XYZ const& Profile::red_matrix_column() const { return xyz_data(redMatrixColumnTag); } XYZ const& Profile::green_matrix_column() const { return xyz_data(greenMatrixColumnTag); } XYZ const& Profile::blue_matrix_column() const { return xyz_data(blueMatrixColumnTag); } diff --git a/Userland/Libraries/LibGfx/ICC/Profile.h b/Userland/Libraries/LibGfx/ICC/Profile.h index de2ee1451a..264454029a 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.h +++ b/Userland/Libraries/LibGfx/ICC/Profile.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -273,6 +274,8 @@ public: ErrorOr to_lab(ReadonlyBytes) const; + ErrorOr convert_image(Bitmap&, Profile const& source_profile) const; + // Only call these if you know that this is an RGB matrix-based profile. XYZ const& red_matrix_column() const; XYZ const& green_matrix_column() const; diff --git a/Userland/Utilities/image.cpp b/Userland/Utilities/image.cpp index 2462f9e2c1..462e61d7c6 100644 --- a/Userland/Utilities/image.cpp +++ b/Userland/Utilities/image.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,9 @@ ErrorOr serenity_main(Main::Arguments arguments) StringView assign_color_profile_path; args_parser.add_option(assign_color_profile_path, "Load color profile from file and assign it to output image", "assign-color-profile", {}, "FILE"); + StringView convert_color_profile_path; + args_parser.add_option(convert_color_profile_path, "Load color profile from file and convert output image from current profile to loaded profile", "convert-to-color-profile", {}, "FILE"); + bool strip_color_profile; args_parser.add_option(strip_color_profile, "Do not write color profile to output", "strip-color-profile", {}); @@ -57,6 +61,22 @@ ErrorOr serenity_main(Main::Arguments arguments) icc_data = icc_file->bytes(); } + if (!convert_color_profile_path.is_empty()) { + if (!icc_data.has_value()) { + warnln("No source color space embedded in image. Pass one with --assign-color-profile."); + return 1; + } + + auto source_icc_file = icc_file; + auto source_icc_data = icc_data.value(); + icc_file = TRY(Core::MappedFile::map(convert_color_profile_path)); + icc_data = icc_file->bytes(); + + auto source_profile = TRY(Gfx::ICC::Profile::try_load_from_externally_owned_memory(source_icc_data)); + auto destination_profile = TRY(Gfx::ICC::Profile::try_load_from_externally_owned_memory(icc_file->bytes())); + TRY(destination_profile->convert_image(*frame, *source_profile)); + } + if (strip_color_profile) icc_data.clear();