mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 08:57:34 +00:00
ICC: Implement TRC inversion in from_pcs for parametric curves
This commit is contained in:
parent
4169c94ebe
commit
9bd35fda56
3 changed files with 71 additions and 8 deletions
|
@ -158,6 +158,10 @@ TEST_CASE(from_pcs)
|
||||||
{
|
{
|
||||||
auto sRGB = MUST(Gfx::ICC::sRGB());
|
auto sRGB = MUST(Gfx::ICC::sRGB());
|
||||||
|
|
||||||
|
auto sRGB_curve_pointer = MUST(Gfx::ICC::sRGB_curve());
|
||||||
|
VERIFY(sRGB_curve_pointer->type() == Gfx::ICC::ParametricCurveTagData::Type);
|
||||||
|
auto const& sRGB_curve = static_cast<Gfx::ICC::ParametricCurveTagData const&>(*sRGB_curve_pointer);
|
||||||
|
|
||||||
auto sRGB_from_xyz = [&sRGB](FloatVector3 const& XYZ) {
|
auto sRGB_from_xyz = [&sRGB](FloatVector3 const& XYZ) {
|
||||||
u8 rgb[3];
|
u8 rgb[3];
|
||||||
MUST(sRGB->from_pcs(XYZ, rgb));
|
MUST(sRGB->from_pcs(XYZ, rgb));
|
||||||
|
@ -185,7 +189,16 @@ TEST_CASE(from_pcs)
|
||||||
EXPECT_EQ(sRGB_from_xyz(g_xyz + b_xyz), Color(0, 255, 255));
|
EXPECT_EQ(sRGB_from_xyz(g_xyz + b_xyz), Color(0, 255, 255));
|
||||||
EXPECT_EQ(sRGB_from_xyz(r_xyz + g_xyz + b_xyz), Color(255, 255, 255));
|
EXPECT_EQ(sRGB_from_xyz(r_xyz + g_xyz + b_xyz), Color(255, 255, 255));
|
||||||
|
|
||||||
// FIXME: Implement and test the inverse curve transform.
|
// Test the inverse curve transform.
|
||||||
|
float f64 = sRGB_curve.evaluate(64 / 255.f);
|
||||||
|
EXPECT_EQ(sRGB_from_xyz((r_xyz + g_xyz + b_xyz) * f64), Color(64, 64, 64));
|
||||||
|
|
||||||
|
float f128 = sRGB_curve.evaluate(128 / 255.f);
|
||||||
|
EXPECT_EQ(sRGB_from_xyz((r_xyz + g_xyz + b_xyz) * f128), Color(128, 128, 128));
|
||||||
|
|
||||||
|
// Test for curve and matrix combined.
|
||||||
|
float f192 = sRGB_curve.evaluate(192 / 255.f);
|
||||||
|
EXPECT_EQ(sRGB_from_xyz(r_xyz * f64 + g_xyz * f128 + b_xyz * f192), Color(64, 128, 192));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE(to_lab)
|
TEST_CASE(to_lab)
|
||||||
|
|
|
@ -1551,6 +1551,7 @@ ErrorOr<void> Profile::from_pcs(FloatVector3 const& pcs, Bytes color) const
|
||||||
// greenTRC^-1, and blueTRC^-1 function is undefined. If a one-dimensional curve is constant, the curve cannot be
|
// greenTRC^-1, and blueTRC^-1 function is undefined. If a one-dimensional curve is constant, the curve cannot be
|
||||||
// inverted."
|
// inverted."
|
||||||
|
|
||||||
|
// Convert from XYZ to linear rgb.
|
||||||
// FIXME: Inverting matrix and curve on every call to this function is very inefficient.
|
// FIXME: Inverting matrix and curve on every call to this function is very inefficient.
|
||||||
auto const& red_matrix_column = this->red_matrix_column();
|
auto const& red_matrix_column = this->red_matrix_column();
|
||||||
auto const& green_matrix_column = this->green_matrix_column();
|
auto const& green_matrix_column = this->green_matrix_column();
|
||||||
|
@ -1561,13 +1562,22 @@ ErrorOr<void> Profile::from_pcs(FloatVector3 const& pcs, Bytes color) const
|
||||||
red_matrix_column.Y, green_matrix_column.Y, blue_matrix_column.Y,
|
red_matrix_column.Y, green_matrix_column.Y, blue_matrix_column.Y,
|
||||||
red_matrix_column.Z, green_matrix_column.Z, blue_matrix_column.Z
|
red_matrix_column.Z, green_matrix_column.Z, blue_matrix_column.Z
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!forward_matrix.is_invertible())
|
if (!forward_matrix.is_invertible())
|
||||||
return Error::from_string_literal("ICC::Profile::from_pcs: matrix not invertible");
|
return Error::from_string_literal("ICC::Profile::from_pcs: matrix not invertible");
|
||||||
auto matrix = forward_matrix.inverse();
|
auto matrix = forward_matrix.inverse();
|
||||||
|
|
||||||
FloatVector3 linear_rgb = matrix * pcs;
|
FloatVector3 linear_rgb = matrix * pcs;
|
||||||
|
|
||||||
|
auto evaluate_curve_inverse = [this](TagSignature curve_tag, float f) {
|
||||||
|
auto const& trc = *m_tag_table.get(curve_tag).value();
|
||||||
|
VERIFY(trc.type() == CurveTagData::Type || trc.type() == ParametricCurveTagData::Type);
|
||||||
|
if (trc.type() == CurveTagData::Type) {
|
||||||
|
TODO();
|
||||||
|
return 0.f;
|
||||||
|
}
|
||||||
|
return static_cast<ParametricCurveTagData const&>(trc).evaluate_inverse(f);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert from linear rgb to device rgb.
|
||||||
// See equations (F.8) - (F.16) above.
|
// See equations (F.8) - (F.16) above.
|
||||||
// FIXME: The spec says to do this, but it loses information. Color.js returns unclamped
|
// FIXME: The spec says to do this, but it loses information. Color.js returns unclamped
|
||||||
// values instead (...but how do those make it through the TRC?) and has a separate
|
// values instead (...but how do those make it through the TRC?) and has a separate
|
||||||
|
@ -1576,12 +1586,13 @@ ErrorOr<void> Profile::from_pcs(FloatVector3 const& pcs, Bytes color) const
|
||||||
// (For LUT profiles, I think the gamut mapping is baked into the BToA* data in the profile (?).
|
// (For LUT profiles, I think the gamut mapping is baked into the BToA* data in the profile (?).
|
||||||
// But for matrix profiles, it'd have to be done in code.)
|
// But for matrix profiles, it'd have to be done in code.)
|
||||||
linear_rgb.clamp(0.f, 1.f);
|
linear_rgb.clamp(0.f, 1.f);
|
||||||
|
float device_r = evaluate_curve_inverse(redTRCTag, linear_rgb[0]);
|
||||||
|
float device_g = evaluate_curve_inverse(greenTRCTag, linear_rgb[1]);
|
||||||
|
float device_b = evaluate_curve_inverse(blueTRCTag, linear_rgb[2]);
|
||||||
|
|
||||||
// FIXME: Implement curve inversion and apply inverse curve transform here.
|
color[0] = round(255 * device_r);
|
||||||
|
color[1] = round(255 * device_g);
|
||||||
color[0] = round(255 * linear_rgb[0]);
|
color[2] = round(255 * device_b);
|
||||||
color[1] = round(255 * linear_rgb[1]);
|
|
||||||
color[2] = round(255 * linear_rgb[2]);
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -739,6 +739,45 @@ public:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// y must be in [0..1].
|
||||||
|
float evaluate_inverse(float y) const
|
||||||
|
{
|
||||||
|
VERIFY(0.f <= y && y <= 1.f);
|
||||||
|
|
||||||
|
// See "Recommendations" section in https://www.color.org/whitepapers/ICC_White_Paper35-Use_of_the_parametricCurveType.pdf
|
||||||
|
// Requirements for the curve to be non-decreasing:
|
||||||
|
// * γ > 0
|
||||||
|
// * a > 0 for types 1-4
|
||||||
|
// * c ≥ 0 for types 3 and 4
|
||||||
|
//
|
||||||
|
// Types 3 and 4 additionally require:
|
||||||
|
// To prevent negative discontinuities:
|
||||||
|
// * cd ≤ (ad + b) for type 3
|
||||||
|
// * cd + f ≤ (ad + b)^γ + e for type 4
|
||||||
|
// To prevent complex numbers:
|
||||||
|
// * ad + b ≥ 0
|
||||||
|
// FIXME: Check these requirements somewhere.
|
||||||
|
|
||||||
|
switch (function_type()) {
|
||||||
|
case FunctionType::Type0:
|
||||||
|
return powf(y, 1.f / (float)g());
|
||||||
|
case FunctionType::Type1:
|
||||||
|
return (powf(y, 1.f / (float)g()) - (float)b()) / (float)a();
|
||||||
|
case FunctionType::Type2:
|
||||||
|
// Only defined for Y >= c, so I suppose this requires c <= 0 in practice (?).
|
||||||
|
return (powf(y - (float)c(), 1.f / (float)g()) - (float)b()) / (float)a();
|
||||||
|
case FunctionType::Type3:
|
||||||
|
if (y >= (float)c() * (float)d())
|
||||||
|
return (powf(y, 1.f / (float)g()) - (float)b()) / (float)a();
|
||||||
|
return y / (float)c();
|
||||||
|
case FunctionType::Type4:
|
||||||
|
if (y >= (float)c() * (float)d())
|
||||||
|
return (powf(y - (float)e(), 1.f / (float)g()) - (float)b()) / (float)a();
|
||||||
|
return (y - (float)f()) / (float)c();
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FunctionType m_function_type;
|
FunctionType m_function_type;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue