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

◆ buildStreamingPipelineQ16()

bool fl::buildStreamingPipelineQ16 ( const SourceProfile & source,
const colorimetric_response::EmitterProfile & device,
GamutPolicy policy,
StreamingPipelineQ16 * out,
const Chromaticity * target_white = nullptr )

Bind a source declaration and a device profile into a pipeline.

False when either half is degenerate – collinear primaries, a white with no cone response, a device that cannot make a neutral. The individual stages decide that; this reports it.

The flux scalar starts at unity. setPipelineFluxQ16 is how brightness and the power limiter reach it, because they change between frames while everything else here does not. With target_white, D65 working XYZ is adapted to the selected physical neutral at bind time. A white outside the emitter gamut rejects the build; nullptr preserves the historical D65 output byte-for-byte.

Definition at line 140 of file pipeline.cpp.hpp.

144 {
145 if (out == nullptr) {
146 return false;
147 }
148 out->gamut_policy = policy;
149 if (!buildSourceMatrixQ16(source.primaries, &out->source)) {
150 return false;
151 }
152
153 // Fold the source white's adaptation to D65 into the matrix above, so
154 // the per-pixel path never pays for it. Skipped when the source already
155 // renders to D65, where the transform is the identity and folding it
156 // would only add a round trip's worth of quantization.
157 //
158 // Compared in s16.16 from the float's bits, so this check links no float
159 // either (FastLED#4458). 1e-4 is 6.55 steps; 7 keeps the same window.
160 const Chromaticity source_white = source.primaries.white;
161 i32 white_x = 0;
162 i32 white_y = 0;
163 if (!q16FromFloatBits(source_white.x, &white_x) ||
164 !q16FromFloatBits(source_white.y, &white_y)) {
165 return false;
166 }
167 constexpr i32 kD65XQ16 = 20493; // 0.3127
168 constexpr i32 kD65YQ16 = 21561; // 0.3290
169 constexpr i32 kD65ToleranceQ16 = 7; // ~1e-4
170 const bool already_d65 = white_x > kD65XQ16 - kD65ToleranceQ16 &&
171 white_x < kD65XQ16 + kD65ToleranceQ16 &&
172 white_y > kD65YQ16 - kD65ToleranceQ16 &&
173 white_y < kD65YQ16 + kD65ToleranceQ16;
174 if (!already_d65) {
175 AdaptationMatrixQ16 adaptation;
176 if (!buildBradfordMatrixQ16(source_white, kPipelineD65, &adaptation)) {
177 return false;
178 }
179 foldAdaptationIntoSourceMatrix(adaptation, &out->source);
180 }
181
182 out->wide.reset();
183 const bool is_rgb =
185 if (target_white == nullptr) {
186 if (is_rgb) {
187 if (!buildGamutMapQ16(device, &out->gamut)) return false;
188 } else if (!buildWidePipelineQ16(device, nullptr, nullptr,
189 &out->wide)) {
190 return false;
191 }
192 } else {
193 // The source and OKLab objective remain in D65 coordinates. Map the
194 // physical gamut into that space by composing the inverse emitter
195 // solve with Bradford D65 -> selected rendering white. The neutral
196 // cap must be computed from the composed solve, not the raw profile.
197 AdaptationMatrixQ16 to_target;
198 if (!buildBradfordMatrixQ16(kPipelineD65, *target_white, &to_target))
199 return false;
200 EmitterSolveMatrixQ16 physical_solve;
201 if (!buildRgbSolveMatrixQ16(device, &physical_solve)) return false;
202 EmitterSolveMatrixQ16 effective_solve;
203 for (int row = 0; row < 3; ++row) {
204 for (int col = 0; col < 3; ++col) {
205 i64 sum = 0;
206 constexpr i64 kMax = 9223372036854775807LL;
207 for (int k = 0; k < 3; ++k) {
208 // Each i32 product fits i64, but their sum need not.
209 const i64 term = static_cast<i64>(physical_solve.m[row][k]) *
210 to_target.m[k][col];
211 if ((term > 0 && sum > kMax - term) ||
212 (term < 0 && sum < -kMax - term)) return false;
213 sum += term;
214 }
215 // Keep rounding/negation away from the i64 boundary too.
216 if (sum > 2147483647LL * 65536 + 32768 ||
217 sum < -2147483648LL * 65536 - 32768) return false;
218 const i64 value = sum >= 0 ? (sum + 32768) >> 16
219 : -((-sum + 32768) >> 16);
221 return false;
222 effective_solve.m[row][col] = static_cast<i32>(value);
223 }
224 }
225 if (is_rgb) {
226 if (!buildGamutMapFromSolveQ16(effective_solve, &out->gamut))
227 return false;
228 } else if (!buildWidePipelineQ16(device, target_white,
229 &effective_solve, &out->wide)) {
230 return false;
231 }
232 }
233 out->response.reset();
234 if (device.response_lut_size != 0) {
235 const u16 size = device.response_lut_size;
236 if (!validResponseLut(device.response_lut_r, size) ||
237 !validResponseLut(device.response_lut_g, size) ||
238 !validResponseLut(device.response_lut_b, size)) {
239 return false;
240 }
242 if (!response) return false;
243 response->red.assign(device.response_lut_r, device.response_lut_r + size);
244 response->green.assign(device.response_lut_g, device.response_lut_g + size);
245 response->blue.assign(device.response_lut_b, device.response_lut_b + size);
246 if (response->red.size() != size || response->green.size() != size ||
247 response->blue.size() != size) return false;
248 out->response = response;
249 }
250 out->transfer = source.transfer;
251 out->flux = FluxScalar::unity();
252 return true;
253}
static FluxScalar unity() FL_NO_EXCEPT
Identity. Drives pass through unchanged.
bool validResponseLut(const u16 *values, u16 size) FL_NO_EXCEPT
bool buildWidePipelineQ16(const colorimetric_response::EmitterProfile &device, const Chromaticity *target_white, const EmitterSolveMatrixQ16 *effective_solve, shared_ptr< const WidePipelineQ16 > *out) FL_NO_EXCEPT
constexpr int type_rank< T >::value
bool buildGamutMapQ16(const colorimetric_response::EmitterProfile &profile, GamutMapQ16 *out) FL_NO_EXCEPT
Derive the mapper for a three-emitter profile.
void foldAdaptationIntoSourceMatrix(const AdaptationMatrixQ16 &adaptation, SourceMatrixQ16 *source) FL_NO_EXCEPT
Pre-multiply the adaptation into a source matrix, in place.
bool buildSourceMatrixQ16(const RgbPrimaries &primaries, SourceMatrixQ16 *out) FL_NO_EXCEPT
Quantize the source matrix for primaries.
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
Definition shared_ptr.h:414
bool buildGamutMapFromSolveQ16(const EmitterSolveMatrixQ16 &solve, GamutMapQ16 *out) FL_NO_EXCEPT
Build a mapper from a bind-time solve already transformed into D65 working coordinates.
bool buildRgbSolveMatrixQ16(const colorimetric_response::EmitterProfile &profile, EmitterSolveMatrixQ16 *out) FL_NO_EXCEPT
Invert the emitter matrix for a three-emitter profile.
bool buildBradfordMatrixQ16(Chromaticity source_white, Chromaticity destination_white, AdaptationMatrixQ16 *out) FL_NO_EXCEPT
Build the Bradford transform from source_white to destination_white.
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 –...
TransferFunction transfer
The source's transfer function, applied per code.
Definition pipeline.h:75
GamutPolicy gamut_policy
What to do with a target the device cannot reproduce.
Definition pipeline.h:91
shared_ptr< const ResponseLutsQ16 > response
Absent means linear code-to-light response.
Definition pipeline.h:102
GamutMapQ16 gamut
The device hull, its solve, and the lightness bound.
Definition pipeline.h:81
FluxScalar flux
Brightness times power limiting, as one scalar (C4).
Definition pipeline.h:98
shared_ptr< const WidePipelineQ16 > wide
Definition pipeline.h:104
SourceMatrixQ16 source
Source primaries to XYZ, with adaptation to D65 already folded in.
Definition pipeline.h:78
Inverse emitter matrix in s16.16, mapping XYZ to three emitter drives.
A collapsed adaptation transform in s16.16.
fl::i64 i64
Definition stdint.h:221
CIE 1931 xy chromaticity, independent of encoded storage and chipset.
Chromaticity white
RgbPrimaries primaries
TransferFunction transfer

References buildBradfordMatrixQ16(), buildGamutMapFromSolveQ16(), buildGamutMapQ16(), buildRgbSolveMatrixQ16(), buildSourceMatrixQ16(), FL_NO_EXCEPT, foldAdaptationIntoSourceMatrix(), fl::AdaptationMatrixQ16::m, fl::EmitterSolveMatrixQ16::m, make_shared(), q16FromFloatBits(), fl::colorimetric_response::RGB, type_rank< T >::value, fl::FluxScalar::unity(), fl::Chromaticity::x, and fl::Chromaticity::y.

Referenced by buildPipelineForBinding().

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