mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
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:
parent
9f7b33c31f
commit
b2a1130556
Notes:
sideshowbarker
2024-07-17 07:31:31 +09:00
Author: https://github.com/nico Commit: https://github.com/SerenityOS/serenity/commit/b2a1130556 Pull-request: https://github.com/SerenityOS/serenity/pull/22155
4 changed files with 40 additions and 10 deletions
|
@ -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]);
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue