2011-09-13 15:14:12 +10:00
|
|
|
/*
|
2018-08-04 15:14:06 +10:00
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* 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/.
|
2011-09-13 15:14:12 +10:00
|
|
|
*
|
2021-05-12 13:26:52 +10:00
|
|
|
* Copyright 2010-2021 Danny Robson <danny@nerdcruft.net>
|
2011-09-13 15:14:12 +10:00
|
|
|
*/
|
|
|
|
|
2017-11-22 16:49:37 +11:00
|
|
|
#include "colour.hpp"
|
2013-08-05 16:37:11 +10:00
|
|
|
|
2018-01-10 17:19:39 +11:00
|
|
|
#include "ascii.hpp"
|
2019-03-19 12:38:22 +11:00
|
|
|
#include "parse/value.hpp"
|
2021-05-12 13:26:52 +10:00
|
|
|
#include "debug/assert.hpp"
|
|
|
|
|
|
|
|
using cruft::colour;
|
|
|
|
using cruft::srgba;
|
2011-09-13 15:14:12 +10:00
|
|
|
|
|
|
|
|
2017-05-22 16:20:21 +10:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
2018-08-05 14:42:02 +10:00
|
|
|
static cruft::srgba4f
|
2018-12-16 13:26:48 +11:00
|
|
|
parse_hex (cruft::view<const char*> &str)
|
2018-01-10 17:19:39 +11:00
|
|
|
{
|
|
|
|
if (str.size () != strlen ("#012345"))
|
|
|
|
throw std::invalid_argument ("expected length of 7");
|
|
|
|
|
|
|
|
if (str[0] != '#')
|
|
|
|
throw std::invalid_argument ("expected leading '#'");
|
|
|
|
|
2018-08-05 14:42:02 +10:00
|
|
|
if (!cruft::ascii::is_hex (str[1]) ||
|
|
|
|
!cruft::ascii::is_hex (str[2]) ||
|
|
|
|
!cruft::ascii::is_hex (str[3]) ||
|
|
|
|
!cruft::ascii::is_hex (str[4]) ||
|
|
|
|
!cruft::ascii::is_hex (str[5]) ||
|
|
|
|
!cruft::ascii::is_hex (str[6]))
|
2018-01-10 17:19:39 +11:00
|
|
|
{
|
|
|
|
throw std::invalid_argument ("expected hex digits");
|
2015-04-09 21:50:42 +10:00
|
|
|
}
|
|
|
|
|
2018-08-05 14:42:02 +10:00
|
|
|
uint8_t r = cruft::ascii::from_hex (str[1]) << 4u | cruft::ascii::from_hex (str[2]);
|
|
|
|
uint8_t g = cruft::ascii::from_hex (str[3]) << 4u | cruft::ascii::from_hex (str[4]);
|
|
|
|
uint8_t b = cruft::ascii::from_hex (str[5]) << 4u | cruft::ascii::from_hex (str[6]);
|
2015-04-09 21:50:42 +10:00
|
|
|
|
2019-01-20 17:57:18 +11:00
|
|
|
str = str.consume (7u);
|
2018-08-05 14:42:02 +10:00
|
|
|
return cruft::srgba<4,uint8_t> { r, g, b, 255 }.template cast<float> ();
|
2015-04-09 21:50:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-09-13 15:14:12 +10:00
|
|
|
|
2016-04-02 13:37:09 +11:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
2018-01-10 17:19:39 +11:00
|
|
|
template <>
|
2018-08-05 14:42:02 +10:00
|
|
|
cruft::srgba4f
|
2019-03-19 12:38:22 +11:00
|
|
|
cruft::parse::value<cruft::srgba4f> (cruft::view<const char*> &str)
|
2017-10-10 16:50:15 +11:00
|
|
|
{
|
2018-01-10 17:19:39 +11:00
|
|
|
return parse_hex (str);
|
2021-05-12 13:26:52 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// 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,
|
|
|
|
};
|
2018-01-10 17:19:39 +11:00
|
|
|
}
|