colour: add luminance scaling and XYZ/xyY/sRGB conversions
This commit is contained in:
parent
7d3ae8bd8f
commit
15ee2f5f58
158
colour.cpp
158
colour.cpp
@ -3,13 +3,17 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2010-2017 Danny Robson <danny@nerdcruft.net>
|
||||
* Copyright 2010-2021 Danny Robson <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
#include "colour.hpp"
|
||||
|
||||
#include "ascii.hpp"
|
||||
#include "parse/value.hpp"
|
||||
#include "debug/assert.hpp"
|
||||
|
||||
using cruft::colour;
|
||||
using cruft::srgba;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -48,4 +52,156 @@ cruft::srgba4f
|
||||
cruft::parse::value<cruft::srgba4f> (cruft::view<const char*> &str)
|
||||
{
|
||||
return parse_hex (str);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// Invert the sRGB gamma function
|
||||
static constexpr float
|
||||
linearise (float const u)
|
||||
{
|
||||
if (u <= 0.04045f)
|
||||
return u / 12.92f;
|
||||
else
|
||||
return std::pow ((u + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
|
||||
|
||||
///----------------------------------------------------------------------------
|
||||
/// Apply the sRGB gamma function
|
||||
static constexpr float
|
||||
exponentiate (float const u)
|
||||
{
|
||||
if (u <= 0.0031308f)
|
||||
return 12.92f * u;
|
||||
else
|
||||
return 1.055f * std::pow (u, 1/2.4f) - 0.055f;
|
||||
}
|
||||
|
||||
|
||||
///----------------------------------------------------------------------------
|
||||
/// Convert an sRGB colour to XYZ.
|
||||
///
|
||||
/// Gamma will be removed during the conversion. Do not use with linear RGB.
|
||||
static cruft::vector3f
|
||||
sRGB_to_XYZ (cruft::vector3f sRGB)
|
||||
{
|
||||
auto const [r_, g_, b_] = sRGB;
|
||||
|
||||
float const r = linearise (r_);
|
||||
float const g = linearise (g_);
|
||||
float const b = linearise (b_);
|
||||
|
||||
// D65 whitepoint, 2° observer.
|
||||
//
|
||||
// We use higher precision values than the specification dictates, from
|
||||
// Bruce Lindbloom's reference, so as to minimise numerical errors when
|
||||
// chaining conversions.
|
||||
float const X = r * 0.4124564f + g * 0.3575761f + b * 0.1804375f;
|
||||
float const Y = r * 0.2126729f + g * 0.7151522f + b * 0.0721750f;
|
||||
float const Z = r * 0.0193339f + g * 0.1191920f + b * 0.9503041f;
|
||||
|
||||
return {X, Y, Z};
|
||||
}
|
||||
|
||||
|
||||
///----------------------------------------------------------------------------
|
||||
/// Convert an XYZ colour to sRGB.
|
||||
///
|
||||
/// Gamma will be applied during the conversion.
|
||||
///
|
||||
/// Out of range colours will be returned as is. It is the caller's
|
||||
/// responsibility to process these as desired.
|
||||
static cruft::vector3f
|
||||
XYZ_to_sRGB (cruft::vector3f XYZ)
|
||||
{
|
||||
auto const [X, Y, Z] = XYZ;
|
||||
|
||||
float const r = +3.2404542f * X -1.5371385f * Y -0.4985314f * Z;
|
||||
float const g = -0.9692660f * X +1.8760108f * Y +0.0415560f * Z;
|
||||
float const b = +0.0556434f * X -0.2040259f * Y +1.0572252f * Z;
|
||||
|
||||
return {
|
||||
exponentiate (r),
|
||||
exponentiate (g),
|
||||
exponentiate (b),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
///----------------------------------------------------------------------------
|
||||
/// Convert an XYZ colour to xyY
|
||||
static
|
||||
cruft::vector3f
|
||||
XYZ_to_xyY (cruft::vector3f XYZ)
|
||||
{
|
||||
auto const [X, Y, Z] = XYZ;
|
||||
|
||||
if (cruft::abs (X + Y + Z) < 1e-5f) {
|
||||
// Use the D65 xy if we're at black.
|
||||
return {
|
||||
0.31271f,
|
||||
0.32902f,
|
||||
Y
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
X / (X + Y + Z),
|
||||
Y / (X + Y + Z),
|
||||
Y,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///----------------------------------------------------------------------------
|
||||
/// Convert an xyY colour to XYZ
|
||||
static cruft::vector3f
|
||||
xyY_to_XYZ (cruft::vector3f xyY)
|
||||
{
|
||||
auto const [x, y, Y] = xyY;
|
||||
if (cruft::abs (y) < 1e-5f) {
|
||||
return {0, 0, 0};
|
||||
} else {
|
||||
return {
|
||||
x * Y / y,
|
||||
Y,
|
||||
(1 - x - y) * Y / y,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
cruft::srgba3f
|
||||
cruft::scale_luminance (cruft::srgba3f sRGB, float f)
|
||||
{
|
||||
// We can't scale sRGB directly. Given we probably have to convert to other
|
||||
// spaces via XYZ anyway we may as well scale the luminance value in xyY.
|
||||
//
|
||||
// The conversion chain is thus:
|
||||
// * sRBB -> XYZ -> xyY -> scaling -> XYZ -> sRGB
|
||||
|
||||
auto const XYZ0= sRGB_to_XYZ (sRGB.as<cruft::vector3f> ());
|
||||
auto const xyY0= XYZ_to_xyY (XYZ0);
|
||||
|
||||
auto const xyY1 = xyY0 * cruft::vector3f{1,1,f};
|
||||
auto const XYZ1 = xyY_to_XYZ (xyY1);
|
||||
return XYZ_to_sRGB (XYZ1).as<cruft::srgba3f> ();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
cruft::srgba4f
|
||||
cruft::scale_luminance (cruft::srgba4f sRGBA, float f)
|
||||
{
|
||||
auto const sRGB0 = sRGBA.indices<0,1,2> ();
|
||||
auto const sRGB1 = scale_luminance (sRGB0, f);
|
||||
|
||||
return {
|
||||
sRGB1.r,
|
||||
sRGB1.g,
|
||||
sRGB1.b,
|
||||
sRGBA.a,
|
||||
};
|
||||
}
|
@ -84,6 +84,9 @@ namespace cruft {
|
||||
using srgba3u = srgba<3,uint8_t>;
|
||||
using srgba4u = srgba<4,uint8_t>;
|
||||
|
||||
srgba3f scale_luminance (srgba3f, float factor);
|
||||
srgba4f scale_luminance (srgba4f, float factor);
|
||||
|
||||
template <size_t S, typename T> struct hsva : colour<S,T,hsva<S,T>> {};
|
||||
|
||||
template <size_t S, typename T>
|
||||
|
@ -16,5 +16,15 @@ main (int, char**)
|
||||
tap.expect_eq (u.cast<float> (), f, "cast u8 to float");
|
||||
}
|
||||
|
||||
{
|
||||
cruft::srgba3f white {1, 1, 1};
|
||||
cruft::srgba3f black {0, 0, 0};
|
||||
cruft::srgba3f percent_50 { 0.735359f, 0.735356f, 0.735357f };
|
||||
|
||||
tap.expect (max (scale_luminance (white, 1.f) - white) < 1e-5f, "unit luminance scaling for white");
|
||||
tap.expect (max (scale_luminance (white, 0.f) - black) < 1e-5f, "zero luminance scaling for white");
|
||||
tap.expect (max (scale_luminance (white, .5f) - percent_50) < 1e-5f, "half luminance scaling for white");
|
||||
}
|
||||
|
||||
return tap.status ();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user