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

◆ mapAndSolveDrivesQ16()

void fl::mapAndSolveDrivesQ16 ( const GamutMapQ16 & map,
const i32(&) xyz[3],
i32(&) drives[3] )

One pixel: XYZ in s16.16 to in-gamut emitter drives in s16.16.

Drives always come back inside [0, 1]. A target already inside the hull is returned untouched, so an in-gamut image pays only the solve and the bounds check.

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

356 {
357 solveRgbDrivesQ16(map.solve, xyz, drives);
358 if (gamutDrivesAreInRange(drives, kGamutFeasibilitySlack)) {
359 // Already inside the hull. An in-gamut image pays one solve, this
360 // comparison and the clamp, and never touches OKLab at all.
361 clampGamutDrives(drives);
362 return;
363 }
364
365 i32 lab[3];
366 xyzToOklabQ16(xyz, lab);
367
368 i32 lightness = lab[0];
369 if (lightness < 0) {
370 lightness = 0;
371 }
372
373 // Above the brightest neutral, try keeping the lightness (#4245). The
374 // feasible chroma there is an interval that does not contain zero, so it
375 // has to be located before either edge can be bisected; when it is found,
376 // clamping the target's chroma into it holds the requested lightness
377 // instead of discarding up to 29.7% of what the hull reaches.
378 if (lightness > map.max_neutral_lightness) {
379 i32 low_edge = 0;
380 i32 high_edge = 0;
381 if (feasibleChromaInterval(lab, lightness, RgbFeasible{map.solve},
382 &low_edge, &high_edge)) {
383 i32 candidate_xyz[3];
384 chromaCandidateXyz(lab, lightness, clampChromaFactor(low_edge, high_edge),
385 candidate_xyz);
386 i32 candidate[3];
387 solveRgbDrivesQ16(map.solve, candidate_xyz, candidate);
388 if (gamutDrivesAreInRange(candidate, kGamutFeasibilitySlack)) {
389 for (int i = 0; i < 3; ++i) {
390 drives[i] = candidate[i];
391 }
392 clampGamutDrives(drives);
393 return;
394 }
395 }
396 // No seed at this lightness. Walking down to the highest lightness
397 // that still has one keeps the answer continuous; dropping straight
398 // to the neutral cap does not, and that step measured 234 eight-bit
399 // codes before this search was added.
400 const i32 reachable = highestReachableLightness(
401 lab, map.max_neutral_lightness, lightness, RgbFeasible{map.solve});
402 i32 edge_low = 0;
403 i32 edge_high = 0;
404 if (reachable > map.max_neutral_lightness &&
405 feasibleChromaInterval(lab, reachable, RgbFeasible{map.solve},
406 &edge_low, &edge_high)) {
407 i32 candidate_xyz[3];
408 chromaCandidateXyz(lab, reachable,
409 clampChromaFactor(edge_low, edge_high),
410 candidate_xyz);
411 i32 candidate[3];
412 solveRgbDrivesQ16(map.solve, candidate_xyz, candidate);
413 if (gamutDrivesAreInRange(candidate, kGamutFeasibilitySlack)) {
414 for (int i = 0; i < 3; ++i) {
415 drives[i] = candidate[i];
416 }
417 clampGamutDrives(drives);
418 return;
419 }
420 }
421 // Nothing above the cap works for this hue at all. Fall through to
422 // the clamp, which is what shipped before #4245.
423 lightness = map.max_neutral_lightness;
424 }
425
426 // Then chroma, by scaling (a, b) toward zero.
427 const i32 factor = largestFeasibleChroma(lab, lightness, RgbFeasible{map.solve});
428 i32 mapped_xyz[3];
429 chromaCandidateXyz(lab, lightness, factor, mapped_xyz);
430 solveRgbDrivesQ16(map.solve, mapped_xyz, drives);
431 clampGamutDrives(drives);
432}
bool gamutDrivesAreInRange(const i32(&drives)[3], i32 slack) FL_NO_EXCEPT
True when every drive is inside [0, 1], allowing slack ULP either side.
void clampGamutDrives(i32(&drives)[3]) FL_NO_EXCEPT
Clamp drives into [0, 1].
i32 clampChromaFactor(i32 low, i32 high) FL_NO_EXCEPT
The target's chroma clamped into [low, high], in factor space.
void chromaCandidateXyz(const i32(&lab)[3], i32 lightness, i32 factor, i32(&out_xyz)[3]) FL_NO_EXCEPT
XYZ of the candidate at factor of the target's chroma.
i32 largestFeasibleChroma(const i32(&lab)[3], i32 lightness, Feasible feasible) FL_NO_EXCEPT
Largest chroma factor the hull accepts, by a fixed count of halvings.
i32 highestReachableLightness(const i32(&lab)[3], i32 cap, i32 requested, Feasible feasible) FL_NO_EXCEPT
The highest lightness at or below requested whose feasible chroma interval the probe scan can still f...
bool feasibleChromaInterval(const i32(&lab)[3], i32 lightness, Feasible feasible, i32 *out_low, i32 *out_high) FL_NO_EXCEPT
The feasible chroma interval at the target's own lightness, as factors of the target's chroma.
MapRedBlackTree< Key, T, Compare, fl::allocator_slab< char > > map
Definition map.h:283
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.
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.

References FL_NO_EXCEPT, solveRgbDrivesQ16(), and xyzToOklabQ16().

Referenced by processPixelLinearQ16().

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