FastLED 3.10.6
Loading...
Searching...
No Matches
source_xyz.cpp.hpp
Go to the documentation of this file.
1// ok no header - implementation for fl/gfx/source_xyz.h
2
3#include "fl/gfx/source_xyz.h"
4
6
7namespace fl {
8
9namespace {
10
11constexpr i32 kSourceQ16One = 65536;
12
18 if (!q16FromFloatBits(c.x, &out[0]) || !q16FromFloatBits(c.y, &out[1])) {
19 return false;
20 }
21 return out[0] > 0 && out[0] < kSourceQ16One && out[1] > 0 &&
22 out[1] < kSourceQ16One;
23}
24
32bool sourcePrimariesEncloseAreaQ16(const i32 (&r)[2], const i32 (&g)[2],
33 const i32 (&b)[2]) FL_NO_EXCEPT {
34 const i64 ux = static_cast<i64>(g[0]) - r[0];
35 const i64 uy = static_cast<i64>(g[1]) - r[1];
36 const i64 vx = static_cast<i64>(b[0]) - r[0];
37 const i64 vy = static_cast<i64>(b[1]) - r[1];
38 const i64 twice_area_q32 = ux * vy - uy * vx;
39 constexpr i64 kMinTwiceAreaQ32 = 429497; // 1e-4 * 2^32
40 return twice_area_q32 > kMinTwiceAreaQ32 || twice_area_q32 < -kMinTwiceAreaQ32;
41}
42
46i32 dotRowQ16(const i32 (&row)[3], u16 r, u16 g, u16 b) FL_NO_EXCEPT {
47 const i64 acc = static_cast<i64>(row[0]) * static_cast<i64>(r)
48 + static_cast<i64>(row[1]) * static_cast<i64>(g)
49 + static_cast<i64>(row[2]) * static_cast<i64>(b);
50 // Round to nearest rather than truncating: truncation biases every
51 // pixel downward, and the bias accumulates across the later stages.
52 return static_cast<i32>((acc + 32768) >> 16);
53}
54
55} // namespace
56
57bool buildSourceMatrixQ16(const RgbPrimaries& primaries,
59 if (out == nullptr) {
60 return false;
61 }
62 // In s16.16 throughout (FastLED#4458): the same construction as
63 // `colorimetric_response::build_source_matrix` -- primaries' XYZ at unit
64 // luminance as columns P, scaled per column by k = P^-1 W so that full
65 // RGB lands on the white -- with no float operation on the way.
66 i32 xy_r[2];
67 i32 xy_g[2];
68 i32 xy_b[2];
69 i32 xy_w[2];
70 if (!sourceChromaticityQ16(primaries.red, xy_r) ||
71 !sourceChromaticityQ16(primaries.green, xy_g) ||
72 !sourceChromaticityQ16(primaries.blue, xy_b) ||
73 !sourceChromaticityQ16(primaries.white, xy_w)) {
74 return false;
75 }
76 if (!sourcePrimariesEncloseAreaQ16(xy_r, xy_g, xy_b)) {
77 return false;
78 }
79 i64 red[3];
80 i64 green[3];
81 i64 blue[3];
82 i64 white[3];
83 if (!detail::xyzColumnQ16(xy_r, kSourceQ16One, red) ||
84 !detail::xyzColumnQ16(xy_g, kSourceQ16One, green) ||
85 !detail::xyzColumnQ16(xy_b, kSourceQ16One, blue) ||
86 !detail::xyzColumnQ16(xy_w, kSourceQ16One, white)) {
87 return false;
88 }
89 const i32 primaries_xyz[3][3] = {
90 {static_cast<i32>(red[0]), static_cast<i32>(green[0]), static_cast<i32>(blue[0])},
91 {static_cast<i32>(red[1]), static_cast<i32>(green[1]), static_cast<i32>(blue[1])},
92 {static_cast<i32>(red[2]), static_cast<i32>(green[2]), static_cast<i32>(blue[2])},
93 };
94 i32 inverse[3][3];
95 if (!invert3x3Q16(primaries_xyz, inverse)) {
96 return false;
97 }
98 const i32 white_xyz[3] = {static_cast<i32>(white[0]), static_cast<i32>(white[1]),
99 static_cast<i32>(white[2])};
100 i32 k[3];
101 for (int i = 0; i < 3; ++i) {
102 k[i] = detail::dotRowQ16(inverse[i], white_xyz);
103 }
104 for (int row = 0; row < 3; ++row) {
105 for (int col = 0; col < 3; ++col) {
106 const i64 product = static_cast<i64>(primaries_xyz[row][col]) * k[col];
107 out->m[row][col] = static_cast<i32>(
108 detail::roundedDivideQ16(product, kSourceQ16One));
109 }
110 }
111 return true;
112}
113
114void linearRgbToXyzQ16(const SourceMatrixQ16& matrix, u16 r, u16 g, u16 b,
115 i32 (&out_xyz)[3]) FL_NO_EXCEPT {
116 out_xyz[0] = dotRowQ16(matrix.m[0], r, g, b);
117 out_xyz[1] = dotRowQ16(matrix.m[1], r, g, b);
118 out_xyz[2] = dotRowQ16(matrix.m[2], r, g, b);
119}
120
121} // namespace fl
bool sourcePrimariesEncloseAreaQ16(const i32(&r)[2], const i32(&g)[2], const i32(&b)[2]) FL_NO_EXCEPT
True when the three primaries enclose an actual area of chromaticity.
i32 dotRowQ16(const i32(&row)[3], u16 r, u16 g, u16 b) FL_NO_EXCEPT
One matrix row against the pixel.
bool sourceChromaticityQ16(Chromaticity c, i32(&out)[2]) FL_NO_EXCEPT
A chromaticity, converted from float by its bits, that is real and usable: inside the open unit squar...
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.
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 buildSourceMatrixQ16(const RgbPrimaries &primaries, SourceMatrixQ16 *out) FL_NO_EXCEPT
Quantize the source matrix for primaries.
InputGamut g FL_NO_EXCEPT
Definition rgbw.h:121
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.
InputGamut g
Definition rgbw.h:124
bool invert3x3Q16(const i32(&in)[3][3], i32(&out)[3][3]) FL_NO_EXCEPT
Inverse of an s16.16 3x3 matrix, in s16.16, computed without floats.
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
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.