| 479 | } |
| 480 | |
| 481 | bool solve_strict_subgamut(const ProfileCache& cache, float s_r, |
| 482 | float s_g, float s_b, |
| 483 | float out_rgbw[4]) FL_NOEXCEPT { |
| 484 | zero4(out_rgbw); |
| 485 | |
| 486 | const float value = max3f(s_r, s_g, s_b); |
| 487 | if (value <= 1e-9f) { |
| 488 | return true; |
| 489 | } |
| 490 | |
| 491 | // Native topology authority is split by case: |
| 492 | // n == 1: exact single-axis identity is correct. |
| 493 | // n == 2: keep the active RG/RB/GB edge locked, but still solve that |
| 494 | // two-emitter topology colorimetrically from measured XYZ/Y. |
| 495 | // The previous n<=2 passthrough fixed W/third-channel bleed, but regressed |
| 496 | // dual edges into raw 1:1 input->output tuples. |
| 497 | if (is_native_input_gamut(*cache.profile)) { |
| 498 | const int n_active = count_active_channels(s_r, s_g, s_b); |
| 499 | if (n_active == 1) { |
| 500 | return native_single_identity(s_r, s_g, s_b, out_rgbw); |
| 501 | } |
| 502 | if (n_active == 2) { |
| 503 | return solve_native_dual_edge_fixed_topology(cache, s_r, s_g, |
| 504 | s_b, out_rgbw); |
| 505 | } |
| 506 | } |
| 507 | |
| 508 | // Interior colors must preserve value separately from chroma. Solving the |
| 509 | // already value-scaled node and then normalizing is what collapsed several |
| 510 | // HSV v055/v075/v100 rows to identical saturated tuples. Instead, solve |
| 511 | // the full-chroma endpoint once, normalize/project there, then apply the |
| 512 | // original value scale. |
| 513 | const float c_r = s_r / value; |
| 514 | const float c_g = s_g / value; |
| 515 | const float c_b = s_b / value; |
| 516 | |
| 517 | float X_full[3]; |
| 518 | source_rgb_to_XYZ(cache, c_r, c_g, c_b, X_full); |
| 519 | |
| 520 | float full[4]; |
| 521 | if (!solve_strict_subgamut_from_XYZ(cache, X_full, full)) { |
| 522 | return false; |
| 523 | } |
| 524 | |
| 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); |
| 530 | return true; |
| 531 | } |
| 532 | |
| 533 | bool solve_strict_subgamut_xy(const ProfileCache& cache, |
| 534 | const float xy_t[2], float Y_t, |
no test coverage detected