Browse Source

Math: add Color[34]::{from,to}LinearRgb[a]Int().

Counterparts to the sRGB-converting APIs, for when one doesn't want to
perform sRGB conversion. Or for "wrong sRGB" workflows. Named like this
and not just `fromRgbInt()` to make the calls at least a bit suspicious.
pull/601/head
Vladimír Vondruš 3 years ago
parent
commit
4a87a0bc4b
  1. 5
      doc/changelog.dox
  2. 32
      doc/snippets/MagnumMath.cpp
  3. 177
      src/Magnum/Math/Color.h
  4. 51
      src/Magnum/Math/Test/ColorTest.cpp

5
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<T>&, const Vector2<T>&) and
@ref Math::DualQuaternion::from(const Quaternion<T>&, const Vector3<T>&)
functions mirroring @ref Math::Matrix3::from(const Matrix2x2<T>&, const Vector2<T>&)

32
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<UnsignedByte>(); // {0xff, 0x7c, 0xaa}
UnsignedInt bSrgbInt = a.toSrgbInt(); // 0xff7caa
Color3ub bLinear = Math::pack<Color3ub>(a); // {0xff, 0x33, 0x66}
UnsignedInt bLinearInt = a.toSrgbInt(); // 0xff3366
/* [Color3] */
static_cast<void>(bLinear);
static_cast<void>(bLinearInt);
static_cast<void>(bSrgb);
static_cast<void>(bSrgbInt);
}
{
/* [Color3-fromSrgb] */
Vector3ub srgb;
@ -773,6 +787,15 @@ static_cast<void>(a);
static_cast<void>(b);
}
{
/* [Color3-fromLinearRgbInt] */
Color3 a = Color3::fromLinearRgbInt(0xff3366);
Color3 b = 0xff3366_rgbf;
/* [Color3-fromLinearRgbInt] */
static_cast<void>(a);
static_cast<void>(b);
}
{
/* [Color3-unpack] */
Color3ub a{0xff, 0x33, 0x66};
@ -818,6 +841,15 @@ static_cast<void>(a);
static_cast<void>(b);
}
{
/* [Color4-fromLinearRgbaInt] */
Color4 a = Color4::fromLinearRgbaInt(0xff336680);
Color4 b = 0xff336680_rgbaf;
/* [Color4-fromLinearRgbaInt] */
static_cast<void>(a);
static_cast<void>(b);
}
{
/* [Color4-unpack] */
Color4ub a{0xff, 0x33, 0x66, 0x99};

177
src/Magnum/Math/Color.h

@ -150,6 +150,30 @@ template<class T, class Integral> inline Color4<T> fromSrgbAlphaIntegral(const V
return fromSrgbAlpha<T>(unpack<Vector4<typename Color4<T>::FloatingPointType>>(srgbAlpha));
}
/* Integer linear RGB -> linear RGB conversion */
template<class T> inline typename std::enable_if<IsFloatingPoint<T>::value, Color3<T>>::type fromLinearRgbInt(UnsignedInt linear) {
return {unpack<T>(UnsignedByte(linear >> 16)),
unpack<T>(UnsignedByte(linear >> 8)),
unpack<T>(UnsignedByte(linear))};
}
template<class T> inline typename std::enable_if<IsFloatingPoint<T>::value, Color4<T>>::type fromLinearRgbaInt(UnsignedInt linear) {
return {unpack<T>(UnsignedByte(linear >> 24)),
unpack<T>(UnsignedByte(linear >> 16)),
unpack<T>(UnsignedByte(linear >> 8)),
unpack<T>(UnsignedByte(linear))};
}
template<class T> inline typename std::enable_if<IsIntegral<T>::value, Color3<T>>::type fromLinearRgbInt(UnsignedInt linear) {
return {pack<T>(unpack<Float>(UnsignedByte(linear >> 16))),
pack<T>(unpack<Float>(UnsignedByte(linear >> 8))),
pack<T>(unpack<Float>(UnsignedByte(linear)))};
}
template<class T> inline typename std::enable_if<IsIntegral<T>::value, Color4<T>>::type fromLinearRgbaInt(UnsignedInt linear) {
return {pack<T>(unpack<Float>(UnsignedByte(linear >> 24))),
pack<T>(unpack<Float>(UnsignedByte(linear >> 16))),
pack<T>(unpack<Float>(UnsignedByte(linear >> 8))),
pack<T>(unpack<Float>(UnsignedByte(linear)))};
}
/* RGB -> sRGB conversion */
template<class T> Vector3<typename Color3<T>::FloatingPointType> toSrgb(typename std::enable_if<IsFloatingPoint<T>::value, const Color3<T>&>::type rgb) {
constexpr const T a = T(0.055);
@ -173,6 +197,30 @@ template<class T, class Integral> inline Vector4<Integral> toSrgbAlphaIntegral(c
return pack<Vector4<Integral>>(toSrgbAlpha<T>(rgba));
}
/* Linear RGB -> integer linear RGB conversion */
template<class T> inline typename std::enable_if<IsFloatingPoint<T>::value, UnsignedInt>::type toLinearRgbInt(const Color3<T>& linear) {
return (pack<UnsignedByte>(linear[0]) << 16) |
(pack<UnsignedByte>(linear[1]) << 8) |
pack<UnsignedByte>(linear[2]);
}
template<class T> inline typename std::enable_if<IsFloatingPoint<T>::value, UnsignedInt>::type toLinearRgbaInt(const Color4<T>& linear) {
return (pack<UnsignedByte>(linear[0]) << 24) |
(pack<UnsignedByte>(linear[1]) << 16) |
(pack<UnsignedByte>(linear[2]) << 8) |
pack<UnsignedByte>(linear[3]);
}
template<class T> inline typename std::enable_if<IsIntegral<T>::value, UnsignedInt>::type toLinearRgbInt(const Color3<T>& linear) {
return (pack<UnsignedByte>(unpack<Float>(linear[0])) << 16) |
(pack<UnsignedByte>(unpack<Float>(linear[1])) << 8) |
pack<UnsignedByte>(unpack<Float>(linear[2]));
}
template<class T> inline typename std::enable_if<IsIntegral<T>::value, UnsignedInt>::type toLinearRgbaInt(const Color4<T>& linear) {
return (pack<UnsignedByte>(unpack<Float>(linear[0])) << 24) |
(pack<UnsignedByte>(unpack<Float>(linear[1])) << 16) |
(pack<UnsignedByte>(unpack<Float>(linear[2])) << 8) |
pack<UnsignedByte>(unpack<Float>(linear[3]));
}
/* CIE XYZ -> RGB conversion */
template<class T> typename std::enable_if<IsFloatingPoint<T>::value, Color3<T>>::type fromXyz(const Vector3<T>& 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 T> class Color3: public Vector3<T> {
* 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<Integral>&) / @ref unpack().
* @see @ref Color4::fromSrgbAlphaInt()
* @ref fromSrgb(const Vector3<Integral>&).
* @see @ref fromLinearRgbInt(), @ref Color4::fromSrgbAlphaInt()
*/
static Color3<T> fromSrgbInt(UnsignedInt srgb) {
return fromSrgb<UnsignedByte>({UnsignedByte(srgb >> 16),
@ -444,6 +494,29 @@ template<class T> class Color3: public Vector3<T> {
}
#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<Color3>()" on a @ref Color3ub input.
* @see @ref Color4::fromLinearRgbaInt()
*/
static Color3<T> fromLinearRgbInt(UnsignedInt linear) {
return Implementation::fromLinearRgbInt<T>(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 T> class Color3: public Vector3<T> {
*
* @snippet MagnumMath.cpp Color3-pack
*
* @see @ref toSrgbInt(), @ref Color4::toSrgbAlpha()
* @see @ref toSrgbInt(), @ref Color4::toSrgbAlpha(),
* @ref toLinearRgbInt()
*/
template<class Integral> Vector3<Integral> toSrgb() const {
return Implementation::toSrgbIntegral<T, Integral>(*this);
@ -596,17 +670,33 @@ template<class T> class Color3: public Vector3<T> {
/**
* @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<UnsignedByte>() const".
* @see @ref toLinearRgbInt(), @ref Color4::toSrgbAlphaInt()
*/
UnsignedInt toSrgbInt() const {
const auto srgb = toSrgb<UnsignedByte>();
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<Color3ub>()".
* @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<T> {
}
#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<Color4>()" on a @ref Color4ub input.
* @see @ref Color3::fromLinearRgbInt()
*/
static Color4<T> fromLinearRgbaInt(UnsignedInt linear) {
return Implementation::fromLinearRgbaInt<T>(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<T> fromLinearRgbInt(UnsignedInt linear, T a = Implementation::fullChannel<T>()) {
return {Implementation::fromLinearRgbInt<T>(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<T> {
*
* @snippet MagnumMath.cpp Color4-pack
*
* @see @ref toSrgbAlphaInt()
* @see @ref toSrgbAlphaInt(), @ref toLinearRgbaInt()
*/
template<class Integral> Vector4<Integral> toSrgbAlpha() const {
return Implementation::toSrgbAlphaIntegral<T, Integral>(*this);
@ -1028,18 +1155,36 @@ class Color4: public Vector4<T> {
/**
* @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<UnsignedByte>() const".
* @see @ref toLinearRgbaInt()
*/
UnsignedInt toSrgbAlphaInt() const {
const auto srgbAlpha = toSrgbAlpha<UnsignedByte>();
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<Color4ub>()".
*/
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<UnsignedByte> 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<Float> 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

51
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<Color3>(linear8.rgb()), linear.rgb());
CORRADE_COMPARE(Color3::fromLinearRgbInt(0xf32a80), linear.rgb());
CORRADE_COMPARE(unpack<Color4>(linear8), linear);
CORRADE_COMPARE(Color4::fromLinearRgbaInt(0xf32a8023), linear);
/* No unpack<T>(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<Color3ub>(linear.rgb()), linear8.rgb());
CORRADE_COMPARE(linear.rgb().toLinearRgbInt(), 0xf32a80);
CORRADE_COMPARE(pack<Color4ub>(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<Color3us>(unpack<Color3>(linear8.rgb())), linear16.rgb());
CORRADE_COMPARE(Color3us::fromLinearRgbInt(0xf32a80), linear16.rgb());
CORRADE_COMPARE(pack<Color4us>(unpack<Color4>(linear8)), linear16);
CORRADE_COMPARE(Color4us::fromLinearRgbaInt(0xf32a8023), linear16);
/* No unpack<T>(rgb, alpha) variant that would make sense to compare to */
CORRADE_COMPARE(Color4us::fromLinearRgbInt(0xf32a80, 15299),
(Color4us{linear16.rgb(), 15299}));
CORRADE_COMPARE(pack<Color3ub>(unpack<Color3>(linear16.rgb())), linear8.rgb());
CORRADE_COMPARE(linear16.rgb().toLinearRgbInt(), 0xf32a80);
CORRADE_COMPARE(pack<Color4ub>(unpack<Color4>(linear16)), linear8);
CORRADE_COMPARE(linear16.toLinearRgbaInt(), 0xf32a8023);
}
void ColorTest::srgbMonotonic() {
Color3 rgbPrevious = Color3::fromSrgb(Vector3us(testCaseRepeatId()));
Color3 rgb = Color3::fromSrgb(Vector3us(testCaseRepeatId() + 1));

Loading…
Cancel
Save