19 if (values ==
nullptr || size < 2 || values[0] != 0 ||
20 values[size - 1] != 65535) {
23 for (u16 i = 1; i < size; ++i) {
24 if (values[i] < values[i - 1])
return false;
32 if (light <= 0)
return 0;
33 if (light >= 65536)
return 65536;
35 (
static_cast<u32
>(light) * 65535u + 32768u) >> 16;
37 u32 high =
static_cast<u32
>(values.size() - 1);
39 const u32 middle = low + (high - low) / 2;
40 if (values[middle] >= target) high = middle;
41 else low = middle + 1;
44 const u32 low_value = values[high - 1];
45 const u32 high_value = values[high];
47 if (high_value == low_value) {
48 return static_cast<i32
>((high - 1) * 65536u /
49 static_cast<u32
>(values.size() - 1));
51 const u32 interval = high_value - low_value;
53 ((target - low_value) * 65536u + interval / 2) / interval;
54 const u32 segments =
static_cast<u32
>(values.size() - 1);
55 return static_cast<i32
>(((high - 1) * 65536u + fraction + segments / 2) /
62 i32 luminance_q16 = 0;
66 luminance_q16 <= 0)
return false;
69 for (
int i = 0; i < 3; ++i) {
70 if (column[i] < 0 || column[i] > 2147483647LL)
return false;
71 out[i] =
static_cast<i32
>(column[i]);
82 if (!wide)
return false;
83 wide->topology = device.topology;
91 if (target_white !=
nullptr) {
94 &to_working))
return false;
97 for (
int i = 0; i < 3; ++i) white1[i] = adapted1[i];
101 for (
int i = 0; i < 3; ++i) white2[i] = adapted2[i];
106 const bool ok = effective_solve ==
nullptr
109 allocation, &wide->rgbw);
110 if (!ok)
return false;
112 const bool ok = effective_solve ==
nullptr
116 white2, allocation, &wide->rgbww);
117 if (!ok)
return false;
121 if (device.response_lut_size != 0) {
122 const u16 size = device.response_lut_size;
124 wide->white1_response.assign(device.response_lut_white1,
125 device.response_lut_white1 + size);
126 if (wide->white1_response.size() != size)
return false;
129 wide->white2_response.assign(device.response_lut_white2,
130 device.response_lut_white2 + size);
131 if (wide->white2_response.size() != size)
return false;
145 if (out ==
nullptr) {
148 out->gamut_policy = policy;
160 const Chromaticity source_white = source.primaries.white;
167 constexpr i32 kD65XQ16 = 20493;
168 constexpr i32 kD65YQ16 = 21561;
169 constexpr i32 kD65ToleranceQ16 = 7;
170 const bool already_d65 = white_x > kD65XQ16 - kD65ToleranceQ16 &&
171 white_x < kD65XQ16 + kD65ToleranceQ16 &&
172 white_y > kD65YQ16 - kD65ToleranceQ16 &&
173 white_y < kD65YQ16 + kD65ToleranceQ16;
185 if (target_white ==
nullptr) {
188 }
else if (!buildWidePipelineQ16(device,
nullptr,
nullptr,
203 for (
int row = 0; row < 3; ++row) {
204 for (
int col = 0; col < 3; ++col) {
206 constexpr i64 kMax = 9223372036854775807LL;
207 for (
int k = 0; k < 3; ++k) {
209 const i64 term =
static_cast<i64>(physical_solve.
m[row][k]) *
211 if ((term > 0 && sum > kMax - term) ||
212 (term < 0 && sum < -kMax - term))
return false;
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);
222 effective_solve.
m[row][col] =
static_cast<i32
>(
value);
228 }
else if (!buildWidePipelineQ16(device, target_white,
229 &effective_solve, &out->wide)) {
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)) {
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;
250 out->transfer = source.transfer;
257 if (pipeline ==
nullptr) {
260 pipeline->flux = flux;
281 for (
int i = 0; i < 3; ++i) {
284 }
else if (drives[i] > 65536) {
303 drives[0] = inverseResponseQ16(drives[0], pipeline.
response->red);
304 drives[1] = inverseResponseQ16(drives[1], pipeline.
response->green);
305 drives[2] = inverseResponseQ16(drives[2], pipeline.
response->blue);
311 for (
int i = 0; i < 5; ++i) drives[i] = 0;
312 if (!pipeline.
wide)
return;
318 const bool rgbww = pipeline.
wide->topology ==
328 for (
int i = 0; i < 3; ++i) {
329 wide_drives[i] = rgb_drives[i] < 0 ? 0 :
330 (rgb_drives[i] > 65536 ? 65536 : rgb_drives[i]);
337 for (
int i = 0; i < 5; ++i) drives[i] = wide_drives[i];
346 for (
int i = 0; i < 3; ++i) {
347 wide_drives[i] = rgb_drives[i] < 0 ? 0 :
348 (rgb_drives[i] > 65536 ? 65536 : rgb_drives[i]);
354 for (
int i = 0; i < 4; ++i) drives[i] = wide_drives[i];
361 if (!pipeline.
wide)
return;
362 const bool rgbww = pipeline.
wide && pipeline.
wide->topology ==
364 const int count = rgbww ? 5 : 4;
367 drives[0] = inverseResponseQ16(drives[0], pipeline.
response->red);
368 drives[1] = inverseResponseQ16(drives[1], pipeline.
response->green);
369 drives[2] = inverseResponseQ16(drives[2], pipeline.
response->blue);
370 drives[3] = inverseResponseQ16(drives[3],
371 pipeline.
wide->white1_response);
373 drives[4] = inverseResponseQ16(drives[4],
374 pipeline.
wide->white2_response);
381 i32 scaled[1] = {light};
383 if (!pipeline.
response)
return scaled[0];
384 if (emitter == 0)
return inverseResponseQ16(scaled[0], pipeline.
response->red);
385 if (emitter == 1)
return inverseResponseQ16(scaled[0], pipeline.
response->green);
386 if (emitter == 2)
return inverseResponseQ16(scaled[0], pipeline.
response->blue);
387 if (!pipeline.
wide)
return 0;
388 if (emitter == 3)
return inverseResponseQ16(scaled[0], pipeline.
wide->white1_response);
389 if (emitter == 4)
return inverseResponseQ16(scaled[0], pipeline.
wide->white2_response);
unsigned int xy(unsigned int x, unsigned int y)
static FluxScalar unity() FL_NO_EXCEPT
Identity. Drives pass through unchanged.
A linear flux scalar in s16.16.
bool validResponseLut(const u16 *values, u16 size) FL_NO_EXCEPT
bool wideWhiteColumnQ16(const float(&xy)[2], float luminance, i32(&out)[3]) FL_NO_EXCEPT
i32 inverseResponseQ16(i32 light, const vector< u16 > &values) FL_NO_EXCEPT
Invert piecewise-linear code-to-light data.
bool buildWidePipelineQ16(const colorimetric_response::EmitterProfile &device, const Chromaticity *target_white, const EmitterSolveMatrixQ16 *effective_solve, shared_ptr< const WidePipelineQ16 > *out) FL_NO_EXCEPT
constexpr Chromaticity kPipelineD65
The working domain's rendering white.
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...
bool buildGamutMapRgbwFromSolveQ16(const colorimetric_response::EmitterProfile &profile, const EmitterSolveMatrixQ16 &solve, const i32(&white_xyz)[3], WhiteAllocationPolicy policy, GamutMapRgbwQ16 *out) FL_NO_EXCEPT
Bind a four-emitter map from an effective RGB solve and a white column already adapted into the same ...
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 mapAndAllocateRgbwwQ16(const GamutMapRgbwwQ16 &map, const i32(&xyz)[3], i32(&drives)[5]) FL_NO_EXCEPT
One pixel: XYZ in s16.16 to five in-gamut drives – red, green, blue, white1, white2.
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 ...
void foldAdaptationIntoSourceMatrix(const AdaptationMatrixQ16 &adaptation, SourceMatrixQ16 *source) FL_NO_EXCEPT
Pre-multiply the adaptation into a source matrix, in place.
void mapAndSolveDrivesQ16(const GamutMapQ16 &map, const i32(&xyz)[3], i32(&drives)[3]) FL_NO_EXCEPT
One pixel: XYZ in s16.16 to in-gamut emitter drives in s16.16.
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...
void setPipelineFluxQ16(StreamingPipelineQ16 *pipeline, FluxScalar flux) FL_NO_EXCEPT
Set the composed brightness-and-power scalar for the frames that follow.
bool buildSourceMatrixQ16(const RgbPrimaries &primaries, SourceMatrixQ16 *out) FL_NO_EXCEPT
Quantize the source matrix for primaries.
WhiteAllocationPolicy
Which end of the feasible white interval to take (C3).
@ WhitePreferred
As much white as the target allows.
void applyFluxScalar(FluxScalar scalar, span< i32 > drives) FL_NO_EXCEPT
Scale every drive by one scalar, in place.
void processPixelLinearQ16(const StreamingPipelineQ16 &pipeline, u8 r, u8 g, u8 b, i32(&drives)[3]) FL_NO_EXCEPT
Pre-response linear emitter light for a power prepass.
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
bool buildGamutMapRgbwwQ16(const colorimetric_response::EmitterProfile &profile, const i32(&white1_xyz)[3], const i32(&white2_xyz)[3], WhiteAllocationPolicy policy, GamutMapRgbwwQ16 *out) FL_NO_EXCEPT
Derive the mapper for a three-primary profile plus two white emitters.
void processPixelWideLinearQ16(const StreamingPipelineQ16 &pipeline, u8 r, u8 g, u8 b, i32(&drives)[5]) FL_NO_EXCEPT
InputGamut g FL_NO_EXCEPT
u16 decodeTransferU16(TransferFunction transfer, u8 code) FL_NO_EXCEPT
Decode one 8-bit source code to u16 linear light under transfer.
void linearRgbToXyzQ16(const SourceMatrixQ16 &matrix, u16 r, u16 g, u16 b, i32(&out_xyz)[3]) FL_NO_EXCEPT
One pixel: u16 linear RGB -> s16.16 XYZ.
bool buildGamutMapFromSolveQ16(const EmitterSolveMatrixQ16 &solve, GamutMapQ16 *out) FL_NO_EXCEPT
Build a mapper from a bind-time solve already transformed into D65 working coordinates.
void adaptXyzQ16(const AdaptationMatrixQ16 &matrix, const i32(&xyz)[3], i32(&out_xyz)[3]) FL_NO_EXCEPT
Adapt one XYZ triple.
bool buildStreamingPipelineQ16(const SourceProfile &source, const colorimetric_response::EmitterProfile &device, GamutPolicy policy, StreamingPipelineQ16 *out, const Chromaticity *target_white) FL_NO_EXCEPT
Bind a source declaration and a device profile into a pipeline.
bool buildGamutMapRgbwQ16(const colorimetric_response::EmitterProfile &profile, const i32(&white_xyz)[3], WhiteAllocationPolicy policy, GamutMapRgbwQ16 *out) FL_NO_EXCEPT
Derive the mapper for a three-primary profile plus one white emitter.
void processPixelWideQ16(const StreamingPipelineQ16 &pipeline, u8 r, u8 g, u8 b, i32(&drives)[5]) FL_NO_EXCEPT
Like processPixelQ16, but solves the physical 4/5-emitter hull.
i32 encodeLinearEmitterQ16(const StreamingPipelineQ16 &pipeline, u8 emitter, i32 light, FluxScalar flux) FL_NO_EXCEPT
Scale one linear emitter and invert its physical response, using the identical final stage as process...
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 –...
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.
void processPixelQ16(const StreamingPipelineQ16 &pipeline, u8 r, u8 g, u8 b, i32(&drives)[3]) FL_NO_EXCEPT
One pixel: an RGB8 code triple to three physical emitter drives in s16.16.
void mapAndAllocateRgbwQ16(const GamutMapRgbwQ16 &map, const i32(&xyz)[3], i32(&drives)[4]) FL_NO_EXCEPT
One pixel: XYZ in s16.16 to four in-gamut drives, red, green, blue, white.
bool buildGamutMapRgbwwFromSolveQ16(const colorimetric_response::EmitterProfile &profile, const EmitterSolveMatrixQ16 &solve, const i32(&white1_xyz)[3], const i32(&white2_xyz)[3], WhiteAllocationPolicy policy, GamutMapRgbwwQ16 *out) FL_NO_EXCEPT
Bind a five-emitter map from one effective RGB solve and two white columns, all in the same D65 worki...
Base definition for an LED controller.
TransferFunction transfer
The source's transfer function, applied per code.
GamutPolicy gamut_policy
What to do with a target the device cannot reproduce.
shared_ptr< const ResponseLutsQ16 > response
Absent means linear code-to-light response.
GamutMapQ16 gamut
The device hull, its solve, and the lightness bound.
FluxScalar flux
Brightness times power limiting, as one scalar (C4).
shared_ptr< const WidePipelineQ16 > wide
EmitterSolveMatrixQ16 solve
SourceMatrixQ16 source
Source primaries to XYZ, with adaptation to D65 already folded in.
Inverse emitter matrix in s16.16, mapping XYZ to three emitter drives.
A collapsed adaptation transform in s16.16.
Everything the per-pixel path needs, derived once when a profile binds.
CIE 1931 xy chromaticity, independent of encoded storage and chipset.
A source declaration, not a container or an output-device selection.