6#define FASTLED_INTERNAL
13#if FASTLED_RGBW_COLORIMETRIC
38 { 0.700606f, 0.299300f },
39 { 0.097940f, 0.831593f },
40 { 0.129086f, 0.049450f },
41 { 0.322080f, 0.338050f },
47 { 0.700606f, 0.299300f },
48 { 0.097940f, 0.831593f },
49 { 0.129086f, 0.049450f },
50 { 0.31272f, 0.32903f },
72 u16
y = (u16(
x) * 85) >> 8;
73 return static_cast<u8>(
y);
81 u8 b,
u8 r_scale,
u8 g_scale,
82 u8 b_scale,
u8 *out_r,
u8 *out_g,
83 u8 *out_b,
u8 *out_w) {
84 (void)w_color_temperature;
85 r = scale8(r, r_scale);
86 g = scale8(g, g_scale);
87 b = scale8(b, b_scale);
88 u8 min_component = min3(r, g, b);
89 *out_r = r - min_component;
90 *out_g = g - min_component;
91 *out_b = b - min_component;
92 *out_w = min_component;
97 u8 g_scale,
u8 b_scale,
u8 *out_r,
98 u8 *out_g,
u8 *out_b,
u8 *out_w) {
99 (void)w_color_temperature;
100 *out_r = scale8(r, r_scale);
101 *out_g = scale8(g, g_scale);
102 *out_b = scale8(b, b_scale);
103 *out_w = min3(*out_r, *out_g, *out_b);
108 u8 g_scale,
u8 b_scale,
109 u8 *out_r,
u8 *out_g,
u8 *out_b,
111 (void)w_color_temperature;
112 *out_r = scale8(r, r_scale);
113 *out_g = scale8(g, g_scale);
114 *out_b = scale8(b, b_scale);
120 u8 g_scale,
u8 b_scale,
u8 *out_r,
121 u8 *out_g,
u8 *out_b,
u8 *out_w) {
122 (void)w_color_temperature;
123 r = scale8(r, r_scale);
124 g = scale8(g, g_scale);
125 b = scale8(b, b_scale);
126 u8 min_component = min3(r, g, b);
129 if (min_component <= 84) {
130 w = 3 * min_component;
139 r_prime = r - min_component;
140 g_prime = g - min_component;
141 b_prime = b - min_component;
143 u8 w3 = divide_by_3(w);
179 u8 g_scale,
u8 b_scale,
u8 *out_r,
180 u8 *out_g,
u8 *out_b,
u8 *out_w) {
185 fn(w_color_temperature, r, g, b, r_scale, g_scale, b_scale,
186 out_r, out_g, out_b, out_w);
225 {0.1500f, 0.0600f}, {0.31272f, 0.32903f}};
227 {0.1310f, 0.0460f}, {0.31272f, 0.32903f}};
229 {0.1500f, 0.0600f}, {0.31272f, 0.32903f}};
231 {0.1500f, 0.0600f}, {0.32168f, 0.33767f}};
240namespace {
void invalidate_colorimetric_caches_for(
const DiodeProfile* profile)
FL_NOEXCEPT; }
244 if (profile ==
nullptr)
return;
245 auto apply = [profile](
const float r[2],
const float gp[2],
246 const float b[2],
const float w[2]) {
247 profile->input_xy_r[0] = r[0]; profile->input_xy_r[1] = r[1];
248 profile->input_xy_g[0] = gp[0]; profile->input_xy_g[1] = gp[1];
249 profile->input_xy_b[0] = b[0]; profile->input_xy_b[1] = b[1];
250 profile->input_xy_w[0] = w[0]; profile->input_xy_w[1] = w[1];
256 const float d65[2] = {0.31272f, 0.32903f};
257 const float* w = (white_xy !=
nullptr) ? white_xy : d65;
258 apply(profile->xy_r, profile->xy_g, profile->xy_b, w);
259 invalidate_colorimetric_caches_for(profile);
263 white_xy !=
nullptr ? white_xy : kRec709.
xy_w);
264 invalidate_colorimetric_caches_for(profile);
return;
266 white_xy !=
nullptr ? white_xy : kRec2020.
xy_w);
267 invalidate_colorimetric_caches_for(profile);
return;
269 white_xy !=
nullptr ? white_xy : kDciP3D65.
xy_w);
270 invalidate_colorimetric_caches_for(profile);
return;
272 white_xy !=
nullptr ? white_xy : kDciP3D60.
xy_w);
273 invalidate_colorimetric_caches_for(profile);
return;
283#if FASTLED_RGBW_COLORIMETRIC
290struct ColorimetricCacheHolder {
291 colorimetric_detail::ProfileCache cache;
292 const DiodeProfile* cached_for =
nullptr;
300 if (requested_cct < 1500 || requested_cct > 15000)
return 0;
301 if (requested_cct == p->nominal_cct)
return 0;
302 return requested_cct;
306 ColorimetricCacheHolder& h =
309 const int override_cct = resolve_cct_override(active, cct);
310 if (h.cached_for != active || h.cached_cct != override_cct) {
312 h.cached_for = active;
313 h.cached_cct = override_cct;
326struct LutStateHolder {
327 fl::unique_ptr<colorimetric_detail::LutTable> table;
328 const DiodeProfile* built_for =
nullptr;
330 int requested_grid_n = 0;
332 bool enabled =
false;
342inline void rebuild_lut_if_stale(LutStateHolder& s,
int cct)
FL_NOEXCEPT {
343 if (!s.enabled || s.requested_grid_n <= 0)
return;
345 const int override_cct = resolve_cct_override(active, cct);
347 to_internal_interp(s.requested_interp);
348 if (s.table && s.built_for == active && s.built_cct == override_cct
349 && s.table->N == s.requested_grid_n
350 && s.table->interp == interp) {
356 s.built_for = active;
357 s.built_cct = override_cct;
365 ColorimetricCacheHolder& ch =
367 if (ch.cached_for == profile) {
368 ch.cached_for =
nullptr;
372 if (lh.built_for == profile) {
373 lh.built_for =
nullptr;
381 u8 g_scale,
u8 b_scale,
u8 *out_r,
383 r = scale8(r, r_scale);
384 g = scale8(g, g_scale);
385 b = scale8(b, b_scale);
386 if ((r | g | b) == 0) {
387 *out_r = *out_g = *out_b = *out_w = 0;
390 const float s_r = r * (1.0f / 255.0f);
391 const float s_g = g * (1.0f / 255.0f);
392 const float s_b = b * (1.0f / 255.0f);
399 if (lut_state.enabled) {
400 rebuild_lut_if_stale(lut_state, w_color_temperature);
402 if (cache.has_source_space) {
405 const float s_vec[3] = { s_r, s_g, s_b };
408 X_t[0] = cache.P_R[0] * s_r + cache.P_G[0] * s_g + cache.P_B[0] * s_b;
409 X_t[1] = cache.P_R[1] * s_r + cache.P_G[1] * s_g + cache.P_B[1] * s_b;
410 X_t[2] = cache.P_R[2] * s_r + cache.P_G[2] * s_g + cache.P_B[2] * s_b;
412 const float sum = X_t[0] + X_t[1] + X_t[2];
414 *out_r = *out_g = *out_b = *out_w = 0;
417 const float xy_t[2] = { X_t[0] / sum, X_t[1] / sum };
430 out_r, out_g, out_b, out_w);
441 u8 g_scale,
u8 b_scale,
u8 *out_r,
443 r = scale8(r, r_scale);
444 g = scale8(g, g_scale);
445 b = scale8(b, b_scale);
446 if ((r | g | b) == 0) {
447 *out_r = *out_g = *out_b = *out_w = 0;
450 const float s_r = r * (1.0f / 255.0f);
451 const float s_g = g * (1.0f / 255.0f);
452 const float s_b = b * (1.0f / 255.0f);
455 get_cache(w_color_temperature),
467 if (grid_n < 4) grid_n = 4;
468 if (grid_n > 256) grid_n = 256;
471 s.requested_grid_n = grid_n;
472 s.requested_interp = interp;
474 s.built_for =
nullptr;
487 s.requested_grid_n = 0;
489 s.built_for =
nullptr;
518 u8 g_scale,
u8 b_scale,
u8 *out_r,
520#ifndef FASTLED_SUPPRESS_COLORIMETRIC_FALLBACK_WARNING
521 FL_WARN_ONCE(
"RGBW: kRGBWColorimetric requested but FASTLED_RGBW_COLORIMETRIC is not defined — falling back to kRGBWExactColors. Define FASTLED_RGBW_COLORIMETRIC=1 to enable the colorimetric path.");
524 out_r, out_g, out_b, out_w);
529 u8 g_scale,
u8 b_scale,
u8 *out_r,
531#ifndef FASTLED_SUPPRESS_COLORIMETRIC_FALLBACK_WARNING
532 FL_WARN_ONCE(
"RGBW: kRGBWColorimetricBoosted requested but FASTLED_RGBW_COLORIMETRIC is not defined — falling back to kRGBWExactColors. Define FASTLED_RGBW_COLORIMETRIC=1 to enable the colorimetric path.");
535 out_r, out_g, out_b, out_w);
542 u8 *out_b1,
u8 *out_b2,
u8 *out_b3) {
544 u8 out[4] = {b0, b1, b2, 0};
545 switch (w_placement) {
static T & instance() FL_NOEXCEPT
Functions for red, green, blue, white (RGBW) output.
Internal FastLED header for implementation files.
Centralized logging categories for FastLED hardware interfaces and subsystems.
constexpr NamedGamut kRec2020
u8 min3(u8 a, u8 b, u8 c)
constexpr NamedGamut kDciP3D60
constexpr NamedGamut kRec709
void invalidate_colorimetric_caches_for(const DiodeProfile *profile) FL_NOEXCEPT
constexpr NamedGamut kDciP3D65
const DiodeProfile * profile
LutTable build_lut(const ProfileCache &cache, int grid_n, LutInterp interp) FL_NOEXCEPT
void matvec3(const float M[3][3], const float v[3], float out[3]) FL_NOEXCEPT
void solve_wx_overdrive(const ProfileCache &cache, float s_r, float s_g, float s_b, float overdrive_ratio, float out_rgbw[4]) FL_NOEXCEPT
constexpr float kDefaultOverdriveRatio
void build_profile_cache(const DiodeProfile *p, int cct_override, ProfileCache *cache) FL_NOEXCEPT
void lookup_lut(const LutTable &lut, const float xy_t[2], float Y_t, float out_rgbw[4]) FL_NOEXCEPT
u8 quantize_u8(float v) FL_NOEXCEPT
bool solve_strict_subgamut(const ProfileCache &cache, float s_r, float s_g, float s_b, float out_rgbw[4]) FL_NOEXCEPT
void rgbw_partial_reorder(EOrderW w_placement, u8 b0, u8 b1, u8 b2, u8 w, u8 *out_b0, u8 *out_b1, u8 *out_b2, u8 *out_b3)
bool rgbw_colorimetric_lut_enabled() FL_NOEXCEPT
void rgb_2_rgbw_colorimetric_boosted(u16 w_color_temperature, u8 r, u8 g, u8 b, u8 r_scale, u8 g_scale, u8 b_scale, u8 *out_r, u8 *out_g, u8 *out_b, u8 *out_w) FL_NOEXCEPT
const DiodeProfile kRgbwDefaultProfile
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
void set_rgbw_colorimetric_profile(const DiodeProfile *profile) FL_NOEXCEPT
const DiodeProfile * get_rgbw_colorimetric_profile() FL_NOEXCEPT
void(* rgb_2_rgbw_function)(u16 w_color_temperature, u8 r, u8 g, u8 b, u8 r_scale, u8 g_scale, u8 b_scale, u8 *out_r, u8 *out_g, u8 *out_b, u8 *out_w)
void rgb_2_rgbw_exact(u16 w_color_temperature, u8 r, u8 g, u8 b, u8 r_scale, u8 g_scale, u8 b_scale, u8 *out_r, u8 *out_g, u8 *out_b, u8 *out_w)
Converts RGB to RGBW using a color transfer method from saturated color channels to white.
void rgb_2_rgbw_null_white_pixel(u16 w_color_temperature, u8 r, u8 g, u8 b, u8 r_scale, u8 g_scale, u8 b_scale, u8 *out_r, u8 *out_g, u8 *out_b, u8 *out_w)
Converts RGB to RGBW with the W channel set to black, always.
void disable_rgbw_colorimetric_lut() FL_NOEXCEPT
void rgb_2_rgbw_user_function(u16 w_color_temperature, u8 r, u8 g, u8 b, u8 r_scale, u8 g_scale, u8 b_scale, u8 *out_r, u8 *out_g, u8 *out_b, u8 *out_w)
void rgb_2_rgbw_white_boosted(u16 w_color_temperature, u8 r, u8 g, u8 b, u8 r_scale, u8 g_scale, u8 b_scale, u8 *out_r, u8 *out_g, u8 *out_b, u8 *out_w)
Converts RGB to RGBW with a boosted white channel.
bool enable_rgbw_colorimetric_lut(int, RgbwLutInterp) FL_NOEXCEPT
void set_rgb_2_rgbw_function(rgb_2_rgbw_function func)
void rgb_2_rgbw_colorimetric(u16 w_color_temperature, u8 r, u8 g, u8 b, u8 r_scale, u8 g_scale, u8 b_scale, u8 *out_r, u8 *out_g, u8 *out_b, u8 *out_w) FL_NOEXCEPT
void set_input_gamut(DiodeProfile *profile, InputGamut g, const float white_xy[2]) FL_NOEXCEPT
void rgb_2_rgbw_max_brightness(u16 w_color_temperature, u8 r, u8 g, u8 b, u8 r_scale, u8 g_scale, u8 b_scale, u8 *out_r, u8 *out_g, u8 *out_b, u8 *out_w)
The minimum brigthness of the RGB channels is used to set the W channel.
Base definition for an LED controller.
Chromaticity-aware RGBW solvers — strict sub-gamut + wx_lp_legacy white extraction + boosted overdriv...