diff --git a/src/color/ucs.hpp b/src/color/ucs.hpp
new file mode 100644
index 0000000..5fbeb5b
--- /dev/null
+++ b/src/color/ucs.hpp
@@ -0,0 +1,56 @@
+/*
+ * 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_UCS_HPP
+#define ANTKEEPER_COLOR_UCS_HPP
+
+#include "math/math.hpp"
+
+namespace color {
+
+/// Functions which operate in the CIE 1960 UCS colorspace.
+namespace ucs {
+
+/**
+ * Transforms a CIE 1960 UCS color into the CIE xyY colorspace.
+ *
+ * @param uv CIE 1960 UCS color coordinates.
+ * @param luminance Luminance or `Y` value of the resulting xyY color.
+ * @return CIE xyY color.
+ */
+template
+math::vector3 to_xyy(const math::vector2& uv, T luminance);
+
+template
+math::vector3 to_xyy(const math::vector2& uv, T luminance);
+{
+ const T inverse_denom = 1.0 / (2.0 * uv[0] - 8.0 * uv[1] + 4.0);
+
+ return math::vector3
+ {
+ (3.0 * uv[0]) * inverse_denom,
+ (2.0 * uv[1]) * inverse_denom,
+ luminance
+ };
+}
+
+} // namespace ucs
+} // namespace color
+
+#endif // ANTKEEPER_COLOR_UCS_HPP
diff --git a/src/color/xyy.hpp b/src/color/xyy.hpp
new file mode 100644
index 0000000..a133df6
--- /dev/null
+++ b/src/color/xyy.hpp
@@ -0,0 +1,89 @@
+/*
+ * 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_XYY_HPP
+#define ANTKEEPER_COLOR_XYY_HPP
+
+#include "math/math.hpp"
+
+namespace color {
+
+/// Functions which operate in the CIE xyY colorspace.
+namespace xyy {
+
+/**
+ * Returns the luminance of a CIE xyY color.
+ *
+ * @param x CIE xyY color.
+ * @return return Luminance of @p x.
+ */
+template
+T luminance(const math::vector3& x);
+
+/**
+ * Transforms a CIE xyY color into the CIE 1960 UCS colorspace.
+ *
+ * @param x CIE xyY color.
+ * @return CIE 1960 UCS color.
+ */
+template
+math::vector2 to_ucs(const math::vector3& x);
+
+/**
+ * Transforms a CIE xyY color into the CIE XYZ colorspace.
+ *
+ * @param x CIE xyY color.
+ * @return CIE XYZ color.
+ */
+template
+math::vector3 to_xyz(const math::vector3& x);
+
+template
+inline T luminance(const math::vector3& x)
+{
+ return x[2];
+}
+
+template
+math::vector2 to_ucs(const math::vector3& x)
+{
+ const T inverse_denom = 1.0 / (-2.0 * x[0] + 12.0 * x[1] + 3.0);
+
+ return math::vector2
+ {
+ (4.0 * x[0]) * inverse_denom,
+ (6.0 * x[1]) * inverse_denom
+ };
+}
+
+template
+math::vector3 to_xyz(const math::vector3& x)
+{
+ return math::vector3
+ {
+ (x[0] * x[2]) / x[1],
+ x[2],
+ ((1.0 - x[0] - x[1]) * x[2]) / x[1]
+ };
+}
+
+} // namespace xyy
+} // namespace color
+
+#endif // ANTKEEPER_COLOR_XYY_HPP
diff --git a/src/color/xyz.hpp b/src/color/xyz.hpp
index 1c79531..6d4350f 100644
--- a/src/color/xyz.hpp
+++ b/src/color/xyz.hpp
@@ -54,6 +54,15 @@ math::vector3 to_acescg(const math::vector3& x);
template
math::vector3 to_srgb(const math::vector3& x);
+/**
+ * Transforms a CIE XYZ color into the CIE xyY colorspace.
+ *
+ * @param x CIE XYZ color.
+ * @return CIE xyY color.
+ */
+template
+math::vector3 to_xyy(const math::vector3& x);
+
template
inline T luminance(const math::vector3& x)
{
@@ -86,6 +95,19 @@ math::vector3 to_srgb(const math::vector3& x)
return xyz_to_srgb * x;
}
+template
+math::vector3 to_xyy(const math::vector3& x)
+{
+ const T sum = x[0] + x[1] + x[2];
+
+ return math::vector3
+ {
+ x[0] / sum
+ x[1] / sum,
+ x[1]
+ };
+}
+
} // namespace xyz
} // namespace color