LibGfx/ICC: Refactor matrix/matrix conversion code a bit

No behavior change; the goal is to make it usable by LibPDF too.
This commit is contained in:
Nico Weber 2024-01-08 18:53:40 -05:00 committed by Andreas Kling
parent 2fc6ec0f46
commit 4704e6aa5f
Notes: sideshowbarker 2024-07-17 04:32:07 +09:00
2 changed files with 104 additions and 53 deletions

View file

@ -1571,33 +1571,25 @@ ErrorOr<CIELAB> Profile::to_lab(ReadonlyBytes color) const
return CIELAB { lab[0], lab[1], lab[2] }; 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<void> 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) { auto evaluate_curve = [](TagData const& trc, float f) {
VERIFY(trc.type() == CurveTagData::Type || trc.type() == ParametricCurveTagData::Type); VERIFY(trc.type() == CurveTagData::Type || trc.type() == ParametricCurveTagData::Type);
if (trc.type() == CurveTagData::Type) if (trc.type() == CurveTagData::Type)
@ -1605,12 +1597,6 @@ ErrorOr<void> Profile::convert_image_matrix_matrix(Gfx::Bitmap& bitmap, Profile
return static_cast<ParametricCurveTagData const&>(trc).evaluate(f); return static_cast<ParametricCurveTagData const&>(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) { auto evaluate_curve_inverse = [](TagData const& trc, float f) {
VERIFY(trc.type() == CurveTagData::Type || trc.type() == ParametricCurveTagData::Type); VERIFY(trc.type() == CurveTagData::Type || trc.type() == ParametricCurveTagData::Type);
if (trc.type() == CurveTagData::Type) if (trc.type() == CurveTagData::Type)
@ -1618,36 +1604,77 @@ ErrorOr<void> Profile::convert_image_matrix_matrix(Gfx::Bitmap& bitmap, Profile
return static_cast<ParametricCurveTagData const&>(trc).evaluate_inverse(f); return static_cast<ParametricCurveTagData const&>(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<MatrixMatrixConversion> 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<void> Profile::convert_image_matrix_matrix(Gfx::Bitmap& bitmap, MatrixMatrixConversion const& map) const
{
for (auto& pixel : bitmap) { for (auto& pixel : bitmap) {
FloatVector3 rgb { (float)Color::from_argb(pixel).red(), (float)Color::from_argb(pixel).green(), (float)Color::from_argb(pixel).blue() }; FloatVector3 rgb { (float)Color::from_argb(pixel).red(), (float)Color::from_argb(pixel).green(), (float)Color::from_argb(pixel).blue() };
rgb = rgb / 255.0f; auto out = map.map(rgb / 255.0f);
out.set_alpha(Color::from_argb(pixel).alpha());
FloatVector3 linear_rgb = { pixel = out.value();
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();
} }
return {}; return {};
} }
ErrorOr<void> Profile::convert_image(Gfx::Bitmap& bitmap, Profile const& source_profile) const ErrorOr<void> Profile::convert_image(Gfx::Bitmap& bitmap, Profile const& source_profile) const
{ {
if (is_matrix_matrix_conversion(source_profile)) if (auto map = matrix_matrix_conversion(source_profile); map.has_value())
return convert_image_matrix_matrix(bitmap, source_profile); return convert_image_matrix_matrix(bitmap, map.value());
for (auto& pixel : bitmap) { for (auto& pixel : bitmap) {
u8 rgb[] = { Color::from_argb(pixel).red(), Color::from_argb(pixel).green(), Color::from_argb(pixel).blue() }; u8 rgb[] = { Color::from_argb(pixel).red(), Color::from_argb(pixel).green(), Color::from_argb(pixel).blue() };

View file

@ -149,6 +149,29 @@ struct ProfileHeader {
Optional<Crypto::Hash::MD5::DigestType> id; Optional<Crypto::Hash::MD5::DigestType> 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<Profile> { class Profile : public RefCounted<Profile> {
public: public:
static ErrorOr<NonnullRefPtr<Profile>> try_load_from_externally_owned_memory(ReadonlyBytes); static ErrorOr<NonnullRefPtr<Profile>> try_load_from_externally_owned_memory(ReadonlyBytes);
@ -225,6 +248,8 @@ public:
XYZ const& green_matrix_column() const; XYZ const& green_matrix_column() const;
XYZ const& blue_matrix_column() const; XYZ const& blue_matrix_column() const;
Optional<MatrixMatrixConversion> matrix_matrix_conversion(Profile const& source_profile) const;
private: private:
Profile(ProfileHeader const& header, OrderedHashMap<TagSignature, NonnullRefPtr<TagData>> tag_table) Profile(ProfileHeader const& header, OrderedHashMap<TagSignature, NonnullRefPtr<TagData>> tag_table)
: m_header(header) : m_header(header)
@ -248,8 +273,7 @@ private:
// FIXME: The color conversion stuff should be in some other class. // FIXME: The color conversion stuff should be in some other class.
ErrorOr<FloatVector3> to_pcs_a_to_b(TagData const& tag_data, ReadonlyBytes) const; ErrorOr<FloatVector3> to_pcs_a_to_b(TagData const& tag_data, ReadonlyBytes) const;
ErrorOr<void> from_pcs_b_to_a(TagData const& tag_data, FloatVector3 const&, Bytes) const; ErrorOr<void> from_pcs_b_to_a(TagData const& tag_data, FloatVector3 const&, Bytes) const;
bool is_matrix_matrix_conversion(Profile const& source_profile) const; ErrorOr<void> convert_image_matrix_matrix(Gfx::Bitmap&, MatrixMatrixConversion const&) const;
ErrorOr<void> convert_image_matrix_matrix(Gfx::Bitmap& bitmap, Profile const& source_profile) const;
// Cached values. // Cached values.
bool m_cached_has_any_a_to_b_tag { false }; bool m_cached_has_any_a_to_b_tag { false };