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

◆ allocateEmitterDrivesQ16()

bool fl::allocateEmitterDrivesQ16 ( const WhiteAllocationQ16 & allocation,
const i32(&) xyz[3],
i32(&) drives[4] )

One pixel: XYZ in s16.16 to four drives, in the order red, green, blue, white, at whichever end of the feasible interval the profile's policy names.

False when no white level keeps the RGB drives in range, which means the target is outside the device hull – the gamut mapper's job, not this one's. drives is not written in that case.

Cost note, recorded rather than optimized away on a guess: this performs up to six s16.16 divisions per pixel, and is the only stage in the pipeline that divides per pixel at all. Each bound can instead be compared by cross-multiplication, leaving one division for the chosen level, at the price of i64 multiplies that are not obviously cheaper on an 8-bit target. Nobody has measured which wins, so the straightforward version is what ships.

Definition at line 177 of file white_allocation.cpp.hpp.

179 {
180 i32 at_zero[3];
181 solveRgbDrivesQ16(allocation.rgb_solve, xyz, at_zero);
182
183 // The interval of white levels that keeps every RGB drive in range.
184 // Each bound is one inequality in the white level because d(w) is affine
185 // in w -- that is the whole reason no search is needed here.
186 //
187 // Strict bounds, deliberately. Slack here would be *spent* rather than
188 // merely tolerated: the policy maximizes white, so widening the bounds
189 // by 64 raw units lets it take 64 units out of every RGB drive on every
190 // pixel. That is not a rounding allowance, it is a systematic colour
191 // shift -- measured at white = 886 where the reference says 0. The
192 // slack lives on the drive check below, which is what actually decides
193 // feasibility.
194 i64 low = 0;
195 i64 high = kWhiteFullDrive;
196 for (int i = 0; i < 3; ++i) {
197 const i32 slope = allocation.per_white[i];
198 if (slope != 0) {
199 // Dividing by a negative slope swaps which bound is which.
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;
204 if (upper < high) {
205 high = upper;
206 }
207 if (lower > low) {
208 low = lower;
209 }
210 } else if (at_zero[i] < -allocation.slack[i] ||
211 at_zero[i] > kWhiteFullDrive + allocation.slack[i]) {
212 // White cannot move this drive at all, and it is already out.
213 return false;
214 }
215 }
216 // At the dark floor the solve rounds a drive to -1 raw on a colour the
217 // device reproduces exactly -- corpus target (4, 1, 19) solves to
218 // (0, -1, 1) -- which drags `high` below zero. Clamp rather than refuse;
219 // the drive check below decides whether the target is really reachable.
220 if (high < 0) {
221 high = 0;
222 }
223 if (low > high) {
224 return false;
225 }
226
227 // Either end reproduces the target exactly; which one is policy. Both
228 // ends are inside [0, full] by construction, so the narrowing is safe.
229 const i32 level = static_cast<i32>(
230 allocation.policy == WhiteAllocationPolicy::RgbPreferred ? low : high);
231 i32 rgb[3];
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) {
236 return false;
237 }
238 rgb[i] = drive < 0 ? 0 : (drive > kWhiteFullDrive ? kWhiteFullDrive : drive);
239 }
240 drives[0] = rgb[0];
241 drives[1] = rgb[1];
242 drives[2] = rgb[2];
243 drives[3] = level < 0 ? 0
244 : (level > kWhiteFullDrive ? kWhiteFullDrive : level);
245 return true;
246}
i32 scaleWhiteQ16(i32 value, i32 factor) FL_NO_EXCEPT
Scale an s16.16 value by an s16.16 factor, rounding to nearest.
constexpr i32 kWhiteFullDrive
Full drive, as an s16.16 raw value.
@ RgbPreferred
As little as the target allows, which for most targets is none.
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.
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.
fl::i64 i64
Definition stdint.h:221

References FL_NO_EXCEPT, fl::WhiteAllocationQ16::per_white, fl::WhiteAllocationQ16::policy, fl::WhiteAllocationQ16::rgb_solve, RgbPreferred, fl::WhiteAllocationQ16::slack, and solveRgbDrivesQ16().

Referenced by buildGamutMapRgbwFromSolveQ16(), mapAndAllocateRgbwQ16(), fl::anonymous_namespace{gamut_map.cpp.hpp}::RgbwFeasible::operator()(), and processPixelWideLinearQ16().

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