77 const i64 scaled =
static_cast<i64>(numerator) * 65536;
78 return scaled /
static_cast<i64>(denominator);
83 const i64 product =
static_cast<i64>(
value) *
static_cast<i64>(factor);
84 return static_cast<i32
>((product + 32768) >> 16);
97 out->rgb_solve = solve;
104 const float luminance[3] = {profile.
lum_r, profile.
lum_g, profile.
lum_b};
105 for (
int channel = 0; channel < 3; ++channel) {
120 for (
int axis = 0; axis < 3; ++axis) {
121 const i64 magnitude = column[axis] < 0 ? -column[axis]
123 if (magnitude > peak) {
129 i64 scaled = (
static_cast<i64>(kWhiteSlackAtUnitColumn) * 65536 +
149 if (scaled > kWhiteSlackAtUnitColumn) {
150 scaled = kWhiteSlackAtUnitColumn;
152 out->slack[channel] =
static_cast<i32
>(scaled);
159 bool expressible =
false;
160 for (
int i = 0; i < 3; ++i) {
161 if (out->per_white[i] != 0) {
169 const i32 (&white_xyz)[3],
195 i64 high = kWhiteFullDrive;
196 for (
int i = 0; i < 3; ++i) {
197 const i32 slope = allocation.
per_white[i];
200 const i64 first = divideWhiteQ16(at_zero[i], slope);
201 const i64 second = divideWhiteQ16(at_zero[i] - kWhiteFullDrive, slope);
202 const i64 upper = slope > 0 ? first : second;
203 const i64 lower = slope > 0 ? second : first;
210 }
else if (at_zero[i] < -allocation.
slack[i] ||
211 at_zero[i] > kWhiteFullDrive + allocation.
slack[i]) {
229 const i32 level =
static_cast<i32
>(
232 for (
int i = 0; i < 3; ++i) {
233 const i32 drive = at_zero[i] - scaleWhiteQ16(allocation.
per_white[i], level);
234 const i32 channel_slack = allocation.
slack[i];
235 if (drive < -channel_slack || drive > kWhiteFullDrive + channel_slack) {
238 rgb[i] = drive < 0 ? 0 : (drive > kWhiteFullDrive ? kWhiteFullDrive : drive);
243 drives[3] = level < 0 ? 0
244 : (level > kWhiteFullDrive ? kWhiteFullDrive : level);
289 static_cast<i64>(bound.numerator) * 65536 +
static_cast<i64>(bound.slope) * total;
290 return scaled /
static_cast<i64>(bound.denominator);
307 if (denominator < 0) {
309 bound.
slope = -slope;
334 const i64 slope =
static_cast<i64>(upper.denominator) * lower.slope -
335 static_cast<i64>(lower.denominator) * upper.slope;
336 const i64 constant =
static_cast<i64>(lower.denominator) * upper.numerator -
337 static_cast<i64>(upper.denominator) * lower.numerator;
341 return constant >= 0;
343 const i64 bound = (constant * 65536) / slope;
348 }
else if (bound > *low) {
360 if (out ==
nullptr) {
363 out->policy = policy;
364 out->rgb_solve = solve;
369 for (
int i = 0; i < 3; ++i) {
373 if (per_white1[i] > kTwoWhiteMaxColumn || per_white1[i] < -kTwoWhiteMaxColumn ||
374 out->per_white2[i] > kTwoWhiteMaxColumn ||
375 out->per_white2[i] < -kTwoWhiteMaxColumn) {
378 out->difference[i] = per_white1[i] - out->per_white2[i];
384 const i32 (&white1_xyz)[3],
385 const i32 (&white2_xyz)[3],
399 for (
int i = 0; i < 3; ++i) {
403 if (at_zero[i] > kTwoWhiteMaxColumn || at_zero[i] < -kTwoWhiteMaxColumn) {
413 SplitBound lowers[5];
414 SplitBound uppers[5];
417 lowers[lower_count++] = normalizedSplitBound(0, 0, kWhiteFullDrive);
418 lowers[lower_count++] =
419 normalizedSplitBound(-kWhiteFullDrive, kWhiteFullDrive, kWhiteFullDrive);
420 uppers[upper_count++] = normalizedSplitBound(kWhiteFullDrive, 0, kWhiteFullDrive);
421 uppers[upper_count++] = normalizedSplitBound(0, kWhiteFullDrive, kWhiteFullDrive);
424 i64 high_total = 2 *
static_cast<i64>(kWhiteFullDrive);
426 for (
int i = 0; i < 3; ++i) {
435 if (at_zero[i] < -kWhiteSlack ||
436 at_zero[i] > kWhiteFullDrive + kWhiteSlack) {
441 const i64 first = divideWhiteQ16(at_zero[i], per);
443 divideWhiteQ16(at_zero[i] - kWhiteFullDrive - kTwoWhiteBoundSlack, per);
444 const i64 upper = per > 0 ? first : second;
445 const i64 lower = per > 0 ? second : first;
446 if (upper < high_total) {
449 if (lower > low_total) {
468 const SplitBound at_full =
469 normalizedSplitBound(at_zero[i] - kWhiteFullDrive - kTwoWhiteBoundSlack,
471 const SplitBound at_dark =
472 normalizedSplitBound(at_zero[i], -allocation.
per_white2[i], slope);
473 if (!splitBoundInRange(at_full) || !splitBoundInRange(at_dark)) {
477 uppers[upper_count++] = at_dark;
478 lowers[lower_count++] = at_full;
480 uppers[upper_count++] = at_full;
481 lowers[lower_count++] = at_dark;
488 for (
int k = 0; k < lower_count; ++k) {
489 for (
int j = 0; j < upper_count; ++j) {
490 if (!narrowTotalForPair(lowers[k], uppers[j], &low_total, &high_total)) {
495 if (high_total < 0) {
498 if (low_total > high_total) {
510 i64 split_high = kWhiteFullDrive;
511 for (
int k = 0; k < lower_count; ++k) {
512 const i64 value = splitBoundAt(lowers[k], total);
513 if (
value > split_low) {
517 for (
int j = 0; j < upper_count; ++j) {
518 const i64 value = splitBoundAt(uppers[j], total);
519 if (
value < split_high) {
523 if (split_low > split_high) {
531 if (split_low - split_high > kWhiteSlack) {
534 const i64 middle = (split_low + split_high) / 2;
540 for (
int i = 0; i < 3; ++i) {
541 at_total[i] =
static_cast<i64>(at_zero[i]) -
542 ((
static_cast<i64>(allocation.
per_white2[i]) * total + 32768) >> 16);
564 const i64 split = split_low;
567 for (
int i = 0; i < 3; ++i) {
570 ((
static_cast<i64>(allocation.
difference[i]) * split + 32768) >> 16);
571 if (drive < -kWhiteSlack || drive > kWhiteFullDrive + kWhiteSlack) {
574 rgb[i] = drive < 0 ? 0
575 : (drive > kWhiteFullDrive ? kWhiteFullDrive
576 :
static_cast<i32
>(drive));
578 const i64 second = total - split;
579 if (second < -kWhiteSlack || second > kWhiteFullDrive + kWhiteSlack ||
580 split < -kWhiteSlack || split > kWhiteFullDrive + kWhiteSlack) {
586 drives[3] = split < 0 ? 0
587 : (split > kWhiteFullDrive ? kWhiteFullDrive
588 :
static_cast<i32
>(split));
589 drives[4] = second < 0 ? 0
590 : (second > kWhiteFullDrive ? kWhiteFullDrive
591 :
static_cast<i32
>(second));
unsigned int xy(unsigned int x, unsigned int y)
i32 scaleWhiteQ16(i32 value, i32 factor) FL_NO_EXCEPT
Scale an s16.16 value by an s16.16 factor, rounding to nearest.
i64 splitBoundAt(const SplitBound &bound, i64 total) FL_NO_EXCEPT
value at total s, in s16.16.
constexpr i32 kWhiteSlackAtUnitColumn
Slack on the drive bounds, for an emitter whose XYZ column peaks at 1.
SplitBound normalizedSplitBound(i32 numerator, i32 slope, i32 denominator) FL_NO_EXCEPT
Fold a negative denominator into the numerator and slope, so every bound compares the same way round.
bool splitBoundInRange(const SplitBound &bound) FL_NO_EXCEPT
Whether every part of a bound stays inside the range the accumulators were sized for.
constexpr i32 kTwoWhiteMaxColumn
The largest magnitude a per-white column or a solved drive may reach before the pairwise bounds stop ...
constexpr i32 kWhiteFullDrive
Full drive, as an s16.16 raw value.
bool narrowTotalForPair(const SplitBound &lower, const SplitBound &upper, i64 *low, i64 *high) FL_NO_EXCEPT
The totals at which lower <= upper holds, narrowed into [*low, *high].
constexpr i32 kTwoWhiteBoundSlack
How far past full drive the bounds let an RGB drive reach.
constexpr i32 kWhiteSlack
The same allowance, uniform, for the two-white (RGBWW) path.
i64 divideWhiteQ16(i32 numerator, i32 denominator) FL_NO_EXCEPT
(numerator << 16) / denominator as s16.16, kept in i64.
A bound on the split, as (numerator + slope * s) / denominator with a positive denominator.
bool xyzColumnQ16(const i32(&xy)[2], i32 luminance, i64(&column)[3]) FL_NO_EXCEPT
XYZ of chromaticity xy at luminance luminance, all s16.16; false for a non-positive or out-of-simplex...
constexpr int type_rank< T >::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 ...
bool allocateEmitterDrivesQ16(const WhiteAllocationQ16 &allocation, const i32(&xyz)[3], i32(&drives)[4]) FL_NO_EXCEPT
One pixel: XYZ in s16.16 to four drives, in the order red, green, blue, white, at whichever end of th...
WhiteAllocationPolicy
Which end of the feasible white interval to take (C3).
@ RgbPreferred
As little as the target allows, which for most targets is none.
bool buildWhiteAllocationQ16(const colorimetric_response::EmitterProfile &profile, const i32(&white_xyz)[3], WhiteAllocationPolicy policy, WhiteAllocationQ16 *out) FL_NO_EXCEPT
Derive the allocation for a three-primary profile plus one white emitter.
InputGamut g FL_NO_EXCEPT
bool buildWhiteAllocationFromSolveQ16(const colorimetric_response::EmitterProfile &profile, const EmitterSolveMatrixQ16 &solve, const i32(&white_xyz)[3], WhiteAllocationPolicy policy, WhiteAllocationQ16 *out) FL_NO_EXCEPT
Bind from an RGB solve already adapted to the D65 working domain.
bool buildRgbSolveMatrixQ16(const colorimetric_response::EmitterProfile &profile, EmitterSolveMatrixQ16 *out) FL_NO_EXCEPT
Invert the emitter matrix for a three-emitter profile.
bool q16FromFloatBits(float value, i32 *out) FL_NO_EXCEPT
s16.16 of a float, rounded to nearest, computed from its IEEE-754 bits with integer arithmetic only –...
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.
bool buildTwoWhiteAllocationQ16(const colorimetric_response::EmitterProfile &profile, const i32(&white1_xyz)[3], const i32(&white2_xyz)[3], WhiteAllocationPolicy policy, TwoWhiteAllocationQ16 *out) FL_NO_EXCEPT
Derive the allocation for three primaries plus two white emitters.
Base definition for an LED controller.
i32 per_white2[3]
M^-1 . w2: the RGB drives one unit of the second white replaces.
WhiteAllocationPolicy policy
Which end of the feasible total this profile takes.
EmitterSolveMatrixQ16 rgb_solve
Inverse RGB emitter matrix.
EmitterSolveMatrixQ16 rgb_solve
Inverse RGB emitter matrix.
i32 slack[3]
Per-channel drive slack, in s16.16 raw units.
WhiteAllocationPolicy policy
Which end of the interval this profile takes.
Inverse emitter matrix in s16.16, mapping XYZ to three emitter drives.
Everything the per-pixel allocation needs, derived once when a profile binds.
Everything the two-white allocation needs, derived once when a profile binds (C3, #4198).