FastLED 3.10.6
Loading...
Searching...
No Matches
chromatic_adaptation.cpp.hpp
Go to the documentation of this file.
1// ok no header - implementation for fl/gfx/chromatic_adaptation.h
2
4
7
8namespace fl {
9
10namespace detail {
11
13const float kBradford[3][3] = {
14 {0.8951f, 0.2664f, -0.1614f},
15 {-0.7502f, 1.7135f, 0.0367f},
16 {0.0389f, -0.0685f, 1.0296f},
17};
18
32const float kBradfordInverse[3][3] = {
33 {0.986992955f, -0.14705427f, 0.159962654f},
34 {0.432305276f, 0.518360257f, 0.049291227f},
35 {-0.0085286675f, 0.0400428213f, 0.968486726f},
36};
37
40const i32 kBradfordQ16[3][3] = {
41 {58661, 17459, -10578},
42 {-49165, 112296, 2405},
43 {2549, -4489, 67476},
44};
45const i32 kBradfordInverseQ16[3][3] = {
46 {64684, -9637, 10483},
47 {28332, 33971, 3230},
48 {-559, 2624, 63471},
49};
50
51} // namespace detail
52
53namespace {
54
55// Helper names carry an `adaptation` qualifier because .cpp.hpp files are
56// concatenated into one translation unit by the unity build, so an anonymous
57// namespace does not isolate them from a same-named helper in a sibling file
58// -- source_xyz.cpp.hpp defines its own quantizer.
59
60constexpr i32 kAdaptationQ16One = 65536;
61
66 if (!q16FromFloatBits(c.x, &out[0]) || !q16FromFloatBits(c.y, &out[1])) {
67 return false;
68 }
69 return out[0] > 0 && out[0] < kAdaptationQ16One && out[1] > 0 &&
70 out[1] < kAdaptationQ16One;
71}
72
73i32 dotAdaptationRowQ16(const i32 (&row)[3], const i32 (&v)[3]) FL_NO_EXCEPT {
74 const i64 acc = static_cast<i64>(row[0]) * static_cast<i64>(v[0])
75 + static_cast<i64>(row[1]) * static_cast<i64>(v[1])
76 + static_cast<i64>(row[2]) * static_cast<i64>(v[2]);
77 return static_cast<i32>(acc >= 0 ? (acc + 32768) >> 16
78 : -((-acc + 32768) >> 16));
79}
80
81} // namespace
82
84 Chromaticity destination_white,
86 if (out == nullptr) {
87 return false;
88 }
89 // In s16.16 throughout (FastLED#4458). The same construction as the
90 // float build it replaces: both whites to XYZ at Y = 1, into cone space,
91 // the per-cone ratio as a diagonal, and B^-1 * diag * B collapsed once.
92 i32 source_xy[2];
93 i32 destination_xy[2];
94 if (!adaptationWhiteQ16(source_white, source_xy) ||
95 !adaptationWhiteQ16(destination_white, destination_xy)) {
96 return false;
97 }
98 i64 source_xyz[3];
99 i64 destination_xyz[3];
100 if (!detail::xyzColumnQ16(source_xy, kAdaptationQ16One, source_xyz) ||
101 !detail::xyzColumnQ16(destination_xy, kAdaptationQ16One, destination_xyz)) {
102 return false;
103 }
104 const i32 source_v[3] = {static_cast<i32>(source_xyz[0]), static_cast<i32>(source_xyz[1]),
105 static_cast<i32>(source_xyz[2])};
106 const i32 destination_v[3] = {static_cast<i32>(destination_xyz[0]),
107 static_cast<i32>(destination_xyz[1]),
108 static_cast<i32>(destination_xyz[2])};
109 i64 scale[3];
110 for (int i = 0; i < 3; ++i) {
111 const i32 source_cone = detail::dotRowQ16(detail::kBradfordQ16[i], source_v);
112 const i32 destination_cone =
113 detail::dotRowQ16(detail::kBradfordQ16[i], destination_v);
114 // A zero cone response would divide by zero. No physical white does
115 // this; a corrupt profile can.
116 if (source_cone == 0) {
117 return false;
118 }
120 static_cast<i64>(destination_cone) * kAdaptationQ16One, source_cone);
121 }
122 for (int row = 0; row < 3; ++row) {
123 for (int col = 0; col < 3; ++col) {
124 // sum_k Binv[row][k] * scale[k] * B[k][col]: each term is Q48,
125 // brought back to Q16 once at the end rather than per product.
126 i64 sum_q32 = 0;
127 for (int k = 0; k < 3; ++k) {
128 const i64 inv_scale_q16 = detail::roundedDivideQ16(
129 static_cast<i64>(detail::kBradfordInverseQ16[row][k]) * scale[k],
130 kAdaptationQ16One);
131 sum_q32 += inv_scale_q16 * detail::kBradfordQ16[k][col];
132 }
133 const i64 value = detail::roundedDivideQ16(sum_q32, kAdaptationQ16One);
134 if (value > 2147483647LL || value < -2147483648LL) {
135 return false;
136 }
137 out->m[row][col] = static_cast<i32>(value);
138 }
139 }
140 return true;
141}
142
143void adaptXyzQ16(const AdaptationMatrixQ16& matrix, const i32 (&xyz)[3],
144 i32 (&out_xyz)[3]) FL_NO_EXCEPT {
145 const i32 in[3] = {xyz[0], xyz[1], xyz[2]};
146 out_xyz[0] = dotAdaptationRowQ16(matrix.m[0], in);
147 out_xyz[1] = dotAdaptationRowQ16(matrix.m[1], in);
148 out_xyz[2] = dotAdaptationRowQ16(matrix.m[2], in);
149}
150
153 if (source == nullptr) {
154 return;
155 }
157 for (int row = 0; row < 3; ++row) {
158 for (int col = 0; col < 3; ++col) {
159 const i64 acc =
160 static_cast<i64>(adaptation.m[row][0]) * source->m[0][col]
161 + static_cast<i64>(adaptation.m[row][1]) * source->m[1][col]
162 + static_cast<i64>(adaptation.m[row][2]) * source->m[2][col];
163 result.m[row][col] = static_cast<i32>(
164 acc >= 0 ? (acc + 32768) >> 16 : -((-acc + 32768) >> 16));
165 }
166 }
167 *source = result;
168}
169
170} // namespace fl
fl::UISlider scale("Scale", 4,.1, 4,.1)
Shared colorimetric primitives reusable by the RGBW and (future) RGB colorimetric paths (issue #3255)...
bool adaptationWhiteQ16(Chromaticity c, i32(&out)[2]) FL_NO_EXCEPT
A white point, converted from float by its bits, inside the open unit square with a non-zero y – the ...
i32 dotAdaptationRowQ16(const i32(&row)[3], const i32(&v)[3]) FL_NO_EXCEPT
i32 dotRowQ16(const i32(&row)[3], const i32(&v)[3]) FL_NO_EXCEPT
One s16.16 matrix row against a vector, rounded and saturated.
i64 roundedDivideQ16(i64 numerator, i64 denominator) FL_NO_EXCEPT
The s16.16 building blocks the bind-time builders share.
const i32 kBradfordInverseQ16[3][3]
const float kBradfordInverse[3][3]
Its inverse, precomputed.
const float kBradford[3][3]
ICC linear Bradford cone-response matrix.
const i32 kBradfordQ16[3][3]
The two matrices above in s16.16, rounded from the float32 values, so the bind-time build needs no fl...
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...
Compile-time linker keep-alive hook for a single fl::Bus.
Definition bus_info.h:57
constexpr int type_rank< T >::value
void foldAdaptationIntoSourceMatrix(const AdaptationMatrixQ16 &adaptation, SourceMatrixQ16 *source) FL_NO_EXCEPT
Pre-multiply the adaptation into a source matrix, in place.
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
InputGamut g FL_NO_EXCEPT
Definition rgbw.h:121
void adaptXyzQ16(const AdaptationMatrixQ16 &matrix, const i32(&xyz)[3], i32(&out_xyz)[3]) FL_NO_EXCEPT
Adapt one XYZ triple.
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 –...
Base definition for an LED controller.
Definition crgb.hpp:179
A collapsed adaptation transform in s16.16.
Source-primaries matrix in s16.16, rows ordered X, Y, Z.
Definition source_xyz.h:20
fl::i64 i64
Definition stdint.h:221
CIE 1931 xy chromaticity, independent of encoded storage and chipset.