FastLED 3.10.6
Loading...
Searching...
No Matches
colorimetric_response.h
Go to the documentation of this file.
1
22
23#pragma once
24
25#include "fl/math/math.h"
27#include "fl/stl/stdint.h"
28
29namespace fl {
31
32// ===== Pure-math inline helpers ==============================================
33// Tight, leaf-level operations kept inline because:
34// 1. They're trivial bodies (≤ 25 lines).
35// 2. They appear in hot inner loops; inlining lets the optimizer fold them.
36// 3. Tests can validate them directly without a full library rebuild.
37
38// Krystek's approximation for blackbody chromaticity (good 1000K - 15000K).
39// Krystek's outputs are (u, v) in the CIE 1960 UCS; convert to xy via the
40// standard 1960->xy formulas. Reference: Krystek, "An algorithm to calculate
41// correlated color temperature", Color Research & Application, 1985.
42inline void cct_to_xy(int cct, float out[2]) FL_NO_EXCEPT {
43 const float T = static_cast<float>(
44 (cct < 1500) ? 1500 : ((cct > 15000) ? 15000 : cct));
45 const float T2 = T * T;
46 const float u_num = 0.860117757f + 1.54118254e-4f * T + 1.28641212e-7f * T2;
47 const float u_den = 1.0f + 8.42420235e-4f * T + 7.08145163e-7f * T2;
48 const float v_num = 0.317398726f + 4.22806245e-5f * T + 4.20481691e-8f * T2;
49 const float v_den = 1.0f - 2.89741816e-5f * T + 1.61456053e-7f * T2;
50 const float u = u_num / u_den;
51 const float v = v_num / v_den;
52 const float den = 2.0f * u - 8.0f * v + 4.0f;
53 out[0] = 3.0f * u / den;
54 out[1] = 2.0f * v / den;
55}
56
57inline void xyY_to_XYZ(float x, float y, float Y, float out[3]) FL_NO_EXCEPT {
58 if (y < 1e-12f) {
59 out[0] = out[1] = out[2] = 0.0f;
60 return;
61 }
62 const float inv_y = 1.0f / y;
63 out[0] = x * Y * inv_y;
64 out[1] = Y;
65 out[2] = (1.0f - x - y) * Y * inv_y;
66}
67
68namespace detail {
69
99constexpr float kRelativeSingularity = 1e-6f;
100
102inline float columnScale(float a, float b, float c) FL_NO_EXCEPT {
103 const float x = fl::fabs(a);
104 const float y = fl::fabs(b);
105 const float z = fl::fabs(c);
106 float largest = x > y ? x : y;
107 if (z > largest) {
108 largest = z;
109 }
110 return largest;
111}
112
113} // namespace detail
114
115inline bool invert3x3(const float in[3][3], float out[3][3]) FL_NO_EXCEPT {
116 const float a = in[0][0], b = in[0][1], c = in[0][2];
117 const float d = in[1][0], e = in[1][1], f = in[1][2];
118 const float g = in[2][0], h = in[2][1], i = in[2][2];
119 const float det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
120
121 // Scale the test by the matrix's own magnitude, because an absolute
122 // threshold cannot see the ratio that actually decides invertibility.
123 //
124 // Each column is divided by its largest entry and the determinant of
125 // *that* matrix is what gets tested. The obvious alternative -- compare
126 // |det| against the product of the column 2-norms -- needs `a * a`, which
127 // overflows float32 for entries past about 1.8e19: `diag(1e20, 1e-20, 1)`
128 // has determinant 1 and a perfectly representable inverse, and squaring
129 // would send its first column norm to infinity and reject it. Dividing
130 // first cannot overflow, and it costs three divisions instead of three
131 // square roots.
132 const float scale_a = detail::columnScale(a, d, g);
133 const float scale_b = detail::columnScale(b, e, h);
134 const float scale_c = detail::columnScale(c, f, i);
135
136 // One negated `>`, which covers three cases at once and is why there is
137 // no separate zero or NaN check: a zero column makes its scale zero, so
138 // the division below yields infinity or NaN and the comparison fails; a
139 // NaN anywhere makes every comparison false, so the negation rejects. An
140 // explicit `scale > 0` clause was written here first, and mutation
141 // testing showed it could not fire.
142 const float an = a / scale_a, dn = d / scale_a, gn = g / scale_a;
143 const float bn = b / scale_b, en = e / scale_b, hn = h / scale_b;
144 const float cn = c / scale_c, fn = f / scale_c, in_ = i / scale_c;
145 const float det_normalized = an * (en * in_ - fn * hn) -
146 bn * (dn * in_ - fn * gn) +
147 cn * (dn * hn - en * gn);
148 if (!(fl::fabs(det_normalized) > detail::kRelativeSingularity)) {
149 return false;
150 }
151
152 const float inv_det = 1.0f / det;
153 out[0][0] = (e * i - f * h) * inv_det;
154 out[0][1] = (c * h - b * i) * inv_det;
155 out[0][2] = (b * f - c * e) * inv_det;
156 out[1][0] = (f * g - d * i) * inv_det;
157 out[1][1] = (a * i - c * g) * inv_det;
158 out[1][2] = (c * d - a * f) * inv_det;
159 out[2][0] = (d * h - e * g) * inv_det;
160 out[2][1] = (b * g - a * h) * inv_det;
161 out[2][2] = (a * e - b * d) * inv_det;
162 return true;
163}
164
165inline void matvec3(const float M[3][3], const float v[3], float out[3]) FL_NO_EXCEPT {
166 out[0] = M[0][0] * v[0] + M[0][1] * v[1] + M[0][2] * v[2];
167 out[1] = M[1][0] * v[0] + M[1][1] * v[1] + M[1][2] * v[2];
168 out[2] = M[2][0] * v[0] + M[2][1] * v[1] + M[2][2] * v[2];
169}
170
171inline bool barycentric_xy(const float t[2], const float A[2], const float B[2],
172 const float C[2], float bary[3]) FL_NO_EXCEPT {
173 const float v0x = B[0] - A[0], v0y = B[1] - A[1];
174 const float v1x = C[0] - A[0], v1y = C[1] - A[1];
175 const float v2x = t[0] - A[0], v2y = t[1] - A[1];
176 const float d00 = v0x * v0x + v0y * v0y;
177 const float d01 = v0x * v1x + v0y * v1y;
178 const float d11 = v1x * v1x + v1y * v1y;
179 const float d20 = v2x * v0x + v2y * v0y;
180 const float d21 = v2x * v1x + v2y * v1y;
181 const float den = d00 * d11 - d01 * d01;
182 if (fl::fabs(den) < 1e-20f) {
183 return false;
184 }
185 const float inv_den = 1.0f / den;
186 const float u = (d11 * d20 - d01 * d21) * inv_den;
187 const float v = (d00 * d21 - d01 * d20) * inv_den;
188 bary[0] = 1.0f - u - v;
189 bary[1] = u;
190 bary[2] = v;
191 return true;
192}
193
194inline u8 quantize_u8(float v) FL_NO_EXCEPT {
195 const float scaled = v * 255.0f + 0.5f;
196 if (scaled <= 0.0f) return 0;
197 if (scaled >= 255.0f) return 255;
198 return static_cast<u8>(scaled);
199}
200
201// Standard CIE primary-matrix construction (#2705). Given source primary
202// chromaticities xy_r/g/b and a source white chromaticity xy_w, build the
203// 3x3 matrix M such that M·[1,1,1]^T = xyY_to_XYZ(xy_w, 1.0). Columns are
204// per-channel scaled XYZ vectors of the primaries at Y=1, with scaling
205// chosen so the (1,1,1) input lands at source white in XYZ. This is the
206// classic linear-sRGB -> XYZ derivation generalized to arbitrary primaries.
207// Returns false if the primary matrix is singular (collinear chromaticities).
208inline bool build_source_matrix(const float xy_r[2], const float xy_g[2],
209 const float xy_b[2], const float xy_w[2],
210 float M_out[3][3]) FL_NO_EXCEPT {
211 float xyz_R[3], xyz_G[3], xyz_B[3], xyz_W[3];
212 xyY_to_XYZ(xy_r[0], xy_r[1], 1.0f, xyz_R);
213 xyY_to_XYZ(xy_g[0], xy_g[1], 1.0f, xyz_G);
214 xyY_to_XYZ(xy_b[0], xy_b[1], 1.0f, xyz_B);
215 xyY_to_XYZ(xy_w[0], xy_w[1], 1.0f, xyz_W);
216
217 float P[3][3];
218 P[0][0] = xyz_R[0]; P[0][1] = xyz_G[0]; P[0][2] = xyz_B[0];
219 P[1][0] = xyz_R[1]; P[1][1] = xyz_G[1]; P[1][2] = xyz_B[1];
220 P[2][0] = xyz_R[2]; P[2][1] = xyz_G[2]; P[2][2] = xyz_B[2];
221
222 float P_inv[3][3];
223 if (!invert3x3(P, P_inv)) {
224 return false;
225 }
226 float k[3];
227 matvec3(P_inv, xyz_W, k);
228
229 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];
230 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];
231 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];
232 return true;
233}
234
235// Non-negative least squares for the 3×3 sub-system M·t = b with t ≥ 0
236// (#2708, gist §3). Projected-gradient form matching the reference
237// `_nnls_solve` fallback used when scipy is unavailable: 500 iterations at
238// step 0.01. Cheap (~50 µs scalar on a typical MCU at -O2; only invoked for
239// out-of-hull source targets, never on the in-hull fast path) and free of
240// dynamic allocation, which makes it safe to inline behind the colorimetric
241// solvers' rare-branch.
242inline void nnls3(const float M[3][3], const float b[3], // ok array parameter
243 float t_out[3], float* residual_out) FL_NO_EXCEPT {
244 float t[3] = {0.0f, 0.0f, 0.0f};
245 constexpr float kStep = 0.01f;
246 constexpr int kIters = 500;
247 for (int it = 0; it < kIters; ++it) {
248 float r[3];
249 r[0] = M[0][0]*t[0] + M[0][1]*t[1] + M[0][2]*t[2] - b[0];
250 r[1] = M[1][0]*t[0] + M[1][1]*t[1] + M[1][2]*t[2] - b[1];
251 r[2] = M[2][0]*t[0] + M[2][1]*t[1] + M[2][2]*t[2] - b[2];
252 // grad = Mᵀ · r
253 float g[3];
254 g[0] = M[0][0]*r[0] + M[1][0]*r[1] + M[2][0]*r[2];
255 g[1] = M[0][1]*r[0] + M[1][1]*r[1] + M[2][1]*r[2];
256 g[2] = M[0][2]*r[0] + M[1][2]*r[1] + M[2][2]*r[2];
257 for (int j = 0; j < 3; ++j) {
258 float v = t[j] - kStep * g[j];
259 t[j] = v > 0.0f ? v : 0.0f;
260 }
261 }
262 if (residual_out != nullptr) {
263 float r[3];
264 r[0] = M[0][0]*t[0] + M[0][1]*t[1] + M[0][2]*t[2] - b[0];
265 r[1] = M[1][0]*t[0] + M[1][1]*t[1] + M[1][2]*t[2] - b[1];
266 r[2] = M[2][0]*t[0] + M[2][1]*t[1] + M[2][2]*t[2] - b[2];
267 *residual_out = fl::sqrt(r[0]*r[0] + r[1]*r[1] + r[2]*r[2]);
268 }
269 t_out[0] = t[0]; t_out[1] = t[1]; t_out[2] = t[2];
270}
271
272// ===== LUT quantization and interpolation constants =========================
273// Used by both the RGBW LUT (today) and any future RGB / RGBCCT LUT paths
274// extracted out of the topology-specific solver modules.
275
276// LUT cell quantization scale (value in [-8, +8) maps to i16 via *kLutQ).
277constexpr i16 kLutQ = 4096;
278
279// Storage stride per grid point. Bilinear LUTs store 4 (rgbw) values; Hermite
280// LUTs additionally store ∂/∂t_x and ∂/∂t_y per channel (in cell-parameter
281// units), enabling bicubic Hermite interpolation that reaches comparable
282// accuracy at ~half the grid edge length — ~25 % of the memory at ~ the
283// same error vs. bilinear. The grid step is uniform so derivatives are
284// naturally expressed in cell-parameter units (t ∈ [0, 1] per cell).
285constexpr int kLutStrideBilinear = 4;
286constexpr int kLutStrideHermite = 12;
287
288enum class LutInterp : u8 {
291};
292
293// Cubic Hermite basis on [0, 1]. Output layout: { h00, h01, h10, h11 } where
294// h00(t) = 2t³ - 3t² + 1 value at t=0
295// h01(t) = -2t³ + 3t² value at t=1
296// h10(t) = t³ - 2t² + t derivative at t=0
297// h11(t) = t³ - t² derivative at t=1
298// Lifted into a header inline so `lookup_lut` and the test suite consume
299// exactly the same evaluator (CodeRabbit #2707: tests that redefine basis
300// locally cannot catch regressions in the production code).
301inline void hermite_basis(float t, float out[4]) FL_NO_EXCEPT {
302 const float t2 = t * t;
303 const float t3 = t2 * t;
304 out[0] = 2.0f * t3 - 3.0f * t2 + 1.0f;
305 out[1] = -2.0f * t3 + 3.0f * t2;
306 out[2] = t3 - 2.0f * t2 + t;
307 out[3] = t3 - t2;
308}
309
310inline i16 quantize_lut_cell(float v) FL_NO_EXCEPT {
311 const float scaled = v * static_cast<float>(kLutQ) + 0.5f;
312 if (scaled <= -32768.0f) return -32768;
313 if (scaled >= 32767.0f) return 32767;
314 return static_cast<i16>(scaled);
315}
316
317// ===== Physical emitter profile =============================================
318enum class EmitterTopology : u8 { RGB = 0, RGBW = 1, RGBWW = 2 };
319
320// The RGB fields retain their original layout; wide topology data is appended
321// so existing RGB profiles and factory calls remain source-compatible.
323 float xy_r[2]; // R diode chromaticity (CIE 1931 xy)
324 float xy_g[2]; // G diode chromaticity
325 float xy_b[2]; // B diode chromaticity
326 float lum_r; // R diode peak luminance (relative to source white = 1.0)
327 float lum_g;
328 float lum_b;
329
330 // Source / input color space (#2705 semantics). When `input_xy_w[1]` is
331 // 0 (the default for value-initialized profiles), solvers fall back to
332 // the legacy interpretation in which the input RGB triple IS the
333 // device-emitter drive coordinates — preserved for backward compat.
334 float input_xy_r[2]; // source R primary chromaticity (default: native LED R)
335 float input_xy_g[2]; // source G primary chromaticity (default: native LED G)
336 float input_xy_b[2]; // source B primary chromaticity (default: native LED B)
337 float input_xy_w[2]; // source white chromaticity (default D65)
338
339 const char* id;
340 const u16* response_lut_r;
341 const u16* response_lut_g;
342 const u16* response_lut_b;
346 const char* provenance_kind;
347 const char* report_id;
353
354 // Appended so existing RGB aggregate initializers retain their meaning.
355 // In RGBW white1 is the sole white; in RGBWW it is warm white and white2
356 // is cool white. Unused white slots stay zero/null.
358 float xy_white1[2];
359 float xy_white2[2];
366
367 static constexpr EmitterProfile rgb(const char* profile_id, Chromaticity red,
368 Chromaticity green, Chromaticity blue,
369 float red_y, float green_y, float blue_y,
370 const char* provenance = nullptr,
371 const char* report = nullptr) FL_NO_EXCEPT {
372 return EmitterProfile{{red.x, red.y}, {green.x, green.y}, {blue.x, blue.y}, red_y, green_y, blue_y,
373 {0, 0}, {0, 0}, {0, 0}, {0, 0}, profile_id, nullptr, nullptr, nullptr, 0, 8,
374 FiveBitSemantics::NotApplicable, provenance, report, 0, 0, 0, 0, 0,
375 EmitterTopology::RGB, {0, 0}, {0, 0}, 0, 0,
376 nullptr, nullptr, 0, 0};
377 }
378
379 static constexpr EmitterProfile rgbw(const char* profile_id, Chromaticity red,
380 Chromaticity green, Chromaticity blue,
381 Chromaticity white, float red_y,
382 float green_y, float blue_y,
383 float white_y,
384 const char* provenance = nullptr,
385 const char* report = nullptr) FL_NO_EXCEPT {
386 return EmitterProfile{{red.x, red.y}, {green.x, green.y}, {blue.x, blue.y},
387 red_y, green_y, blue_y,
388 {0, 0}, {0, 0}, {0, 0}, {0, 0}, profile_id,
389 nullptr, nullptr, nullptr, 0, 8,
390 FiveBitSemantics::NotApplicable, provenance, report,
391 0, 0, 0, 0, 0,
392 EmitterTopology::RGBW, {white.x, white.y}, {0, 0},
393 white_y, 0, nullptr, nullptr, 0, 0};
394 }
395
396 static constexpr EmitterProfile rgbww(const char* profile_id, Chromaticity red,
397 Chromaticity green, Chromaticity blue,
398 Chromaticity warm_white,
399 Chromaticity cool_white, float red_y,
400 float green_y, float blue_y,
401 float warm_white_y, float cool_white_y,
402 const char* provenance = nullptr,
403 const char* report = nullptr) FL_NO_EXCEPT {
404 return EmitterProfile{{red.x, red.y}, {green.x, green.y}, {blue.x, blue.y},
405 red_y, green_y, blue_y,
406 {0, 0}, {0, 0}, {0, 0}, {0, 0}, profile_id,
407 nullptr, nullptr, nullptr, 0, 8,
408 FiveBitSemantics::NotApplicable, provenance, report,
409 0, 0, 0, 0, 0,
411 {warm_white.x, warm_white.y},
412 {cool_white.x, cool_white.y},
413 warm_white_y, cool_white_y,
414 nullptr, nullptr, 0, 0};
415 }
416};
417
418// Backward-compat alias for the original PR vocabulary (issue #3231 → PR 2
419// of #3255). New code should prefer the topology-independent
420// `EmitterProfile` name; existing call sites that spell out
421// `RgbColorimetricProfile` keep compiling.
423
424namespace profiles {
425#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 6
426extern const EmitterProfile WS2812B;
427#else
429 "ws2812b/placeholder/uncalibrated", Chromaticity(.640f, .330f),
430 Chromaticity(.300f, .600f), Chromaticity(.150f, .060f), 1.0f, 1.0f, 1.0f,
431 "placeholder", "uncalibrated");
432#endif
433} // namespace profiles
434
435// Cache derived from `EmitterProfile`: precomputed XYZ primaries, inverse
436// primary matrix, and source-space matrix when an input gamut is supplied.
437// Built once per profile and reused across pixels.
439 float P_R[3], P_G[3], P_B[3];
440 float P_RGB_inv[3][3];
441 float M_src[3][3];
443};
444
445// Build the precomputed primaries / inverse matrices for `p`. Returns false
446// when the device-emitter primary matrix is singular (collinear
447// chromaticities), or when a declared source space cannot be built; the
448// cache is still populated with zeros so callers can safely fall back to the
449// no-op solve.
450//
451// Source-space handling matches the RGBW path (#2705 semantics): when
452// `input_xy_w[1]` is effectively zero, the input RGB triple IS treated as
453// drive coordinates. Otherwise a source-primary matrix is built and
454// normalized so that the source white at unit drive lands on the maximum
455// per-channel column — keeping outputs in [0, 1] for in-gamut inputs.
456//
457// "No source space declared" and "a source space was declared and is
458// unusable" are different answers and must not collapse into one. Folding
459// the second into the first leaves `has_source_space` false, which means the
460// caller's RGB is reinterpreted as device drive coordinates -- a different
461// colour, produced silently, from primaries the caller believed were in
462// effect. FastLED#4156 R6 asks for that specifically: a near-singular
463// transform needs deterministic rejection, not an arbitrary fallback that
464// destroys the accuracy promise.
467 xyY_to_XYZ(p.xy_r[0], p.xy_r[1], p.lum_r, cache->P_R);
468 xyY_to_XYZ(p.xy_g[0], p.xy_g[1], p.lum_g, cache->P_G);
469 xyY_to_XYZ(p.xy_b[0], p.xy_b[1], p.lum_b, cache->P_B);
470
471 const float P_RGB[3][3] = {
472 { cache->P_R[0], cache->P_G[0], cache->P_B[0] },
473 { cache->P_R[1], cache->P_G[1], cache->P_B[1] },
474 { cache->P_R[2], cache->P_G[2], cache->P_B[2] },
475 };
476 const bool ok_rgb = invert3x3(P_RGB, cache->P_RGB_inv);
477 const bool declares_source_space = (p.input_xy_w[1] > 1e-6f);
478 cache->has_source_space = declares_source_space
479 && build_source_matrix(p.input_xy_r, p.input_xy_g,
480 p.input_xy_b, p.input_xy_w,
481 cache->M_src);
482 if (cache->has_source_space && ok_rgb) {
483 float X_w[3];
484 xyY_to_XYZ(p.input_xy_w[0], p.input_xy_w[1], 1.0f, X_w);
485 float t[3];
486 matvec3(cache->P_RGB_inv, X_w, t);
487 const float mt = fl::max(fl::max(fl::fabs(t[0]), fl::fabs(t[1])),
488 fl::fabs(t[2]));
489 if (mt > 1e-6f) {
490 const float scale_k = 1.0f / mt;
491 for (int i = 0; i < 3; ++i) {
492 for (int j = 0; j < 3; ++j) {
493 cache->M_src[i][j] *= scale_k;
494 }
495 }
496 }
497 }
498 if (!ok_rgb) {
499 for (int i = 0; i < 3; ++i) {
500 for (int j = 0; j < 3; ++j) {
501 cache->P_RGB_inv[i][j] = 0.0f;
502 }
503 }
504 }
505 if (!cache->has_source_space) {
506 for (int i = 0; i < 3; ++i) {
507 for (int j = 0; j < 3; ++j) {
508 cache->M_src[i][j] = 0.0f;
509 }
510 }
511 }
512 if (declares_source_space && !cache->has_source_space) {
513 return false;
514 }
515 return ok_rgb;
516}
517
518// Map a source-space RGB triple to absolute XYZ. With a configured source
519// gamut, this is the M_src multiplication; without one, the device-emitter
520// primary columns are used directly (the input is treated as drive
521// coordinates).
522inline void rgb_source_to_XYZ(const RgbColorimetricCache& cache, float s_r,
523 float s_g, float s_b,
524 float X_t[3]) FL_NO_EXCEPT {
525 if (cache.has_source_space) {
526 const float s[3] = { s_r, s_g, s_b };
527 matvec3(cache.M_src, s, X_t);
528 } else {
529 X_t[0] = cache.P_R[0] * s_r + cache.P_G[0] * s_g + cache.P_B[0] * s_b;
530 X_t[1] = cache.P_R[1] * s_r + cache.P_G[1] * s_g + cache.P_B[1] * s_b;
531 X_t[2] = cache.P_R[2] * s_r + cache.P_G[2] * s_g + cache.P_B[2] * s_b;
532 }
533}
534
535// Solve a source-space RGB target for the device RGB drives. The result is
536// clamped to non-negative and normalized so the per-channel max ≤ 1.
538 float s_r, float s_g, float s_b,
539 float out_rgb[3]) FL_NO_EXCEPT {
540 float X_t[3];
541 rgb_source_to_XYZ(cache, s_r, s_g, s_b, X_t);
542 matvec3(cache.P_RGB_inv, X_t, out_rgb);
543 out_rgb[0] = fl::max(out_rgb[0], 0.0f);
544 out_rgb[1] = fl::max(out_rgb[1], 0.0f);
545 out_rgb[2] = fl::max(out_rgb[2], 0.0f);
546 const float m = fl::max(fl::max(out_rgb[0], out_rgb[1]), out_rgb[2]);
547 if (m > 1.0f) {
548 const float inv_m = 1.0f / m;
549 out_rgb[0] *= inv_m;
550 out_rgb[1] *= inv_m;
551 out_rgb[2] *= inv_m;
552 }
553 return true;
554}
555
556} // namespace colorimetric_response
557
558namespace profiles {
559using colorimetric_response::profiles::WS2812B; // ok bare using: public P2 profile API
560} // namespace profiles
561} // namespace fl
uint32_t z[NUM_LAYERS]
Definition Fire2023.h:93
constexpr float kRelativeSingularity
How small a determinant may be, relative to the matrix's own scale, before the inverse is rounding no...
float columnScale(float a, float b, float c) FL_NO_EXCEPT
Largest absolute entry of a column, which is its infinity norm.
static constexpr EmitterProfile WS2812B
u8 quantize_u8(float v) FL_NO_EXCEPT
void hermite_basis(float t, float out[4]) FL_NO_EXCEPT
void matvec3(const float M[3][3], const float v[3], float out[3]) FL_NO_EXCEPT
bool invert3x3(const float in[3][3], float out[3][3]) FL_NO_EXCEPT
bool barycentric_xy(const float t[2], const float A[2], const float B[2], const float C[2], float bary[3]) FL_NO_EXCEPT
void nnls3(const float M[3][3], const float b[3], float t_out[3], float *residual_out) FL_NO_EXCEPT
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_NO_EXCEPT
void rgb_source_to_XYZ(const RgbColorimetricCache &cache, float s_r, float s_g, float s_b, float X_t[3]) FL_NO_EXCEPT
void xyY_to_XYZ(float x, float y, float Y, float out[3]) FL_NO_EXCEPT
bool solve_rgb_colorimetric(const RgbColorimetricCache &cache, float s_r, float s_g, float s_b, float out_rgb[3]) FL_NO_EXCEPT
void cct_to_xy(int cct, float out[2]) FL_NO_EXCEPT
bool build_rgb_colorimetric_cache(const EmitterProfile &p, RgbColorimetricCache *cache) FL_NO_EXCEPT
i16 quantize_lut_cell(float v) FL_NO_EXCEPT
unsigned char u8
Definition stdint.h:131
constexpr common_type_t< T, U > max(T a, U b) FL_NO_EXCEPT
Definition math.h:75
FL_DISABLE_WARNING_PUSH unsigned char * B
FiveBitSemantics
FASTLED_FORCE_INLINE fl::u8 P(fl::u8 x)
InputGamut g FL_NO_EXCEPT
Definition rgbw.h:121
double fabs(double value) FL_NO_EXCEPT
Definition math.h:509
InputGamut g
Definition rgbw.h:124
constexpr enable_if< is_fixed_point< T >::value, T >::type sqrt(T x) FL_NO_EXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
CIE 1931 xy chromaticity, independent of encoded storage and chipset.
static constexpr EmitterProfile rgb(const char *profile_id, Chromaticity red, Chromaticity green, Chromaticity blue, float red_y, float green_y, float blue_y, const char *provenance=nullptr, const char *report=nullptr) FL_NO_EXCEPT
static constexpr EmitterProfile rgbww(const char *profile_id, Chromaticity red, Chromaticity green, Chromaticity blue, Chromaticity warm_white, Chromaticity cool_white, float red_y, float green_y, float blue_y, float warm_white_y, float cool_white_y, const char *provenance=nullptr, const char *report=nullptr) FL_NO_EXCEPT
static constexpr EmitterProfile rgbw(const char *profile_id, Chromaticity red, Chromaticity green, Chromaticity blue, Chromaticity white, float red_y, float green_y, float blue_y, float white_y, const char *provenance=nullptr, const char *report=nullptr) FL_NO_EXCEPT