/* * 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/. * * Copyright 2010-2021 Danny Robson */ #include "colour.hpp" #include "ascii.hpp" #include "parse/value.hpp" #include "debug/assert.hpp" using cruft::colour; using cruft::srgba; /////////////////////////////////////////////////////////////////////////////// static cruft::srgba4f parse_hex (cruft::view &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"); } 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]); str = str.consume (7u); return cruft::srgba<4,uint8_t> { r, g, b, 255 }.template cast (); } /////////////////////////////////////////////////////////////////////////////// template <> cruft::srgba4f cruft::parse::value (cruft::view &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 ()); 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::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, }; }