From f22d4c3d809f727b694d920ff39fb06696073dd4 Mon Sep 17 00:00:00 2001 From: admar Date: Tue, 24 Sep 2024 11:27:59 +0200 Subject: [PATCH] Added color-preserving dimming of display --- .vscode/settings.json | 49 ++++- include/color_conversion.hpp | 24 +++ include/display.hpp | 2 + src/color_conversion.cpp | 248 ++++++++++++++++++++++++++ src/display.cpp | 18 +- src/main.cpp | 2 + test/cieluv.cpp | 335 +++++++++++++++++++++++++++++++++++ 7 files changed, 676 insertions(+), 2 deletions(-) create mode 100644 include/color_conversion.hpp create mode 100644 src/color_conversion.cpp create mode 100644 test/cieluv.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index a478c1f..b72cb1c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,52 @@ { "files.associations": { - "string_view": "cpp" + "string_view": "cpp", + "array": "cpp", + "atomic": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "map": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "string": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp" } } \ No newline at end of file diff --git a/include/color_conversion.hpp b/include/color_conversion.hpp new file mode 100644 index 0000000..d8a2e38 --- /dev/null +++ b/include/color_conversion.hpp @@ -0,0 +1,24 @@ +#ifndef __COLOR_CONVERSION_HPP__ +#define __COLOR_CONVERSION_HPP__ + +#include +#include + +std::tuple rgb_to_xyz(uint8_t p_red, uint8_t p_green, uint8_t p_blue); +std::tuple xyz_to_rgb(float p_x, float p_y, float p_z); + +std::tuple xyz_to_cieluv(float p_x, float p_y, float p_z); +std::tuple cieluv_to_xyz(float p_l_star, float p_u_star, float p_v_star); + +std::tuple cieluv_to_cielchuv(float p_l_star, float p_u_star, float p_v_star); +std::tuple cielchuv_to_cieluv(float p_l_star, float p_c_star, float p_h_star); + +bool is_xyz_in_rgb_range(float p_x, float p_y, float p_z); +bool is_cieluv_in_rgb_range(float p_l_star, float p_u_star, float p_v_star); +bool is_cielchuv_in_rgb_range(float p_l_star, float p_c_star, float p_h_star); + +std::tuple map_cielchuv_to_visible_color(float p_l_star, float p_c_star, float p_h_star); + +std::tuple rgb_to_cielchuv(uint8_t p_red, uint8_t p_green, uint8_t p_blue); +std::tuple cielchuv_to_rgb(float p_l_star, float p_c_star, float p_h_star); +#endif // __COLOR_CONVERSION_HPP__ \ No newline at end of file diff --git a/include/display.hpp b/include/display.hpp index afe153c..c6c259b 100644 --- a/include/display.hpp +++ b/include/display.hpp @@ -20,6 +20,7 @@ class Display { void setWifiStatusLed(bool p_is_connected); void setErrorLed(bool p_is_error); void setPixelColor(uint32_t p_index, uint8_t p_red, uint8_t p_green, uint8_t p_blue, uint8_t p_white); + void setBrightness(float p_brightness); void clear(); void show(); @@ -51,6 +52,7 @@ class Display { uint8_t m_uvi_map; uint8_t m_local_air_quality_map; uint8_t m_wifi_status_map; + float m_brightness; }; #endif // __DISPLAY_HPP__ \ No newline at end of file diff --git a/src/color_conversion.cpp b/src/color_conversion.cpp new file mode 100644 index 0000000..25d9227 --- /dev/null +++ b/src/color_conversion.cpp @@ -0,0 +1,248 @@ +#include +#include + +#include "../include/color_conversion.hpp" + +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; + + 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; + 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}; +} diff --git a/src/display.cpp b/src/display.cpp index b82d483..a3b5fb5 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -1,3 +1,4 @@ +#include "../include/color_conversion.hpp" #include "../include/display.hpp" Display::Display(Adafruit_NeoPixel& p_leds) : m_leds(p_leds) { @@ -8,6 +9,7 @@ Display::Display(Adafruit_NeoPixel& p_leds) : m_leds(p_leds) { m_uvi_map = 27; m_local_air_quality_map = 25; m_wifi_status_map = 28; + m_brightness = 0.2F; return; } @@ -122,6 +124,11 @@ void Display::setPixelColor(uint32_t p_index, uint8_t p_red, uint8_t p_green, ui m_leds.setPixelColor(p_index, m_leds.Color(p_red, p_green, p_blue, p_white)); } +void Display::setBrightness(float p_brightness) { + m_brightness = p_brightness; + return; +} + void Display::clear() { m_leds.clear(); return; @@ -308,5 +315,14 @@ std::tuple Display::mapValueToColorRGBW(floa void Display::setPixelToColorMappedValue(uint8_t p_index, float p_value) { auto [red, green, blue, white] = mapValueToColorRGBW(p_value); - m_leds.setPixelColor(p_index, m_leds.Color(red, green, blue, white)); + + auto [l_star, c_star, h_star] = rgb_to_cielchuv(red, green, blue); + + l_star = l_star * m_brightness; + + auto [l_star_dimmed, c_star_dimmed, h_star_dimmed] = map_cielchuv_to_visible_color(l_star, c_star, h_star); + + auto [red_dimmed, green_dimmed, blue_dimmed] = cielchuv_to_rgb(l_star_dimmed, c_star_dimmed, h_star_dimmed); + + m_leds.setPixelColor(p_index, m_leds.Color(red_dimmed, green_dimmed, blue_dimmed, white)); } diff --git a/src/main.cpp b/src/main.cpp index 5e71dd3..b2a6929 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -102,6 +102,8 @@ void loop(void) { display.setUVIForecastLed(utc_time, backend_communication.m_weather_data["UVI"].as()); display.setWifiStatusLed(false); + + display.setBrightness(backend_communication.m_config["brightness"].as()); } display.show(); diff --git a/test/cieluv.cpp b/test/cieluv.cpp new file mode 100644 index 0000000..b4b2d4b --- /dev/null +++ b/test/cieluv.cpp @@ -0,0 +1,335 @@ +#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; +}