8#if FASTLED_RGBW_COLORIMETRIC
65 xyY_to_XYZ(p->xy_r[0], p->xy_r[1], p->lum_r, cache->P_R);
66 xyY_to_XYZ(p->xy_g[0], p->xy_g[1], p->lum_g, cache->P_G);
67 xyY_to_XYZ(p->xy_b[0], p->xy_b[1], p->lum_b, cache->P_B);
68 cache->xy_w[0] = p->xy_w[0];
69 cache->xy_w[1] = p->xy_w[1];
70 if (cct_override >= 1500 && cct_override <= 15000) {
73 xyY_to_XYZ(cache->xy_w[0], cache->xy_w[1], p->lum_w, cache->P_W);
75 auto pack = [](
const float* a,
const float* b,
const float* c,
77 out[0][0] = a[0]; out[0][1] = b[0]; out[0][2] = c[0];
78 out[1][0] = a[1]; out[1][1] = b[1]; out[1][2] = c[1];
79 out[2][0] = a[2]; out[2][1] = b[2]; out[2][2] = c[2];
82 float P_RGB[3][3], P_RGW[3][3], P_RBW[3][3], P_BGW[3][3];
83 pack(cache->P_R, cache->P_G, cache->P_B, P_RGB);
84 pack(cache->P_R, cache->P_G, cache->P_W, P_RGW);
85 pack(cache->P_R, cache->P_B, cache->P_W, P_RBW);
86 pack(cache->P_B, cache->P_G, cache->P_W, P_BGW);
92 const bool ok_rgb =
invert3x3(P_RGB, cache->P_RGB_inv);
93 const bool ok_rgw =
invert3x3(P_RGW, cache->P_RGW_inv);
94 const bool ok_rbw =
invert3x3(P_RBW, cache->P_RBW_inv);
95 const bool ok_bgw =
invert3x3(P_BGW, cache->P_BGW_inv);
96 if (!(ok_rgb && ok_rgw && ok_rbw && ok_bgw)) {
97 FL_WARN_ONCE(
"RGBW colorimetric: profile has degenerate primaries — "
98 "one or more sub-gamut matrix inversions failed. Output "
99 "colors will be incorrect. Check DiodeProfile xy/lum values.");
103 for (
int i = 0; i < 3; ++i)
104 for (
int j = 0; j < 3; ++j) m[i][j] = 0.0f;
106 if (!ok_rgb) zero3x3(cache->P_RGB_inv);
107 if (!ok_rgw) zero3x3(cache->P_RGW_inv);
108 if (!ok_rbw) zero3x3(cache->P_RBW_inv);
109 if (!ok_bgw) zero3x3(cache->P_BGW_inv);
112 matvec3(cache->P_RGB_inv, cache->P_W, cache->d_W);
117 cache->has_source_space = (p->input_xy_w[1] > 1e-6f)
119 p->input_xy_b, p->input_xy_w,
121 if (cache->has_source_space) {
131 xyY_to_XYZ(p->input_xy_w[0], p->input_xy_w[1], 1.0f, X_w);
133 const float (*invs[3])[3] = {
134 cache->P_RGW_inv, cache->P_RBW_inv, cache->P_BGW_inv,
136 float best_max_t = 0.0f;
137 bool found_scale =
false;
138 constexpr float kScaleEps = 1e-6f;
139 for (
int k = 0; k < 3; ++k) {
142 if (
t[0] < -kScaleEps ||
t[1] < -kScaleEps ||
t[2] < -kScaleEps) {
146 if (mt > kScaleEps && (!found_scale || mt < best_max_t)) {
156 if (mt > kScaleEps) {
161 const float scale_k = found_scale ? (1.0f / best_max_t) : 1.0f;
162 for (
int i = 0; i < 3; ++i) {
163 for (
int j = 0; j < 3; ++j) {
164 cache->M_src[i][j] *= scale_k;
168 for (
int i = 0; i < 3; ++i)
169 for (
int j = 0; j < 3; ++j) cache->M_src[i][j] = 0.0f;
185 const float sum = X_t[0] + X_t[1] + X_t[2];
186 if (sum < 1e-12f)
return false;
187 xy_t[0] = X_t[0] / sum;
188 xy_t[1] = X_t[1] / sum;
190 const DiodeProfile& p = *cache.profile;
195 && bary[0] >= -1e-9f && bary[1] >= -1e-9f && bary[2] >= -1e-9f) {
199 struct Tri {
const float* a;
const float* b;
const float* c; };
200 const Tri tris[3] = {
201 { cache.P_R, cache.P_G, cache.P_W },
202 { cache.P_R, cache.P_B, cache.P_W },
203 { cache.P_B, cache.P_G, cache.P_W },
205 float best_xyz[3] = {0, 0, 0};
206 float best_residual = 1e30f;
207 for (
int k = 0; k < 3; ++k) {
208 const Tri& tri = tris[k];
209 const float M[3][3] = {
210 { tri.a[0], tri.b[0], tri.c[0] },
211 { tri.a[1], tri.b[1], tri.c[1] },
212 { tri.a[2], tri.b[2], tri.c[2] },
214 float t[3], residual;
215 nnls3(M, X_t,
t, &residual);
218 if (
t[1] > mt) mt =
t[1];
219 if (
t[2] > mt) mt =
t[2];
220 if (mt > 1.0f) {
const float inv = 1.0f / mt;
t[0] *= inv;
t[1] *= inv;
t[2] *= inv; }
222 xyz[0] = M[0][0]*
t[0] + M[0][1]*
t[1] + M[0][2]*
t[2];
223 xyz[1] = M[1][0]*
t[0] + M[1][1]*
t[1] + M[1][2]*
t[2];
224 xyz[2] = M[2][0]*
t[0] + M[2][1]*
t[1] + M[2][2]*
t[2];
225 if (xyz[1] <= 1e-12f)
continue;
226 if (residual < best_residual) {
227 best_residual = residual;
228 best_xyz[0] = xyz[0]; best_xyz[1] = xyz[1]; best_xyz[2] = xyz[2];
231 if (best_xyz[1] <= 1e-12f) {
237 const float s2 = best_xyz[0] + best_xyz[1] + best_xyz[2];
238 xy_t[0] = best_xyz[0] / s2;
239 xy_t[1] = best_xyz[1] / s2;
250static float max3f(
float a,
float b,
float c)
FL_NOEXCEPT {
255 out[0] = out[1] = out[2] = out[3] = 0.0f;
258static void normalize4_if_needed(
float out[4])
FL_NOEXCEPT {
261 const float inv_m = 1.0f / m;
262 out[0] *= inv_m; out[1] *= inv_m; out[2] *= inv_m; out[3] *= inv_m;
268 case 0:
return cache.P_R;
269 case 1:
return cache.P_G;
270 case 2:
return cache.P_B;
271 default:
return cache.P_W;
282static void source_rgb_to_XYZ(
const ProfileCache& cache,
float s_r,
283 float s_g,
float s_b,
285 if (cache.has_source_space) {
286 const float s[3] = { s_r, s_g, s_b };
289 X_t[0] = cache.P_R[0] * s_r + cache.P_G[0] * s_g + cache.P_B[0] * s_b;
290 X_t[1] = cache.P_R[1] * s_r + cache.P_G[1] * s_g + cache.P_B[1] * s_b;
291 X_t[2] = cache.P_R[2] * s_r + cache.P_G[2] * s_g + cache.P_B[2] * s_b;
299static bool native_single_identity(
float s_r,
float s_g,
float s_b,
315static bool solve_fixed_topology_least_squares(
const ProfileCache& cache,
321 constexpr float kEps = 1e-9f;
324 const float* A = column_for_idx(cache, idx[0]);
325 const float aa = A[0]*A[0] + A[1]*A[1] + A[2]*A[2];
326 const float ax = A[0]*X[0] + A[1]*X[1] + A[2]*X[2];
327 out[idx[0]] = (aa > kEps) ?
fl::max(ax / aa, 0.0f) : 0.0f;
332 const float* A = column_for_idx(cache, idx[0]);
333 const float*
B = column_for_idx(cache, idx[1]);
334 const float aa = A[0]*A[0] + A[1]*A[1] + A[2]*A[2];
335 const float ab = A[0]*
B[0] + A[1]*
B[1] + A[2]*
B[2];
336 const float bb =
B[0]*
B[0] +
B[1]*
B[1] +
B[2]*
B[2];
337 const float ax = A[0]*X[0] + A[1]*X[1] + A[2]*X[2];
338 const float bx =
B[0]*X[0] +
B[1]*X[1] +
B[2]*X[2];
339 const float det = aa * bb - ab * ab;
344 float t0 = ( ax * bb - bx * ab) / det;
345 float t1 = (-ax * ab + bx * aa) / det;
349 if (t0 < 0.0f && t1 >= 0.0f) {
351 t1 = (bb > kEps) ?
fl::max(bx / bb, 0.0f) : 0.0f;
352 }
else if (t1 < 0.0f && t0 >= 0.0f) {
354 t0 = (aa > kEps) ?
fl::max(ax / aa, 0.0f) : 0.0f;
355 }
else if (t0 < 0.0f && t1 < 0.0f) {
360 out[idx[0]] =
fl::max(t0, 0.0f);
361 out[idx[1]] =
fl::max(t1, 0.0f);
374static bool solve_native_dual_edge_fixed_topology(
const ProfileCache& cache,
375 float s_r,
float s_g,
379 constexpr float kEps = 1.0f / 65535.0f;
380 const float value = max3f(s_r, s_g, s_b);
389 const float c_r = s_r /
value;
390 const float c_g = s_g /
value;
391 const float c_b = s_b /
value;
394 source_rgb_to_XYZ(cache, c_r, c_g, c_b, X_full);
398 if (s_r > kEps) active[n++] = 0;
399 if (s_g > kEps) active[n++] = 1;
400 if (s_b > kEps) active[n++] = 2;
406 if (!solve_fixed_topology_least_squares(cache, X_full, active, n, full)) {
409 normalize4_if_needed(full);
411 out[0] = full[0] *
value;
412 out[1] = full[1] *
value;
413 out[2] = full[2] *
value;
415 normalize4_if_needed(out);
425static bool solve_strict_subgamut_from_XYZ(
const ProfileCache& cache,
429 const float sum_xyz = X_t[0] + X_t[1] + X_t[2];
430 if (sum_xyz < 1e-9f) {
433 float xy_t[2] = { X_t[0] / sum_xyz, X_t[1] / sum_xyz };
438 project_to_hull(cache, X_t, xy_t);
444 const float (*Pinv)[3];
445 int idx_a, idx_b, idx_c;
447 const SubGamut sgs[3] = {
448 { cache.profile->xy_r, cache.profile->xy_g, cache.xy_w,
449 cache.P_RGW_inv, 0, 1, 3 },
450 { cache.profile->xy_r, cache.profile->xy_b, cache.xy_w,
451 cache.P_RBW_inv, 0, 2, 3 },
452 { cache.profile->xy_b, cache.profile->xy_g, cache.xy_w,
453 cache.P_BGW_inv, 2, 1, 3 },
456 constexpr float kEps = 1e-4f;
457 for (
int k = 0; k < 3; ++k) {
458 const SubGamut& sg = sgs[k];
460 if (!
barycentric_xy(xy_t, sg.xy_a, sg.xy_b, sg.xy_c, bary))
continue;
461 if (bary[0] < -kEps || bary[1] < -kEps || bary[2] < -kEps)
continue;
464 if (
t[0] < -kEps ||
t[1] < -kEps ||
t[2] < -kEps)
continue;
468 const float m = max3f(
t[0],
t[1],
t[2]);
470 const float inv_m = 1.0f / m;
471 t[0] *= inv_m;
t[1] *= inv_m;
t[2] *= inv_m;
473 out_rgbw[sg.idx_a] =
t[0];
474 out_rgbw[sg.idx_b] =
t[1];
475 out_rgbw[sg.idx_c] =
t[2];
482 float s_g,
float s_b,
486 const float value = max3f(s_r, s_g, s_b);
487 if (
value <= 1e-9f) {
500 return native_single_identity(s_r, s_g, s_b, out_rgbw);
503 return solve_native_dual_edge_fixed_topology(cache, s_r, s_g,
513 const float c_r = s_r /
value;
514 const float c_g = s_g /
value;
515 const float c_b = s_b /
value;
518 source_rgb_to_XYZ(cache, c_r, c_g, c_b, X_full);
521 if (!solve_strict_subgamut_from_XYZ(cache, X_full, full)) {
525 out_rgbw[0] = full[0] *
value;
526 out_rgbw[1] = full[1] *
value;
527 out_rgbw[2] = full[2] *
value;
528 out_rgbw[3] = full[3] *
value;
529 normalize4_if_needed(out_rgbw);
534 const float xy_t[2],
float Y_t,
536 out_rgbw[0] = out_rgbw[1] = out_rgbw[2] = out_rgbw[3] = 0.0f;
547 float local_xy[2] = { xy_t[0], xy_t[1] };
548 project_to_hull(cache, X_t, local_xy);
554 const float (*Pinv)[3];
555 int idx_a, idx_b, idx_c;
558 const SubGamut sgs[3] = {
559 { cache.profile->xy_r, cache.profile->xy_g, cache.xy_w,
560 cache.P_RGW_inv, 0, 1, 3 },
561 { cache.profile->xy_r, cache.profile->xy_b, cache.xy_w,
562 cache.P_RBW_inv, 0, 2, 3 },
563 { cache.profile->xy_b, cache.profile->xy_g, cache.xy_w,
564 cache.P_BGW_inv, 2, 1, 3 },
567 constexpr float kEps = 1e-4f;
568 for (
int k = 0; k < 3; ++k) {
569 const SubGamut& sg = sgs[k];
571 if (!
barycentric_xy(local_xy, sg.xy_a, sg.xy_b, sg.xy_c, bary))
continue;
572 if (bary[0] < -kEps || bary[1] < -kEps || bary[2] < -kEps)
continue;
575 if (
t[0] < -kEps ||
t[1] < -kEps ||
t[2] < -kEps)
continue;
579 out_rgbw[sg.idx_a] =
t[0];
580 out_rgbw[sg.idx_b] =
t[1];
581 out_rgbw[sg.idx_c] =
t[2];
601static bool solve_wx_balanced_fraction_for_xy(
const ProfileCache& cache,
602 const float target_xy[2],
605 if (!(target_xy[0] == target_xy[0]) || !(target_xy[1] == target_xy[1])) {
609 const float* cols[4] = { cache.P_R, cache.P_G, cache.P_B, cache.P_W };
611 const float x = target_xy[0];
612 const float y = target_xy[1];
613 for (
int i = 0; i < 4; ++i) {
614 const float*
P = cols[i];
615 const float S =
P[0] +
P[1] +
P[2];
616 A[0][i] =
P[0] -
x * S;
617 A[1][i] =
P[1] -
y * S;
621 fl::max(cache.P_B[1], cache.P_W[1]));
622 const float y_weight[4] = {
623 (y_max > 1e-12f) ? cache.P_R[1] / y_max : 0.0f,
624 (y_max > 1e-12f) ? cache.P_G[1] / y_max : 0.0f,
625 (y_max > 1e-12f) ? cache.P_B[1] / y_max : 0.0f,
626 (y_max > 1e-12f) ? cache.P_W[1] / y_max : 0.0f,
630 float best_obj = -1e30f;
631 float best[4] = {0, 0, 0, 0};
632 const float floors[4] = { 1.0f / 1024.0f, 1.0f / 2048.0f,
633 1.0f / 4096.0f, 0.0f };
634 constexpr float kEps = 1e-5f;
636 for (
int fidx = 0; fidx < 4 && !found; ++fidx) {
637 const float lo = floors[fidx];
638 const float hi = 1.0f;
639 for (
int free0 = 0; free0 < 4; ++free0) {
640 for (
int free1 = free0 + 1; free1 < 4; ++free1) {
643 for (
int i = 0; i < 4; ++i) {
644 if (i != free0 && i != free1) fixed[nf++] = i;
646 for (
int b0 = 0; b0 < 2; ++b0) {
647 for (
int b1 = 0; b1 < 2; ++b1) {
648 float cand[4] = {0, 0, 0, 0};
649 cand[fixed[0]] = b0 ? hi : lo;
650 cand[fixed[1]] = b1 ? hi : lo;
652 const float rhs0 = -(A[0][fixed[0]] * cand[fixed[0]]
653 + A[0][fixed[1]] * cand[fixed[1]]);
654 const float rhs1 = -(A[1][fixed[0]] * cand[fixed[0]]
655 + A[1][fixed[1]] * cand[fixed[1]]);
656 const float a00 = A[0][free0];
657 const float a01 = A[0][free1];
658 const float a10 = A[1][free0];
659 const float a11 = A[1][free1];
660 const float det = a00 * a11 - a01 * a10;
664 cand[free0] = (rhs0 * a11 - a01 * rhs1) / det;
665 cand[free1] = (a00 * rhs1 - rhs0 * a10) / det;
668 for (
int i = 0; i < 4; ++i) {
669 if (cand[i] < lo - kEps || cand[i] > hi + kEps) {
677 const float residual0 = A[0][0]*cand[0] + A[0][1]*cand[1]
678 + A[0][2]*cand[2] + A[0][3]*cand[3];
679 const float residual1 = A[1][0]*cand[0] + A[1][1]*cand[1]
680 + A[1][2]*cand[2] + A[1][3]*cand[3];
685 const float obj = cand[3]
686 + 1e-6f * (y_weight[0]*cand[0]
687 + y_weight[1]*cand[1]
688 + y_weight[2]*cand[2]
689 + y_weight[3]*cand[3]);
690 if (!found || obj > best_obj) {
693 best[0] = cand[0]; best[1] = cand[1];
694 best[2] = cand[2]; best[3] = cand[3];
710 const float inv_m = 1.0f / m;
711 out[0] =
fl::clamp(best[0] * inv_m, 0.0f, 1.0f);
712 out[1] =
fl::clamp(best[1] * inv_m, 0.0f, 1.0f);
713 out[2] =
fl::clamp(best[2] * inv_m, 0.0f, 1.0f);
714 out[3] =
fl::clamp(best[3] * inv_m, 0.0f, 1.0f);
725 float s_g,
float s_b,
729 const float value = max3f(s_r, s_g, s_b);
730 if (
value <= 1e-9f) {
740 return native_single_identity(s_r, s_g, s_b, out_rgbw);
743 return solve_native_dual_edge_fixed_topology(cache, s_r, s_g,
754 const float c_r = s_r /
value;
755 const float c_g = s_g /
value;
756 const float c_b = s_b /
value;
759 source_rgb_to_XYZ(cache, c_r, c_g, c_b, X_t);
762 project_to_hull(cache, X_t, xy_t);
765 if (!solve_wx_balanced_fraction_for_xy(cache, xy_t, full)) {
769 if (!solve_strict_subgamut_from_XYZ(cache, X_t, full)) {
772 normalize4_if_needed(full);
775 out_rgbw[0] = full[0] *
value;
776 out_rgbw[1] = full[1] *
value;
777 out_rgbw[2] = full[2] *
value;
778 out_rgbw[3] = full[3] *
value;
779 normalize4_if_needed(out_rgbw);
790 float s_b,
float overdrive_ratio,
794 const float value = max3f(s_r, s_g, s_b);
795 if (
value <= 1e-9f) {
805 native_single_identity(s_r, s_g, s_b, out_rgbw);
809 solve_native_dual_edge_fixed_topology(cache, s_r, s_g,
818 const float c_r = s_r /
value;
819 const float c_g = s_g /
value;
820 const float c_b = s_b /
value;
823 source_rgb_to_XYZ(cache, c_r, c_g, c_b, X_t);
826 project_to_hull(cache, X_t, xy_t);
829 matvec3(cache.P_RGB_inv, X_t, a);
830 const float* d = cache.d_W;
832 float w_strict = 1.0f;
833 for (
int i = 0; i < 3; ++i) {
834 if (d[i] > 1e-9f && a[i] >= 0.0f) {
835 const float lim = a[i] / d[i];
836 if (lim < w_strict) {
841 w_strict =
fl::clamp(w_strict, 0.0f, 1.0f);
843 const float rho =
fl::clamp(overdrive_ratio, 0.0f, 1.0f);
844 float w = w_strict + rho * (1.0f - w_strict);
848 full[0] =
fl::max(a[0] - w * d[0], 0.0f);
849 full[1] =
fl::max(a[1] - w * d[1], 0.0f);
850 full[2] =
fl::max(a[2] - w * d[2], 0.0f);
852 normalize4_if_needed(full);
854 out_rgbw[0] = full[0] *
value;
855 out_rgbw[1] = full[1] *
value;
856 out_rgbw[2] = full[2] *
value;
857 out_rgbw[3] = full[3] *
value;
858 normalize4_if_needed(out_rgbw);
877 const float* R = cache.profile->xy_r;
878 const float* G = cache.profile->xy_g;
879 const float*
B = cache.profile->xy_b;
884 const float xpad = (xmax - xmin) * 0.02f;
885 const float ypad = (ymax - ymin) * 0.02f;
886 lut.xy_min[0] = xmin - xpad;
887 lut.xy_min[1] = ymin - ypad;
888 lut.xy_max[0] = xmax + xpad;
889 lut.xy_max[1] = ymax + ypad;
891 i16* cells = lut.cells.get();
892 const int N = grid_n;
893 const float inv_Nm1 = 1.0f /
static_cast<float>(N - 1);
894 const float cell_dx = (lut.xy_max[0] - lut.xy_min[0]) * inv_Nm1;
895 const float cell_dy = (lut.xy_max[1] - lut.xy_min[1]) * inv_Nm1;
898 const float xy[2] = {
x,
y};
902 for (
int j = 0; j < N; ++j) {
903 const float y = lut.xy_min[1] + cell_dy * j;
904 for (
int i = 0; i < N; ++i) {
905 const float x = lut.xy_min[0] + cell_dx * i;
908 i16* cell = &cells[(j * N + i) * stride];
921 const float eps_x = cell_dx * 0.5f;
922 const float eps_y = cell_dy * 0.5f;
923 const float x_hi =
fl::clamp(
x + eps_x, lut.xy_min[0], lut.xy_max[0]);
924 const float x_lo =
fl::clamp(
x - eps_x, lut.xy_min[0], lut.xy_max[0]);
925 const float y_hi =
fl::clamp(
y + eps_y, lut.xy_min[1], lut.xy_max[1]);
926 const float y_lo =
fl::clamp(
y - eps_y, lut.xy_min[1], lut.xy_max[1]);
928 float v_xp[4] = {0}, v_xn[4] = {0}, v_yp[4] = {0}, v_yn[4] = {0};
934 const float step_x = x_hi - x_lo;
935 const float step_y = y_hi - y_lo;
936 const float scale_x = (step_x > 1e-9f) ? (cell_dx / step_x) : 0.0f;
937 const float scale_y = (step_y > 1e-9f) ? (cell_dy / step_y) : 0.0f;
938 for (
int k = 0; k < 4; ++k) {
952 out_rgbw[0] = out_rgbw[1] = out_rgbw[2] = out_rgbw[3] = 0.0f;
953 if (lut.N < 2 || lut.cells.get() ==
nullptr) {
957 const float dx = lut.xy_max[0] - lut.xy_min[0];
958 const float dy = lut.xy_max[1] - lut.xy_min[1];
959 if (dx <= 0.0f || dy <= 0.0f) {
962 float x_norm = (xy_t[0] - lut.xy_min[0]) / dx * (N - 1);
963 float y_norm = (xy_t[1] - lut.xy_min[1]) / dy * (N - 1);
964 x_norm =
fl::clamp(x_norm, 0.0f,
static_cast<float>(N - 1) - 1e-4f);
965 y_norm =
fl::clamp(y_norm, 0.0f,
static_cast<float>(N - 1) - 1e-4f);
966 const int i =
static_cast<int>(x_norm);
967 const int j =
static_cast<int>(y_norm);
968 const float fx = x_norm - i;
969 const float fy = y_norm - j;
971 const i16* base = lut.cells.get();
974 const i16* c00 = &base[(j * N + i) * stride];
975 const i16* c10 = &base[(j * N + i + 1) * stride];
976 const i16* c01 = &base[((j + 1) * N + i) * stride];
977 const i16* c11 = &base[((j + 1) * N + i + 1) * stride];
979 const float inv_Q = 1.0f /
static_cast<float>(
kLutQ);
988 const float h00x = bx[0], h01x = bx[1], h10x = bx[2], h11x = bx[3];
989 const float h00y = by[0], h01y = by[1], h10y = by[2], h11y = by[3];
991 for (
int k = 0; k < 4; ++k) {
992 const float v00 = c00[k] * inv_Q;
993 const float v10 = c10[k] * inv_Q;
994 const float v01 = c01[k] * inv_Q;
995 const float v11 = c11[k] * inv_Q;
996 const float dx00 = c00[4 + k] * inv_Q;
997 const float dx10 = c10[4 + k] * inv_Q;
998 const float dx01 = c01[4 + k] * inv_Q;
999 const float dx11 = c11[4 + k] * inv_Q;
1000 const float dy00 = c00[8 + k] * inv_Q;
1001 const float dy10 = c10[8 + k] * inv_Q;
1002 const float dy01 = c01[8 + k] * inv_Q;
1003 const float dy11 = c11[8 + k] * inv_Q;
1006 v00 * h00x * h00y + v10 * h01x * h00y
1007 + v01 * h00x * h01y + v11 * h01x * h01y
1008 + dx00 * h10x * h00y + dx10 * h11x * h00y
1009 + dx01 * h10x * h01y + dx11 * h11x * h01y
1010 + dy00 * h00x * h10y + dy10 * h01x * h10y
1011 + dy01 * h00x * h11y + dy11 * h01x * h11y;
1013 float t = per_Y * Y_t;
1014 if (
t < 0.0f)
t = 0.0f;
1018 const float w00 = (1 - fx) * (1 - fy);
1019 const float w10 = fx * (1 - fy);
1020 const float w01 = (1 - fx) * fy;
1021 const float w11 = fx * fy;
1022 for (
int k = 0; k < 4; ++k) {
1023 const float per_Y = (w00 * c00[k] + w10 * c10[k]
1024 + w01 * c01[k] + w11 * c11[k]) * inv_Q;
1025 float t = per_Y * Y_t;
1026 if (
t < 0.0f)
t = 0.0f;
1032 fl::max(out_rgbw[2], out_rgbw[3]));
1034 const float inv_m = 1.0f / m;
1035 out_rgbw[0] *= inv_m;
1036 out_rgbw[1] *= inv_m;
1037 out_rgbw[2] *= inv_m;
1038 out_rgbw[3] *= inv_m;
1049 float warm_rgbw[4] = {0};
1050 float cool_rgbw[4] = {0};
1055 const float w_warm = 1.0f - eta;
1056 const float w_cool = eta;
1058 out[0] = w_warm * warm_rgbw[0] + w_cool * cool_rgbw[0];
1059 out[1] = w_warm * warm_rgbw[1] + w_cool * cool_rgbw[1];
1060 out[2] = w_warm * warm_rgbw[2] + w_cool * cool_rgbw[2];
1061 out[3] = w_warm * warm_rgbw[3];
1062 out[4] = w_cool * cool_rgbw[3];
1067 const float inv_m = 1.0f / m;
1068 for (
int k = 0; k < 5; ++k) out[k] *= inv_m;
uint32_t scale_y[NUM_LAYERS]
uint32_t scale_x[NUM_LAYERS]
unsigned int xy(unsigned int x, unsigned int y)
Centralized logging categories for FastLED hardware interfaces and subsystems.
bool solve_wx_lp_legacy(const ProfileCache &cache, float s_r, float s_g, float s_b, float out_rgbw[4]) FL_NOEXCEPT
constexpr int kLutStrideHermite
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
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
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
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
constexpr int kLutStrideBilinear
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
FL_DISABLE_WARNING_PUSH U constexpr common_type_t< T, U > min(T a, U b) FL_NOEXCEPT
double fabs(double value) FL_NOEXCEPT
constexpr int type_rank< T >::value
constexpr common_type_t< T, U > max(T a, U b) FL_NOEXCEPT
FL_DISABLE_WARNING_PUSH unsigned char * B
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
CRGB sample(const CRGB *grid, const XYMap &xyMap, float x, float y, SampleMode mode)
Sample a pixel from a 2D CRGB grid at floating-point coordinates.
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.
Chromaticity-aware RGBW solvers — strict sub-gamut + wx_lp_legacy white extraction + boosted overdriv...