diff --git a/doc/changelog.dox b/doc/changelog.dox index dd9e083c8..997d9d3ed 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -163,6 +163,11 @@ See also: - Added @ref Math::fmod() (see [mosra/magnum#454](https://github.com/mosra/magnum/pull/454)) - Added @ref Math::binomialCoefficient() (see [mosra/magnum#461](https://github.com/mosra/magnum/pull/461)) - Added @ref Math::popcount() +- Added @ref Math::Color3::fromLinearRgbInt(), + @ref Math::Color4::fromLinearRgbInt() and + @ref Math::Color4::fromLinearRgbaInt() as counterparts to + @ref Math::Color3::fromSrgbInt(), @ref Math::Color4::fromSrgbInt() and + @ref Math::Color4::fromSrgbAlphaInt() that don't perform a sRGB conversion - New @ref Math::DualComplex::from(const Complex&, const Vector2&) and @ref Math::DualQuaternion::from(const Quaternion&, const Vector3&) functions mirroring @ref Math::Matrix3::from(const Matrix2x2&, const Vector2&) diff --git a/doc/snippets/MagnumMath.cpp b/doc/snippets/MagnumMath.cpp index 44a56cb57..926e3aed9 100644 --- a/doc/snippets/MagnumMath.cpp +++ b/doc/snippets/MagnumMath.cpp @@ -756,6 +756,20 @@ if(!(b < a - epsilon || a + epsilon < b)) { /* [BitVector-boolean] */ } +{ +/* [Color3] */ +Color3 a{1.0f, 0.2f, 0.4f}; +Vector3ub bSrgb = a.toSrgb(); // {0xff, 0x7c, 0xaa} +UnsignedInt bSrgbInt = a.toSrgbInt(); // 0xff7caa +Color3ub bLinear = Math::pack(a); // {0xff, 0x33, 0x66} +UnsignedInt bLinearInt = a.toSrgbInt(); // 0xff3366 +/* [Color3] */ +static_cast(bLinear); +static_cast(bLinearInt); +static_cast(bSrgb); +static_cast(bSrgbInt); +} + { /* [Color3-fromSrgb] */ Vector3ub srgb; @@ -773,6 +787,15 @@ static_cast(a); static_cast(b); } +{ +/* [Color3-fromLinearRgbInt] */ +Color3 a = Color3::fromLinearRgbInt(0xff3366); +Color3 b = 0xff3366_rgbf; +/* [Color3-fromLinearRgbInt] */ +static_cast(a); +static_cast(b); +} + { /* [Color3-unpack] */ Color3ub a{0xff, 0x33, 0x66}; @@ -818,6 +841,15 @@ static_cast(a); static_cast(b); } +{ +/* [Color4-fromLinearRgbaInt] */ +Color4 a = Color4::fromLinearRgbaInt(0xff336680); +Color4 b = 0xff336680_rgbaf; +/* [Color4-fromLinearRgbaInt] */ +static_cast(a); +static_cast(b); +} + { /* [Color4-unpack] */ Color4ub a{0xff, 0x33, 0x66, 0x99}; diff --git a/src/Magnum/Math/Color.h b/src/Magnum/Math/Color.h index 381c1f5a4..d0579e38b 100644 --- a/src/Magnum/Math/Color.h +++ b/src/Magnum/Math/Color.h @@ -150,6 +150,30 @@ template inline Color4 fromSrgbAlphaIntegral(const V return fromSrgbAlpha(unpack::FloatingPointType>>(srgbAlpha)); } +/* Integer linear RGB -> linear RGB conversion */ +template inline typename std::enable_if::value, Color3>::type fromLinearRgbInt(UnsignedInt linear) { + return {unpack(UnsignedByte(linear >> 16)), + unpack(UnsignedByte(linear >> 8)), + unpack(UnsignedByte(linear))}; +} +template inline typename std::enable_if::value, Color4>::type fromLinearRgbaInt(UnsignedInt linear) { + return {unpack(UnsignedByte(linear >> 24)), + unpack(UnsignedByte(linear >> 16)), + unpack(UnsignedByte(linear >> 8)), + unpack(UnsignedByte(linear))}; +} +template inline typename std::enable_if::value, Color3>::type fromLinearRgbInt(UnsignedInt linear) { + return {pack(unpack(UnsignedByte(linear >> 16))), + pack(unpack(UnsignedByte(linear >> 8))), + pack(unpack(UnsignedByte(linear)))}; +} +template inline typename std::enable_if::value, Color4>::type fromLinearRgbaInt(UnsignedInt linear) { + return {pack(unpack(UnsignedByte(linear >> 24))), + pack(unpack(UnsignedByte(linear >> 16))), + pack(unpack(UnsignedByte(linear >> 8))), + pack(unpack(UnsignedByte(linear)))}; +} + /* RGB -> sRGB conversion */ template Vector3::FloatingPointType> toSrgb(typename std::enable_if::value, const Color3&>::type rgb) { constexpr const T a = T(0.055); @@ -173,6 +197,30 @@ template inline Vector4 toSrgbAlphaIntegral(c return pack>(toSrgbAlpha(rgba)); } +/* Linear RGB -> integer linear RGB conversion */ +template inline typename std::enable_if::value, UnsignedInt>::type toLinearRgbInt(const Color3& linear) { + return (pack(linear[0]) << 16) | + (pack(linear[1]) << 8) | + pack(linear[2]); +} +template inline typename std::enable_if::value, UnsignedInt>::type toLinearRgbaInt(const Color4& linear) { + return (pack(linear[0]) << 24) | + (pack(linear[1]) << 16) | + (pack(linear[2]) << 8) | + pack(linear[3]); +} +template inline typename std::enable_if::value, UnsignedInt>::type toLinearRgbInt(const Color3& linear) { + return (pack(unpack(linear[0])) << 16) | + (pack(unpack(linear[1])) << 8) | + pack(unpack(linear[2])); +} +template inline typename std::enable_if::value, UnsignedInt>::type toLinearRgbaInt(const Color4& linear) { + return (pack(unpack(linear[0])) << 24) | + (pack(unpack(linear[1])) << 16) | + (pack(unpack(linear[2])) << 8) | + pack(unpack(linear[3])); +} + /* CIE XYZ -> RGB conversion */ template typename std::enable_if::value, Color3>::type fromXyz(const Vector3& xyz) { /* Taken from https://en.wikipedia.org/wiki/Talk:SRGB#Rounded_vs._Exact, @@ -241,9 +289,11 @@ bit count of given integer type. Note that constructor conversion between different types (like in @ref Vector classes) doesn't do any (un)packing, you need to use either @ref pack() / @ref unpack(), the integer variants of @ref toSrgb() / @ref fromSrgb() or @ref toSrgbInt() / @ref fromSrgbInt() -instead: +instead. For convenience, conversion from and to 8bpp representation without +sRGB conversion is possible with @ref fromLinearRgbInt() and +@ref toLinearRgbInt(). -@snippet MagnumMath.cpp Color3-pack +@snippet MagnumMath.cpp Color3 Conversion from and to HSV is done always using floating-point types, so hue is always in range in range @f$ [0.0\degree, 360.0\degree] @f$, saturation and @@ -425,8 +475,8 @@ template class Color3: public Vector3 { * Note that the integral value is endian-dependent (the red channel * being in the *last* byte on little-endian platforms), for conversion * from endian-independent sRGB / linear representation use - * @ref fromSrgb(const Vector3&) / @ref unpack(). - * @see @ref Color4::fromSrgbAlphaInt() + * @ref fromSrgb(const Vector3&). + * @see @ref fromLinearRgbInt(), @ref Color4::fromSrgbAlphaInt() */ static Color3 fromSrgbInt(UnsignedInt srgb) { return fromSrgb({UnsignedByte(srgb >> 16), @@ -444,6 +494,29 @@ template class Color3: public Vector3 { } #endif + /** + * @brief Create linear RGB color from 24-bit linear representation + * @param linear 24-bit linear RGB color + * @m_since_latest + * + * Compared to @ref fromSrgbInt() *does not* peform a sRGB conversion + * on the input. See @ref toLinearRgbInt() for an inverse operation, + * there's also a @link operator""_rgbf() @endlink that does this + * conversion directly from hexadecimal literals. The following two + * statements are equivalent: + * + * @snippet MagnumMath.cpp Color3-fromLinearRgbInt + * + * Note that the integral value is endian-dependent (the red channel + * being in the *last* byte on little-endian platforms), for conversion + * from endian-independent linear RGB representation use + * @ref unpack() "unpack()" on a @ref Color3ub input. + * @see @ref Color4::fromLinearRgbaInt() + */ + static Color3 fromLinearRgbInt(UnsignedInt linear) { + return Implementation::fromLinearRgbInt(linear); + } + /** * @brief Create RGB color from [CIE XYZ representation](https://en.wikipedia.org/wiki/CIE_1931_color_space) * @param xyz Color in CIE XYZ color space @@ -587,7 +660,8 @@ template class Color3: public Vector3 { * * @snippet MagnumMath.cpp Color3-pack * - * @see @ref toSrgbInt(), @ref Color4::toSrgbAlpha() + * @see @ref toSrgbInt(), @ref Color4::toSrgbAlpha(), + * @ref toLinearRgbInt() */ template Vector3 toSrgb() const { return Implementation::toSrgbIntegral(*this); @@ -596,17 +670,33 @@ template class Color3: public Vector3 { /** * @brief Convert to 24-bit integral sRGB representation * - * See @ref toSrgb() const for more information. Note that the integral - * value is endian-dependent (the red channel being in the *last* byte - * on little-endian platforms), for conversion to an endian-independent - * sRGB / linear representation use @ref toSrgb() const / @ref pack(). - * @see @ref Color4::toSrgbAlphaInt() + * See @ref toSrgb() const for more information and @ref fromSrgbInt() + * for an inverse operation. Note that the integral value is + * endian-dependent (the red channel being in the *last* byte on + * little-endian platforms), for conversion to an endian-independent + * sRGB representation use @ref toSrgb() const "toSrgb() const". + * @see @ref toLinearRgbInt(), @ref Color4::toSrgbAlphaInt() */ UnsignedInt toSrgbInt() const { const auto srgb = toSrgb(); return (srgb[0] << 16) | (srgb[1] << 8) | srgb[2]; } + /** + * @brief Convert to 24-bit integral linear RGB representation + * + * Compared to @ref toSrgbInt() *does not* perform a sRGB conversion on + * the output. See @ref fromLinearRgbInt() for an inverse operation. + * Note that the integral value is endian-dependent (the red channel + * being in the *last* byte on little-endian platforms), for conversion + * to an endian-independent linear representation use + * @ref pack() "pack()". + * @see @ref Color4::toLinearRgbaInt() + */ + UnsignedInt toLinearRgbInt() const { + return Implementation::toLinearRgbInt(*this); + } + /** * @brief Convert to [CIE XYZ representation](https://en.wikipedia.org/wiki/CIE_1931_color_space) * @@ -882,6 +972,43 @@ class Color4: public Vector4 { } #endif + /** + * @brief Create linear RGBA color from 32-bit linear representation + * @param linear 32-bit linear RGBA color + * @m_since_latest + * + * Compared to @ref fromSrgbAlphaInt() *does not* peform a sRGB + * conversion on the input. See @ref toLinearRgbaInt() for an inverse + * operation, there's also a @link operator""_rgbaf() @endlink that + * does this conversion directly from hexadecimal literals. The + * following two statements are equivalent: + * + * @snippet MagnumMath.cpp Color4-fromLinearRgbaInt + * + * Note that the integral value is endian-dependent (the red channel + * being in the *last* byte on little-endian platforms), for conversion + * from endian-independent linear RGBA representation use + * @ref unpack() "unpack()" on a @ref Color4ub input. + * @see @ref Color3::fromLinearRgbInt() + */ + static Color4 fromLinearRgbaInt(UnsignedInt linear) { + return Implementation::fromLinearRgbaInt(linear); + } + + /** + * @brief Create linear RGBA color from 32-bit linear RGB + alpha representation + * @param linear 24-bit linear RGB color + * @param a Linear alpha value, defaults to @cpp 1.0 @ce for + * floating-point types and maximum positive value for integral + * types + * @m_since_latest + * + * Same as above, but with alpha as a separate parameter. + */ + static Color4 fromLinearRgbInt(UnsignedInt linear, T a = Implementation::fullChannel()) { + return {Implementation::fromLinearRgbInt(linear), a}; + } + /** * @brief Create RGBA color from [CIE XYZ representation](https://en.wikipedia.org/wiki/CIE_1931_color_space) * @param xyz Color in CIE XYZ color space @@ -1019,7 +1146,7 @@ class Color4: public Vector4 { * * @snippet MagnumMath.cpp Color4-pack * - * @see @ref toSrgbAlphaInt() + * @see @ref toSrgbAlphaInt(), @ref toLinearRgbaInt() */ template Vector4 toSrgbAlpha() const { return Implementation::toSrgbAlphaIntegral(*this); @@ -1028,18 +1155,36 @@ class Color4: public Vector4 { /** * @brief Convert to 32-bit integral sRGB + linear alpha representation * - * See @ref Color3::toSrgb() const for more information. Use @ref rgb() + * See @ref Color3::toSrgb() const for more information and + * @ref fromSrgbAlphaInt() for an inverse operation. Use @ref rgb() * together with @ref Color3::toSrgbInt() to output a 24-bit sRGB * color. Note that the integral value is endian-dependent (the red * channel being in the *last* byte on little-endian platforms), for - * conversion to an endian-independent sRGB / linear representation use - * @ref toSrgbAlpha() const / @ref pack(). + * conversion to an endian-independent sRGB representation use + * @ref toSrgbAlpha() const "toSrgbAlpha() const". + * @see @ref toLinearRgbaInt() */ UnsignedInt toSrgbAlphaInt() const { const auto srgbAlpha = toSrgbAlpha(); return (srgbAlpha[0] << 24) | (srgbAlpha[1] << 16) | (srgbAlpha[2] << 8) | srgbAlpha[3]; } + /** + * @brief Convert to 32-bit integral linear RGBA representation + * + * Compared to @ref toSrgbAlphaInt() *does not* perform a sRGB + * conversion on the output. See @ref fromLinearRgbaInt() for an + * inverse operation. Use @ref rgb() together with + * @ref Color3::toLinearRgbInt() to output a 24-bit linear RGBA color. + * Note that the integral value is endian-dependent (the red channel + * being in the *last* byte on little-endian platforms), for conversion + * to an endian-independent linear representation use + * @ref pack() "pack()". + */ + UnsignedInt toLinearRgbaInt() const { + return Implementation::toLinearRgbaInt(*this); + } + /** * @brief Convert to [CIE XYZ representation](https://en.wikipedia.org/wiki/CIE_1931_color_space) * @@ -1303,7 +1448,7 @@ constexpr Vector4 operator "" _srgba(unsigned long long value) { /** @relatesalso Magnum::Math::Color3 @brief Float linear RGB literal -Unpacks the 8-bit values into three floats. Example usage: +Calls @ref Color3::fromLinearRgbInt() on the literal value. Example usage: @snippet MagnumMath.cpp _rgbf @@ -1337,7 +1482,7 @@ inline Color3 operator "" _srgbf(unsigned long long value) { /** @relatesalso Magnum::Math::Color4 @brief Float linear RGBA literal -Unpacks the 8-bit values into four floats. Example usage: +Calls @ref Color4::fromLinearRgbaInt() on the literal value. Example usage: @snippet MagnumMath.cpp _rgbaf diff --git a/src/Magnum/Math/Test/ColorTest.cpp b/src/Magnum/Math/Test/ColorTest.cpp index 0debfba74..6890a0238 100644 --- a/src/Magnum/Math/Test/ColorTest.cpp +++ b/src/Magnum/Math/Test/ColorTest.cpp @@ -112,8 +112,12 @@ struct ColorTest: Corrade::TestSuite::Tester { void srgb(); void fromSrgbDefaultAlpha(); void srgbToIntegral(); + /* no linearRgbToIntegral() as there's no builtin linear RGB -> integral + APIs (it's done with pack() instead) */ void fromIntegralSrgb(); + void fromIntegralLinearRgb(); void integralSrgbToIntegral(); + void integralLinearRgbToIntegral(); void srgbMonotonic(); void srgb8bitRoundtrip(); void srgbLiterals(); @@ -243,7 +247,9 @@ ColorTest::ColorTest() { &ColorTest::fromSrgbDefaultAlpha, &ColorTest::srgbToIntegral, &ColorTest::fromIntegralSrgb, - &ColorTest::integralSrgbToIntegral}); + &ColorTest::fromIntegralLinearRgb, + &ColorTest::integralSrgbToIntegral, + &ColorTest::integralLinearRgbToIntegral}); /* Comparing with the previous one, so not 65536 */ addRepeatedTests({&ColorTest::srgbMonotonic}, 65535); @@ -891,6 +897,27 @@ void ColorTest::fromIntegralSrgb() { CORRADE_COMPARE(linear.toSrgbAlphaInt(), 0xf32a8023); } +void ColorTest::fromIntegralLinearRgb() { + /* Like fromIntegralSrgb(), but without the sRGB conversion -- thus only + the int-taking / int-producing APIs are tested, and compared to + unpack() / pack() as the ground truth */ + + Color4ub linear8{0xf3, 0x2a, 0x80, 0x23}; + Color4 linear{0.952941f, 0.164706f, 0.501961f, 0.137255f}; + + CORRADE_COMPARE(unpack(linear8.rgb()), linear.rgb()); + CORRADE_COMPARE(Color3::fromLinearRgbInt(0xf32a80), linear.rgb()); + CORRADE_COMPARE(unpack(linear8), linear); + CORRADE_COMPARE(Color4::fromLinearRgbaInt(0xf32a8023), linear); + /* No unpack(rgb, alpha) variant that would make sense to compare to */ + CORRADE_COMPARE(Color4::fromLinearRgbInt(0xf32a80, 0.175f), (Color4{linear.rgb(), 0.175f})); + + CORRADE_COMPARE(pack(linear.rgb()), linear8.rgb()); + CORRADE_COMPARE(linear.rgb().toLinearRgbInt(), 0xf32a80); + CORRADE_COMPARE(pack(linear), linear8); + CORRADE_COMPARE(linear.toLinearRgbaInt(), 0xf32a8023); +} + void ColorTest::integralSrgbToIntegral() { Vector4ub srgb{0xf3, 0x2a, 0x80, 0x23}; Color4us linear{58737, 1517, 14146, 8995}; @@ -909,6 +936,28 @@ void ColorTest::integralSrgbToIntegral() { CORRADE_COMPARE(linear.toSrgbAlphaInt(), 0xf32a8023); } +void ColorTest::integralLinearRgbToIntegral() { + /* Compared to integralSrgbToIntegral() this operates with linear values on + both sides again, similarly as fromIntegralLinearRgb(), but as there's + no direct operation to go from 8-bit to a 16-bit type and vice versa, + it's always combination of unpack() and pack() */ + + Color4ub linear8{0xf3, 0x2a, 0x80, 0x23}; + Color4us linear16{62451, 10794, 32896, 8995}; + + CORRADE_COMPARE(pack(unpack(linear8.rgb())), linear16.rgb()); + CORRADE_COMPARE(Color3us::fromLinearRgbInt(0xf32a80), linear16.rgb()); + CORRADE_COMPARE(pack(unpack(linear8)), linear16); + CORRADE_COMPARE(Color4us::fromLinearRgbaInt(0xf32a8023), linear16); + /* No unpack(rgb, alpha) variant that would make sense to compare to */ + CORRADE_COMPARE(Color4us::fromLinearRgbInt(0xf32a80, 15299), + (Color4us{linear16.rgb(), 15299})); + CORRADE_COMPARE(pack(unpack(linear16.rgb())), linear8.rgb()); + CORRADE_COMPARE(linear16.rgb().toLinearRgbInt(), 0xf32a80); + CORRADE_COMPARE(pack(unpack(linear16)), linear8); + CORRADE_COMPARE(linear16.toLinearRgbaInt(), 0xf32a8023); +} + void ColorTest::srgbMonotonic() { Color3 rgbPrevious = Color3::fromSrgb(Vector3us(testCaseRepeatId())); Color3 rgb = Color3::fromSrgb(Vector3us(testCaseRepeatId() + 1));