Verbatim copy of solve_strict_subgamut (post-#2708 — includes hull projection, post-#2748 — includes native topology authority guard).
| 668 | // Verbatim copy of solve_strict_subgamut (post-#2708 — includes hull projection, |
| 669 | // post-#2748 — includes native topology authority guard). |
| 670 | inline bool strict_subgamut(const ProfileCache& cache, float s_r, float s_g, float s_b, float out[4]) { |
| 671 | out[0] = out[1] = out[2] = out[3] = 0.0f; |
| 672 | |
| 673 | // #2748 native topology authority guard. Mirrors the production check. |
| 674 | if (is_native_input_gamut(*cache.profile)) { |
| 675 | const int n_active = count_active_channels(s_r, s_g, s_b); |
| 676 | if (n_active <= 2) { |
| 677 | out[0] = fl::clamp(s_r, 0.0f, 1.0f); |
| 678 | out[1] = fl::clamp(s_g, 0.0f, 1.0f); |
| 679 | out[2] = fl::clamp(s_b, 0.0f, 1.0f); |
| 680 | out[3] = 0.0f; |
| 681 | return true; |
| 682 | } |
| 683 | } |
| 684 | |
| 685 | float X_t[3]; |
| 686 | if (cache.has_source_space) { |
| 687 | const float s[3] = { s_r, s_g, s_b }; |
| 688 | matvec3(cache.M_src, s, X_t); |
| 689 | } else { |
| 690 | X_t[0] = cache.P_R[0]*s_r + cache.P_G[0]*s_g + cache.P_B[0]*s_b; |
| 691 | X_t[1] = cache.P_R[1]*s_r + cache.P_G[1]*s_g + cache.P_B[1]*s_b; |
| 692 | X_t[2] = cache.P_R[2]*s_r + cache.P_G[2]*s_g + cache.P_B[2]*s_b; |
| 693 | } |
| 694 | const float sum = X_t[0]+X_t[1]+X_t[2]; |
| 695 | if (sum < 1e-9f) return true; |
| 696 | float xy_t[2] = { X_t[0]/sum, X_t[1]/sum }; |
| 697 | project_to_hull_mirror(cache, X_t, xy_t); |
| 698 | struct SG { const float* a; const float* b; const float* c; |
| 699 | const float (*Pinv)[3]; int ia,ib,ic; }; |
| 700 | const SG sgs[3] = { |
| 701 | { cache.profile->xy_r, cache.profile->xy_g, cache.xy_w, cache.P_RGW_inv, 0,1,3 }, |
| 702 | { cache.profile->xy_r, cache.profile->xy_b, cache.xy_w, cache.P_RBW_inv, 0,2,3 }, |
| 703 | { cache.profile->xy_b, cache.profile->xy_g, cache.xy_w, cache.P_BGW_inv, 2,1,3 }, |
| 704 | }; |
| 705 | const float kEps = 1e-4f; |
| 706 | for (int k = 0; k < 3; ++k) { |
| 707 | const SG& sg = sgs[k]; |
| 708 | float bary[3]; |
| 709 | if (!barycentric_xy(xy_t, sg.a, sg.b, sg.c, bary)) continue; |
| 710 | if (bary[0] < -kEps || bary[1] < -kEps || bary[2] < -kEps) continue; |
| 711 | float t[3]; matvec3(sg.Pinv, X_t, t); |
| 712 | if (t[0] < -kEps || t[1] < -kEps || t[2] < -kEps) continue; |
| 713 | if (t[0] < 0) t[0] = 0; if (t[1] < 0) t[1] = 0; if (t[2] < 0) t[2] = 0; |
| 714 | float m = t[0]; if (t[1] > m) m = t[1]; if (t[2] > m) m = t[2]; |
| 715 | if (m > 1.0f) { float inv = 1.0f/m; t[0]*=inv; t[1]*=inv; t[2]*=inv; } |
| 716 | out[sg.ia] = t[0]; out[sg.ib] = t[1]; out[sg.ic] = t[2]; |
| 717 | return true; |
| 718 | } |
| 719 | return false; |
| 720 | } |
| 721 | |
| 722 | } // namespace fastled_mirror |
| 723 |
no test coverage detected