From e15097ffaeed24b3bf76be7ddd717b0f483b8717 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Sat, 2 Feb 2013 20:23:03 -0500 Subject: [PATCH] added support for the IPT color space by Ebner and Fairchild --- include/mitsuba/core/spectrum.h | 30 ++++++++++++++++++++- src/libcore/spectrum.cpp | 46 +++++++++++++++++++++++++++++++++ src/libpython/core.cpp | 11 +++++++- 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/include/mitsuba/core/spectrum.h b/include/mitsuba/core/spectrum.h index 9d1095d3..cd612a7c 100644 --- a/include/mitsuba/core/spectrum.h +++ b/include/mitsuba/core/spectrum.h @@ -718,7 +718,7 @@ public: /** * \brief Convert XYZ tristimulus into a plausible spectral - * power distribution + * reflectance or spectral power distribution * * The \ref EConversionIntent parameter can be used to provide more * information on how to solve this highly under-constrained problem. @@ -727,6 +727,34 @@ public: void fromXYZ(Float x, Float y, Float z, EConversionIntent intent = EReflectance); + /** + * \brief Convert from a spectral power distribution to + * the perceptually uniform IPT color space by Ebner and Fairchild + * + * This is useful e.g. for computing color differences. + * \c I encodes intensity, \c P (protan) roughly encodes + * red-green color opponency, and \c T (tritan) encodes + * blue-red color opponency. For normalized input, the + * range of attainable values is given by + * \f I\in $[0,1], P,T\in [-1,1]\f$. + * + * In the Python API, this function returns a 3-tuple + * with the result of the operation. + */ + void toIPT(Float &I, Float &P, Float &T) const; + + /** + * \brief Convert a color value represented in the IPT + * space into a plausible spectral reflectance or + * spectral power distribution. + * + * The \ref EConversionIntent parameter can be used to provide more + * information on how to solve this highly under-constrained problem. + * The default is \ref EReflectance. + */ + void fromIPT(Float I, Float P, Float T, + EConversionIntent intent = EReflectance); + #if SPECTRUM_SAMPLES == 3 /** * \brief Convert to linear RGB diff --git a/src/libcore/spectrum.cpp b/src/libcore/spectrum.cpp index 2a8b97a0..afefc2b8 100644 --- a/src/libcore/spectrum.cpp +++ b/src/libcore/spectrum.cpp @@ -262,6 +262,15 @@ void Spectrum::toLinearRGB(Float &r, Float &g, Float &b) const { b = 0.055648f * x + -0.204043f * y + 1.057311f * z; } +void Spectrum::fromXYZ(Float x, Float y, Float z, EConversionIntent intent) { + /* Convert from XYZ tristimulus values to ITU-R Rec. BT.709 linear RGB */ + Float r = 3.240479f*x - 1.537150f*y - 0.498535f*z; + Float g = -0.969256f*x + 1.875991f*y + 0.041556f*z; + Float b = 0.055648f*x - 0.204043f*y + 1.057311f*z; + + fromLinearRGB(r, g, b, intent); +} + void Spectrum::fromLinearRGB(Float r, Float g, Float b, EConversionIntent intent) { Spectrum result(0.0f); @@ -342,6 +351,43 @@ void Spectrum::fromLinearRGB(Float r, Float g, Float b, EConversionIntent intent #endif +void Spectrum::toIPT(Float &I, Float &P, Float &T) const { + /* Based on "High Dynamic Range Imaging" by Reinhard et al. */ + Float X, Y, Z; + toXYZ(X, Y, Z); + + /* Convert to LMS cone excitation space (assumes D65 illum.) */ + Float L = 0.4002 * X + 0.7075 * Y - 0.0807 * Z; + Float M = - 0.2280 * X + 1.1500 * Y + 0.0612 * Z; + Float S = 0.0000 * X + 0.0000 * Y + 0.9184 * Z; + + /* Nonlinear transformation for perceptual uniformity */ + Float Lp = math::signum(L) * std::pow(std::abs(L), (Float) 0.43); + Float Mp = math::signum(M) * std::pow(std::abs(M), (Float) 0.43); + Float Sp = math::signum(S) * std::pow(std::abs(S), (Float) 0.43); + + /* Second linear transformation to get to IPT space */ + I = 0.4000 * Lp + 0.4000 * Mp + 0.2000 * Sp; + P = 4.4550 * Lp - 4.8510 * Mp + 0.3960 * Sp; + T = 0.8056 * Lp + 0.3572 * Mp - 1.1628 * Sp; +} + +void Spectrum::fromIPT(Float I, Float P, Float T, EConversionIntent intent) { + Float Lp = 1.0000 * I + 0.0976 * P + 0.2052 * T; + Float Mp = 1.0000 * I - 0.1139 * P + 0.1332 * T; + Float Sp = 1.0000 * I + 0.0326 * P - 0.6769 * T; + + Float L = math::signum(Lp) * std::pow(std::abs(Lp), (Float) (1.0/0.43)); + Float M = math::signum(Mp) * std::pow(std::abs(Mp), (Float) (1.0/0.43)); + Float S = math::signum(Sp) * std::pow(std::abs(Sp), (Float) (1.0/0.43)); + + Float X = 1.8502 * L - 1.1383 * M + 0.2384 * S; + Float Y = 0.3668 * L + 0.6439 * M - 0.0107 * S; + Float Z = 0.0000 * L + 0.0000 * M + 1.0889 * S; + + fromXYZ(X, Y, Z, intent); +} + inline Float toSRGBComponent(Float value) { if (value <= (Float) 0.0031308) return (Float) 12.92 * value; diff --git a/src/libpython/core.cpp b/src/libpython/core.cpp index f65c7164..0c3a730e 100644 --- a/src/libpython/core.cpp +++ b/src/libpython/core.cpp @@ -347,6 +347,12 @@ static bp::tuple spectrum_toXYZ(const Spectrum &s) { return bp::make_tuple(x, y, z); } +static bp::tuple spectrum_toIPT(const Spectrum &s) { + Float I, P, T; + s.toIPT(I, P, T); + return bp::make_tuple(I, P, T); +} + void aabb_expandby_aabb(AABB *aabb, const AABB &aabb2) { aabb->expandBy(aabb2); } void aabb_expandby_point(AABB *aabb, const Point &p) { aabb->expandBy(p); } Float aabb_distanceto_aabb(AABB *aabb, const AABB &aabb2) { return aabb->distanceTo(aabb2); } @@ -526,9 +532,10 @@ Transform transform_glOrthographic2(Float clipLeft, Float clipRight, clipBottom, clipTop, clipNear, clipFar); } - BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(fromLinearRGB_overloads, fromLinearRGB, 3, 4) BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(fromXYZ_overloads, fromXYZ, 3, 4) +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(fromIPT_overloads, fromIPT, 3, 4) + void export_core() { bp::to_python_converter(); @@ -981,6 +988,8 @@ void export_core() { .def("getLuminance", &Spectrum::getLuminance) .def("fromXYZ", &Spectrum::fromXYZ, fromXYZ_overloads()) .def("toXYZ", &spectrum_toXYZ) + .def("fromIPT", &Spectrum::fromIPT, fromIPT_overloads()) + .def("toIPT", &spectrum_toIPT) .def("fromLinearRGB", &Spectrum::fromLinearRGB, fromLinearRGB_overloads()) .def("toLinearRGB", &spectrum_toLinearRGB) .def("fromSRGB", &Spectrum::fromSRGB)