From 9c3e36e72cdd569f866aa7477a0f2e8dee901081 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 1 May 2023 20:52:21 -0400 Subject: [PATCH] ICC: Implement TRC inversion in from_pcs for point curves This allows converting to a color space that uses a non-parametric curve, for example: Build/lagom/image -o foo.png \ --convert-to-color-profile .../profiles/sRGB-v2-micro.icc \ input.jpg ...where profiles/sRGB-v2-micro.icc is from https://github.com/saucecontrol/Compact-ICC-Profiles/ (Parametric curves are new in ICC v4, which means all v2 profiles use point curves.) --- Userland/Libraries/LibGfx/ICC/Profile.cpp | 6 ++-- Userland/Libraries/LibGfx/ICC/TagTypes.h | 35 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/Userland/Libraries/LibGfx/ICC/Profile.cpp b/Userland/Libraries/LibGfx/ICC/Profile.cpp index 5fa6b66621..94c337315d 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.cpp +++ b/Userland/Libraries/LibGfx/ICC/Profile.cpp @@ -1570,10 +1570,8 @@ ErrorOr Profile::from_pcs(FloatVector3 const& pcs, Bytes color) const 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; - } + if (trc.type() == CurveTagData::Type) + return static_cast(trc).evaluate_inverse(f); return static_cast(trc).evaluate_inverse(f); }; diff --git a/Userland/Libraries/LibGfx/ICC/TagTypes.h b/Userland/Libraries/LibGfx/ICC/TagTypes.h index 8c57f0075a..751207f8cf 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.h +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.h @@ -179,6 +179,41 @@ public: return mix(values()[i] / 65535.f, values()[i + 1] / 65535.f, f); } + // y must be in [0..1]. + float evaluate_inverse(float y) const + { + VERIFY(0.f <= y && y <= 1.f); + + if (values().is_empty()) + return y; + + if (values().size() == 1) + return powf(y, 1.f / (values()[0] / (float)0x100)); + + // FIXME: Verify somewhere that: + // * values() is non-decreasing + // * values()[0] is 0, values()[values().size() - 1] is 65535 + + // FIXME: Use binary search. + size_t n = values().size() - 1; + size_t i = 0; + for (; i < n; ++i) { + if (values()[i] / 65535.f <= y && y <= values()[i + 1] / 65535.f) + break; + } + + float x1 = i / (float)n; + float y1 = values()[i] / 65535.f; + float x2 = (i + 1) / (float)n; + float y2 = values()[i + 1] / 65535.f; + + // Flat line segment? + if (y1 == y2) + return (x1 + x2) / 2; + + return (y - y1) / (y2 - y1) * (x2 - x1) + x1; // Same as `((y - y1) / (y2 - y1) + i) / (float)n` + } + private: Vector m_values; };