MCPcopy Create free account
hub / github.com/FastLED/FastLED / project_to_hull

Function project_to_hull

tests/fl/gfx/rgbw_colorimetric.cpp:766–816  ·  view source on GitHub ↗

Port of `_strict_project_target_xyz_to_led_hull`. When target_xy is outside the LED RGB triangle, projects to the nearest in-hull point by trying NNLS across all three sub-gamuts and picking the one with smallest residual. Returns true if a projection was applied. Updates X_t and xy_t in place.

Source from the content-addressed store, hash-verified

764// across all three sub-gamuts and picking the one with smallest residual.
765// Returns true if a projection was applied. Updates X_t and xy_t in place.
766inline bool project_to_hull(const DiodeProfile& p, float X_t[3], float xy_t[2]) {
767 const float sum = X_t[0] + X_t[1] + X_t[2];
768 if (sum < 1e-12f) return false;
769 xy_t[0] = X_t[0] / sum; xy_t[1] = X_t[1] / sum;
770 // Test full RGB triangle containment.
771 float bary[3];
772 if (barycentric_xy(xy_t, p.xy_r, p.xy_g, p.xy_b, bary) &&
773 bary[0] >= -1e-9f && bary[1] >= -1e-9f && bary[2] >= -1e-9f) {
774 return false; // already in hull
775 }
776 // Out-of-hull: NNLS across each sub-gamut, pick min residual.
777 float P_R[3], P_G[3], P_B[3], P_W[3];
778 xyY_to_XYZ(p.xy_r[0], p.xy_r[1], p.lum_r, P_R);
779 xyY_to_XYZ(p.xy_g[0], p.xy_g[1], p.lum_g, P_G);
780 xyY_to_XYZ(p.xy_b[0], p.xy_b[1], p.lum_b, P_B);
781 xyY_to_XYZ(p.xy_w[0], p.xy_w[1], p.lum_w, P_W);
782 struct Tri { const float* a; const float* b; const float* c; };
783 const Tri tris[3] = {
784 {P_R, P_G, P_W}, {P_R, P_B, P_W}, {P_B, P_G, P_W},
785 };
786 float best_xyz[3] = {0,0,0};
787 float best_residual = 1e30f;
788 for (const Tri& tri : tris) {
789 float M[3][3] = {
790 {tri.a[0], tri.b[0], tri.c[0]},
791 {tri.a[1], tri.b[1], tri.c[1]},
792 {tri.a[2], tri.b[2], tri.c[2]}
793 };
794 float t[3], res;
795 nnls3(M, X_t, t, res);
796 // Cap drive to 1.0 per the reference (`if max_t > 1.0: t /= max_t`).
797 float mt = t[0]; if (t[1] > mt) mt = t[1]; if (t[2] > mt) mt = t[2];
798 if (mt > 1.0f) { float inv = 1.0f/mt; t[0]*=inv; t[1]*=inv; t[2]*=inv; }
799 float xyz[3];
800 xyz[0] = M[0][0]*t[0] + M[0][1]*t[1] + M[0][2]*t[2];
801 xyz[1] = M[1][0]*t[0] + M[1][1]*t[1] + M[1][2]*t[2];
802 xyz[2] = M[2][0]*t[0] + M[2][1]*t[1] + M[2][2]*t[2];
803 if (xyz[1] <= 1e-12f) continue;
804 if (res < best_residual) {
805 best_residual = res;
806 best_xyz[0] = xyz[0]; best_xyz[1] = xyz[1]; best_xyz[2] = xyz[2];
807 }
808 }
809 if (best_xyz[1] <= 1e-12f) return false;
810 // Per reference: return achievable xy at unit Y. The full-chroma topology
811 // is re-solved by the caller.
812 const float s2 = best_xyz[0] + best_xyz[1] + best_xyz[2];
813 xy_t[0] = best_xyz[0] / s2; xy_t[1] = best_xyz[1] / s2;
814 xyY_to_XYZ(xy_t[0], xy_t[1], 1.0f, X_t);
815 return true;
816}
817
818// Port of the reference's strict_subgamut: project, then triangle-route.
819inline bool strict_with_projection(const DiodeProfile& p, ProfileCache& cache,

Callers 1

strict_with_projectionFunction · 0.70

Calls 3

barycentric_xyFunction · 0.85
xyY_to_XYZFunction · 0.85
nnls3Function · 0.70

Tested by

no test coverage detected