FastLED 3.9.15
Loading...
Searching...
No Matches
rgbw.h
Go to the documentation of this file.
1
3
4#pragma once
5
6#include "fl/stl/stdint.h"
7
8#include "eorder.h"
10#include "fl/stl/noexcept.h"
11
12namespace fl {
13
14enum class RGBW_MODE {
20 // Chromaticity-aware modes (issue #2545). Off by default at compile time:
21 // require `#define FASTLED_RGBW_COLORIMETRIC 1` to enable the real math
22 // path. Without the define, selecting either mode emits FL_WARN_ONCE and
23 // falls back to kRGBWExactColors.
26 // IMPORTANT: kRGBWUserFunction MUST remain the last enumerator. New modes
27 // are added immediately above this line so the user-function ordinal does
28 // not shift and the dispatch table can rely on it being the maximum value.
30};
31
32// Per-strip diode chromaticity + peak luminance, used by the colorimetric
33// modes. The default constant (`kRgbwDefaultProfile`) ships with the library
34// and uses datasheet-derived values for SK6812 RGBW3535 at 6000K. Users with a
35// colorimeter can build their own and install it via
36// `set_rgbw_colorimetric_profile`.
37//
38// Source-space fields (input_xy_*, issue #2705) define the color space the
39// input RGB triple is interpreted in. The default profile (#2710) ships
40// with **native LED gamut + D65 white** — input primaries equal the LED's
41// own xy_r/g/b — so a saturated input like RGB=(255,0,0) reaches the LED
42// red diode at full drive and RGB=(255,255,255) lands on D65. Solvers
43// compute `X_t = M_src · source_rgb` where M_src is the standard CIE
44// primary-matrix construction from these four chromaticities.
45//
46// Users wanting standard color-space input semantics (Rec709 / sRGB,
47// Rec2020, DCI-P3 D65/D60) should opt in explicitly via
48// `fl::set_input_gamut(profile, fl::InputGamut::Rec709)` etc.
49//
50// If input_xy_w[1] is left at 0.0f (the default for value-initialized
51// profiles `DiodeProfile{}`), solvers fall back to the legacy
52// interpretation in which the input RGB triple IS the device-emitter
53// drive coordinates — preserved for backward compatibility.
55 float xy_r[2]; // R diode chromaticity (CIE 1931 xy)
56 float xy_g[2]; // G diode chromaticity
57 float xy_b[2]; // B diode chromaticity
58 float xy_w[2]; // W diode chromaticity (at nominal_cct)
59 float lum_r; // R diode peak luminance (relative to W = 1.0)
60 float lum_g;
61 float lum_b;
62 float lum_w; // typically 1.0 by convention
63 int nominal_cct; // CCT at which xy_w was measured (e.g. 6000)
64
65 // Source / input color space (#2705). See above for default behavior.
66 float input_xy_r[2]; // source R primary chromaticity (default: native LED R)
67 float input_xy_g[2]; // source G primary chromaticity (default: native LED G)
68 float input_xy_b[2]; // source B primary chromaticity (default: native LED B)
69 float input_xy_w[2]; // source white chromaticity (default D65)
70};
71
72// Named source color spaces for opting into standard input-gamut semantics
73// (#2710). The default `kRgbwDefaultProfile` ships as `Native`; pick a
74// named gamut here when feeding content authored for that space (e.g.
75// sRGB / Rec.709 for ambilight, Rec.2020 for wide-gamut video, DCI-P3
76// for cinema-mastered material).
77enum class InputGamut : u8 {
78 Native = 0, // Input primaries == this profile's LED primaries + D65 white
79 Rec709, // sRGB / Rec.709 primaries + D65 white
80 Rec2020, // Rec.2020 (UHDTV) primaries + D65 white
81 DciP3D65, // DCI-P3 D65 — consumer display variant (e.g. Apple)
82 DciP3D60, // DCI-P3 D60 — ACES / cinema mastering variant
83};
84
85// Reconfigure `profile`'s input_xy_r/g/b/w to one of the named gamuts.
86// For `InputGamut::Native`, copies the profile's own xy_r/g/b into
87// input_xy_r/g/b and sets input_xy_w to D65. For named gamuts, uses the
88// standard published primary chromaticities + that gamut's reference white.
89// Mutates `profile` in place; subsequent solver calls observe the new gamut
90// (the cache is keyed on the profile pointer and rebuilds automatically).
91// No-op if `profile == nullptr`.
92void set_input_gamut(DiodeProfile* profile, InputGamut g) FL_NOEXCEPT;
93
94// Same as the above, but lets you optionally override the input white point.
95// `white_xy` either points to a 2-float (x, y) chromaticity OR is `nullptr`
96// to fall back to the gamut's standard reference white (equivalent to the
97// 2-argument overload above). Use the override for niche cases (D50
98// photography workflow, D60 ACES cinema, a custom calibration target) where
99// the standard gamut's reference white doesn't match your content.
100void set_input_gamut(DiodeProfile* profile, InputGamut g,
101 const float white_xy[2]) FL_NOEXCEPT;
102
103// Default profile: SK6812 RGBW3535 @ ~6000K (datasheet wavelengths -> xy +
104// typical luminance ratios). Always declared so user code referencing it
105// compiles whether or not FASTLED_RGBW_COLORIMETRIC is defined.
106extern const DiodeProfile kRgbwDefaultProfile;
107
108// Install a user-supplied profile. The pointer is stored — caller must keep
109// the DiodeProfile alive for as long as a colorimetric mode is active.
110// No-op when FASTLED_RGBW_COLORIMETRIC is undefined.
111void set_rgbw_colorimetric_profile(const DiodeProfile* profile) FL_NOEXCEPT;
112
113// Currently active profile (defaults to &kRgbwDefaultProfile).
114const DiodeProfile* get_rgbw_colorimetric_profile() FL_NOEXCEPT;
115
116// Interpolation scheme used by the colorimetric LUT (#2720). Bilinear stores
117// 4 channel values per grid cell (8 B/cell at i16 quantization) — cheapest in
118// flash/RAM. Hermite additionally stores ∂/∂t_x and ∂/∂t_y per channel
119// (24 B/cell, 3x storage) and reaches lower error at the same grid size,
120// enabling small grids (N=8..16) on memory-constrained targets.
121enum class RgbwLutInterp : u8 {
124};
125
126// Enable the 2D + 1D factored LUT path (issue #2545 Phase 2). When enabled,
127// the colorimetric modes use an interpolated LUT instead of solving per pixel.
128// Available whenever FASTLED_RGBW_COLORIMETRIC=1 — no separate
129// FASTLED_RGBW_COLORIMETRIC_LUT flag exists; gc-sections drops the LUT path
130// for sketches that never call this. `grid_n` is clamped to [4, 256] and
131// controls the LUT edge length. LUT is rebuilt whenever the active profile,
132// CCT, grid size, or interp scheme changes. Returns false on allocation
133// failure (or always false when FASTLED_RGBW_COLORIMETRIC is undefined).
134//
135// Memory cost = grid_n * grid_n * (interp == Hermite ? 24 : 8) bytes:
136// N=8 -> Bilinear 512 B / Hermite 1 536 B
137// N=16 -> Bilinear 2 048 B / Hermite 6 144 B
138// N=32 -> Bilinear 8 192 B / Hermite 24 576 B
139// N=64 -> Bilinear 32 KB / Hermite ~96 KB
140//
141// The single-arg overload preserves source compatibility and forwards to
142// RgbwLutInterp::Hermite (the historical default since #2707).
143bool enable_rgbw_colorimetric_lut(int grid_n,
146
147// Free the LUT and revert colorimetric calls to the closed-form solver.
149
150// Returns true if the LUT path is currently active.
152
153// Compile-time memory-cost accessor. Mirrors the storage math used by the
154// LUT builder so users can size their RAM/flash budget before calling
155// enable_rgbw_colorimetric_lut(). The runtime API clamps grid_n to [4, 256],
156// so this helper applies the same clamp so callers do not under-estimate
157// for grid_n < 4 or over-estimate for grid_n > 256. Returns 0 for grid_n < 1.
159 int grid_n, RgbwLutInterp interp) FL_NOEXCEPT {
160 return (grid_n < 1)
161 ? 0UL
162 : (static_cast<unsigned long>(
163 grid_n < 4 ? 4 : (grid_n > 256 ? 256 : grid_n))
164 * static_cast<unsigned long>(
165 grid_n < 4 ? 4 : (grid_n > 256 ? 256 : grid_n))
166 * (interp == RgbwLutInterp::Hermite ? 24UL : 8UL));
167}
168
169enum {
171};
172
173struct Rgbw {
180 fl::EOrderW w_placement = EOrderW::WDefault;
185
186 static u32 size_as_rgb(u32 num_of_rgbw_pixels) FL_NOEXCEPT {
187 // The ObjectFLED controller expects the raw pixel byte data in
188 // multiples of 3. In the case of src data not a multiple of 3, then we
189 // need to add pad bytes so that the delegate controller doesn't walk
190 // off the end of the array and invoke a buffer overflow panic.
191 num_of_rgbw_pixels = (num_of_rgbw_pixels * 4 + 2) / 3;
192 u32 extra = num_of_rgbw_pixels % 3 ? 1 : 0;
193 num_of_rgbw_pixels += extra;
194 return num_of_rgbw_pixels;
195 }
196};
197
198struct RgbwInvalid : public Rgbw {
204 RgbwInvalid invalid;
205 return invalid;
206 }
207};
208
209struct RgbwDefault : public Rgbw {
215 RgbwDefault _default;
216 return _default;
217 }
218};
219
220struct RgbwWhiteIsOff : public Rgbw {
226 RgbwWhiteIsOff _default;
227 return _default;
228 }
229};
230
231typedef void (*rgb_2_rgbw_function)(u16 w_color_temperature, u8 r,
232 u8 g, u8 b, u8 r_scale,
233 u8 g_scale, u8 b_scale,
234 u8 *out_r, u8 *out_g,
235 u8 *out_b, u8 *out_w);
236
250void rgb_2_rgbw_exact(u16 w_color_temperature, u8 r, u8 g,
251 u8 b, u8 r_scale, u8 g_scale,
252 u8 b_scale, u8 *out_r, u8 *out_g,
253 u8 *out_b, u8 *out_w) FL_NOEXCEPT;
254
263void rgb_2_rgbw_max_brightness(u16 w_color_temperature, u8 r,
264 u8 g, u8 b, u8 r_scale,
265 u8 g_scale, u8 b_scale, u8 *out_r,
266 u8 *out_g, u8 *out_b, u8 *out_w) FL_NOEXCEPT;
267
273void rgb_2_rgbw_null_white_pixel(u16 w_color_temperature, u8 r,
274 u8 g, u8 b, u8 r_scale,
275 u8 g_scale, u8 b_scale,
276 u8 *out_r, u8 *out_g, u8 *out_b,
277 u8 *out_w) FL_NOEXCEPT;
278
280void rgb_2_rgbw_white_boosted(u16 w_color_temperature, u8 r,
281 u8 g, u8 b, u8 r_scale,
282 u8 g_scale, u8 b_scale, u8 *out_r,
283 u8 *out_g, u8 *out_b, u8 *out_w) FL_NOEXCEPT;
284
285void rgb_2_rgbw_user_function(u16 w_color_temperature, u8 r,
286 u8 g, u8 b, u8 r_scale,
287 u8 g_scale, u8 b_scale, u8 *out_r,
288 u8 *out_g, u8 *out_b, u8 *out_w) FL_NOEXCEPT;
289
290// Strict sub-gamut colorimetric solver (gist sec 5, issue #2545). Maps the
291// input through the diode chromaticity model; routes the chromaticity to one
292// of the RGW / RBW / BGW sub-gamuts and solves the 3x3 system. Color-accurate
293// but uses fewer than all 4 channels per pixel. Requires
294// FASTLED_RGBW_COLORIMETRIC; stub falls back to rgb_2_rgbw_exact + warn-once
295// otherwise.
296void rgb_2_rgbw_colorimetric(u16 w_color_temperature, u8 r,
297 u8 g, u8 b, u8 r_scale,
298 u8 g_scale, u8 b_scale, u8 *out_r,
299 u8 *out_g, u8 *out_b, u8 *out_w) FL_NOEXCEPT;
300
301// White-overdrive colorimetric solver (wx_lp_legacy from gist sec 9).
302// Closed-form scalar LP: maximize W subject to RGB residual >= 0. Brighter
303// than kRGBWColorimetric for in-gamut targets, same color accuracy. Requires
304// FASTLED_RGBW_COLORIMETRIC; stub falls back to rgb_2_rgbw_exact + warn-once
305// otherwise.
306void rgb_2_rgbw_colorimetric_boosted(u16 w_color_temperature, u8 r,
307 u8 g, u8 b, u8 r_scale,
308 u8 g_scale, u8 b_scale, u8 *out_r,
309 u8 *out_g, u8 *out_b, u8 *out_w) FL_NOEXCEPT;
310
312
317rgb_2_rgbw(RGBW_MODE mode, u16 w_color_temperature, u8 r, u8 g,
318 u8 b, u8 r_scale, u8 g_scale, u8 b_scale,
319 u8 *out_r, u8 *out_g, u8 *out_b, u8 *out_w) FL_NOEXCEPT {
320 switch (mode) {
323 rgb_2_rgbw_null_white_pixel(w_color_temperature, r, g, b, r_scale,
324 g_scale, b_scale, out_r, out_g, out_b,
325 out_w);
326 return;
328 rgb_2_rgbw_exact(w_color_temperature, r, g, b, r_scale, g_scale,
329 b_scale, out_r, out_g, out_b, out_w);
330 return;
332 rgb_2_rgbw_white_boosted(w_color_temperature, r, g, b, r_scale, g_scale,
333 b_scale, out_r, out_g, out_b, out_w);
334 return;
336 rgb_2_rgbw_max_brightness(w_color_temperature, r, g, b, r_scale,
337 g_scale, b_scale, out_r, out_g, out_b, out_w);
338 return;
340 rgb_2_rgbw_user_function(w_color_temperature, r, g, b, r_scale, g_scale,
341 b_scale, out_r, out_g, out_b, out_w);
342 return;
344 rgb_2_rgbw_colorimetric(w_color_temperature, r, g, b, r_scale, g_scale,
345 b_scale, out_r, out_g, out_b, out_w);
346 return;
348 rgb_2_rgbw_colorimetric_boosted(w_color_temperature, r, g, b, r_scale,
349 g_scale, b_scale, out_r, out_g, out_b,
350 out_w);
351 return;
352 }
353 rgb_2_rgbw_null_white_pixel(w_color_temperature, r, g, b, r_scale, g_scale,
354 b_scale, out_r, out_g, out_b, out_w);
355}
356
357// @brief Converts RGB to RGBW using one of the functions.
358template <RGBW_MODE MODE>
360rgb_2_rgbw(u16 w_color_temperature, u8 r, u8 g, u8 b,
361 u8 r_scale, u8 g_scale, u8 b_scale, u8 *out_r,
362 u8 *out_g, u8 *out_b, u8 *out_w) FL_NOEXCEPT {
363 // We trust that the compiler will inline all of this.
364 rgb_2_rgbw(MODE, w_color_temperature, r, g, b, r_scale, g_scale, b_scale,
365 out_r, out_g, out_b, out_w);
366}
367
368// Assuming all RGB pixels are already ordered in native led ordering, then this
369// function will reorder them so that white is also the correct position.
370// b0-b2 are actually rgb that are already in native LED order.
371// and out_b0-out_b3 are the output RGBW in native LED chipset order.
372// w is the white component that needs to be inserted into the RGB data at
373// the correct position.
374void rgbw_partial_reorder(fl::EOrderW w_placement, u8 b0, u8 b1,
375 u8 b2, u8 w, u8 *out_b0,
376 u8 *out_b1, u8 *out_b2, u8 *out_b3) FL_NOEXCEPT;
377
378} // namespace fl
#define constexpr
Declares that it is possible to evaluate a value at compile time, introduced in C++11.
Definition cpp_compat.h:15
unsigned char u8
Definition stdint.h:131
unsigned char u8
Definition stdint.h:131
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)
Definition rgbw.cpp.hpp:540
bool rgbw_colorimetric_lut_enabled() FL_NOEXCEPT
Definition rgbw.cpp.hpp:512
@ kRGBWDefaultColorTemp
Definition rgbw.h:170
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
Definition rgbw.cpp.hpp:527
FASTLED_FORCE_INLINE void rgb_2_rgbw(RGBW_MODE mode, 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
Converts RGB to RGBW using one of the functions.
Definition rgbw.h:317
const DiodeProfile kRgbwDefaultProfile
Definition rgbw.cpp.hpp:37
void set_rgbw_colorimetric_profile(const DiodeProfile *profile) FL_NOEXCEPT
Definition rgbw.cpp.hpp:199
const DiodeProfile * get_rgbw_colorimetric_profile() FL_NOEXCEPT
Definition rgbw.cpp.hpp:203
RGBW_MODE
Definition rgbw.h:14
@ kRGBWUserFunction
Definition rgbw.h:29
@ kRGBWNullWhitePixel
Definition rgbw.h:16
@ kRGBWColorimetricBoosted
Definition rgbw.h:25
@ kRGBWMaxBrightness
Definition rgbw.h:19
@ kRGBWColorimetric
Definition rgbw.h:24
@ kRGBWBoostedWhite
Definition rgbw.h:18
@ kRGBWExactColors
Definition rgbw.h:17
@ kRGBWInvalid
Definition rgbw.h:15
InputGamut
Definition rgbw.h:77
RgbwLutInterp
Definition rgbw.h:121
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)
Definition rgbw.h:231
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.
Definition rgbw.cpp.hpp:80
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.
Definition rgbw.cpp.hpp:106
void disable_rgbw_colorimetric_lut() FL_NOEXCEPT
Definition rgbw.cpp.hpp:511
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)
Definition rgbw.cpp.hpp:177
constexpr unsigned long rgbw_colorimetric_lut_memory_bytes(int grid_n, RgbwLutInterp interp) FL_NOEXCEPT
Definition rgbw.h:158
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.
Definition rgbw.cpp.hpp:118
bool enable_rgbw_colorimetric_lut(int, RgbwLutInterp) FL_NOEXCEPT
Definition rgbw.cpp.hpp:506
EOrderW
Definition eorder.h:23
void set_rgb_2_rgbw_function(rgb_2_rgbw_function func)
Definition rgbw.cpp.hpp:173
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
Definition rgbw.cpp.hpp:516
void set_input_gamut(DiodeProfile *profile, InputGamut g, const float white_xy[2]) FL_NOEXCEPT
Definition rgbw.cpp.hpp:242
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.
Definition rgbw.cpp.hpp:95
Base definition for an LED controller.
Definition crgb.hpp:179
float input_xy_b[2]
Definition rgbw.h:68
float xy_g[2]
Definition rgbw.h:56
float input_xy_r[2]
Definition rgbw.h:66
float xy_b[2]
Definition rgbw.h:57
float input_xy_g[2]
Definition rgbw.h:67
float input_xy_w[2]
Definition rgbw.h:69
int nominal_cct
Definition rgbw.h:63
float lum_r
Definition rgbw.h:59
float xy_r[2]
Definition rgbw.h:55
float lum_b
Definition rgbw.h:61
float lum_w
Definition rgbw.h:62
float lum_g
Definition rgbw.h:60
float xy_w[2]
Definition rgbw.h:58
#define FASTLED_FORCE_INLINE
#define FL_NOEXCEPT
static u32 size_as_rgb(u32 num_of_rgbw_pixels) FL_NOEXCEPT
Definition rgbw.h:186
Rgbw(u16 white_color_temp=fl::kRGBWDefaultColorTemp, fl::RGBW_MODE rgbw_mode=fl::RGBW_MODE::kRGBWExactColors, fl::EOrderW _w_placement=EOrderW::WDefault) FL_NOEXCEPT
Definition rgbw.h:174
FASTLED_FORCE_INLINE bool active() const FL_NOEXCEPT
Definition rgbw.h:182
RGBW_MODE rgbw_mode
Definition rgbw.h:181
fl::EOrderW w_placement
Definition rgbw.h:180
u16 white_color_temp
Definition rgbw.h:179
RgbwDefault() FL_NOEXCEPT
Definition rgbw.h:210
static Rgbw value() FL_NOEXCEPT
Definition rgbw.h:214
RgbwInvalid() FL_NOEXCEPT
Definition rgbw.h:199
static Rgbw value() FL_NOEXCEPT
Definition rgbw.h:203
RgbwWhiteIsOff() FL_NOEXCEPT
Definition rgbw.h:221
static Rgbw value() FL_NOEXCEPT
Definition rgbw.h:225