mirror of
https://git.lynn.is/Gwen/pretty-automata.git
synced 2024-05-18 15:21:07 +02:00
321 lines
9.3 KiB
C++
321 lines
9.3 KiB
C++
#include "color.h"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
constexpr double cie_epsilon = 216. / 24389.;
|
|
constexpr double cie_kappa = 24389. / 27.;
|
|
|
|
constexpr double D65_X = 0.95047;
|
|
constexpr double D65_Y = 1.;
|
|
constexpr double D65_Z = 1.08883;
|
|
|
|
static std::array<double, 3> linrgb_to_srgb(std::array<double, 3> linrgb);
|
|
static std::array<double, 3> srgb_to_linrgb(std::array<double, 3> srgb);
|
|
|
|
static std::array<double, 3> linrgb_to_xyz(std::array<double, 3> linrgb);
|
|
static std::array<double, 3> xyz_to_linrgb(std::array<double, 3> xyz);
|
|
|
|
static std::array<double, 3> cielab_to_xyz(std::array<double, 3> cielab);
|
|
static std::array<double, 3> xyz_to_cielab(std::array<double, 3> xyz);
|
|
|
|
static std::array<double, 3> cieluv_to_xyz(std::array<double, 3> cieluv);
|
|
static std::array<double, 3> xyz_to_cieluv(std::array<double, 3> xyz);
|
|
|
|
static std::array<double, 3> linrgb_to_oklab(std::array<double, 3> linrgb);
|
|
static std::array<double, 3> oklab_to_linrgb(std::array<double, 3> oklab);
|
|
|
|
static std::array<double, 3> xyzmatrix_mult(const std::array<std::array<double, 3>, 3> M, const std::array<double, 3> v);
|
|
|
|
ColorSpace Color::default_colorspace = ColorSpace::CIELAB;
|
|
|
|
std::array<double, 3> Color::values() const {
|
|
return {v1, v2, v3};
|
|
}
|
|
|
|
std::array<uint8_t, 3> Color::to_rgb() const {
|
|
std::array<double, 3> normalised = this->to_normalized_rgb();
|
|
return {
|
|
static_cast<uint8_t>(std::round(normalised[0]*255.)),
|
|
static_cast<uint8_t>(std::round(normalised[1]*255.)),
|
|
static_cast<uint8_t>(std::round(normalised[2]*255.)),
|
|
};
|
|
}
|
|
|
|
std::array<double, 3> Color::to_normalized_rgb() const {
|
|
std::array<double, 3> linrgb{};
|
|
switch (colorspace) {
|
|
case ColorSpace::CIELAB:
|
|
linrgb = xyz_to_linrgb(cielab_to_xyz({v1, v2, v3}));
|
|
break;
|
|
case ColorSpace::CIELUV:
|
|
linrgb = xyz_to_linrgb(cieluv_to_xyz({v1, v2, v3}));
|
|
break;
|
|
case ColorSpace::OKLAB:
|
|
linrgb = oklab_to_linrgb({v1, v2, v3});
|
|
break;
|
|
}
|
|
return linrgb_to_srgb(linrgb);
|
|
}
|
|
|
|
Color Color::from_rgb(const std::array<uint8_t, 3> rgb, ColorSpace colorspace) {
|
|
return from_rgb(rgb[0], rgb[1], rgb[2], colorspace);
|
|
}
|
|
|
|
Color Color::from_rgb(double red, double green, double blue, ColorSpace colorspace) {
|
|
return from_normalized_rgb(red / 255., green / 255., blue / 255., colorspace);
|
|
}
|
|
|
|
Color Color::from_rgb(const std::array<double, 3> rgb, ColorSpace colorspace) {
|
|
return from_rgb(rgb[0], rgb[1], rgb[2], colorspace);
|
|
}
|
|
|
|
Color Color::from_normalized_rgb(double red, double green, double blue, ColorSpace colorspace) {
|
|
return from_normalized_rgb({red, green, blue}, colorspace);
|
|
}
|
|
|
|
Color Color::from_normalized_rgb(const std::array<double, 3> rgb, ColorSpace colorspace) {
|
|
auto linrgb = srgb_to_linrgb(rgb);
|
|
std::array<double, 3> values{};
|
|
switch (colorspace) {
|
|
case ColorSpace::CIELAB:
|
|
values = xyz_to_cielab(linrgb_to_xyz(linrgb));
|
|
break;
|
|
case ColorSpace::CIELUV:
|
|
values = xyz_to_cieluv(linrgb_to_xyz(linrgb));
|
|
break;
|
|
case ColorSpace::OKLAB:
|
|
values = linrgb_to_oklab(linrgb);
|
|
break;
|
|
}
|
|
|
|
return {values, colorspace};
|
|
}
|
|
|
|
Color Color::from_rgb(uint8_t red, uint8_t green, uint8_t blue) {
|
|
return from_rgb(red, green, blue, default_colorspace);
|
|
}
|
|
|
|
Color Color::from_rgb(const std::array<uint8_t, 3> rgb) {
|
|
return from_rgb(rgb, default_colorspace);
|
|
}
|
|
|
|
Color Color::from_rgb(double red, double green, double blue) {
|
|
return from_rgb(red, green, blue, default_colorspace);
|
|
}
|
|
|
|
Color Color::from_rgb(const std::array<double, 3> rgb) {
|
|
return from_rgb(rgb, default_colorspace);
|
|
}
|
|
|
|
Color Color::from_normalized_rgb(double red, double green, double blue) {
|
|
return from_normalized_rgb(red, green, blue, default_colorspace);
|
|
}
|
|
|
|
Color Color::from_normalized_rgb(const std::array<double, 3> rgb) {
|
|
return from_normalized_rgb(rgb, default_colorspace);
|
|
}
|
|
|
|
void Color::set_default_colorspace(ColorSpace colorspace) {
|
|
default_colorspace = colorspace;
|
|
}
|
|
|
|
std::array<double, 3> linrgb_to_srgb(const std::array<double, 3> linrgb) {
|
|
std::array<double, 3> srgb{};
|
|
std::transform(linrgb.begin(), linrgb.end(), srgb.begin(), [](double v) {
|
|
if (v <= 0.0031308) {
|
|
return v * 12.92;
|
|
} else {
|
|
return 1.055 * pow(v, 1. / 2.4) - 0.055;
|
|
}
|
|
});
|
|
return srgb;
|
|
}
|
|
|
|
std::array<double, 3> srgb_to_linrgb(const std::array<double, 3> srgb) {
|
|
std::array<double, 3> linrgb{};
|
|
std::transform(srgb.begin(), srgb.end(), linrgb.begin(), [](double v) {
|
|
if (v <= 0.04045) {
|
|
return v / 12.92;
|
|
} else {
|
|
return pow(((v + 0.055) / 1.055), 2.4);
|
|
}
|
|
});
|
|
return linrgb;
|
|
}
|
|
|
|
static std::array<double, 3> xyzmatrix_mult(const std::array<std::array<double, 3>, 3> M, const std::array<double, 3> v) {
|
|
return {
|
|
M[0][0] * v[0] + M[0][1] * v[1] + M[0][2] * v[2],
|
|
M[1][0] * v[0] + M[1][1] * v[1] + M[1][2] * v[2],
|
|
M[2][0] * v[0] + M[2][1] * v[1] + M[2][2] * v[2],
|
|
};
|
|
}
|
|
|
|
constexpr std::array<std::array<double, 3>, 3> RGB_TO_XYZ_M = {
|
|
std::array<double, 3>{0.4124564, 0.3575761, 0.1804375},
|
|
std::array<double, 3>{0.2126729, 0.7151522, 0.0721750},
|
|
std::array<double, 3>{0.0193339, 0.1191920, 0.9503041},
|
|
};
|
|
|
|
constexpr std::array<std::array<double, 3>, 3> XYZ_TO_RGB_M = {
|
|
std::array<double, 3>{3.2404542, -1.5371385, -0.4985314},
|
|
std::array<double, 3>{-0.9692660, 1.8760108, 0.0415560},
|
|
std::array<double, 3>{0.0556434, -0.2040259, 1.0572252},
|
|
};
|
|
|
|
std::array<double, 3> linrgb_to_xyz(const std::array<double, 3> linrgb) {
|
|
return xyzmatrix_mult(RGB_TO_XYZ_M, linrgb);
|
|
}
|
|
std::array<double, 3> xyz_to_linrgb(const std::array<double, 3> xyz) {
|
|
return xyzmatrix_mult(XYZ_TO_RGB_M, xyz);
|
|
}
|
|
std::array<double, 3> cielab_to_xyz(const std::array<double, 3> cielab) {
|
|
auto L = cielab[0];
|
|
auto a = cielab[1];
|
|
auto b = cielab[2];
|
|
|
|
auto fy = (L + 16.) / 116.;
|
|
auto fz = fy - b / 200.;
|
|
auto fx = a / 500. + fy;
|
|
|
|
double xr, yr, zr;
|
|
if (pow(fx, 3.) > cie_epsilon) {
|
|
xr = pow(fx, 3.);
|
|
} else {
|
|
xr = (116. * fx - 16.) / cie_kappa;
|
|
}
|
|
|
|
if (L > cie_kappa * cie_epsilon) {
|
|
yr = pow((L + 16.) / 116., 3.);
|
|
} else {
|
|
yr = L / cie_kappa;
|
|
}
|
|
|
|
if (pow(fz, 3.) > cie_epsilon) {
|
|
zr = pow(fz, 3.);
|
|
} else {
|
|
zr = (116. * fz - 16.) / cie_kappa;
|
|
}
|
|
|
|
auto X = xr * D65_X;
|
|
auto Y = yr * D65_Y;
|
|
auto Z = zr * D65_Z;
|
|
|
|
return {X, Y, Z};
|
|
}
|
|
std::array<double, 3> xyz_to_cielab(const std::array<double, 3> xyz) {
|
|
|
|
double xr = xyz[0] / D65_X;
|
|
double yr = xyz[1] / D65_Y;
|
|
double zr = xyz[2] / D65_Z;
|
|
|
|
double fx, fy, fz;
|
|
if (xr > cie_epsilon) {
|
|
fx = cbrt(xr);
|
|
} else {
|
|
fx = (cie_kappa * xr + 16.) / 116.;
|
|
}
|
|
if (yr > cie_epsilon) {
|
|
fy = cbrt(yr);
|
|
} else {
|
|
fy = (cie_kappa * yr + 16.) / 116.;
|
|
}
|
|
if (zr > cie_epsilon) {
|
|
fz = cbrt(zr);
|
|
} else {
|
|
fz = (cie_kappa * zr + 16.) / 116.;
|
|
}
|
|
|
|
double L = 116. * fy - 16.;
|
|
double a = 500. * (fx - fy);
|
|
double b = 200. * (fy - fz);
|
|
|
|
return {L, a, b};
|
|
}
|
|
std::array<double, 3> cieluv_to_xyz(const std::array<double, 3> cieluv) {
|
|
constexpr double ur_ = (4. * D65_X) / (D65_X + 15. * D65_Y + 3. * D65_Z);
|
|
constexpr double vr_ = (9. * D65_Y) / (D65_X + 15. * D65_Y + 3. * D65_Z);
|
|
|
|
double L = cieluv[0];
|
|
double u = cieluv[1];
|
|
double v = cieluv[2];
|
|
|
|
double Y;
|
|
if (L > cie_kappa * cie_epsilon) {
|
|
Y = pow((L + 16.) / 116., 3.);
|
|
} else {
|
|
Y = L / cie_kappa;
|
|
}
|
|
|
|
double a_ = u + 13. * L * ur_;
|
|
double a = a_ != 0. ? 1. / 3. * (52. * L / a_ - 1.) : 0.;
|
|
double b = -5. * Y;
|
|
double c = -1. / 3.;
|
|
double d_ = v + 13. * L * vr_;
|
|
double d = d_ != 0. ? Y * (39. * L / d_ - 5.) : 0.;
|
|
|
|
double X = (d - b) / (a - c);
|
|
double Z = X * a + b;
|
|
return {X, Y, Z};
|
|
}
|
|
std::array<double, 3> xyz_to_cieluv(const std::array<double, 3> xyz) {
|
|
constexpr double ur_ = (4. * D65_X) / (D65_X + 15. * D65_Y + 3. * D65_Z);
|
|
constexpr double vr_ = (9. * D65_Y) / (D65_X + 15. * D65_Y + 3. * D65_Z);
|
|
|
|
double X = xyz[0];
|
|
double Y = xyz[1];
|
|
double Z = xyz[2];
|
|
|
|
double yr = Y / D65_Y;
|
|
double D = (X + 15. * Y + 3. * Z);
|
|
double u_, v_;
|
|
if (D != 0) {
|
|
u_ = (4. * X) / D;
|
|
v_ = (9. * Y) / D;
|
|
} else {
|
|
u_ = 0;
|
|
v_ = 0;
|
|
}
|
|
|
|
double L;
|
|
if (yr > cie_epsilon) {
|
|
L = 116. * cbrt(yr) - 16.;
|
|
} else {
|
|
L = cie_kappa * yr;
|
|
}
|
|
double u = 13. * L * (u_ - ur_);
|
|
double v = 13. * L * (v_ - vr_);
|
|
|
|
return {L, u, v};
|
|
}
|
|
std::array<double, 3> linrgb_to_oklab(const std::array<double, 3> linrgb) {
|
|
// https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
|
|
double l = 0.4122214708 * linrgb[0] + 0.5363325363 * linrgb[1] + 0.0514459929 * linrgb[2];
|
|
double m = 0.2119034982 * linrgb[0] + 0.6806995451 * linrgb[1] + 0.1073969566 * linrgb[2];
|
|
double s = 0.0883024619 * linrgb[0] + 0.2817188376 * linrgb[1] + 0.6299787005 * linrgb[2];
|
|
|
|
double l_ = cbrt(l);
|
|
double m_ = cbrt(m);
|
|
double s_ = cbrt(s);
|
|
|
|
return {
|
|
0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
|
|
1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
|
|
0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
|
|
};
|
|
}
|
|
std::array<double, 3> oklab_to_linrgb(const std::array<double, 3> oklab) {
|
|
double l_ = oklab[0] + 0.3963377774 * oklab[1] + 0.2158037573 * oklab[2];
|
|
double m_ = oklab[0] - 0.1055613458 * oklab[1] - 0.0638541728 * oklab[2];
|
|
double s_ = oklab[0] - 0.0894841775 * oklab[1] - 1.2914855480 * oklab[2];
|
|
|
|
double l = l_ * l_ * l_;
|
|
double m = m_ * m_ * m_;
|
|
double s = s_ * s_ * s_;
|
|
|
|
return {
|
|
+4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
|
|
};
|
|
} |