diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8b94016..7faddc1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.7)
+
option(VERSION_STRING "Project version string" "0.0.0")
project(antkeeper VERSION ${VERSION_STRING} LANGUAGES CXX)
diff --git a/src/color/aces.hpp b/src/color/aces.hpp
new file mode 100644
index 0000000..a127ca1
--- /dev/null
+++ b/src/color/aces.hpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 Christopher J. Howard
+ *
+ * This file is part of Antkeeper source code.
+ *
+ * Antkeeper source code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper source code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper source code. If not, see .
+ */
+
+#ifndef ANTKEEPER_COLOR_ACES_HPP
+#define ANTKEEPER_COLOR_ACES_HPP
+
+#include "color/rgb.hpp"
+#include "math/math.hpp"
+
+namespace color {
+
+/// ACES color spaces.
+namespace aces {
+
+/// CIE xy chromaticity coordinates of the ACES white point (~D60).
+template
+constexpr math::vector2 white_point = {T{0.32168}, {0.33767}};
+
+/// ACES AP0 color space.
+template
+constexpr rgb::color_space ap0
+(
+ {T{0.7347}, T{ 0.2653}},
+ {T{0.0000}, T{ 1.0000}},
+ {T{0.0001}, T{-0.0770}},
+ aces::white_point,
+ nullptr,
+ nullptr
+);
+
+/// ACES AP1 color space.
+template
+constexpr rgb::color_space ap1
+(
+ {T{0.713}, T{0.293}},
+ {T{0.165}, T{0.830}},
+ {T{0.128}, T{0.044}},
+ aces::white_point,
+ nullptr,
+ nullptr
+);
+
+/**
+ * Constructs a saturation adjustment matrix.
+ *
+ * @param s Saturation adjustment factor.
+ * @param to_y Color space to CIE XYZ luminance vector.
+ *
+ * @return Saturation adjustment matrix.
+ */
+template
+constexpr math::matrix adjust_saturation(T s, const math::vector3& to_y)
+{
+ const math::vector3 v = to_y * (T{1} - s);
+ return math::matrix
+ {
+ v[0] + s, v[0], v[0],
+ v[1], v[1] + s, v[1],
+ v[2], v[2], v[2] + s
+ };
+}
+
+/// ACES AP1 RRT saturation adjustment matrix.
+template
+constexpr math::matrix ap1_rrt_sat = aces::adjust_saturation(T{0.96}, ap1.to_y);
+
+/// ACES AP1 ODT saturation adjustment matrix.
+template
+constexpr math::matrix ap1_odt_sat = aces::adjust_saturation(T{0.93}, ap1.to_y);
+
+} // namespace aces
+
+} // namespace color
+
+#endif // ANTKEEPER_COLOR_ACES_HPP
diff --git a/src/color/acescg.hpp b/src/color/acescg.hpp
deleted file mode 100644
index 1b50f34..0000000
--- a/src/color/acescg.hpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2021 Christopher J. Howard
- *
- * This file is part of Antkeeper source code.
- *
- * Antkeeper source code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Antkeeper source code is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Antkeeper source code. If not, see .
- */
-
-#ifndef ANTKEEPER_COLOR_ACESCG_HPP
-#define ANTKEEPER_COLOR_ACESCG_HPP
-
-#include "math/math.hpp"
-
-namespace color {
-
-/// Functions which operate in the ACEScg colorspace.
-namespace acescg {
-
-/// CIE chromaticity coordinates of the ACEScg white point (~D60).
-template
-constexpr math::vector2 whitepoint = {0.32168, 0.33767};
-
-/**
- * Calculates the luminance of an ACEScg color.
- *
- * @param x ACEScg color.
- * @return return Luminance of @p x.
- */
-template
-T luminance(const math::vector3& x);
-
-/**
- * Transforms an ACEScg color into the linear sRGB colorspace using the Bradford chromatic adaption transform.
- *
- * @param x ACEScg color.
- * @return return Linear sRGB color.
- *
- * @see https://www.colour-science.org/apps/
- */
-template
-math::vector3 to_srgb(const math::vector3& x);
-
-/**
- * Transforms an ACEScg color into the CIE XYZ colorspace.
- *
- * @param x ACEScg color.
- * @return return CIE XYZ color.
- */
-template
-math::vector3 to_xyz(const math::vector3& x);
-
-template
-T luminance(const math::vector3& x)
-{
- static const math::vector3 luma = {0.272228716780914, 0.674081765811148, 0.053689517407937};
- return math::dot(x, luma);
-}
-
-template
-math::vector3 to_srgb(const math::vector3& x)
-{
- static const math::matrix3 acescg_to_srgb
- {{
- { 1.704858676289160, -0.130076824208823, -0.023964072927574},
- {-0.621716021885330, 1.140735774822504, -0.128975508299318},
- {-0.083299371729058, -0.010559801677511, 1.153014018916862}
- }};
-
- return acescg_to_srgb * x;
-}
-
-template
-math::vector3 to_xyz(const math::vector3& x)
-{
- static const math::matrix3 acescg_to_xyz
- {{
- {0.662454181108505, 0.272228716780914, -0.005574649490394},
- {0.134004206456433, 0.674081765811148, 0.004060733528983},
- {0.156187687004908, 0.053689517407937, 1.010339100312997}
- }};
-
- return acescg_to_xyz * x;
-}
-
-} // namespace acescg
-} // namespace color
-
-#endif // ANTKEEPER_COLOR_ACESCG_HPP
diff --git a/src/color/cat.hpp b/src/color/cat.hpp
new file mode 100644
index 0000000..3b56d03
--- /dev/null
+++ b/src/color/cat.hpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 Christopher J. Howard
+ *
+ * This file is part of Antkeeper source code.
+ *
+ * Antkeeper source code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper source code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper source code. If not, see .
+ */
+
+#ifndef ANTKEEPER_COLOR_CAT_HPP
+#define ANTKEEPER_COLOR_CAT_HPP
+
+#include "math/math.hpp"
+
+namespace color {
+
+/// Chromatic adaption transforms (CAT).
+namespace cat {
+
+/**
+ * Bradford cone response matrix.
+ *
+ * @see Specification ICC.1:2010 (Profile version 4.3.0.0). Image technology colour management — Architecture, profile format, and data structure, Annex E.3, pp. 102.
+ * @see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ */
+template
+constexpr math::matrix bradford =
+{
+ 0.8951, -0.7502, 0.0389,
+ 0.2664, 1.7135, -0.0685,
+ -0.1614, 0.0367, 1.0296
+};
+
+/**
+ * von Kries cone response matrix.
+ *
+ * @see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ */
+template
+constexpr math::matrix von_kries =
+{
+ 0.40024, -0.22630, 0.00000
+ 0.70760, 1.16532, 0.00000
+ -0.08081, 0.04570, 0.91822
+};
+
+/**
+ * XYZ scaling cone response matrix.
+ *
+ * @see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ */
+template
+constexpr math::matrix xyz_scaling =
+{
+ T{1}, T{0}. T{0},
+ T{0}, T{1}. T{0},
+ T{0}, T{0}. T{1}
+};
+
+/**
+ * Constructs a chromatic adaptation transform (CAT) matrix.
+ *
+ * @param w0 CIE xy chromaticity coordinates of the source illuminant.
+ * @param w1 CIE xy chromaticity coordinates of the destination illuminant.
+ * @param cone_response Cone response matrix.
+ *
+ * @return CAT matrix.
+ *
+ * @see Specification ICC.1:2010 (Profile version 4.3.0.0). Image technology colour management — Architecture, profile format, and data structure, Annex E.3, pp. 102.
+ * @see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ */
+template
+constexpr math::matrix matrix(const math::vector2& w0, const math::vector2& w1, const math::matrix& cone_response = bradford)
+{
+ // Convert CIE xy chromaticity coordinates to CIE XYZ colors
+ const math::vector3 w0_xyz = {w0[0] / w0[1], T{1}, (T{1} - w0[0] - w0[1]) / w0[1]};
+ const math::vector3 w1_xyz = {w1[0] / w1[1], T{1}, (T{1} - w1[0] - w1[1]) / w1[1]};
+
+ // Calculate cone response of CIE XYZ colors
+ const math::vector3 w0_cone_response = cone_response * w0_xyz;
+ const math::vector3 w1_cone_response = cone_response * w1_xyz;
+
+ const math::matrix scale =
+ {
+ w1_cone_response[0] / w0_cone_response[0], T{0}, T{0},
+ T{0}, w1_cone_response[1] / w0_cone_response[1], T{0},
+ T{0}, T{0}, w1_cone_response[2] / w0_cone_response[2],
+ };
+
+ return math::inverse(cone_response) * scale * cone_response;
+}
+
+} // namespace cat
+} // namespace color
+
+#endif // ANTKEEPER_COLOR_CAT_HPP
diff --git a/src/color/cct.hpp b/src/color/cct.hpp
index c9d676c..e9f3eb5 100644
--- a/src/color/cct.hpp
+++ b/src/color/cct.hpp
@@ -38,7 +38,15 @@ namespace cct {
* @see Krystek, M. (1985), An algorithm to calculate correlated colour temperature. Color Res. Appl., 10: 38-40.
*/
template
-math::vector2 to_ucs(T t);
+math::vector2 to_ucs(T t)
+{
+ const T tt = t * t;
+ return math::vector2
+ {
+ (T{0.860117757} + T{1.54118254e-4} * t + T{1.28641212e-7} * tt) / (T{1} + T{8.42420235e-4} * t + T{7.08145163e-7} * tt),
+ (T{0.317398726} + T{4.22806245e-5} * t + T{4.20481691e-8} * tt) / (T{1} - T{2.89741816e-5} * t + T{1.61456053e-7} * tt)
+ };
+}
/**
* Calculates CIE xyY colorspace chromaticity coordinates given a correlated color temperature using Krystek's algorithm.
@@ -47,7 +55,10 @@ math::vector2 to_ucs(T t);
* @return CIE xyY color with `Y = 1`.
*/
template
-math::vector3 to_xyy(T t);
+math::vector3 to_xyy(T t)
+{
+ return ucs::to_xyy(to_ucs(t), T{1});
+}
/**
* Calculates CIE XYZ colorspace chromaticity coordinates given a correlated color temperature using Krystek's algorithm.
@@ -55,27 +66,6 @@ math::vector3 to_xyy(T t);
* @param t Correlated color temperature, in Kelvin.
* @return CIE XYZ color with `Y = 1`.
*/
-template
-math::vector3 to_xyz(T t);
-
-template
-math::vector2 to_ucs(T t)
-{
- // Approximate the Planckian locus in CIE 1960 UCS colorspace (Krystek's algorithm)
- const T tt = t * t;
- return math::vector2
- {
- (T(0.860117757) + T(1.54118254e-4) * t + T(1.28641212e-7) * tt) / (T(1.0) + T(8.42420235e-4) * t + T(7.08145163e-7) * tt),
- (T(0.317398726) + T(4.22806245e-5) * t + T(4.20481691e-8) * tt) / (T(1.0) - T(2.89741816e-5) * t + T(1.61456053e-7) * tt)
- };
-}
-
-template
-math::vector3 to_xyy(T t)
-{
- return ucs::to_xyy(to_ucs(t), T(1.0));
-}
-
template
math::vector3 to_xyz(T t)
{
diff --git a/src/color/color.hpp b/src/color/color.hpp
index b5bcf10..8be5a20 100644
--- a/src/color/color.hpp
+++ b/src/color/color.hpp
@@ -23,12 +23,15 @@
/// Color manipulation functions.
namespace color {}
-#include "acescg.hpp"
+#include "aces.hpp"
#include "cct.hpp"
#include "index.hpp"
+#include "rgb.hpp"
#include "srgb.hpp"
#include "ucs.hpp"
#include "xyy.hpp"
#include "xyz.hpp"
+#include "cat.hpp"
+#include "illuminant.hpp"
#endif // ANTKEEPER_COLOR_HPP
diff --git a/src/color/illuminant.hpp b/src/color/illuminant.hpp
new file mode 100644
index 0000000..0b134ec
--- /dev/null
+++ b/src/color/illuminant.hpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2021 Christopher J. Howard
+ *
+ * This file is part of Antkeeper source code.
+ *
+ * Antkeeper source code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper source code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper source code. If not, see .
+ */
+
+#ifndef ANTKEEPER_COLOR_ILLUMINANT_HPP
+#define ANTKEEPER_COLOR_ILLUMINANT_HPP
+
+#include "math/math.hpp"
+
+namespace color {
+
+/**
+ * CIE standard illuminants.
+ *
+ * @see https://en.wikipedia.org/wiki/Standard_illuminant
+ */
+namespace illuminant {
+
+/// CIE 1931 2 Degree Standard Observer illuminants.
+namespace deg2 {
+
+ template
+ constexpr math::vector2 a = {T{0.44757}, T{0.40745}};
+ template
+ constexpr math::vector2 b = {T{0.34842}, T{0.35161}};
+ template
+ constexpr math::vector2 c = {T{0.31006}, T{0.31616}};
+ template
+ constexpr math::vector2 d50 = {T{0.34567}, T{0.35850}};
+ template
+ constexpr math::vector2 d55 = {T{0.33242}, T{0.34743}};
+ template
+ constexpr math::vector2 d65 = {T{0.31271}, T{0.32902}};
+ template
+ constexpr math::vector2 d75 = {T{0.29902}, T{0.31485}};
+ template
+ constexpr math::vector2 d93 = {T{0.28315}, T{0.29711}};
+ template
+ constexpr math::vector2 e = {T{0.33333}, T{0.33333}};
+ template
+ constexpr math::vector2 f1 = {T{0.31310}, T{0.33727}};
+ template
+ constexpr math::vector2 f2 = {T{0.37208}, T{0.37529}};
+ template
+ constexpr math::vector2 f3 = {T{0.40910}, T{0.39430}};
+ template
+ constexpr math::vector2 f4 = {T{0.44018}, T{0.40329}};
+ template
+ constexpr math::vector2 f5 = {T{0.31379}, T{0.34531}};
+ template
+ constexpr math::vector2 f6 = {T{0.37790}, T{0.38835}};
+ template
+ constexpr math::vector2 f7 = {T{0.31292}, T{0.32933}};
+ template
+ constexpr math::vector2 f8 = {T{0.34588}, T{0.35875}};
+ template
+ constexpr math::vector2 f9 = {T{0.37417}, T{0.37281}};
+ template
+ constexpr math::vector2 f10 = {T{0.34609}, T{0.35986}};
+ template
+ constexpr math::vector2 f11 = {T{0.38052}, T{0.37713}};
+ template
+ constexpr math::vector2 f12 = {T{0.43695}, T{0.40441}};
+ template
+ constexpr math::vector2 led_b1 = {T{0.4560}, T{0.4078}};
+ template
+ constexpr math::vector2 led_b2 = {T{0.4357}, T{0.4012}};
+ template
+ constexpr math::vector2 led_b3 = {T{0.3756}, T{0.3723}};
+ template
+ constexpr math::vector2 led_b4 = {T{0.3422}, T{0.3502}};
+ template
+ constexpr math::vector2 led_b5 = {T{0.3118}, T{0.3236}};
+ template
+ constexpr math::vector2 led_bh1 = {T{0.4474}, T{0.4066}};
+ template
+ constexpr math::vector2 led_rgb1 = {T{0.4557}, T{0.4211}};
+ template
+ constexpr math::vector2 led_v1 = {T{0.4560}, T{0.4548}};
+ template
+ constexpr math::vector2 led_v2 = {T{0.3781}, T{0.3775}};
+
+} // deg2
+
+/// CIE 1964 10 Degree Standard Observer illuminants.
+namespace deg10 {
+
+ template
+ constexpr math::vector2 a = {T{0.45117}, T{0.40594}};
+ template
+ constexpr math::vector2 b = {T{0.34980}, T{0.35270}};
+ template
+ constexpr math::vector2 c = {T{0.31039}, T{0.31905}};
+ template
+ constexpr math::vector2 d50 = {T{0.34773}, T{0.35952}};
+ template
+ constexpr math::vector2 d55 = {T{0.33411}, T{0.34877}};
+ template
+ constexpr math::vector2 d65 = {T{0.31382}, T{0.33100}};
+ template
+ constexpr math::vector2 d75 = {T{0.29968}, T{0.31740}};
+ template
+ constexpr math::vector2 d93 = {T{0.28327}, T{0.30043}};
+ template
+ constexpr math::vector2 e = {T{0.33333}, T{0.33333}};
+ template
+ constexpr math::vector2 f1 = {T{0.31811}, T{0.33559}};
+ template
+ constexpr math::vector2 f2 = {T{0.37925}, T{0.36733}};
+ template
+ constexpr math::vector2 f3 = {T{0.41761}, T{0.38324}};
+ template
+ constexpr math::vector2 f4 = {T{0.44920}, T{0.39074}};
+ template
+ constexpr math::vector2 f5 = {T{0.31975}, T{0.34246}};
+ template
+ constexpr math::vector2 f6 = {T{0.38660}, T{0.37847}};
+ template
+ constexpr math::vector2 f7 = {T{0.31569}, T{0.32960}};
+ template
+ constexpr math::vector2 f8 = {T{0.34902}, T{0.35939}};
+ template
+ constexpr math::vector2 f9 = {T{0.37829}, T{0.37045}};
+ template
+ constexpr math::vector2 f10 = {T{0.35090}, T{0.35444}};
+ template
+ constexpr math::vector2 f11 = {T{0.38541}, T{0.37123}};
+ template
+ constexpr math::vector2 f12 = {T{0.44256}, T{0.39717}};
+
+} // namespace deg10
+
+} // namespace illuminant
+
+} // namespace color
+
+#endif // ANTKEEPER_COLOR_ILLUMINANT_HPP
diff --git a/src/color/index.hpp b/src/color/index.hpp
index 6033035..ff7b552 100644
--- a/src/color/index.hpp
+++ b/src/color/index.hpp
@@ -33,13 +33,10 @@ namespace index {
*
* @see Ballesteros, F. J. (2012). "New insights into black bodies". EPL 97 (2012) 34008.
*/
-template
-T bv_to_cct(T bv);
-
template
T bv_to_cct(T bv)
{
- return T(4600.0) * (T(1.0) / (T(0.92) * bv + T(1.7)) + T(1.0) / (T(0.92) * bv + T(0.62)));
+ return T{4600} * (T{1} / (T{0.92} * bv + T{1.7}) + T{1} / (T{0.92} * bv + T{0.62}));
}
} // namespace index
diff --git a/src/color/rgb.hpp b/src/color/rgb.hpp
new file mode 100644
index 0000000..4e83a7f
--- /dev/null
+++ b/src/color/rgb.hpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 Christopher J. Howard
+ *
+ * This file is part of Antkeeper source code.
+ *
+ * Antkeeper source code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper source code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper source code. If not, see .
+ */
+
+#ifndef ANTKEEPER_COLOR_RGB_HPP
+#define ANTKEEPER_COLOR_RGB_HPP
+
+#include "color/cat.hpp"
+#include "math/math.hpp"
+
+namespace color {
+
+/// Functions which operate on various RGB colorspaces.
+namespace rgb {
+
+/**
+ * Constructs a matrix which transforms an RGB color into a CIE XYZ color.
+ *
+ * @param r CIE xy chromaticity coordinates of the red primary.
+ * @param g CIE xy chromaticity coordinates of the green primary.
+ * @param b CIE xy chromaticity coordinates of the blue primary.
+ * @param w CIE xy chromaticity coordinates of the white point.
+ *
+ * @return Matrix which transforms an RGB color into a CIE XYZ color.
+ *
+ * @see https://www.ryanjuckett.com/rgb-color-space-conversion/
+ * @see https://mina86.com/2019/srgb-xyz-matrix/
+ */
+template
+constexpr math::matrix to_xyz(const math::vector2& r, const math::vector2& g, const math::vector2& b, const math::vector2& w)
+{
+ const math::matrix m =
+ {
+ r[0], r[1], T{1} - (r[0] + r[1]),
+ g[0], g[1], T{1} - (g[0] + g[1]),
+ b[0], b[1], T{1} - (b[0] + b[1])
+ };
+
+ const math::vector3 scale = math::inverse(m) * math::vector3{w[0] / w[1], T{1}, (T{1} - (w[0] + w[1])) / w[1]};
+
+ return math::matrix
+ {
+ m[0][0] * scale[0], m[0][1] * scale[0], m[0][2] * scale[0],
+ m[1][0] * scale[1], m[1][1] * scale[1], m[1][2] * scale[1],
+ m[2][0] * scale[2], m[2][1] * scale[2], m[2][2] * scale[2],
+ };
+}
+
+/**
+ * RGB color space.
+ */
+template
+struct color_space
+{
+ /// Transfer function function pointer type.
+ typedef math::vector3 (*transfer_function_type)(const math::vector3&);
+
+ /// CIE xy chromaticity coordinates of the red primary.
+ const math::vector2 r;
+
+ /// CIE xy chromaticity coordinates of the green primary.
+ const math::vector2 g;
+
+ /// CIE xy chromaticity coordinates of the blue primary.
+ const math::vector2 b;
+
+ /// CIE xy chromaticity coordinates of the white point.
+ const math::vector2 w;
+
+ /// Function pointer to the electro-optical transfer function.
+ const transfer_function_type eotf;
+
+ /// Function pointer to the inverse electro-optical transfer function.
+ const transfer_function_type inverse_eotf;
+
+ /// Matrix which transforms an RGB color to a CIE XYZ color.
+ const math::matrix3x3 to_xyz;
+
+ /// Matrix which transforms a CIE XYZ color to an RGB color.
+ const math::matrix3x3 from_xyz;
+
+ /// Vector which gives the luminance of an RGB color via dot product.
+ const math::vector3 to_y;
+
+ /**
+ * Constructs an RGB color space.
+ *
+ * @param r CIE xy chromaticity coordinates of the red primary.
+ * @param g CIE xy chromaticity coordinates of the green primary.
+ * @param b CIE xy chromaticity coordinates of the blue primary.
+ * @param w CIE xy chromaticity coordinates of the white point.
+ */
+ constexpr color_space(const math::vector2& r, const math::vector2& g, const math::vector2& b, const math::vector2& w, transfer_function_type eotf, transfer_function_type inverse_eotf);
+
+ /**
+ * Measures the luminance of a linear RGB color.
+ *
+ * @param x Linear RGB color.
+ * @return return Luminance of @p x.
+ */
+ constexpr T luminance(const math::vector3& x) const;
+};
+
+template
+constexpr color_space::color_space(const math::vector2& r, const math::vector2& g, const math::vector2& b, const math::vector2& w, transfer_function_type eotf, transfer_function_type inverse_eotf):
+ r(r),
+ g(g),
+ b(b),
+ w(w),
+ eotf(eotf),
+ inverse_eotf(inverse_eotf),
+ to_xyz(color::rgb::to_xyz(r, g, b, w)),
+ from_xyz(math::inverse(to_xyz)),
+ to_y{to_xyz[0][1], to_xyz[1][1], to_xyz[2][1]}
+{}
+
+template
+constexpr T color_space::luminance(const math::vector3& x) const
+{
+ return math::dot(x, to_y);
+}
+
+/**
+ * Constructs a matrix which transforms a color from one RGB color space to another RGB color space.
+ *
+ * @param s0 Source color space.
+ * @param s1 Destination color space.
+ * @param cone_response Chromatic adaptation transform cone response matrix.
+ *
+ * @return Color space transformation matrix.
+ */
+template
+constexpr math::matrix3x3 to_rgb(const color_space& s0, const color_space& s1, const math::matrix3x3& cone_response = color::cat::bradford)
+{
+ return s1.from_xyz * color::cat::matrix(s0.w, s1.w, cone_response) * s0.to_xyz;
+}
+
+} // namespace rgb
+} // namespace color
+
+#endif // ANTKEEPER_COLOR_RGB_HPP
diff --git a/src/color/srgb.hpp b/src/color/srgb.hpp
index 03561be..a43e429 100644
--- a/src/color/srgb.hpp
+++ b/src/color/srgb.hpp
@@ -20,149 +20,71 @@
#ifndef ANTKEEPER_COLOR_SRGB_HPP
#define ANTKEEPER_COLOR_SRGB_HPP
+#include "color/rgb.hpp"
+#include "color/illuminant.hpp"
#include "math/math.hpp"
#include
namespace color {
-/// Functions which operate in the sRGB colorspace.
-namespace srgb {
-
-/// CIE chromaticity coordinates of the sRGB white point (D65)
-template
-constexpr math::vector2 whitepoint = {0.31271, 0.32902};
-
/**
- * Performs the sRGB Electro-Optical Transfer Function (EOTF), also known as the sRGB decoding function.
+ * sRGB electro-optical transfer function (EOTF), also known as the sRGB decoding function.
*
* @param v sRGB electrical signal (gamma-encoded sRGB).
+ *
* @return Corresponding luminance of the signal (linear sRGB).
*/
-/// @{
-float eotf(float v);
-double eotf(double v);
template
-math::vector3 eotf(const math::vector3& v);
-/// @}
+math::vector3 srgb_eotf(const math::vector3& v)
+{
+ auto f = [](T x) -> T
+ {
+ return x < T{0.04045} ? x / T{12.92} : std::pow((x + T{0.055}) / T{1.055}, T{2.4});
+ };
+
+ return math::vector3
+ {
+ f(v[0]),
+ f(v[1]),
+ f(v[2])
+ };
+}
/**
- * Performs the sRGB inverse Electro-Optical Transfer Function (EOTF), also known as the sRGB encoding function.
+ * sRGB inverse electro-optical transfer function (EOTF), also known as the sRGB encoding function.
*
* @param l sRGB luminance (linear sRGB).
- * @return Corresponding electrical signal (gamma-encoded sRGB).
- */
-/// @{
-float eotf_inverse(float v);
-double eotf_inverse(double v);
-template
-math::vector3 eotf_inverse(const math::vector3& l);
-/// @}
-
-/**
- * Calculates the luminance of a linear sRGB color.
*
- * @param x Linear sRGB color.
- * @return return Luminance of @p x.
- */
-template
-T luminance(const math::vector3& x);
-
-/**
- * Transforms a linear sRGB color into the ACEScg colorspace using the Bradford chromatic adaption transform.
- *
- * @param x Linear sRGB color.
- * @return ACEScg color.
- *
- * @see https://www.colour-science.org/apps/
- */
-template
-math::vector3 to_acescg(const math::vector3& x);
-
-/**
- * Transforms a linear sRGB color into the CIE XYZ colorspace.
- *
- * @param x Linear sRGB color.
- * @return CIE XYZ color.
+ * @return Corresponding electrical signal (gamma-encoded sRGB).
*/
template
-math::vector3 to_xyz(const math::vector3& x);
-
-inline float eotf(float v)
-{
- return (v < 0.04045f) ? (v / 12.92f) : std::pow((v + 0.055f) / 1.055f, 2.4f);
-}
-
-inline double eotf(double v)
+math::vector3 srgb_inverse_eotf(const math::vector3& l)
{
- return (v < 0.04045) ? (v / 12.92) : std::pow((v + 0.055) / 1.055, 2.4);
-}
-
-template
-math::vector3 eotf(const math::vector3& v)
-{
- return math::vector3
- {
- eotf(v[0]),
- eotf(v[1]),
- eotf(v[2])
- };
-}
-
-inline float eotf_inverse(float l)
-{
- return (l <= 0.0031308f) ? (l * 12.92f) : (std::pow(l, 1.0f / 2.4f) * 1.055f - 0.055f);
-}
-
-inline double eotf_inverse(double l)
-{
- return (l <= 0.0031308) ? (l * 12.92) : (std::pow(l, 1.0 / 2.4) * 1.055 - 0.055);
-}
-
-template
-math::vector3 eotf_inverse(const math::vector3& l)
-{
- return math::vector3
- {
- eotf_inverse(l[0]),
- eotf_inverse(l[1]),
- eotf_inverse(l[2])
- };
-}
-
-template
-T luminance(const math::vector3& x)
-{
- static const math::vector3 luma = {0.212639005871510, 0.715168678767756, 0.072192315360734};
- return math::dot(x, luma);
-}
-
-template
-math::vector3