6#define FASTLED_INTERNAL
13#if FASTLED_RGBW_COLORIMETRIC
40 { 0.700606f, 0.299300f },
41 { 0.097940f, 0.831593f },
42 { 0.129086f, 0.049450f },
43 { 0.322080f, 0.338050f },
49 { 0.700606f, 0.299300f },
50 { 0.097940f, 0.831593f },
51 { 0.129086f, 0.049450f },
52 { 0.31272f, 0.32903f },
76 u16
y = (u16(
x) * 85) >> 8;
77 return static_cast<u8>(
y);
85 u8 b,
u8 r_scale,
u8 g_scale,
86 u8 b_scale,
u8 *out_r,
u8 *out_g,
88 (void)w_color_temperature;
89 r = scale8(r, r_scale);
90 g = scale8(
g, g_scale);
91 b = scale8(b, b_scale);
92 u8 min_component = rgbw_min3(r,
g, b);
93 *out_r = r - min_component;
94 *out_g =
g - min_component;
95 *out_b = b - min_component;
96 *out_w = min_component;
101 u8 g_scale,
u8 b_scale,
u8 *out_r,
103 (void)w_color_temperature;
104 *out_r = scale8(r, r_scale);
105 *out_g = scale8(
g, g_scale);
106 *out_b = scale8(b, b_scale);
107 *out_w = rgbw_min3(*out_r, *out_g, *out_b);
112 u8 g_scale,
u8 b_scale,
113 u8 *out_r,
u8 *out_g,
u8 *out_b,
115 (void)w_color_temperature;
116 *out_r = scale8(r, r_scale);
117 *out_g = scale8(
g, g_scale);
118 *out_b = scale8(b, b_scale);
124 u8 g_scale,
u8 b_scale,
u8 *out_r,
126 (void)w_color_temperature;
127 r = scale8(r, r_scale);
128 g = scale8(
g, g_scale);
129 b = scale8(b, b_scale);
130 u8 min_component = rgbw_min3(r,
g, b);
133 if (min_component <= 84) {
134 w = 3 * min_component;
143 r_prime = r - min_component;
144 g_prime =
g - min_component;
145 b_prime = b - min_component;
147 u8 w3 = divide_by_3(w);
183 u8 g_scale,
u8 b_scale,
u8 *out_r,
189 fn(w_color_temperature, r,
g, b, r_scale, g_scale, b_scale,
190 out_r, out_g, out_b, out_w);
232 {0.1500f, 0.0600f}, {0.31272f, 0.32903f}};
234 {0.1310f, 0.0460f}, {0.31272f, 0.32903f}};
236 {0.1500f, 0.0600f}, {0.31272f, 0.32903f}};
238 {0.1500f, 0.0600f}, {0.32168f, 0.33767f}};
247namespace {
void invalidate_colorimetric_caches_for(
const DiodeProfile* profile)
FL_NO_EXCEPT; }
252 auto apply = [profile](
const float r[2],
const float gp[2],
253 const float b[2],
const float w[2]) {
263 const float d65[2] = {0.31272f, 0.32903f};
264 const float* w = (white_xy !=
nullptr) ? white_xy : d65;
266 invalidate_colorimetric_caches_for(profile);
270 white_xy !=
nullptr ? white_xy : kRec709.
xy_w);
271 invalidate_colorimetric_caches_for(profile);
return;
273 white_xy !=
nullptr ? white_xy : kRec2020.
xy_w);
274 invalidate_colorimetric_caches_for(profile);
return;
276 white_xy !=
nullptr ? white_xy : kDciP3D65.
xy_w);
277 invalidate_colorimetric_caches_for(profile);
return;
279 white_xy !=
nullptr ? white_xy : kDciP3D60.
xy_w);
280 invalidate_colorimetric_caches_for(profile);
return;
295 if (profile ==
nullptr)
return;
300 if (profile ==
nullptr)
return;
323#if FASTLED_RGBW_COLORIMETRIC
330struct ColorimetricCacheHolder {
331 colorimetric_detail::ProfileCache cache;
332 const DiodeProfile* cached_for =
nullptr;
340 if (requested_cct < 1500 || requested_cct > 15000)
return 0;
341 if (requested_cct == p->nominal_cct)
return 0;
342 return requested_cct;
351 const fl::shared_ptr<const DiodeProfile>& profile)
FL_NO_EXCEPT {
352 return resolve_rgbw_profile(profile.get());
357 ColorimetricCacheHolder& h =
359 const DiodeProfile* active = resolve_rgbw_profile(profile);
360 const int override_cct = resolve_cct_override(active, cct);
361 if (h.cached_for != active || h.cached_cct != override_cct) {
363 h.cached_for = active;
364 h.cached_cct = override_cct;
370 const fl::shared_ptr<const DiodeProfile>& profile,
int cct)
FL_NO_EXCEPT {
371 return get_cache(resolve_rgbw_profile(profile), cct);
375 return get_cache(
static_cast<const DiodeProfile*
>(
nullptr), cct);
386struct LutStateHolder {
387 fl::unique_ptr<colorimetric_detail::LutTable> table;
388 const DiodeProfile* built_for =
nullptr;
390 int requested_grid_n = 0;
392 bool enabled =
false;
398 ? colorimetric_detail::LutInterp::Hermite
399 : colorimetric_detail::LutInterp::Bilinear;
402inline void rebuild_lut_if_stale(LutStateHolder& s,
const DiodeProfile* profile,
404 if (!s.enabled || s.requested_grid_n <= 0)
return;
405 const DiodeProfile* active = resolve_rgbw_profile(profile);
406 const int override_cct = resolve_cct_override(active, cct);
408 to_internal_interp(s.requested_interp);
409 if (s.table && s.built_for == active && s.built_cct == override_cct
410 && s.table->N == s.requested_grid_n
411 && s.table->interp == interp) {
417 s.built_for = active;
418 s.built_cct = override_cct;
421inline void rebuild_lut_if_stale(LutStateHolder& s,
422 const fl::shared_ptr<const DiodeProfile>& profile,
424 rebuild_lut_if_stale(s, resolve_rgbw_profile(profile), cct);
427inline void rebuild_lut_if_stale(LutStateHolder& s,
int cct)
FL_NO_EXCEPT {
428 rebuild_lut_if_stale(s,
static_cast<const DiodeProfile*
>(
nullptr), cct);
436 ColorimetricCacheHolder& ch =
438 if (ch.cached_for == profile) {
439 ch.cached_for =
nullptr;
443 if (lh.built_for == profile) {
444 lh.built_for =
nullptr;
455 (void)get_cache(cfg.profile, cfg.white_color_temp);
457 rebuild_lut_if_stale(lut_state, cfg.profile, cfg.white_color_temp);
462 u8 g_scale,
u8 b_scale,
u8 *out_r,
466 out_r, out_g, out_b, out_w);
471 u8 g_scale,
u8 b_scale,
u8 *out_r,
473 const int w_color_temperature = cfg.white_color_temp;
474 r = scale8(r, r_scale);
475 g = scale8(
g, g_scale);
476 b = scale8(b, b_scale);
477 if ((r |
g | b) == 0) {
478 *out_r = *out_g = *out_b = *out_w = 0;
481 const float s_r = r * (1.0f / 255.0f);
482 const float s_g =
g * (1.0f / 255.0f);
483 const float s_b = b * (1.0f / 255.0f);
485 get_cache(cfg.profile, w_color_temperature);
491 if (lut_state.enabled) {
498 cache, s_r, s_g, s_b,
rgbw);
501 out_r, out_g, out_b, out_w);
510 rebuild_lut_if_stale(lut_state, cfg.profile, w_color_temperature);
512 if (cache.has_source_space) {
515 const float s_vec[3] = { s_r, s_g, s_b };
518 X_t[0] = cache.P_R[0] * s_r + cache.P_G[0] * s_g + cache.P_B[0] * s_b;
519 X_t[1] = cache.P_R[1] * s_r + cache.P_G[1] * s_g + cache.P_B[1] * s_b;
520 X_t[2] = cache.P_R[2] * s_r + cache.P_G[2] * s_g + cache.P_B[2] * s_b;
522 const float sum = X_t[0] + X_t[1] + X_t[2];
524 *out_r = *out_g = *out_b = *out_w = 0;
527 const float xy_t[2] = { X_t[0] / sum, X_t[1] / sum };
541 out_r, out_g, out_b, out_w);
552 u8 g_scale,
u8 b_scale,
u8 *out_r,
556 out_r, out_g, out_b, out_w);
561 u8 g_scale,
u8 b_scale,
u8 *out_r,
563 r = scale8(r, r_scale);
564 g = scale8(
g, g_scale);
565 b = scale8(b, b_scale);
566 if ((r |
g | b) == 0) {
567 *out_r = *out_g = *out_b = *out_w = 0;
570 const float s_r = r * (1.0f / 255.0f);
571 const float s_g =
g * (1.0f / 255.0f);
572 const float s_b = b * (1.0f / 255.0f);
575 get_cache(cfg.profile, cfg.white_color_temp),
587 if (grid_n < 4) grid_n = 4;
588 if (grid_n > 256) grid_n = 256;
591 s.requested_grid_n = grid_n;
592 s.requested_interp = interp;
594 s.built_for =
nullptr;
607 s.requested_grid_n = 0;
609 s.built_for =
nullptr;
640 u8 g_scale,
u8 b_scale,
u8 *out_r,
642#ifndef FASTLED_SUPPRESS_COLORIMETRIC_FALLBACK_WARNING
643 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.");
646 out_r, out_g, out_b, out_w);
651 u8 g_scale,
u8 b_scale,
u8 *out_r,
653#ifndef FASTLED_SUPPRESS_COLORIMETRIC_FALLBACK_WARNING
654 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.");
657 out_r, out_g, out_b, out_w);
662 u8 g_scale,
u8 b_scale,
u8 *out_r,
665 b_scale, out_r, out_g, out_b, out_w);
670 u8 g_scale,
u8 b_scale,
u8 *out_r,
673 g_scale, b_scale, out_r, out_g, out_b,
683 u8 out[4] = {b0, b1, b2, 0};
684 switch (w_placement) {
719 const u8 (&premix)[3],
const u8 (&order)[3],
u8* b0,
u8* b1,
u8* b2,
725 rgb_2_rgbw(
rgbw, raw[0], raw[1], raw[2], premix[0], premix[1], premix[2],
726 &c[0], &c[1], &c[2], &w);
728 c[order[2]], w, b0, b1, b2, b3);
733#if !defined(FL_IS_AVR)
735 rgbw_convert_slot() = &rgbw_convert_pixel;
static T & instance() FL_NO_EXCEPT
Functions for red, green, blue, white (RGBW) output.
Internal FastLED header for implementation files.
#define FL_WARN_ONCE(...)
Centralized logging categories for FastLED hardware interfaces and subsystems.
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NO_EXCEPT
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
constexpr NamedGamut kRec2020
constexpr NamedGamut kDciP3D60
constexpr NamedGamut kRec709
u8 rgbw_min3(u8 a, u8 b, u8 c) FL_NO_EXCEPT
void invalidate_colorimetric_caches_for(const DiodeProfile *profile) FL_NO_EXCEPT
constexpr NamedGamut kDciP3D65
u8 divide_by_3(u8 x) FL_NO_EXCEPT
const DiodeProfile * profile
void solve_wx_overdrive(const ProfileCache &cache, float s_r, float s_g, float s_b, float overdrive_ratio, float out_rgbw[4]) FL_NO_EXCEPT
int count_active_channels(float s_r, float s_g, float s_b) FL_NO_EXCEPT
u8 quantize_u8(float v) FL_NO_EXCEPT
bool is_native_input_gamut(const DiodeProfile &p) FL_NO_EXCEPT
void matvec3(const float M[3][3], const float v[3], float out[3]) FL_NO_EXCEPT
LutTable build_lut(const ProfileCache &cache, int grid_n, LutInterp interp) FL_NO_EXCEPT
void lookup_lut(const LutTable &lut, const float xy_t[2], float source_value, float out_rgbw[4]) FL_NO_EXCEPT
constexpr float kDefaultOverdriveRatio
bool solve_strict_subgamut(const ProfileCache &cache, float s_r, float s_g, float s_b, float out_rgbw[4]) FL_NO_EXCEPT
void build_profile_cache(const DiodeProfile *p, int cct_override, ProfileCache *cache) FL_NO_EXCEPT
RgbwConvertFn & rgbw_convert_slot() FL_NO_EXCEPT
void rgbw_convert_pixel(const Rgbw &rgbw, const u8(&raw)[3], const u8(&premix)[3], const u8(&order)[3], u8 *b0, u8 *b1, u8 *b2, u8 *b3) FL_NO_EXCEPT
void(*)(const Rgbw &rgbw, const u8(&raw)[3], const u8(&premix)[3], const u8(&order)[3], u8 *b0, u8 *b1, u8 *b2, u8 *b3) RgbwConvertFn
Per-pixel RGBW conversion: raw source-order RGB + premixed scale -> 4 wire bytes.
void enable_rgbw_conversion() FL_NO_EXCEPT
Installs the RGBW pixel conversion used by PixelIterator.
RgbwConvertFn rgbw_conversion() FL_NO_EXCEPT
Null until enable_rgbw_conversion() has run.
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) FL_NO_EXCEPT
constexpr common_type_t< T, U > max(T a, U b) FL_NO_EXCEPT
u8 u8 fract8 blur_amount FL_NO_EXCEPT
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) FL_NO_EXCEPT
Converts RGB to RGBW with the W channel set to black, always.
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_NO_EXCEPT
const DiodeProfile kRgbwDefaultProfile
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) FL_NO_EXCEPT
void prepare_rgbw_colorimetric(const Rgbw &) FL_NO_EXCEPT
FASTLED_FORCE_INLINE void rgb_2_rgbw(const Rgbw &cfg, 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_NO_EXCEPT
Converts RGB to RGBW using one of the functions.
fl::shared_ptr< const DiodeProfile > make_diode_profile(const DiodeProfile &profile) FL_NO_EXCEPT
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) FL_NO_EXCEPT
Converts RGB to RGBW using a color transfer method from saturated color channels to white.
bool rgbw_colorimetric_lut_enabled() FL_NO_EXCEPT
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 disable_rgbw_colorimetric_lut() FL_NO_EXCEPT
const DiodeProfile * get_rgbw_colorimetric_profile() FL_NO_EXCEPT
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) FL_NO_EXCEPT
The minimum brigthness of the RGB channels is used to set the W channel.
void set_rgbw_colorimetric_profile(const DiodeProfile *profile) FL_NO_EXCEPT
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_NO_EXCEPT
@ kRGBWColorimetricBoosted
shared_ptr< T > static_pointer_cast(const shared_ptr< Y > &other) FL_NO_EXCEPT
void set_input_gamut(DiodeProfile &profile_ref, InputGamut g, const float *white_xy) FL_NO_EXCEPT
bool enable_rgbw_colorimetric_lut(int, RgbwLutInterp) FL_NO_EXCEPT
void set_rgb_2_rgbw_function(rgb_2_rgbw_function func) FL_NO_EXCEPT
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) FL_NO_EXCEPT
Converts RGB to RGBW with a boosted white channel.
Base definition for an LED controller.
Chromaticity-aware RGBW solvers — strict sub-gamut + wx_lp_legacy white extraction + boosted overdriv...