libcruft-util/colour.cpp

207 lines
5.6 KiB
C++
Raw Normal View History

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
*
* Copyright 2010-2021 Danny Robson <danny@nerdcruft.net>
2011-09-13 15:14:12 +10:00
*/
#include "colour.hpp"
#include "ascii.hpp"
#include "parse/value.hpp"
#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
///////////////////////////////////////////////////////////////////////////////
static cruft::srgba4f
parse_hex (cruft::view<const char*> &str)
{
if (str.size () != strlen ("#012345"))
throw std::invalid_argument ("expected length of 7");
if (str[0] != '#')
throw std::invalid_argument ("expected leading '#'");
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]))
{
throw std::invalid_argument ("expected hex digits");
2015-04-09 21:50:42 +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
str = str.consume (7u);
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
///////////////////////////////////////////////////////////////////////////////
template <>
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,
};
}