LibGfx/ICC: Implement conversion between different connection spaces

If one profile uses PCSXYZ and the other PCSLAB as connection space,
we now do the necessary XYZ/LAB conversion.

With this and the previous commits, we can now convert from profiles
that use PCSLAB with mAB, such as stress.jpeg from
https://littlecms.com/blog/2020/09/09/browser-check/ :

    % Build/lagom/icc --name sRGB --reencode-to serenity-sRGB.icc
    % Build/lagom/bin/image -o out.png \
        --convert-to-color-profile serenity-sRGB.icc \
        ~/src/jpegfiles/stress.jpeg
This commit is contained in:
Nico Weber 2023-12-03 21:40:47 -05:00 committed by Sam Atkins
parent 9f7b33c31f
commit b2a1130556
Notes: sideshowbarker 2024-07-17 07:31:31 +09:00
4 changed files with 40 additions and 10 deletions

View file

@ -184,7 +184,10 @@ TEST_CASE(from_pcs)
auto sRGB_from_xyz = [&sRGB](FloatVector3 const& XYZ) {
u8 rgb[3];
MUST(sRGB->from_pcs(XYZ, rgb));
// The first parameter, the source profile, is used to check if the PCS data is XYZ or LAB,
// and what the source whitepoint is. We just need any profile with an XYZ PCS space,
// so passing sRGB as source profile too is fine.
MUST(sRGB->from_pcs(sRGB, XYZ, rgb));
return Color(rgb[0], rgb[1], rgb[2]);
};

View file

@ -1376,6 +1376,27 @@ static FloatVector3 lab_from_xyz(FloatVector3 xyz, XYZ white_point)
return { L, a, b };
}
static FloatVector3 xyz_from_lab(FloatVector3 lab, XYZ white_point)
{
// Inverse of lab_from_xyz().
auto L_star = lab[0];
auto a_star = lab[1];
auto b_star = lab[2];
auto L = (L_star + 16) / 116 + a_star / 500; // f(x)
auto M = (L_star + 16) / 116; // f(y)
auto N = (L_star + 16) / 116 - b_star / 200; // f(z)
// Inverse of f in lab_from_xyz().
auto g = [](float x) {
if (x >= 6.0f / 29.0f)
return powf(x, 3);
return (x - 4.0f / 29.0f) * (3 * powf(6.f / 29.f, 2));
};
return { white_point.X * g(L), white_point.Y * g(M), white_point.Z * g(N) };
}
static TagSignature backward_transform_tag_for_rendering_intent(RenderingIntent rendering_intent)
{
// ICCv4, Table 25 — Profile type/profile tag and defined rendering intents
@ -1408,8 +1429,19 @@ ErrorOr<void> Profile::from_pcs_b_to_a(TagData const& tag_data, FloatVector3 con
VERIFY_NOT_REACHED();
}
ErrorOr<void> Profile::from_pcs(FloatVector3 const& pcs, Bytes color) const
ErrorOr<void> Profile::from_pcs(Profile const& source_profile, FloatVector3 pcs, Bytes color) const
{
if (source_profile.connection_space() != connection_space()) {
if (source_profile.connection_space() == ColorSpace::PCSLAB) {
VERIFY(connection_space() == ColorSpace::PCSXYZ);
pcs = xyz_from_lab(pcs, source_profile.pcs_illuminant());
} else {
VERIFY(source_profile.connection_space() == ColorSpace::PCSXYZ);
VERIFY(connection_space() == ColorSpace::PCSLAB);
pcs = lab_from_xyz(pcs, pcs_illuminant());
}
}
// See `to_pcs()` for spec links.
// This function is very similar, but uses BToAn instead of AToBn for LUT profiles,
// and an inverse transform for matrix profiles.
@ -1543,15 +1575,10 @@ ErrorOr<CIELAB> Profile::to_lab(ReadonlyBytes color) const
ErrorOr<void> 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));
TRY(from_pcs(source_profile, pcs, rgb));
pixel = Color(rgb[0], rgb[1], rgb[2], Color::from_argb(pixel).alpha()).value();
}

View file

@ -213,7 +213,7 @@ public:
// Converts from the profile connection space to an 8-bits-per-channel color.
// The notes on `to_pcs()` apply to this too.
ErrorOr<void> from_pcs(FloatVector3 const&, Bytes) const;
ErrorOr<void> from_pcs(Profile const& source_profile, FloatVector3, Bytes) const;
ErrorOr<CIELAB> to_lab(ReadonlyBytes) const;

View file

@ -506,7 +506,7 @@ PDFErrorOr<Color> ICCBasedColorSpace::color(ReadonlySpan<Value> arguments) const
auto pcs = TRY(m_profile->to_pcs(bytes));
Array<u8, 3> output;
TRY(s_srgb_profile->from_pcs(pcs, output.span()));
TRY(s_srgb_profile->from_pcs(m_profile, pcs, output.span()));
return Color(output[0], output[1], output[2]);
}