Claire/test/cieluv.cpp

335 lines
12 KiB
C++

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <tuple>
#include <iostream>
#include <utility>
std::tuple<float, float, float> rgb_to_xyz(const uint8_t p_red, const uint8_t p_green, const uint8_t p_blue) {
auto normalize_uint8_t = [](const uint8_t p_value) -> float {
return std::min(static_cast<float>(p_value) / 255.0F, 1.0F);
};
auto gamma_correction = [](const float p_value) -> float {
float gamma_corrected_value = 0.0F;
if (p_value <= 0.0405F) {
gamma_corrected_value = p_value / 12.92F;
} else {
gamma_corrected_value = std::pow((p_value + 0.055F) / 1.055F, 2.4F);
}
return gamma_corrected_value;
};
float red = gamma_correction(normalize_uint8_t(p_red));
float green = gamma_correction(normalize_uint8_t(p_green));
float blue = gamma_correction(normalize_uint8_t(p_blue));
float x = 0.4124564F * red + 0.3575761F * green + 0.1804375F * blue;
float y = 0.2126729F * red + 0.7151522F * green + 0.0721750F * blue;
float z = 0.0193339F * red + 0.1191920F * green + 0.9503041F * blue;
return {x * 100.0F, y * 100.0F, z * 100.0F};
}
std::tuple<float, float, float> xyz_to_rgb_float(float p_x, float p_y, float p_z) {
auto reverse_gamma_correction = [](const float p_value) -> float {
float reverse_gamma_corrected_value = 0.0F;
if (p_value <= 0.0031308F) {
reverse_gamma_corrected_value = p_value * 12.92F;
} else {
reverse_gamma_corrected_value = std::pow(p_value, 1.0F / 2.4F) * 1.055F - 0.055F;
}
return reverse_gamma_corrected_value;
};
auto clamp_if_within_error_margin = [](const float p_value) -> float {
float value = p_value;
constexpr float MARGIN = 0.001F;
if ((p_value >= -MARGIN) and (p_value < 0.0F)) {
value = 0.0F;
} else if ((p_value <= 1.0F + MARGIN) and (p_value > 1.0F)) {
value = 1.0F;
}
return value;
};
float red = 3.2404542F * p_x / 100.0F - 1.5371385F * p_y / 100.0F - 0.4985314F * p_z / 100.0F;
float green = -0.9692660F * p_x / 100.0F + 1.8760108F * p_y / 100.0F + 0.0415560F * p_z / 100.0F;
float blue = 0.0556434F * p_x / 100.0F - 0.2040259F * p_y / 100.0F + 1.0572252F * p_z / 100.0F;
red = clamp_if_within_error_margin(reverse_gamma_correction(red));
green = clamp_if_within_error_margin(reverse_gamma_correction(green));
blue = clamp_if_within_error_margin(reverse_gamma_correction(blue));
return {red, green, blue};
}
std::tuple<uint8_t, uint8_t, uint8_t> xyz_to_rgb(float p_x, float p_y, float p_z) {
auto uint8_t_scale = [](const float p_value) -> uint8_t {
return static_cast<uint8_t>(std::clamp(p_value, 0.0F, 1.0F) * 255.0F + 0.5F);
};
auto [red, green, blue] = xyz_to_rgb_float(p_x, p_y, p_z);
uint8_t red_uint8_t = uint8_t_scale(red);
uint8_t green_uint8_t = uint8_t_scale(green);
uint8_t blue_uint8_t = uint8_t_scale(blue);
return {red_uint8_t, green_uint8_t, blue_uint8_t};
}
std::tuple<float, float> xyz_to_uv(const float p_x, const float p_y, const float p_z) {
float denominator = (p_x + 15.0F * p_y + 3.0F * p_z);
if (denominator == 0.0F) {
denominator = 1.0F;
}
float u = (4.0F * p_x) / denominator;
float v = (9.0F * p_y) / denominator;
return {u, v};
}
std::tuple<float, float> uv_to_xy(const float p_u, const float p_v) {
float denominator = 6.0F * p_u - 16.0F * p_v + 12.0F;
if (denominator == 0.0F) {
denominator = 1.0F;
}
float x = 9.0F * p_u / denominator;
float y = 4.0F * p_v / denominator;
return {x, y};
}
std::tuple<float, float, float> xyz_d65_reference_white_point() {
// Standard D64 reference white point values
constexpr float X_N = 95.047F;
constexpr float Y_N = 100.0F;
constexpr float Z_N = 108.833F;
return {X_N, Y_N, Z_N};
}
std::tuple<float, float, float> xyz_to_cieluv(const float p_x, const float p_y, const float p_z) {
auto [X_N, Y_N, Z_N] = xyz_d65_reference_white_point();
auto [U_N, V_N] = xyz_to_uv(X_N, Y_N, Z_N);
auto [u, v] = xyz_to_uv(p_x, p_y, p_z);
float l_star = 0.0F;
if (p_y == 0.0F) {
l_star = 0.0F;
} else if ((p_y / Y_N) > 0.0088565F) {
l_star = 116.0F * std::cbrt(p_y / Y_N) - 16.0F;
} else {
l_star = p_y / Y_N * 903.3F;
}
float u_star = 13.0F * l_star * (u - U_N);
float v_star = 13.0F * l_star * (v - V_N);
return {l_star, u_star, v_star};
}
std::tuple<float, float, float> cieluv_to_xyz(float p_l_star, float p_u_star, float p_v_star) {
auto [X_N, Y_N, Z_N] = xyz_d65_reference_white_point();
auto [U_N, V_N] = xyz_to_uv(X_N, Y_N, Z_N);
float denominator = 13.0F * p_l_star;
if (denominator == 0.0F) {
denominator = 13.0F;
}
float u = p_u_star / denominator + U_N;
float v = p_v_star / denominator + V_N;
// auto [x, y] = uv_to_xy(u, v);
float y = 0.0F;
if (p_l_star <= 8.0F) {
y = Y_N * p_l_star * 0.0011071F;
} else {
y = Y_N * std::pow((p_l_star + 16.F) / 116.0F, 3.0F);
}
if (v == 0.0F) {
denominator = 4.0F;
} else {
denominator = 4.0F * v;
}
float x = y * 9.0F * u / denominator;
float z = y * (12.0F - 3.0F * u - 20.0F * v) / denominator;
return {x, y, z};
}
std::tuple<float, float, float> cieluv_to_cielchuv(float p_l_star, float p_u_star, float p_v_star) {
float c_star = std::sqrt(p_u_star * p_u_star + p_v_star * p_v_star);
float h_star = std::atan2(p_v_star, p_u_star);
if (h_star < 0.0F) {
h_star += 2.0F * M_PI;
}
return {p_l_star, c_star, h_star};
}
std::tuple<float, float, float> cielchuv_to_cieluv(float p_l_star, float p_c_star, float p_h_star) {
float u_star = p_c_star * std::cos(p_h_star);
float v_star = p_c_star * std::sin(p_h_star);
return {p_l_star, u_star, v_star};
}
bool is_xyz_in_rgb_range(float p_x, float p_y, float p_z) {
auto is_in_range_inclusive = [](const float p_value, const float p_min, const float p_max) -> bool {
return (p_value >= p_min and p_value <= p_max);
};
auto [red, green, blue] = xyz_to_rgb_float(p_x, p_y, p_z);
bool is_red_in_range = is_in_range_inclusive(red, 0.0F, 1.0F);
bool is_green_in_range = is_in_range_inclusive(green, 0.0F, 1.0F);
bool is_blue_in_range = is_in_range_inclusive(blue, 0.0F, 1.0F);
return is_red_in_range and is_green_in_range and is_blue_in_range;
}
bool is_cieluv_in_rgb_range(float p_l_star, float p_u_star, float p_v_star) {
auto [x, y, z] = cieluv_to_xyz(p_l_star, p_u_star, p_v_star);
return is_xyz_in_rgb_range(x, y, z);
}
bool is_cielchuv_in_rgb_range(float p_l_star, float p_c_star, float p_h_star) {
auto [l, u, v] = cielchuv_to_cieluv(p_l_star, p_c_star, p_h_star);
return is_cieluv_in_rgb_range(l, u, v);
}
std::tuple<float, float, float> map_cielchuv_to_visible_color(float p_l_star, float p_c_star, float p_h_star) {
float c_star = p_c_star;
uint32_t count = 0U;
while (not is_cielchuv_in_rgb_range(p_l_star, c_star, p_h_star)) {
c_star = c_star * 0.99F;
count = count + 1U;
if (count > 1000U) {
c_star = 0.0F;
std::cout << "breaky breaky!" << std::endl;
break;
}
}
return {p_l_star, c_star, p_h_star};
}
std::tuple<float, float, float> rgb_to_cielchuv(uint8_t p_red, uint8_t p_green, uint8_t p_blue) {
auto [x, y, z] = rgb_to_xyz(p_red, p_green, p_blue);
auto [l, u, v] = xyz_to_cieluv(x, y, z);
auto [l_star, c_star, h_star] = cieluv_to_cielchuv(l, u, v);
return {l_star, c_star, h_star};
}
std::tuple<uint8_t, uint8_t, uint8_t> cielchuv_to_rgb(float p_l_star, float p_c_star, float p_h_star) {
auto [l, u, v] = cielchuv_to_cieluv(p_l_star, p_c_star, p_h_star);
auto [x, y, z] = cieluv_to_xyz(l, u, v);
auto [red, green, blue] = xyz_to_rgb(x, y, z);
return {red, green, blue};
}
int main(void) {
uint8_t r = 0;
uint8_t g = 0;
uint8_t b = 0;
for (r = 0; r < 192; r = r + 64) {
for (g = 0; g < 192; g = g + 64) {
for (b = 0; b < 192; b = b + 64) {
auto [x, y, z] = rgb_to_xyz(r, g, b);
auto [l, u, v] = xyz_to_cieluv(x, y, z);
auto [x2, y2, z2] = cieluv_to_xyz(l, u, v);
auto [r2, g2, b2] = xyz_to_rgb(x2, y2, z2);
std::cout << "RGB: [" + std::to_string(r) + ", " + std::to_string(g) + ", " + std::to_string(b) + "]" +
" --> XYZ: [" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + "]" +
" --> LUV: [" + std::to_string(l) + ", " + std::to_string(u) + ", " + std::to_string(v) + "]" +
" --> XYZ: [" + std::to_string(x2) + ", " + std::to_string(y2) + ", " + std::to_string(z2) + "]" +
" --> RGB: [" + std::to_string(r2) + ", " + std::to_string(g2) + ", " + std::to_string(b2) + "]" +
" --> E: [" + std::to_string(r - r2) + ", " + std::to_string(g - g2) + ", " + std::to_string(b - b2) + "]" << std::endl;
}
}
}
std::cout << std::endl;
for (r = 0; r < 192; r = r + 64) {
for (g = 0; g < 192; g = g + 64) {
for (b = 0; b < 192; b = b + 64) {
auto [x, y, z] = rgb_to_xyz(r, g, b);
auto [l, u, v] = xyz_to_cieluv(x, y, z);
auto [l_, c, h] = cieluv_to_cielchuv(l, u, v);
auto [l2, c2, h2] = map_cielchuv_to_visible_color(l / 2.0F, c, h);
auto [l2_, u2, v2] = cielchuv_to_cieluv(l2, c2, h2);
auto [x2, y2, z2] = cieluv_to_xyz(l2, u2, v2);
auto [r2_f, g2_f, b2_f] = xyz_to_rgb_float(x2, y2, z2);
auto [r2, g2, b2] = xyz_to_rgb(x2, y2, z2);
std::cout << "RGB: [" + std::to_string(r) + ", " + std::to_string(g) + ", " + std::to_string(b) + "]" +
" --> XYZ: [" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + "]" +
" --> LUV: [" + std::to_string(l) + ", " + std::to_string(u) + ", " + std::to_string(v) + "]" +
" --> LCH: [" + std::to_string(l) + ", " + std::to_string(c) + ", " + std::to_string(h) + "]" +
" --> / 2: [" + std::to_string(l2) + ", " + std::to_string(c2) + ", " + std::to_string(h2) + "]" +
"; visible: " + std::to_string(is_cielchuv_in_rgb_range(l / 2.0F, c, h)) +
" / " + std::to_string(is_cielchuv_in_rgb_range(l2, c2, h2)) +
" --> LUV: [" + std::to_string(l2) + ", " + std::to_string(u2) + ", " + std::to_string(v2) + "]" +
" --> XYZ: [" + std::to_string(x2) + ", " + std::to_string(y2) + ", " + std::to_string(z2) + "]" +
" --> RGB: [" + std::to_string(r2_f) + ", " + std::to_string(g2_f) + ", " + std::to_string(b2_f) + "]" +
" --> RGB: [" + std::to_string(r2) + ", " + std::to_string(g2) + ", " + std::to_string(b2) + "]" << std::endl;
}
}
}
// std::cout << std::endl;
// float x = 0.925094F;
// float y = 0.370037F;
// float z = 4.872159F;
// auto [red, green, blue] = xyz_to_rgb_float(x, y, z);
// std::cout << std::to_string(red) + " " + std::to_string(green) + " " + std::to_string(blue) + " " + std::to_string(is_xyz_in_rgb_range(x, y, z)) << std::endl;
// float x2 = 0.740333F;
// float y2 = 0.185027F;
// float z2 = 0.031254F;
// auto [red2, green2, blue2] = xyz_to_rgb_float(x2, y2, z2);
// std::cout << std::to_string(red2) + " " + std::to_string(green2) + " " + std::to_string(blue2) + " " + std::to_string(is_xyz_in_rgb_range(x2, y2, z2)) << std::endl;
// // float L = 3.342548F;
// float L = 1.671274F;
// float C = 13.526361F;
// float H = 4.640314F;
//
// auto [L_, U, V] = cielchuv_to_cieluv(L, C, H);
// auto [X, Y, Z] = cieluv_to_xyz(L_, U, V);
// auto [R, G, B] = xyz_to_rgb_float(X, Y, Z);
// std::cout << "RGB: " << std::to_string(R) + " " + std::to_string(G) + " " + std::to_string(B) +
// ", XYZ: " + std::to_string(X) + " " + std::to_string(Y) + " " + std::to_string(Z) +
// ", LUV: " + std::to_string(L_) + " " + std::to_string(U) + " " + std::to_string(V) +
// " " + std::to_string(is_xyz_in_rgb_range(X, Y, Z)) << std::endl;
}