FastLED 3.9.15
Loading...
Searching...
No Matches
rgbw_colorimetric.h
Go to the documentation of this file.
1
28
29#pragma once
30
31#include "fl/gfx/rgbw.h"
32#include "fl/math/math.h"
33#include "fl/stl/stdint.h"
34#include "fl/stl/unique_ptr.h"
35
36namespace fl {
38
39// ===== Pure-math inline helpers ==============================================
40// Tight, leaf-level operations kept inline because:
41// 1. They're trivial bodies (≤ 25 lines).
42// 2. They appear in hot inner loops; inlining lets the optimizer fold them.
43// 3. Tests can validate them directly without a full library rebuild.
44
45// Krystek's approximation for blackbody chromaticity (good 1000K - 15000K).
46// Krystek's outputs are (u, v) in the CIE 1960 UCS; convert to xy via the
47// standard 1960->xy formulas. Reference: Krystek, "An algorithm to calculate
48// correlated color temperature", Color Research & Application, 1985.
49inline void cct_to_xy(int cct, float out[2]) FL_NOEXCEPT {
50 const float T = static_cast<float>(
51 (cct < 1500) ? 1500 : ((cct > 15000) ? 15000 : cct));
52 const float T2 = T * T;
53 const float u_num = 0.860117757f + 1.54118254e-4f * T + 1.28641212e-7f * T2;
54 const float u_den = 1.0f + 8.42420235e-4f * T + 7.08145163e-7f * T2;
55 const float v_num = 0.317398726f + 4.22806245e-5f * T + 4.20481691e-8f * T2;
56 const float v_den = 1.0f - 2.89741816e-5f * T + 1.61456053e-7f * T2;
57 const float u = u_num / u_den;
58 const float v = v_num / v_den;
59 const float den = 2.0f * u - 8.0f * v + 4.0f;
60 out[0] = 3.0f * u / den;
61 out[1] = 2.0f * v / den;
62}
63
64inline void xyY_to_XYZ(float x, float y, float Y, float out[3]) FL_NOEXCEPT {
65 if (y < 1e-12f) {
66 out[0] = out[1] = out[2] = 0.0f;
67 return;
68 }
69 const float inv_y = 1.0f / y;
70 out[0] = x * Y * inv_y;
71 out[1] = Y;
72 out[2] = (1.0f - x - y) * Y * inv_y;
73}
74
75inline bool invert3x3(const float in[3][3], float out[3][3]) FL_NOEXCEPT {
76 const float a = in[0][0], b = in[0][1], c = in[0][2];
77 const float d = in[1][0], e = in[1][1], f = in[1][2];
78 const float g = in[2][0], h = in[2][1], i = in[2][2];
79 const float det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
80 if (fl::fabs(det) < 1e-20f) {
81 return false;
82 }
83 const float inv_det = 1.0f / det;
84 out[0][0] = (e * i - f * h) * inv_det;
85 out[0][1] = (c * h - b * i) * inv_det;
86 out[0][2] = (b * f - c * e) * inv_det;
87 out[1][0] = (f * g - d * i) * inv_det;
88 out[1][1] = (a * i - c * g) * inv_det;
89 out[1][2] = (c * d - a * f) * inv_det;
90 out[2][0] = (d * h - e * g) * inv_det;
91 out[2][1] = (b * g - a * h) * inv_det;
92 out[2][2] = (a * e - b * d) * inv_det;
93 return true;
94}
95
96inline void matvec3(const float M[3][3], const float v[3], float out[3]) FL_NOEXCEPT {
97 out[0] = M[0][0] * v[0] + M[0][1] * v[1] + M[0][2] * v[2];
98 out[1] = M[1][0] * v[0] + M[1][1] * v[1] + M[1][2] * v[2];
99 out[2] = M[2][0] * v[0] + M[2][1] * v[1] + M[2][2] * v[2];
100}
101
102inline bool barycentric_xy(const float t[2], const float A[2], const float B[2],
103 const float C[2], float bary[3]) FL_NOEXCEPT {
104 const float v0x = B[0] - A[0], v0y = B[1] - A[1];
105 const float v1x = C[0] - A[0], v1y = C[1] - A[1];
106 const float v2x = t[0] - A[0], v2y = t[1] - A[1];
107 const float d00 = v0x * v0x + v0y * v0y;
108 const float d01 = v0x * v1x + v0y * v1y;
109 const float d11 = v1x * v1x + v1y * v1y;
110 const float d20 = v2x * v0x + v2y * v0y;
111 const float d21 = v2x * v1x + v2y * v1y;
112 const float den = d00 * d11 - d01 * d01;
113 if (fl::fabs(den) < 1e-20f) {
114 return false;
115 }
116 const float inv_den = 1.0f / den;
117 const float u = (d11 * d20 - d01 * d21) * inv_den;
118 const float v = (d00 * d21 - d01 * d20) * inv_den;
119 bary[0] = 1.0f - u - v;
120 bary[1] = u;
121 bary[2] = v;
122 return true;
123}
124
125inline u8 quantize_u8(float v) FL_NOEXCEPT {
126 const float scaled = v * 255.0f + 0.5f;
127 if (scaled <= 0.0f) return 0;
128 if (scaled >= 255.0f) return 255;
129 return static_cast<u8>(scaled);
130}
131
132// Standard CIE primary-matrix construction (#2705). Given source primary
133// chromaticities xy_r/g/b and a source white chromaticity xy_w, build the
134// 3x3 matrix M such that M·[1,1,1]^T = xyY_to_XYZ(xy_w, 1.0). Columns are
135// per-channel scaled XYZ vectors of the primaries at Y=1, with scaling
136// chosen so the (1,1,1) input lands at source white in XYZ. This is the
137// classic linear-sRGB -> XYZ derivation generalized to arbitrary primaries.
138// Returns false if the primary matrix is singular (collinear chromaticities).
139inline bool build_source_matrix(const float xy_r[2], const float xy_g[2],
140 const float xy_b[2], const float xy_w[2],
141 float M_out[3][3]) FL_NOEXCEPT {
142 float xyz_R[3], xyz_G[3], xyz_B[3], xyz_W[3];
143 xyY_to_XYZ(xy_r[0], xy_r[1], 1.0f, xyz_R);
144 xyY_to_XYZ(xy_g[0], xy_g[1], 1.0f, xyz_G);
145 xyY_to_XYZ(xy_b[0], xy_b[1], 1.0f, xyz_B);
146 xyY_to_XYZ(xy_w[0], xy_w[1], 1.0f, xyz_W);
147
148 float P[3][3];
149 P[0][0] = xyz_R[0]; P[0][1] = xyz_G[0]; P[0][2] = xyz_B[0];
150 P[1][0] = xyz_R[1]; P[1][1] = xyz_G[1]; P[1][2] = xyz_B[1];
151 P[2][0] = xyz_R[2]; P[2][1] = xyz_G[2]; P[2][2] = xyz_B[2];
152
153 float P_inv[3][3];
154 if (!invert3x3(P, P_inv)) {
155 return false;
156 }
157 float k[3];
158 matvec3(P_inv, xyz_W, k);
159
160 M_out[0][0] = k[0] * xyz_R[0]; M_out[0][1] = k[1] * xyz_G[0]; M_out[0][2] = k[2] * xyz_B[0];
161 M_out[1][0] = k[0] * xyz_R[1]; M_out[1][1] = k[1] * xyz_G[1]; M_out[1][2] = k[2] * xyz_B[1];
162 M_out[2][0] = k[0] * xyz_R[2]; M_out[2][1] = k[1] * xyz_G[2]; M_out[2][2] = k[2] * xyz_B[2];
163 return true;
164}
165
166// Native-gamut detection (#2748). Returns true when the source primaries
167// of `p` (input_xy_r/g/b) match the LED's measured primaries (xy_r/g/b)
168// within float tolerance — i.e. the user is feeding "native LED gamut"
169// drive coordinates rather than a foreign gamut like sRGB / Rec.2020.
170//
171// Native mode has stricter topology authority than named-gamut mode:
172//
173// * Single-channel inputs are exact drive identity:
174// (R,0,0)->(R,0,0,0), (0,G,0)->(0,G,0,0), (0,0,B)->(0,0,B,0).
175//
176// * Dual-channel outer edges are channel-set identity, not value identity.
177// For RG/RB/GB edges the inactive RGB channel and W must remain zero, but
178// the two active channels are still solved from the source target XYZ and
179// the measured diode XYZ/Y columns. This is why yellow_half is not simply
180// raw (0.5,0.5,0,0): measured R/G luminance and the D65 source matrix set
181// the correct active-channel ratio.
182//
183// * Three-channel inputs fall through to the strict RGBW sub-gamut routing
184// and may use W inside RGW/RBW/BGW.
185//
186// The W chromaticity (input_xy_w) is intentionally NOT compared here: native
187// authority is about RGB topology. Different target whites affect the source
188// matrix and full-3-channel routing, not whether a pure/edge native input is
189// allowed to introduce W.
191 constexpr float kPrimaryEps = 1e-6f;
192 auto close = [](const float a[2], const float b[2]) FL_NOEXCEPT {
193 const float dx = a[0] - b[0];
194 const float dy = a[1] - b[1];
195 return (dx * dx + dy * dy) < kPrimaryEps;
196 };
197 return close(p.input_xy_r, p.xy_r)
198 && close(p.input_xy_g, p.xy_g)
199 && close(p.input_xy_b, p.xy_b);
200}
201
202// Topology activity classifier (#2748). Counts how many of `s_r, s_g, s_b`
203// are above the LSB-level epsilon. The solvers use this to select the native
204// authority fast path:
205// n == 1 : exact single-axis identity
206// n == 2 : fixed-topology two-emitter solve
207// n == 3 : normal strict / LP / overdrive interior solve
208inline int count_active_channels(float s_r, float s_g, float s_b) FL_NOEXCEPT {
209 // 1 / 65535 — matches the 16-bit verifier precision used in the
210 // reference math model; anything below this is below noise floor.
211 constexpr float kTopoEps = 1.0f / 65535.0f;
212 int n = 0;
213 if (s_r > kTopoEps) ++n;
214 if (s_g > kTopoEps) ++n;
215 if (s_b > kTopoEps) ++n;
216 return n;
217}
218
219// Non-negative least squares for the 3×3 sub-system M·t = b with t ≥ 0
220// (#2708, gist §3). Projected-gradient form matching the reference
221// `_nnls_solve` fallback used when scipy is unavailable: 500 iterations at
222// step 0.01. Cheap (~50 µs scalar on a typical MCU at -O2; only invoked for
223// out-of-hull source targets, never on the in-hull fast path) and free of
224// dynamic allocation, which makes it safe to inline behind the colorimetric
225// solvers' rare-branch.
226inline void nnls3(const float M[3][3], const float b[3],
227 float t_out[3], float* residual_out) FL_NOEXCEPT {
228 float t[3] = {0.0f, 0.0f, 0.0f};
229 constexpr float kStep = 0.01f;
230 constexpr int kIters = 500;
231 for (int it = 0; it < kIters; ++it) {
232 float r[3];
233 r[0] = M[0][0]*t[0] + M[0][1]*t[1] + M[0][2]*t[2] - b[0];
234 r[1] = M[1][0]*t[0] + M[1][1]*t[1] + M[1][2]*t[2] - b[1];
235 r[2] = M[2][0]*t[0] + M[2][1]*t[1] + M[2][2]*t[2] - b[2];
236 // grad = Mᵀ · r
237 float g[3];
238 g[0] = M[0][0]*r[0] + M[1][0]*r[1] + M[2][0]*r[2];
239 g[1] = M[0][1]*r[0] + M[1][1]*r[1] + M[2][1]*r[2];
240 g[2] = M[0][2]*r[0] + M[1][2]*r[1] + M[2][2]*r[2];
241 for (int j = 0; j < 3; ++j) {
242 float v = t[j] - kStep * g[j];
243 t[j] = v > 0.0f ? v : 0.0f;
244 }
245 }
246 if (residual_out != nullptr) {
247 float r[3];
248 r[0] = M[0][0]*t[0] + M[0][1]*t[1] + M[0][2]*t[2] - b[0];
249 r[1] = M[1][0]*t[0] + M[1][1]*t[1] + M[1][2]*t[2] - b[1];
250 r[2] = M[2][0]*t[0] + M[2][1]*t[1] + M[2][2]*t[2] - b[2];
251 *residual_out = fl::sqrt(r[0]*r[0] + r[1]*r[1] + r[2]*r[2]);
252 }
253 t_out[0] = t[0]; t_out[1] = t[1]; t_out[2] = t[2];
254}
255
256// ===== Types =================================================================
257
258// Precomputed per-profile data: emitter XYZ columns + the four matrix inverses
259// the solvers need. Built once when the active (profile, cct) pair changes.
260// xy_w is the *effective* W chromaticity — equal to profile->xy_w when no CCT
261// override is active, otherwise the cct_to_xy() shift. The strict solvers
262// must route barycentric containment against this value (not the profile's
263// raw xy_w) or they will choose the wrong sub-gamut for CCT-shifted whites.
266 float xy_w[2]; // effective W chromaticity (incl. CCT shift)
267 float P_R[3], P_G[3], P_B[3], P_W[3]; // emitter XYZ at full drive
268 float P_RGB_inv[3][3]; // [R G B]^-1, used by wx_lp_legacy
269 float P_RGW_inv[3][3]; // [R G W]^-1
270 float P_RBW_inv[3][3]; // [R B W]^-1
271 float P_BGW_inv[3][3]; // [B G W]^-1
272 float d_W[3]; // P_RGB_inv * P_W (wx_lp_legacy cache)
273
274 // Source-space → measured-device XYZ matrix (#2705).
275 //
276 // build_source_matrix() first derives the standard normalized CIE source
277 // matrix from profile.input_xy_* (source white has Y=1). build_profile_cache()
278 // then scales that matrix by the reference white-fit factor used by the
279 // math model so M_src·[1,1,1] is in the same absolute XYZ/Y domain as the
280 // measured emitter columns P_R/P_G/P_B/P_W.
281 //
282 // This scaling is essential: all runtime solves compare M_src targets
283 // directly against measured diode columns. Leaving M_src at Y=1 would make
284 // native D65 dual-edge and LP solves underpowered and would not match the
285 // reference model/cube.
286 //
287 // When the source chromaticities are degenerate (input_xy_w[1] == 0, the
288 // default for value-initialized `DiodeProfile{}`), `has_source_space` is
289 // false and solvers fall back to direct device-emitter projection.
290 float M_src[3][3];
292};
293
294// LUT cell quantization scale (value in [-8, +8) maps to i16 via *kLutQ).
295constexpr i16 kLutQ = 4096;
296
297// Storage stride per grid point. Bilinear LUTs store 4 (rgbw) values; Hermite
298// LUTs additionally store ∂/∂t_x and ∂/∂t_y per channel (in cell-parameter
299// units), enabling bicubic Hermite interpolation that reaches comparable
300// accuracy at ~half the grid edge length — ~25 % of the memory at ~ the
301// same error vs. bilinear. The grid step is uniform so derivatives are
302// naturally expressed in cell-parameter units (t ∈ [0, 1] per cell).
303constexpr int kLutStrideBilinear = 4;
304constexpr int kLutStrideHermite = 12;
305
306enum class LutInterp : u8 {
309};
310
311// Cubic Hermite basis on [0, 1]. Output layout: { h00, h01, h10, h11 } where
312// h00(t) = 2t³ - 3t² + 1 value at t=0
313// h01(t) = -2t³ + 3t² value at t=1
314// h10(t) = t³ - 2t² + t derivative at t=0
315// h11(t) = t³ - t² derivative at t=1
316// Lifted into a header inline so `lookup_lut` and the test suite consume
317// exactly the same evaluator (CodeRabbit #2707: tests that redefine basis
318// locally cannot catch regressions in the production code).
319inline void hermite_basis(float t, float out[4]) FL_NOEXCEPT {
320 const float t2 = t * t;
321 const float t3 = t2 * t;
322 out[0] = 2.0f * t3 - 3.0f * t2 + 1.0f;
323 out[1] = -2.0f * t3 + 3.0f * t2;
324 out[2] = t3 - 2.0f * t2 + t;
325 out[3] = t3 - t2;
326}
327
328// Owns its cell storage via fl::unique_ptr. Obtain via `build_lut(...)`,
329// which atomically allocates + populates. Lookup code can rely on
330// .cells.get() being non-null for the table's lifetime.
331struct LutTable {
332 int N = 0; // grid edge length
333 LutInterp interp = LutInterp::Bilinear; // storage / interp scheme
334 float xy_min[2] = {0.0f, 0.0f}; // grid origin in xy
335 float xy_max[2] = {0.0f, 0.0f}; // grid extent in xy
336 fl::unique_ptr<i16[]> cells; // size = N*N*stride for chosen interp
337};
338
339// Two-emitter (warm-W, cool-W) profile for RGBCCT layered solver.
341 DiodeProfile warm_path; // R, G, B, W=warm — typical xy_w ≈ 3000K
342 DiodeProfile cool_path; // R, G, B, W=cool — typical xy_w ≈ 6500K
343};
344
345inline i16 quantize_lut_cell(float v) FL_NOEXCEPT {
346 const float scaled = v * static_cast<float>(kLutQ) + 0.5f;
347 if (scaled <= -32768.0f) return -32768;
348 if (scaled >= 32767.0f) return 32767;
349 return static_cast<i16>(scaled);
350}
351
352// Convenience: derive eta from a target CCT relative to the warm/cool
353// reference CCTs. Linear interpolation in CCT space, clamped to [0, 1].
354inline float rgbcct_eta_for_cct(int target_cct, int warm_cct,
355 int cool_cct) FL_NOEXCEPT {
356 if (cool_cct == warm_cct) return 0.5f;
357 const float t = (static_cast<float>(target_cct) - warm_cct)
358 / (static_cast<float>(cool_cct) - warm_cct);
359 return fl::clamp(t, 0.0f, 1.0f);
360}
361
362// ===== Function declarations (definitions in rgbw_colorimetric.cpp.hpp) =====
363// All gated by FASTLED_RGBW_COLORIMETRIC at the definition site. With the
364// macro off, the symbols simply don't exist in the library — the dispatch
365// in rgbw.cpp.hpp falls back to the stub path (warn-once + rgb_2_rgbw_exact).
366
367// Build the per-profile cache. If cct_override is in [1500, 15000], the W
368// vertex chromaticity is replaced with cct_to_xy(cct_override). Pass
369// cct_override = 0 to use the profile's xy_w unchanged.
370void build_profile_cache(const DiodeProfile* p, int cct_override,
372
373// Convenience overload: no CCT override.
375 ProfileCache* cache) FL_NOEXCEPT {
376 build_profile_cache(p, 0, cache);
377}
378
379// Strict sub-gamut solver (gist sec 5).
380//
381// Runtime path:
382// 1. Black/near-black returns zero.
383// 2. Native single-axis inputs return exact identity.
384// 3. Native dual-edge inputs are locked to RG/RB/GB and solved as a measured
385// two-emitter least-squares problem — no W/third-channel bleed, but also
386// not raw passthrough.
387// 4. Interior inputs split source value from chroma, solve the full-chroma
388// endpoint in one of {RGW, RBW, BGW}, then scale by source value.
389//
390// Returns false only for numerically degenerate profiles / matrices where the
391// caller should fall back to a simpler path.
392bool solve_strict_subgamut(const ProfileCache& cache, float s_r,
393 float s_g, float s_b,
394 float out_rgbw[4]) FL_NOEXCEPT;
395
396// Per-chromaticity variant: starts from explicit (xy, Y) instead of input RGB.
397// Used by the LUT builder where xy/Y have already been chosen by the table
398// sampling pass. This path intentionally skips native input topology rules
399// because there is no source RGB active-channel mask attached to an arbitrary
400// xy sample.
402 const float xy_t[2], float Y_t,
403 float out_rgbw[4]) FL_NOEXCEPT;
404
405// Reference wx_lp_legacy solver.
406//
407// This is the analytical runtime counterpart of the reference math-model
408// "wx_lp_legacy" cube. It is not the boosted/overdrive mode. It solves a
409// chromaticity-preserving bounded endpoint:
410// 1. honor native single/dual topology authority;
411// 2. split source value from chroma;
412// 3. project the full-chroma target into the reachable hull if needed;
413// 4. solve the xy constraints while maximizing W;
414// 5. keep a small RGB floor when the four-channel manifold can represent the
415// target, normalize the endpoint, then apply the original source value.
416//
417// Native single-axis inputs remain exact identity. Native dual edges remain
418// locked to RG/RB/GB but are solved from measured/source XYZ instead of raw
419// passthrough. This is intentionally separate from boosted overdrive below.
420bool solve_wx_lp_legacy(const ProfileCache& cache, float s_r, float s_g,
421 float s_b, float out_rgbw[4]) FL_NOEXCEPT;
422
423// White-overdrive / boosted solver (#2706). Uses a separate visual policy
424// that pushes W past the non-overdriven residual boundary by
425// `overdrive_ratio`, accepting chromaticity drift toward the W diode in
426// exchange for higher luminance. Keep this separate from wx_lp_legacy; the LP
427// legacy reference is the bounded chromaticity-preserving solver above.
428void solve_wx_overdrive(const ProfileCache& cache, float s_r, float s_g,
429 float s_b, float overdrive_ratio,
430 float out_rgbw[4]) FL_NOEXCEPT;
431
432// Default overdrive ratio for `kRGBWColorimetricBoosted`. 0.5 = halfway
433// between the strict vertex and full W; produces visibly brighter output
434// than the strict mode while limiting chromaticity drift.
435constexpr float kDefaultOverdriveRatio = 0.5f;
436
437// Allocate + populate a LUT for `cache` at `grid_n` edge length, using
438// `interp` (default Hermite). Hermite tables carry per-cell value + slope
439// (∂/∂t_x, ∂/∂t_y), tripling per-cell storage but typically achieving lower
440// error than bilinear at the same grid size — enabling small tables (N=8..16)
441// suitable for memory-constrained targets.
442LutTable build_lut(const ProfileCache& cache, int grid_n,
443 LutInterp interp) FL_NOEXCEPT;
444
445inline LutTable build_lut(const ProfileCache& cache, int grid_n) FL_NOEXCEPT {
446 return build_lut(cache, grid_n, LutInterp::Hermite);
447}
448
449// Bilinear lookup + Y multiply + normalize.
450void lookup_lut(const LutTable& lut, const float xy_t[2], float Y_t,
451 float out_rgbw[4]) FL_NOEXCEPT;
452
453// RGBCCT layered solver: solve target against each W diode separately, then
454// line-blend the resulting RGBW tuples by an eta in [0, 1].
455// Output layout: out[0..2] = RGB, out[3] = warm-W, out[4] = cool-W.
456void solve_rgbcct(const RgbcctProfile& profile, float s_r, float s_g,
457 float s_b, float eta, float out[5]) FL_NOEXCEPT;
458
459} // namespace colorimetric_detail
460} // namespace fl
Functions for red, green, blue, white (RGBW) output.
bool solve_wx_lp_legacy(const ProfileCache &cache, float s_r, float s_g, float s_b, float out_rgbw[4]) FL_NOEXCEPT
void xyY_to_XYZ(float x, float y, float Y, float out[3]) FL_NOEXCEPT
void cct_to_xy(int cct, float out[2]) FL_NOEXCEPT
i16 quantize_lut_cell(float v) FL_NOEXCEPT
LutTable build_lut(const ProfileCache &cache, int grid_n, LutInterp interp) FL_NOEXCEPT
bool build_source_matrix(const float xy_r[2], const float xy_g[2], const float xy_b[2], const float xy_w[2], float M_out[3][3]) FL_NOEXCEPT
bool solve_strict_subgamut_xy(const ProfileCache &cache, const float xy_t[2], float Y_t, float out_rgbw[4]) 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
void nnls3(const float M[3][3], const float b[3], float t_out[3], float *residual_out) FL_NOEXCEPT
float rgbcct_eta_for_cct(int target_cct, int warm_cct, int cool_cct) FL_NOEXCEPT
bool invert3x3(const float in[3][3], float out[3][3]) FL_NOEXCEPT
bool barycentric_xy(const float t[2], const float A[2], const float B[2], const float C[2], float bary[3]) 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
bool is_native_input_gamut(const DiodeProfile &p) FL_NOEXCEPT
void solve_rgbcct(const RgbcctProfile &profile, float s_r, float s_g, float s_b, float eta, float out[5]) FL_NOEXCEPT
int count_active_channels(float s_r, float s_g, float s_b) FL_NOEXCEPT
void hermite_basis(float t, float out[4]) FL_NOEXCEPT
unsigned char u8
Definition stdint.h:131
double fabs(double value) FL_NOEXCEPT
Definition math.h:509
FL_DISABLE_WARNING_PUSH unsigned char * B
constexpr enable_if< is_fixed_point< T >::value, T >::type sqrt(T x) FL_NOEXCEPT
FASTLED_FORCE_INLINE fl::u8 P(fl::u8 x)
constexpr enable_if< is_fixed_point< T >::value, T >::type clamp(T x, T lo, T hi) FL_NOEXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT