FastLED 3.10.6
Loading...
Searching...
No Matches

◆ buildGamutMapRgbwwFromSolveQ16()

bool fl::buildGamutMapRgbwwFromSolveQ16 ( const colorimetric_response::EmitterProfile & profile,
const EmitterSolveMatrixQ16 & solve,
const i32(&) white1_xyz[3],
const i32(&) white2_xyz[3],
WhiteAllocationPolicy policy,
GamutMapRgbwwQ16 * out )

Bind a five-emitter map from one effective RGB solve and two white columns, all in the same D65 working coordinates.

Definition at line 625 of file gamut_map.cpp.hpp.

629 {
630 if (out == nullptr) {
631 return false;
632 }
633 if (!buildTwoWhiteAllocationFromSolveQ16(solve, white1_xyz, white2_xyz,
634 policy, &out->allocation)) {
635 return false;
636 }
637
638 i32 neutral_drives[3];
639 solveRgbDrivesQ16(out->allocation.rgb_solve, kGamutD65Q16, neutral_drives);
640 for (int i = 0; i < 3; ++i) {
641 // As in the other two builds: every drive, not just the largest.
642 if (neutral_drives[i] <= 0) {
643 return false;
644 }
645 }
646
647 // The three-emitter bound, reached with both whites off, and always
648 // attainable.
649 i32 largest_neutral_drive = neutral_drives[0];
650 for (int i = 1; i < 3; ++i) {
651 if (neutral_drives[i] > largest_neutral_drive) {
652 largest_neutral_drive = neutral_drives[i];
653 }
654 }
655 i64 reachable = (static_cast<i64>(kGamutFullDrive) << 16) /
656 static_cast<i64>(largest_neutral_drive);
657
658 // Clamped for the same reason the three-emitter build clamps it, and to
659 // the same place: `buildRgbSolveMatrixQ16` accepts emitter luminances up
660 // to 1e6, so a profile bright enough to reach D65 on one or two raw
661 // units of drive puts `2^32 / largest` at or past i32's range -- exactly
662 // 2^31 at largest == 2. `optimistic` starts at `kOklabQ16MaxMagnitude`,
663 // so for such a profile the bisection below is skipped and the narrowing
664 // is reached directly. 64.0 is where the OKLab transform's domain ends,
665 // so nothing downstream can tell the difference.
666 if (reachable > kOklabQ16MaxMagnitude) {
667 reachable = kOklabQ16MaxMagnitude;
668 }
669
670 // The one-white relaxation with a second white added. Along the D65 ray
671 // the RGB drives are `s*d0 - w1*dW1 - w2*dW2`, so the upper limit on
672 // drive i is loosest with each white at full when its column is positive
673 // and off when it is not -- hence a `max(.., 0)` term per white.
674 // Dropping the lower limits, and letting each channel choose its own
675 // whites, are both relaxations, so this is an upper bound.
676 //
677 // `per_white1` is not stored: the allocation keeps the second column and
678 // the difference, because that is all the per-pixel path needs. It is
679 // recovered here rather than widening the struct for a bind-time sum.
680 i64 optimistic = static_cast<i64>(kOklabQ16MaxMagnitude);
681 for (int i = 0; i < 3; ++i) {
682 const i32 per_white2 = out->allocation.per_white2[i];
683 const i32 per_white1 = out->allocation.difference[i] + per_white2;
684 i64 numerator = static_cast<i64>(kGamutFullDrive);
685 if (per_white1 > 0) {
686 numerator += static_cast<i64>(per_white1);
687 }
688 if (per_white2 > 0) {
689 numerator += static_cast<i64>(per_white2);
690 }
691 const i64 candidate =
692 (numerator << 16) / static_cast<i64>(neutral_drives[i]);
693 if (candidate < optimistic) {
694 optimistic = candidate;
695 }
696 }
697
698 // Bisected against the attainable bound rather than trusted, for exactly
699 // the reason spelled out on the one-white path: the relaxation enforces
700 // only the upper limits, and full white can push a different channel
701 // negative. Once per profile, never per pixel (A3/B11).
702 if (optimistic > reachable) {
703 i64 low = reachable;
704 i64 high = optimistic;
705 for (int step = 0; step < 24; ++step) {
706 const i64 middle = (low + high) / 2;
707 const i32 trial_scale = static_cast<i32>(middle);
708 const i32 trial[3] = {
709 scaleGamutQ16(kGamutD65Q16[0], trial_scale),
710 scaleGamutQ16(kGamutD65Q16[1], trial_scale),
711 scaleGamutQ16(kGamutD65Q16[2], trial_scale),
712 };
713 i32 trial_drives[5];
714 if (allocateTwoWhiteDrivesQ16(out->allocation, trial, trial_drives)) {
715 low = middle;
716 } else {
717 high = middle;
718 }
719 }
720 reachable = low;
721 }
722
723 const i32 scale = static_cast<i32>(reachable);
724 const i32 brightest_neutral[3] = {
725 scaleGamutQ16(kGamutD65Q16[0], scale),
726 scaleGamutQ16(kGamutD65Q16[1], scale),
727 scaleGamutQ16(kGamutD65Q16[2], scale),
728 };
729 i32 lab[3];
730 xyzToOklabQ16(brightest_neutral, lab);
731 out->max_neutral_lightness = lab[0];
732 return true;
733}
fl::UISlider scale("Scale", 4,.1, 4,.1)
i32 scaleGamutQ16(i32 value, i32 factor) FL_NO_EXCEPT
Scale an s16.16 value by an s16.16 factor, rounding to nearest.
constexpr i32 kGamutFullDrive
Full drive, as an s16.16 raw value.
bool buildTwoWhiteAllocationFromSolveQ16(const EmitterSolveMatrixQ16 &solve, const i32(&white1_xyz)[3], const i32(&white2_xyz)[3], WhiteAllocationPolicy policy, TwoWhiteAllocationQ16 *out) FL_NO_EXCEPT
Bind two adapted white columns with an RGB solve in the same XYZ domain.
bool allocateTwoWhiteDrivesQ16(const TwoWhiteAllocationQ16 &allocation, const i32(&xyz)[3], i32(&drives)[5]) FL_NO_EXCEPT
One pixel: XYZ in s16.16 to five drives – red, green, blue, white1, white2 – at whichever end of the ...
constexpr i32 kOklabQ16MaxMagnitude
Largest magnitude either direction accepts, as an s16.16 raw value (64.0).
Definition oklab_q16.h:40
void xyzToOklabQ16(const i32(&xyz)[3], i32(&out_lab)[3]) FL_NO_EXCEPT
XYZ (s16.16, D65-relative) -> OKLab (s16.16), L then a then b.
constexpr enable_if< is_fixed_point< T >::value, T >::type step(T edge, T x) FL_NO_EXCEPT
void solveRgbDrivesQ16(const EmitterSolveMatrixQ16 &matrix, const i32(&xyz)[3], i32(&drives)[3]) FL_NO_EXCEPT
One pixel: XYZ in s16.16 to three emitter drives in s16.16.
i32 per_white2[3]
M^-1 . w2: the RGB drives one unit of the second white replaces.
TwoWhiteAllocationQ16 allocation
Definition gamut_map.h:123
i32 max_neutral_lightness
OKLab lightness of the brightest D65 neutral this device can reach.
Definition gamut_map.h:132
EmitterSolveMatrixQ16 rgb_solve
Inverse RGB emitter matrix.
fl::i64 i64
Definition stdint.h:221

References allocateTwoWhiteDrivesQ16(), buildTwoWhiteAllocationFromSolveQ16(), FL_NO_EXCEPT, kOklabQ16MaxMagnitude, scale, solveRgbDrivesQ16(), step(), and xyzToOklabQ16().

Referenced by buildGamutMapRgbwwQ16(), and fl::anonymous_namespace{pipeline.cpp.hpp}::buildWidePipelineQ16().

+ Here is the call graph for this function:
+ Here is the caller graph for this function: