Bug 1800456 - Add failIfNot function to reftest-wait.js, r=emilio
[gecko.git] / gfx / gl / Colorspaces.h
blob04fe9e678fe000c0e26f0ebe1014563e1d15a4a6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #ifndef MOZILLA_GFX_GL_COLORSPACES_H_
6 #define MOZILLA_GFX_GL_COLORSPACES_H_
8 // Reference: https://hackmd.io/0wkiLmP7RWOFjcD13M870A
10 // We are going to be doing so, so many transforms, so descriptive labels are
11 // critical.
13 // Colorspace background info: https://hackmd.io/0wkiLmP7RWOFjcD13M870A
15 #include <algorithm>
16 #include <array>
17 #include <cmath>
18 #include <cstdlib>
19 #include <optional>
20 #include <vector>
22 #include "AutoMappable.h"
23 #include "mozilla/Attributes.h"
25 #ifdef DEBUG
26 # define ASSERT(EXPR) \
27 do { \
28 if (!(EXPR)) { \
29 __builtin_trap(); \
30 } \
31 } while (false)
32 #else
33 # define ASSERT(EXPR) (void)(EXPR)
34 #endif
36 namespace mozilla::color {
38 struct YuvLumaCoeffs final {
39 float r = 0.2126;
40 float g = 0.7152;
41 float b = 0.0722;
43 auto Members() const { return std::tie(r, g, b); }
44 INLINE_AUTO_MAPPABLE(YuvLumaCoeffs)
46 static constexpr auto Rec709() { return YuvLumaCoeffs(); }
48 static constexpr auto Rec2020() {
49 return YuvLumaCoeffs{0.2627, 0.6780, 0.0593};
53 struct PiecewiseGammaDesc final {
54 // tf = { k * linear | linear < b
55 // { a * pow(linear, 1/g) - (1-a) | linear >= b
57 // Default to Srgb
58 float a = 1.055;
59 float b = 0.04045 / 12.92;
60 float g = 2.4;
61 float k = 12.92;
63 auto Members() const { return std::tie(a, b, g, k); }
64 INLINE_AUTO_MAPPABLE(PiecewiseGammaDesc)
66 static constexpr auto Srgb() { return PiecewiseGammaDesc(); }
67 static constexpr auto DisplayP3() { return Srgb(); }
69 static constexpr auto Rec709() {
70 return PiecewiseGammaDesc{
71 1.099,
72 0.018,
73 1.0 / 0.45, // ~2.222
74 4.5,
77 static constexpr auto Rec2020_10bit() { return Rec709(); }
79 static constexpr auto Rec2020_12bit() {
80 return PiecewiseGammaDesc{
81 1.0993,
82 0.0181,
83 1.0 / 0.45, // ~2.222
84 4.5,
89 struct YcbcrDesc final {
90 float y0 = 16 / 255.0;
91 float y1 = 235 / 255.0;
92 float u0 = 128 / 255.0;
93 float uPlusHalf = 240 / 255.0;
95 auto Members() const { return std::tie(y0, y1, u0, uPlusHalf); }
96 INLINE_AUTO_MAPPABLE(YcbcrDesc)
98 static constexpr auto Narrow8() { // AKA limited/studio/tv
99 return YcbcrDesc();
101 static constexpr auto Full8() { // AKA pc
102 return YcbcrDesc{
103 0 / 255.0,
104 255 / 255.0,
105 128 / 255.0,
106 254 / 255.0,
109 static constexpr auto Float() { // Best for a LUT
110 return YcbcrDesc{0.0, 1.0, 0.5, 1.0};
114 struct Chromaticities final {
115 float rx = 0.640;
116 float ry = 0.330;
117 float gx = 0.300;
118 float gy = 0.600;
119 float bx = 0.150;
120 float by = 0.060;
121 // D65:
122 static constexpr float wx = 0.3127;
123 static constexpr float wy = 0.3290;
125 auto Members() const { return std::tie(rx, ry, gx, gy, bx, by); }
126 INLINE_AUTO_MAPPABLE(Chromaticities)
128 // -
130 static constexpr auto Rec709() { // AKA limited/studio/tv
131 return Chromaticities();
133 static constexpr auto Srgb() { return Rec709(); }
135 static constexpr auto Rec601_625_Pal() {
136 auto ret = Rec709();
137 ret.gx = 0.290;
138 return ret;
140 static constexpr auto Rec601_525_Ntsc() {
141 return Chromaticities{
142 0.630, 0.340, // r
143 0.310, 0.595, // g
144 0.155, 0.070, // b
147 static constexpr auto Rec2020() {
148 return Chromaticities{
149 0.708, 0.292, // r
150 0.170, 0.797, // g
151 0.131, 0.046, // b
154 static constexpr auto DisplayP3() {
155 return Chromaticities{
156 0.680, 0.320, // r
157 0.265, 0.690, // g
158 0.150, 0.060, // b
163 // -
165 struct YuvDesc final {
166 YuvLumaCoeffs yCoeffs;
167 YcbcrDesc ycbcr;
169 auto Members() const { return std::tie(yCoeffs, ycbcr); }
170 INLINE_AUTO_MAPPABLE(YuvDesc);
173 struct ColorspaceDesc final {
174 Chromaticities chrom;
175 std::optional<PiecewiseGammaDesc> tf;
176 std::optional<YuvDesc> yuv;
178 auto Members() const { return std::tie(chrom, tf, yuv); }
179 INLINE_AUTO_MAPPABLE(ColorspaceDesc);
182 // -
184 template <class TT, int NN>
185 struct avec final {
186 using T = TT;
187 static constexpr auto N = NN;
189 std::array<T, N> data = {};
191 // -
193 constexpr avec() = default;
194 constexpr avec(const avec&) = default;
196 constexpr avec(const avec<T, N - 1>& v, T a) {
197 for (int i = 0; i < N - 1; i++) {
198 data[i] = v[i];
200 data[N - 1] = a;
202 constexpr avec(const avec<T, N - 2>& v, T a, T b) {
203 for (int i = 0; i < N - 2; i++) {
204 data[i] = v[i];
206 data[N - 2] = a;
207 data[N - 1] = b;
210 MOZ_IMPLICIT constexpr avec(const std::array<T, N>& data) {
211 this->data = data;
214 explicit constexpr avec(const T v) {
215 for (int i = 0; i < N; i++) {
216 data[i] = v;
220 template <class T2, int N2>
221 explicit constexpr avec(const avec<T2, N2>& v) {
222 const auto n = std::min(N, N2);
223 for (int i = 0; i < n; i++) {
224 data[i] = static_cast<T>(v[i]);
228 // -
230 const auto& operator[](const size_t n) const { return data[n]; }
231 auto& operator[](const size_t n) { return data[n]; }
233 template <int i>
234 constexpr auto get() const {
235 return (i < N) ? data[i] : 0;
237 constexpr auto x() const { return get<0>(); }
238 constexpr auto y() const { return get<1>(); }
239 constexpr auto z() const { return get<2>(); }
240 constexpr auto w() const { return get<3>(); }
242 constexpr auto xyz() const { return vec3({x(), y(), z()}); }
244 template <int i>
245 void set(const T v) {
246 if (i < N) {
247 data[i] = v;
250 void x(const T v) { set<0>(v); }
251 void y(const T v) { set<1>(v); }
252 void z(const T v) { set<2>(v); }
253 void w(const T v) { set<3>(v); }
255 // -
257 #define _(OP) \
258 friend avec operator OP(const avec a, const avec b) { \
259 avec c; \
260 for (int i = 0; i < N; i++) { \
261 c[i] = a[i] OP b[i]; \
263 return c; \
265 friend avec operator OP(const avec a, const T b) { \
266 avec c; \
267 for (int i = 0; i < N; i++) { \
268 c[i] = a[i] OP b; \
270 return c; \
272 friend avec operator OP(const T a, const avec b) { \
273 avec c; \
274 for (int i = 0; i < N; i++) { \
275 c[i] = a OP b[i]; \
277 return c; \
279 _(+)
280 _(-)
281 _(*)
282 _(/)
283 #undef _
285 friend bool operator==(const avec a, const avec b) {
286 bool eq = true;
287 for (int i = 0; i < N; i++) {
288 eq &= (a[i] == b[i]);
290 return eq;
293 using vec3 = avec<float, 3>;
294 using vec4 = avec<float, 4>;
295 using ivec3 = avec<int32_t, 3>;
296 using ivec4 = avec<int32_t, 4>;
298 template <class T, int N>
299 T dot(const avec<T, N>& a, const avec<T, N>& b) {
300 const auto c = a * b;
301 T ret = 0;
302 for (int i = 0; i < N; i++) {
303 ret += c[i];
305 return ret;
308 template <class V>
309 V mix(const V& zero, const V& one, const float val) {
310 return zero * (1 - val) + one * val;
313 template <class T, int N>
314 auto min(const avec<T, N>& a, const avec<T, N>& b) {
315 auto ret = avec<T, N>{};
316 for (int i = 0; i < ret.N; i++) {
317 ret[i] = std::min(a[i], b[i]);
319 return ret;
322 template <class T, int N>
323 auto max(const avec<T, N>& a, const avec<T, N>& b) {
324 auto ret = avec<T, N>{};
325 for (int i = 0; i < ret.N; i++) {
326 ret[i] = std::max(a[i], b[i]);
328 return ret;
331 template <class T, int N>
332 auto floor(const avec<T, N>& a) {
333 auto ret = avec<T, N>{};
334 for (int i = 0; i < ret.N; i++) {
335 ret[i] = floorf(a[i]);
337 return ret;
340 template <class T, int N>
341 auto round(const avec<T, N>& a) {
342 auto ret = avec<T, N>{};
343 for (int i = 0; i < ret.N; i++) {
344 ret[i] = roundf(a[i]);
346 return ret;
349 template <class T, int N>
350 auto abs(const avec<T, N>& a) {
351 auto ret = avec<T, N>{};
352 for (int i = 0; i < ret.N; i++) {
353 ret[i] = std::abs(a[i]);
355 return ret;
358 // -
360 template <int Y_Rows, int X_Cols>
361 struct mat final {
362 static constexpr int y_rows = Y_Rows;
363 static constexpr int x_cols = X_Cols;
365 static constexpr auto Identity() {
366 auto ret = mat{};
367 for (int x = 0; x < x_cols; x++) {
368 for (int y = 0; y < y_rows; y++) {
369 ret.at(x, y) = (x == y ? 1 : 0);
372 return ret;
375 std::array<avec<float, X_Cols>, Y_Rows> rows = {}; // row-major
377 // -
379 constexpr mat() = default;
381 explicit constexpr mat(const std::array<avec<float, X_Cols>, Y_Rows>& rows) {
382 this->rows = rows;
385 template <int Y_Rows2, int X_Cols2>
386 explicit constexpr mat(const mat<Y_Rows2, X_Cols2>& m) {
387 *this = Identity();
388 for (int x = 0; x < std::min(X_Cols, X_Cols2); x++) {
389 for (int y = 0; y < std::min(Y_Rows, Y_Rows2); y++) {
390 at(x, y) = m.at(x, y);
395 const auto& at(const int x, const int y) const { return rows.at(y)[x]; }
396 auto& at(const int x, const int y) { return rows.at(y)[x]; }
398 friend auto operator*(const mat& a, const avec<float, X_Cols>& b_colvec) {
399 avec<float, Y_Rows> c_colvec;
400 for (int i = 0; i < y_rows; i++) {
401 c_colvec[i] = dot(a.rows.at(i), b_colvec);
403 return c_colvec;
406 friend auto operator*(const mat& a, const float b) {
407 mat c;
408 for (int x = 0; x < x_cols; x++) {
409 for (int y = 0; y < y_rows; y++) {
410 c.at(x, y) = a.at(x, y) * b;
413 return c;
415 friend auto operator/(const mat& a, const float b) { return a * (1 / b); }
417 template <int BCols, int BRows = X_Cols>
418 friend auto operator*(const mat& a, const mat<BRows, BCols>& b) {
419 const auto bt = transpose(b);
420 const auto& b_cols = bt.rows;
422 mat<Y_Rows, BCols> c;
423 for (int x = 0; x < BCols; x++) {
424 for (int y = 0; y < Y_Rows; y++) {
425 c.at(x, y) = dot(a.rows.at(y), b_cols.at(x));
428 return c;
431 using mat3 = mat<3, 3>;
432 using mat4 = mat<4, 4>;
434 inline float determinant(const mat<1, 1>& m) { return m.at(0, 0); }
435 template <class T>
436 float determinant(const T& m) {
437 static_assert(T::x_cols == T::y_rows);
439 float ret = 0;
440 for (int i = 0; i < T::x_cols; i++) {
441 const auto cofact = cofactor(m, i, 0);
442 ret += m.at(i, 0) * cofact;
444 return ret;
447 // -
449 template <class T>
450 float cofactor(const T& m, const int x_col, const int y_row) {
451 ASSERT(0 <= x_col && x_col < T::x_cols);
452 ASSERT(0 <= y_row && y_row < T::y_rows);
454 auto cofactor = minor_val(m, x_col, y_row);
455 if ((x_col + y_row) % 2 == 1) {
456 cofactor *= -1;
458 return cofactor;
461 // -
463 // Unfortunately, can't call this `minor(...)` because there is
464 // `#define minor(dev) gnu_dev_minor (dev)`
465 // in /usr/include/x86_64-linux-gnu/sys/sysmacros.h:62
466 template <class T>
467 float minor_val(const T& a, const int skip_x, const int skip_y) {
468 ASSERT(0 <= skip_x && skip_x < T::x_cols);
469 ASSERT(0 <= skip_y && skip_y < T::y_rows);
471 // A minor matrix is a matrix without its x_col and y_row.
472 mat<T::y_rows - 1, T::x_cols - 1> b;
474 int x_skips = 0;
475 for (int ax = 0; ax < T::x_cols; ax++) {
476 if (ax == skip_x) {
477 x_skips = 1;
478 continue;
481 int y_skips = 0;
482 for (int ay = 0; ay < T::y_rows; ay++) {
483 if (ay == skip_y) {
484 y_skips = 1;
485 continue;
488 b.at(ax - x_skips, ay - y_skips) = a.at(ax, ay);
492 const auto minor = determinant(b);
493 return minor;
496 // -
498 /// The matrix of cofactors.
499 template <class T>
500 auto comatrix(const T& a) {
501 auto b = T{};
502 for (int x = 0; x < T::x_cols; x++) {
503 for (int y = 0; y < T::y_rows; y++) {
504 b.at(x, y) = cofactor(a, x, y);
507 return b;
510 // -
512 template <class T>
513 auto transpose(const T& a) {
514 auto b = mat<T::x_cols, T::y_rows>{};
515 for (int x = 0; x < T::x_cols; x++) {
516 for (int y = 0; y < T::y_rows; y++) {
517 b.at(y, x) = a.at(x, y);
520 return b;
523 // -
525 template <class T>
526 inline T inverse(const T& a) {
527 const auto det = determinant(a);
528 const auto comat = comatrix(a);
529 const auto adjugate = transpose(comat);
530 const auto inv = adjugate / det;
531 return inv;
534 // -
536 template <class F>
537 void ForEachIntWithin(const ivec3 size, const F& f) {
538 ivec3 p;
539 for (p.z(0); p.z() < size.z(); p.z(p.z() + 1)) {
540 for (p.y(0); p.y() < size.y(); p.y(p.y() + 1)) {
541 for (p.x(0); p.x() < size.x(); p.x(p.x() + 1)) {
542 f(p);
547 template <class F>
548 void ForEachSampleWithin(const ivec3 size, const F& f) {
549 const auto div = vec3(size - 1);
550 ForEachIntWithin(size, [&](const ivec3& isrc) {
551 const auto fsrc = vec3(isrc) / div;
552 f(fsrc);
556 // -
558 struct Lut3 final {
559 ivec3 size;
560 std::vector<vec3> data;
562 // -
564 static Lut3 Create(const ivec3 size) {
565 Lut3 lut;
566 lut.size = size;
567 lut.data.resize(size.x() * size.y() * size.z());
568 return lut;
571 // -
573 /// p: [0, N-1] (clamps)
574 size_t Index(ivec3 p) const {
575 const auto scales = ivec3({1, size.x(), size.x() * size.y()});
576 p = max(ivec3(0), min(p, size - 1)); // clamp
577 return dot(p, scales);
580 // -
582 template <class F>
583 void SetMap(const F& dstFromSrc01) {
584 ForEachIntWithin(size, [&](const ivec3 p) {
585 const auto i = Index(p);
586 const auto src01 = vec3(p) / vec3(size - 1);
587 const auto dstVal = dstFromSrc01(src01);
588 data.at(i) = dstVal;
592 // -
594 /// p: [0, N-1] (clamps)
595 vec3 Fetch(ivec3 p) const {
596 const auto i = Index(p);
597 return data.at(i);
600 /// in01: [0.0, 1.0] (clamps)
601 vec3 Sample(vec3 in01) const;
604 // -
607 Naively, it would be ideal to map directly from ycbcr to rgb,
608 but headroom and footroom are problematic: For e.g. narrow-range-8-bit,
609 our naive LUT would start at absolute y=0/255. However, values only start
610 at y=16/255, and depending on where your first LUT sample is, you might get
611 very poor approximations for y=16/255.
612 Further, even for full-range-8-bit, y=-0.5 is encoded as 1/255. U and v
613 aren't *as* important as y, but we should try be accurate for the min and
614 max values. Additionally, it would be embarassing to get whites/greys wrong,
615 so preserving u=0.0 should also be a goal.
616 Finally, when using non-linear transfer functions, the linear approximation of a
617 point between two samples will be fairly inaccurate.
618 We preserve min and max by choosing our input range such that min and max are
619 the endpoints of their LUT axis.
620 We preserve accuracy (at and around) mid by choosing odd sizes for dimentions.
622 But also, the LUT is surprisingly robust, so check if the simple version works
623 before adding complexity!
626 struct ColorspaceTransform final {
627 ColorspaceDesc srcSpace;
628 ColorspaceDesc dstSpace;
629 mat4 srcRgbTfFromSrc;
630 std::optional<PiecewiseGammaDesc> srcTf;
631 mat3 dstRgbLinFromSrcRgbLin;
632 std::optional<PiecewiseGammaDesc> dstTf;
633 mat4 dstFromDstRgbTf;
635 static ColorspaceTransform Create(const ColorspaceDesc& src,
636 const ColorspaceDesc& dst);
638 // -
640 vec3 DstFromSrc(vec3 src) const;
642 std::optional<mat4> ToMat4() const;
644 Lut3 ToLut3(const ivec3 size) const;
645 Lut3 ToLut3() const {
646 auto defaultSize = ivec3({31, 31, 15}); // Order of importance: G, R, B
647 if (srcSpace.yuv) {
648 defaultSize = ivec3({31, 15, 31}); // Y, Cb, Cr
650 return ToLut3(defaultSize);
654 } // namespace mozilla::color
656 #undef ASSERT
658 #endif // MOZILLA_GFX_GL_COLORSPACES_H_