From 2b97e5336022841e1ff4d122d5acea4b10dc276f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 16 Dec 2016 13:21:47 +0100 Subject: [PATCH] Math: sRGB support in Color classes. At first I designed a hugely disrupting change that basically deprecated everything related to 8-bit linear RGB colors, but then I took a step back and reconsidered 8-bit linear RGB as a valid use case. The documentation of Color classes, typedefs and literals was clarified to mention that these classes should always represent linear RGB and that 8-bit colors are commonly treated as *not* linear and one should be aware of it. There is now a new Color3::fromSrgb() and Color3::toSrgb() that converts from sRGB representation to a linear RGB usable for calculations and then back. For four-component colors, there is now Color4::fromSrgbAlpha() and Color4::toSrgbAlpha(). Similarly to what OpenGL sRGB behavior is regarding to alpha, the alpha channel is kept linear, that's why I'm also calling it sRGB + alpha instead of sRGBA. Besides that, there are four new literals _srgb, _srgba, _srgbf and _srgbaf that have different semantics to support the sRGB workflow. The 8-bit versions are equivalent to _rgb and _rgba, though they don't return Color3 but a non-color Vector3 to hint that the result is not a linear RGB color. Main purpose of these is documentation. The float versions apply an inverse sRGB curve to the input, returning a linear RGB color. --- doc/matrix-vector.dox | 19 +- src/Magnum/Magnum.h | 18 +- src/Magnum/Math/Color.h | 359 +++++++++++++++++++++++++++-- src/Magnum/Math/Test/ColorTest.cpp | 111 +++++++++ 4 files changed, 485 insertions(+), 22 deletions(-) diff --git a/doc/matrix-vector.dox b/doc/matrix-vector.dox index fef01297e..7f6ca76f6 100644 --- a/doc/matrix-vector.dox +++ b/doc/matrix-vector.dox @@ -146,11 +146,22 @@ auto cyan = Color4::cyan(0.5f, 0.95f); // {0.5f, 1.0f, 1.0f, 0.95f} auto fadedRed = Color3::fromHSV(219.0_degf, 0.50f, 0.57f) @endcode -Lastly, namespace @ref Math::Literals provides convenient `_rgb`/`_rgbf` and -`_rgba`/`_rgbaf` literals for entering colors in hex representation: +Lastly, namespace @ref Math::Literals provides convenient +@link Literals::operator""_rgb() operator""_rgb() @endlink / +@link Literals::operator""_rgbf() operator""_rgbf() @endlink and +@link Literals::operator""_rgba() operator""_rgba() @endlink / +@link Literals::operator""_rgbaf() operator""_rgbaf() @endlink literals for +entering colors in hex representation. These literals assume linear RGB input +and don't do any gamma correction on it. For sRGB input, there is +@link Literals::operator""_srgb() operator""_srgb() @endlink / +@link Literals::operator""_srgba() operator""_srgba() @endlink and +@link Literals::operator""_srgbf() operator""_srgbf() @endlink / +@link Literals::operator""_srgbaf() operator""_srgbaf() @endlink, see their +documentation for more information. @code -Color3ub a = 0x33b27f_rgb; // {0x33, 0xb2, 0x7f} -Color4 b = 0x33b27fcc_rgbaf; // {0.2f, 0.7f, 0.5f, 0.8f} +Color3ub a = 0x33b27f_rgb; // {0x33, 0xb2, 0x7f} +Color4 b = 0x33b27fcc_rgbaf; // {0.2f, 0.7f, 0.5f, 0.8f} +Color4 c = 0x33b27fcc_srgbaf; // {0.0331048f, 0.445201f, 0.212231f, 0.8f} @endcode @section matrix-vector-component-access Accessing matrix and vector components diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index 769173e84..45261347e 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -245,10 +245,24 @@ typedef Math::Color3 Color3; /** @brief Four-component (RGBA) float color */ typedef Math::Color4 Color4; -/** @brief Three-component (RGB) unsigned byte color */ +/** +@brief Three-component (RGB) unsigned byte color + +@attention 8bit-per-channel colors are commonly treated as being in sRGB color + space, which is not directly usable in calculations and has to be converted + to linear RGB first. Use @ref Color3::fromSrgb() and @ref Color3::toSrgb() + for proper sRGB handling. +*/ typedef Math::Color3 Color3ub; -/** @brief Four-component (RGBA) unsigned byte color */ +/** +@brief Four-component (RGBA) unsigned byte color + +@attention 8bit-per-channel colors are commonly treated as being in sRGB color + space, which is not directly usable in calculations and has to be converted + to linear RGB first. Use @ref Color4::fromSrgbAlpha() and + @ref Color4::toSrgbAlpha() for proper sRGB handling. +*/ typedef Math::Color4 Color4ub; /** diff --git a/src/Magnum/Math/Color.h b/src/Magnum/Math/Color.h index cd25f8ad3..f4511cdcf 100644 --- a/src/Magnum/Math/Color.h +++ b/src/Magnum/Math/Color.h @@ -32,6 +32,7 @@ #include #include "Magnum/Math/Functions.h" +#include "Magnum/Math/Matrix.h" #include "Magnum/Math/Vector4.h" namespace Magnum { namespace Math { @@ -123,6 +124,52 @@ template inline typename Color3::Hsv toHsv(typename std::enable_if::FloatingPointType>(normalize::FloatingPointType>>(color)); } +/* sRGB -> RGB conversion */ +template typename std::enable_if::value, Color3>::type fromSrgb(const Vector3& srgb) { + constexpr const T a(0.055); + return lerp(srgb/T(12.92), pow((srgb + Vector3{a})/(T(1.0) + a), T(2.4)), srgb > Vector3(0.04045)); +} +template typename std::enable_if::value, Color4>::type fromSrgbAlpha(const Vector4& srgbAlpha) { + return {fromSrgb(srgbAlpha.rgb()), srgbAlpha.a()}; +} +template inline typename std::enable_if::value, Color3>::type fromSrgb(const Vector3::FloatingPointType>& srgb) { + return denormalize>(fromSrgb::FloatingPointType>(srgb)); +} +template inline typename std::enable_if::value, Color4>::type fromSrgbAlpha(const Vector4::FloatingPointType>& srgbAlpha) { + return {fromSrgb(srgbAlpha.rgb()), denormalize(srgbAlpha.a())}; +} +template inline Color3 fromSrgbIntegral(const Vector3& srgb) { + static_assert(std::is_integral::value, "only conversion from different integral type is supported"); + return fromSrgb(normalize::FloatingPointType>>(srgb)); +} +template inline Color4 fromSrgbAlphaIntegral(const Vector4& srgbAlpha) { + static_assert(std::is_integral::value, "only conversion from different integral type is supported"); + return fromSrgbAlpha(normalize::FloatingPointType>>(srgbAlpha)); +} + +/* RGB -> sRGB conversion */ +template Vector3::FloatingPointType> toSrgb(typename std::enable_if::value, const Color3&>::type rgb) { + constexpr const T a(0.055); + return lerp(rgb*T(12.92), (T(1.0) + a)*pow(rgb, T(1.0)/T(2.4)) - Vector3{a}, rgb > Vector3(0.0031308)); +} +template Vector4::FloatingPointType> toSrgbAlpha(typename std::enable_if::value, const Color4&>::type rgba) { + return {toSrgb(rgba.rgb()), rgba.a()}; +} +template inline Vector3::FloatingPointType> toSrgb(typename std::enable_if::value, const Color3&>::type rgb) { + return toSrgb::FloatingPointType>(normalize::FloatingPointType>>(rgb)); +} +template inline Vector4::FloatingPointType> toSrgbAlpha(typename std::enable_if::value, const Color4&>::type rgba) { + return {toSrgb(rgba.rgb()), normalize::FloatingPointType>(rgba.a())}; +} +template inline Vector3 toSrgbIntegral(const Color3& rgb) { + static_assert(std::is_integral::value, "only conversion from different integral type is supported"); + return denormalize>(toSrgb(rgb)); +} +template inline Vector4 toSrgbAlphaIntegral(const Color4& rgba) { + static_assert(std::is_integral::value, "only conversion from different integral type is supported"); + return denormalize>(toSrgbAlpha(rgba)); +} + /* Value for full channel (1.0f for floats, 255 for unsigned byte) */ template constexpr typename std::enable_if::value, T>::type fullChannel() { return T(1); @@ -137,12 +184,15 @@ template constexpr typename std::enable_if::value, @brief Color in linear RGB color space The class can store either floating-point (normalized) or integral -(denormalized) representation of RGB color. Note that constructor conversion -between different types (like in @ref Vector classes) doesn't do any -(de)normalization, you should use @ref normalize() and +(denormalized) representation of linear RGB color. Colors in sRGB color space +should not be used directly in calculations -- they should be converted to +linear RGB using @ref fromSrgb(), calculation done on the linear representation +and then converted back to sRGB using @ref toSrgb(). + +Note that constructor conversion between different types (like in @ref Vector +classes) doesn't do any (de)normalization, you should use @ref normalize() and @ref denormalize() instead, for example: @code -typedef Color3 Color3ub; Color3 a(1.0f, 0.5f, 0.75f); auto b = denormalize(a); // b == {255, 127, 191} @endcode @@ -152,6 +202,7 @@ is always in range in range @f$ [0.0, 360.0] @f$, saturation and value in range @f$ [0.0, 1.0] @f$. @see @link operator""_rgb() @endlink, @link operator""_rgbf() @endlink, + @link operator""_srgb() @endlink, @link operator""_srgbf() @endlink, @ref Color4, @ref Magnum::Color3, @ref Magnum::Color3ub */ /* Not using template specialization because some internal functions are @@ -277,6 +328,44 @@ template class Color3: public Vector3 { } #endif + /** + * @brief Create linear RGB color from sRGB representation + * @param srgb Color in sRGB color space + * + * Applies inverse sRGB curve onto input, returning the input in linear + * RGB color space with D65 illuminant and 2° standard colorimetric + * observer. @f[ + * \boldsymbol{c}_\mathrm{linear} = \begin{cases} + * \dfrac{\boldsymbol{c}_\mathrm{sRGB}}{12.92}, & \boldsymbol{c}_\mathrm{sRGB} \le 0.04045 \\ + * \left( \dfrac{\boldsymbol{c}_\mathrm{sRGB} + a}{1 + a} \right)^{2.4}, & \boldsymbol{c}_\mathrm{sRGB} > 0.04045 + * \end{cases} + * @f] + * @see @link operator""_srgbf() @endlink, @ref toSrgb() + */ + /* Input is a Vector3 to hint that it doesn't have any (additive, + multiplicative) semantics of a linear RGB color */ + static Color3 fromSrgb(const Vector3& srgb) { + return Implementation::fromSrgb(srgb); + } + + /** @overload + * @brief Create linear RGB color from integral sRGB representation + * @param srgb Color in sRGB color space + * + * Useful in cases where you have for example an 8-bit sRGB + * representation and want to create a floating-point linear RGB color + * out of it: + * @code + * Math::Vector3 srgb; + * auto rgb = Color3::fromSrgb(srgb); + * @endcode + */ + /* Input is a Vector3 to hint that it doesn't have any (additive, + multiplicative) semantics of a linear RGB color */ + template static Color3 fromSrgb(const Vector3& srgb) { + return Implementation::fromSrgbIntegral(srgb); + } + /** * @brief Default constructor * @@ -388,6 +477,37 @@ template class Color3: public Vector3 { return Implementation::value(*this); } + /** + * @brief Convert to sRGB representation + * + * Assuming the color is in linear RGB with D65 illuminant and 2° + * standard colorimetric observer, applies sRGB curve onto it, + * returning the color represented in sRGB color space: @f[ + * \boldsymbol{c}_\mathrm{sRGB} = \begin{cases} + * 12.92C_\mathrm{linear}, & \boldsymbol{c}_\mathrm{linear} \le 0.0031308 \\ + * (1 + a) \boldsymbol{c}_\mathrm{linear}^{1/2.4}-a, & \boldsymbol{c}_\mathrm{linear} > 0.0031308 + * \end{cases} + * @f] + * @see @ref fromSrgb() + */ + Vector3 toSrgb() const { + return Implementation::toSrgb(*this); + } + + /** @overload + * @brief Convert to integral sRGB representation + * + * Useful in cases where you have a floating-point linear RGB color and + * want to create for example an 8-bit sRGB representation out of it: + * @code + * Color3 color; + * Math::Vector3 srgb = color.toSrgb(); + * @endcode + */ + template Vector3 toSrgb() const { + return Implementation::toSrgbIntegral(*this); + } + MAGNUM_VECTOR_SUBCLASS_IMPLEMENTATION(3, Color3) }; @@ -400,6 +520,7 @@ MAGNUM_VECTORn_OPERATOR_IMPLEMENTATION(3, Color3) See @ref Color3 for more information. @see @link operator""_rgba() @endlink, @link operator""_rgbaf() @endlink, + @link operator""_srgba() @endlink, @link operator""_srgbaf() @endlink, @ref Magnum::Color4, @ref Magnum::Color4ub */ /* Not using template specialization because some internal functions are @@ -517,6 +638,76 @@ class Color4: public Vector4 { } #endif + /** + * @brief Create linear RGBA color from sRGB + alpha representation + * @param srgbAlpha Color in sRGB color space with linear alpha + * + * Applies inverse sRGB curve onto RGB channels of the input, alpha + * channel is assumed to be linear. See @ref Color3::fromSrgb() for + * more information. + * @see @link operator""_srgbaf @endlink, @ref toSrgbAlpha() + */ + /* Input is a Vector4 to hint that it doesn't have any (additive, + multiplicative) semantics of a linear RGB color */ + static Color4 fromSrgbAlpha(const Vector4& srgbAlpha) { + return {Implementation::fromSrgbAlpha(srgbAlpha)}; + } + + /** + * @brief Create linear RGBA color from sRGB representation + * @param srgb Color in sRGB color space + * @param a Alpha value, defaults to `1.0` for floating-point + * types and maximum positive value for integral types. + * + * Applies inverse sRGB curve onto RGB channels of the input. Alpha + * value is taken as-is. See @ref Color3::fromSrgb() for more + * information. + * @see @link operator""_srgbaf @endlink, @ref toSrgbAlpha() + */ + /* Input is a Vector3 to hint that it doesn't have any (additive, + multiplicative) semantics of a linear RGB color */ + static Color4 fromSrgb(const Vector3& srgb, T a = Implementation::fullChannel()) { + return {Implementation::fromSrgb(srgb), a}; + } + + /** @overload + * @brief Create linear RGB color from integral sRGB + alpha representation + * @param srgbAlpha Color in sRGB color space with linear alpha + * + * Useful in cases where you have for example an 8-bit sRGB + alpha + * representation and want to create a floating-point linear RGBA color + * out of it: + * @code + * Math::Vector4 srgbAlpha; + * auto rgba = Color4::fromSrgbAlpha(srgbAlpha); + * @endcode + */ + /* Input is a Vector4 to hint that it doesn't have any (additive, + multiplicative) semantics of a linear RGB color */ + template static Color4 fromSrgbAlpha(const Vector4& srgbAlpha) { + return {Implementation::fromSrgbAlphaIntegral(srgbAlpha)}; + } + + /** @overload + * @brief Create linear RGB color from integral sRGB representation + * @param srgb Color in sRGB color space + * @param a Alpha value, defaults to `1.0` for floating-point + * types and maximum positive value for integral types. + * + * Useful in cases where you have for example an 8-bit sRGB + * representation and want to create a floating-point linear RGBA color + * out of it: + * @code + * Math::Vector3 srgb; + * auto rgba = Color4::fromSrgb(srgb); + * @endcode + */ + /* Input is a Vector3 to hint that it doesn't have any (additive, + multiplicative) semantics of a linear RGB color */ + template static Color4 fromSrgb(const Vector3& srgb, T a = Implementation::fullChannel()) { + return {Implementation::fromSrgbIntegral(srgb), a}; + } + /** * @brief Default constructor * @@ -629,6 +820,35 @@ class Color4: public Vector4 { return Implementation::value(Vector4::rgb()); } + /** + * @brief Convert to sRGB + alpha representation + * + * Assuming the color is in linear RGB, applies sRGB curve onto the RGB + * channels, returning the color represented in sRGB color space. Alpha + * channel is kept linear. See @ref Color3::toSrgb() for more + * information. + * + * @see @ref fromSrgbAlpha() + */ + Vector4 toSrgbAlpha() const { + return Implementation::toSrgbAlpha(*this); + } + + /** @overload + * @brief Convert to integral sRGB + alpha representation + * + * Useful in cases where you have a floating-point linear RGBA color + * and want to create for example an 8-bit sRGB + alpha representation + * out of it: + * @code + * Color4 color; + * Math::Vector4 srgbAlpha = color.toSrgbAlpha(); + * @endcode + */ + template Vector4 toSrgbAlpha() const { + return Implementation::toSrgbAlphaIntegral(*this); + } + MAGNUM_VECTOR_SUBCLASS_IMPLEMENTATION(4, Color4) }; @@ -639,57 +859,164 @@ MAGNUM_VECTORn_OPERATOR_IMPLEMENTATION(4, Color4) namespace Literals { /** @relatesalso Magnum::Math::Color3 -@brief 8bit-per-channel RGB literal +@brief 8bit-per-channel linear RGB literal -Example usage: +Unpacks the literal into three 8-bit values. Example usage: @code -Color3ub a = 0x33b27f_rgb; // {0x33, 0xb2, 0x7f} +Color3ub a = 0x33b27f_rgb; // {0x33, 0xb2, 0x7f} @endcode + +@attention 8bit-per-channel colors are commonly treated as being in sRGB color + space, which is not directly usable in calculations and has to be converted + to linear RGB first. To convey such meaning, use the @link operator""_srgb() @endlink + literal instead. + @see @link operator""_rgba() @endlink, @link operator""_rgbf() @endlink */ constexpr Color3 operator "" _rgb(unsigned long long value) { return {UnsignedByte(value >> 16), UnsignedByte(value >> 8), UnsignedByte(value)}; } +/** @relatesalso Magnum::Math::Color3 +@brief 8bit-per-channel sRGB literal + +Unpacks the literal into three 8-bit values without any colorspace conversion. +Behaves identically to @link operator""_rgb() @endlink though it doesn't +return a @ref Color3 type to indicate that the resulting value is not linear +RGB. Use this literal to document that given value is in sRGB. Example usage: +@code +Math::Vector3 a = 0x33b27f_srgb; // {0x33, 0xb2, 0x7f} +@endcode + +@attention Note that colors in sRGB representation should not be used directly + in calculations -- they should be converted to linear RGB, calculation done + on the linear representation and then converted back to sRGB. Use the + @link operator""_srgbf() @endlink literal if you want to get a linear RGB + representation directly or convert the value using @ref Color3::fromSrgb(). + +@see @link operator""_srgba() @endlink, @link operator""_rgb() @endlink +*/ +/* Output is a Vector3 to hint that it doesn't have any (additive, + multiplicative) semantics of a linear RGB color */ +constexpr Vector3 operator "" _srgb(unsigned long long value) { + return {UnsignedByte(value >> 16), UnsignedByte(value >> 8), UnsignedByte(value)}; +} + /** @relatesalso Magnum::Math::Color4 -@brief 8bit-per-channel RGBA literal +@brief 8bit-per-channel linear RGBA literal -Example usage: +Unpacks the literal into four 8-bit values. Example usage: @code Color4ub a = 0x33b27fcc_rgba; // {0x33, 0xb2, 0x7f, 0xcc} @endcode + +@attention 8bit-per-channel colors are commonly treated as being in sRGB color + space, which is not directly usable in calculations and has to be converted + to linear RGB first. To convey such meaning, use the @link operator""_srgba() @endlink + literal instead. + @see @link operator""_rgb() @endlink, @link operator""_rgbaf() @endlink */ constexpr Color4 operator "" _rgba(unsigned long long value) { return {UnsignedByte(value >> 24), UnsignedByte(value >> 16), UnsignedByte(value >> 8), UnsignedByte(value)}; } +/** @relatesalso Magnum::Math::Color4 +@brief 8bit-per-channel sRGB + alpha literal + +Unpacks the literal into four 8-bit values without any colorspace conversion. +Behaves identically to @link operator""_rgba() @endlink though it doesn't +return a @ref Color4 type to indicate that the resulting value is not linear +RGBA. Use this literal to document that given value is in sRGB + alpha. Example +usage: +@code +Math::Vector4 a = 0x33b27fcc_srgba; // {0x33, 0xb2, 0x7f, 0xcc} +@endcode + +@attention Note that colors in sRGB representation should not be used directly + in calculations -- they should be converted to linear RGB, calculation done + on the linear representation and then converted back to sRGB. Use the + @link operator""_srgbaf() @endlink literal if you want to get a linear RGBA + representation directly or convert the value using @ref Color4::fromSrgbAlpha(). + +@see @link operator""_srgb() @endlink, @link operator""_rgba() @endlink +*/ +/* Output is a Vector3 to hint that it doesn't have any (additive, + multiplicative) semantics of a linear RGB color */ +constexpr Vector4 operator "" _srgba(unsigned long long value) { + return {UnsignedByte(value >> 24), UnsignedByte(value >> 16), UnsignedByte(value >> 8), UnsignedByte(value)}; +} + /** @relatesalso Magnum::Math::Color3 -@brief Float RGB literal +@brief Float linear RGB literal -Example usage: +Unpacks the 8-bit values into three floats. Example usage: @code -Color3 a = 0x33b27f_rgbf; // {0.2f, 0.7f, 0.5f} +Color3 a = 0x33b27f_rgbf; // {0.2f, 0.698039f, 0.498039f} @endcode + +@attention 8bit-per-channel colors are commonly treated as being in sRGB color + space, which is not directly usable in calculations and has to be converted + to linear RGB first. In that case use the @link operator""_srgbf() @endlink + literal instead. + @see @link operator""_rgbaf() @endlink, @link operator""_rgb() @endlink */ inline Color3 operator "" _rgbf(unsigned long long value) { return Math::normalize>(Color3{UnsignedByte(value >> 16), UnsignedByte(value >> 8), UnsignedByte(value)}); } +/** @relatesalso Magnum::Math::Color3 +@brief Float sRGB literal + +Unpacks the 8-bit values into three floats and converts the color space from +sRGB to linear RGB. See @ref Color3::fromSrgb() for more information. Example +usage: +@code +Color3 a = 0x33b27f_srgbf; // {0.0331048f, 0.445201f, 0.212231f} +@endcode +@see @link operator""_srgbaf() @endlink, @link operator""_srgb() @endlink, + @link operator""_rgbf() @endlink +*/ +inline Color3 operator "" _srgbf(unsigned long long value) { + return Color3::fromSrgb({UnsignedByte(value >> 16), UnsignedByte(value >> 8), UnsignedByte(value)}); +} + /** @relatesalso Magnum::Math::Color4 -@brief Float RGBA literal +@brief Float linear RGBA literal -Example usage: +Unpacks the 8-bit values into four floats. Example usage: @code -Color4 a = 0x33b27fcc_rgbaf; // {0.2f, 0.7f, 0.5f, 0.8f} +Color4 a = 0x33b27fcc_rgbaf; // {0.2f, 0.698039f, 0.498039f, 0.8f} @endcode -@see @link operator""_rgbf() @endlink, @link operator""_rgbaf() @endlink + +@attention 8bit-per-channel colors are commonly treated as being in sRGB color + space, which is not directly usable in calculations and has to be converted + to linear RGB first. In that case use the @link operator""_srgbaf() @endlink + literal instead. + +@see @link operator""_rgbf() @endlink, @link operator""_rgba() @endlink */ inline Color4 operator "" _rgbaf(unsigned long long value) { return Math::normalize>(Color4{UnsignedByte(value >> 24), UnsignedByte(value >> 16), UnsignedByte(value >> 8), UnsignedByte(value)}); } +/** @relatesalso Magnum::Math::Color4 +@brief Float sRGB + alpha literal + +Unpacks the 8-bit values into four floats and converts the color space from +sRGB + alpha to linear RGBA. See @ref Color4::fromSrgbAlpha() for more +information. Example usage: +@code +Color4 a = 0x33b27fcc_srgbaf; // {0.0331048f, 0.445201f, 0.212231f, 0.8f} +@endcode +@see @link operator""_srgbf() @endlink, @link operator""_srgba() @endlink, + @link operator""_rgbaf() @endlink +*/ +inline Color4 operator "" _srgbaf(unsigned long long value) { + return Color4::fromSrgbAlpha({UnsignedByte(value >> 24), UnsignedByte(value >> 16), UnsignedByte(value >> 8), UnsignedByte(value)}); +} + } /** diff --git a/src/Magnum/Math/Test/ColorTest.cpp b/src/Magnum/Math/Test/ColorTest.cpp index 82a779060..f370af9b3 100644 --- a/src/Magnum/Math/Test/ColorTest.cpp +++ b/src/Magnum/Math/Test/ColorTest.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include "Magnum/Math/Color.h" @@ -89,6 +90,11 @@ struct ColorTest: Corrade::TestSuite::Tester { void fromHsvHueOverflow(); void fromHsvDefaultAlpha(); + void srgb(); + void fromSrgbDefaultAlpha(); + void srgbMonotonic(); + void srgbLiterals(); + void swizzleType(); void debug(); void debugUb(); @@ -127,6 +133,13 @@ ColorTest::ColorTest() { &ColorTest::fromHsvHueOverflow, &ColorTest::fromHsvDefaultAlpha, + &ColorTest::srgb}); + + addRepeatedTests({&ColorTest::srgbMonotonic}, 65535); + + addTests({&ColorTest::fromSrgbDefaultAlpha, + &ColorTest::srgbLiterals, + &ColorTest::swizzleType, &ColorTest::debug, &ColorTest::debugUb, @@ -533,6 +546,104 @@ void ColorTest::fromHsvDefaultAlpha() { (Math::Color4{7023, 10517, 27983, 65535})); } +void ColorTest::srgb() { + /* Linear start */ + CORRADE_COMPARE(Color3::fromSrgb({0.01292f, 0.01938f, 0.0437875f}), (Color3{0.001f, 0.0015f, 0.0034f})); + CORRADE_COMPARE(Color3::fromSrgb({0.02584f, 0.03876f, 0.0768655f}), (Color3{0.002f, 0.0030f, 0.0068f})); + CORRADE_COMPARE((Color3{0.001f, 0.0015f, 0.0034f}).toSrgb(), (Vector3{0.01292f, 0.01938f, 0.0437875f})); + CORRADE_COMPARE((Color3{0.002f, 0.0030f, 0.0068f}).toSrgb(), (Vector3{0.02584f, 0.03876f, 0.0768655f})); + + /* Power series */ + CORRADE_COMPARE((Color3{0.0005f, 0.135f, 0.278f}).toSrgb(), (Vector3{0.00646f, 0.403027f, 0.563877f})); + CORRADE_COMPARE((Color3{0.0020f, 0.352f, 0.895f}).toSrgb(), (Vector3{0.02584f, 0.627829f, 0.952346f})); + + /* Extremes */ + CORRADE_COMPARE((Color3{0.0f, 1.0f, 0.5f}).toSrgb(), (Vector3{0.0f, 1.0f, 0.735357f})); + + /* RGBA */ + CORRADE_COMPARE(Color4::fromSrgbAlpha({0.625398f, 0.02584f, 0.823257f, 0.175f}), + (Color4{0.349f, 0.0020f, 0.644f, 0.175f})); + CORRADE_COMPARE(Color4::fromSrgb({0.625398f, 0.02584f, 0.823257f}, 0.175f), + (Color4{0.349f, 0.0020f, 0.644f, 0.175f})); + CORRADE_COMPARE((Color4{0.349f, 0.0020f, 0.644f, 0.175f}).toSrgbAlpha(), + (Vector4{0.625398f, 0.02584f, 0.823257f, 0.175f})); + + /* Integral RGB */ + CORRADE_COMPARE(Math::Color3::fromSrgb({0.1523f, 0.00125f, 0.9853f}), + (Math::Color3{1319, 6, 63364})); + CORRADE_COMPARE(Math::Color4::fromSrgbAlpha({0.1523f, 0.00125f, 0.9853f, 0.175f}), + (Math::Color4{1319, 6, 63364, 11468})); + CORRADE_COMPARE(Math::Color4::fromSrgb({0.1523f, 0.00125f, 0.9853f}, 15299), + (Math::Color4{1319, 6, 63364, 15299})); + CORRADE_COMPARE((Math::Color3{1319, 6, 63364}).toSrgb(), + (Vector3{0.152248f, 0.00118288f, 0.985295f})); + CORRADE_COMPARE((Math::Color4{1319, 6, 63364, 11468}).toSrgbAlpha(), + (Vector4{0.152248f, 0.00118288f, 0.985295f, 0.175f})); + + /* Integral 8bit sRGB -- slight precision loss */ + CORRADE_COMPARE(Color3::fromSrgb({0xf3, 0x2a, 0x80}), + (Color3{0.896269f, 0.0231534f, 0.215861f})); + CORRADE_COMPARE(Color4::fromSrgbAlpha({0xf3, 0x2a, 0x80, 0x23}), + (Color4{0.896269f, 0.0231534f, 0.215861f, 0.137255f})); + CORRADE_COMPARE(Color4::fromSrgb({0xf3, 0x2a, 0x80}, 0.175f), + (Color4{0.896269f, 0.0231534f, 0.215861f, 0.175f})); + CORRADE_COMPARE((Color3{0.896269f, 0.0231534f, 0.215861f}).toSrgb(), + (Math::Vector3{0xf2, 0x2a, 0x80})); + CORRADE_COMPARE((Color4{0.896269f, 0.0231534f, 0.215861f, 0.137255f}).toSrgbAlpha(), + (Math::Vector4{0xf2, 0x2a, 0x80, 0x23})); + + /* Integral both -- larger precision loss */ + CORRADE_COMPARE(Math::Color3::fromSrgb({0xf3, 0x2a, 0x80}), + (Math::Color3{58737, 1517, 14146})); + CORRADE_COMPARE(Math::Color4::fromSrgbAlpha({0xf3, 0x2a, 0x80, 0x23}), + (Math::Color4{58737, 1517, 14146, 8995})); + CORRADE_COMPARE(Math::Color4::fromSrgb({0xf3, 0x2a, 0x80}, 15299), + (Math::Color4{58737, 1517, 14146, 15299})); + CORRADE_COMPARE((Math::Color3{58737, 1517, 14146}).toSrgb(), + (Math::Vector3{0xf2, 0x29, 0x7f})); + CORRADE_COMPARE((Math::Color4{58737, 1517, 14146, 8995}).toSrgbAlpha(), + (Math::Vector4{0xf2, 0x29, 0x7f, 0x23})); + + /* Round-trip */ + CORRADE_COMPARE(Color3::fromSrgb({0.00646f, 0.403027f, 0.563877f}).toSrgb(), + (Vector3{0.00646f, 0.403027f, 0.563877f})); + CORRADE_COMPARE(Color4::fromSrgbAlpha({0.00646f, 0.403027f, 0.563877f, 0.175f}).toSrgbAlpha(), + (Vector4{0.00646f, 0.403027f, 0.563877f, 0.175f})); +} + +void ColorTest::fromSrgbDefaultAlpha() { + CORRADE_COMPARE(Color4::fromSrgb({0.625398f, 0.02584f, 0.823257f}), + (Color4{0.349f, 0.0020f, 0.644f, 1.0f})); + + /* Integral */ + CORRADE_COMPARE(Math::Color4::fromSrgb({0.1523f, 0.00125f, 0.9853f}), + (Math::Color4{1319, 6, 63364, 65535})); + CORRADE_COMPARE(Math::Color4::fromSrgb({0xf3, 0x2a, 0x80}), + (Math::Color4{58737, 1517, 14146, 65535})); +} + +void ColorTest::srgbMonotonic() { + Color3 rgbPrevious = Color3::fromSrgb(Math::Vector3(testCaseRepeatId())); + Color3 rgb = Color3::fromSrgb(Math::Vector3(testCaseRepeatId() + 1)); + CORRADE_COMPARE_AS(rgb, rgbPrevious, Corrade::TestSuite::Compare::Greater); + CORRADE_COMPARE_AS(rgb, Color3(0.0f), Corrade::TestSuite::Compare::GreaterOrEqual); + CORRADE_COMPARE_AS(rgb, Color3(1.0f), Corrade::TestSuite::Compare::LessOrEqual); +} + +void ColorTest::srgbLiterals() { + using namespace Literals; + + constexpr Math::Vector3 a = 0x33b27f_srgb; + CORRADE_COMPARE(a, (Math::Vector3{0x33, 0xb2, 0x7f})); + + constexpr Math::Vector4 b = 0x33b27fcc_srgba; + CORRADE_COMPARE(b, (Math::Vector4{0x33, 0xb2, 0x7f, 0xcc})); + + /* Not constexpr yet */ + CORRADE_COMPARE(0x33b27f_srgbf, (Color3{0.0331048f, 0.445201f, 0.212231f})); + CORRADE_COMPARE(0x33b27fcc_srgbaf, (Color4{0.0331048f, 0.445201f, 0.212231f, 0.8f})); +} + void ColorTest::swizzleType() { constexpr Color3 origColor3; constexpr Color4ub origColor4;