#include #include #include #include #include #include std::tuple 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(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 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 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(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 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 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 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 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 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 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 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 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 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 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; }