Added color-preserving dimming of display
This commit is contained in:
parent
7094d20eca
commit
f22d4c3d80
7 changed files with 676 additions and 2 deletions
49
.vscode/settings.json
vendored
49
.vscode/settings.json
vendored
|
@ -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"
|
||||
}
|
||||
}
|
24
include/color_conversion.hpp
Normal file
24
include/color_conversion.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#ifndef __COLOR_CONVERSION_HPP__
|
||||
#define __COLOR_CONVERSION_HPP__
|
||||
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
|
||||
std::tuple<float, float, float> rgb_to_xyz(uint8_t p_red, uint8_t p_green, uint8_t p_blue);
|
||||
std::tuple<uint8_t, uint8_t, uint8_t> xyz_to_rgb(float p_x, float p_y, float p_z);
|
||||
|
||||
std::tuple<float, float, float> xyz_to_cieluv(float p_x, float p_y, float p_z);
|
||||
std::tuple<float, float, float> cieluv_to_xyz(float p_l_star, float p_u_star, float p_v_star);
|
||||
|
||||
std::tuple<float, float, float> cieluv_to_cielchuv(float p_l_star, float p_u_star, float p_v_star);
|
||||
std::tuple<float, float, float> 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<float, float, float> map_cielchuv_to_visible_color(float p_l_star, float p_c_star, float p_h_star);
|
||||
|
||||
std::tuple<float, float, float> rgb_to_cielchuv(uint8_t p_red, uint8_t p_green, uint8_t p_blue);
|
||||
std::tuple<uint8_t, uint8_t, uint8_t> cielchuv_to_rgb(float p_l_star, float p_c_star, float p_h_star);
|
||||
#endif // __COLOR_CONVERSION_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__
|
248
src/color_conversion.cpp
Normal file
248
src/color_conversion.cpp
Normal file
|
@ -0,0 +1,248 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "../include/color_conversion.hpp"
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
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};
|
||||
}
|
|
@ -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<uint8_t, uint8_t, uint8_t, uint8_t> 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));
|
||||
}
|
||||
|
|
|
@ -102,6 +102,8 @@ void loop(void) {
|
|||
display.setUVIForecastLed(utc_time, backend_communication.m_weather_data["UVI"].as<JsonArray>());
|
||||
|
||||
display.setWifiStatusLed(false);
|
||||
|
||||
display.setBrightness(backend_communication.m_config["brightness"].as<JsonFloat>());
|
||||
}
|
||||
|
||||
display.show();
|
||||
|
|
335
test/cieluv.cpp
Normal file
335
test/cieluv.cpp
Normal file
|
@ -0,0 +1,335 @@
|
|||
#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;
|
||||
}
|
Loading…
Reference in a new issue