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
13 // Colorspace background info: https://hackmd.io/0wkiLmP7RWOFjcD13M870A
22 #include "AutoMappable.h"
23 #include "mozilla/Attributes.h"
26 # define ASSERT(EXPR) \
33 # define ASSERT(EXPR) (void)(EXPR)
36 namespace mozilla::color
{
38 struct YuvLumaCoeffs final
{
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
59 float b
= 0.04045 / 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
{
77 static constexpr auto Rec2020_10bit() { return Rec709(); }
79 static constexpr auto Rec2020_12bit() {
80 return PiecewiseGammaDesc
{
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
101 static constexpr auto Full8() { // AKA pc
109 static constexpr auto Float() { // Best for a LUT
110 return YcbcrDesc
{0.0, 1.0, 0.5, 1.0};
114 struct Chromaticities final
{
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
)
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() {
140 static constexpr auto Rec601_525_Ntsc() {
141 return Chromaticities
{
147 static constexpr auto Rec2020() {
148 return Chromaticities
{
154 static constexpr auto DisplayP3() {
155 return Chromaticities
{
165 struct YuvDesc final
{
166 YuvLumaCoeffs yCoeffs
;
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
);
184 template <class TT
, int NN
>
187 static constexpr auto N
= NN
;
189 std::array
<T
, N
> data
= {};
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
++) {
202 constexpr avec(const avec
<T
, N
- 2>& v
, T a
, T b
) {
203 for (int i
= 0; i
< N
- 2; i
++) {
210 MOZ_IMPLICIT
constexpr avec(const std::array
<T
, N
>& data
) {
214 explicit constexpr avec(const T v
) {
215 for (int i
= 0; i
< N
; i
++) {
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
]);
230 const auto& operator[](const size_t n
) const { return data
[n
]; }
231 auto& operator[](const size_t n
) { return data
[n
]; }
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()}); }
245 void set(const T 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
); }
258 friend avec operator OP(const avec a, const avec b) { \
260 for (int i = 0; i < N; i++) { \
261 c[i] = a[i] OP b[i]; \
265 friend avec operator OP(const avec a, const T b) { \
267 for (int i = 0; i < N; i++) { \
272 friend avec operator OP(const T a, const avec b) { \
274 for (int i = 0; i < N; i++) { \
285 friend bool operator==(const avec a
, const avec b
) {
287 for (int i
= 0; i
< N
; i
++) {
288 eq
&= (a
[i
] == b
[i
]);
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
;
302 for (int i
= 0; i
< N
; i
++) {
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
]);
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
]);
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
]);
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
]);
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
]);
360 template <int Y_Rows
, int X_Cols
>
362 static constexpr int y_rows
= Y_Rows
;
363 static constexpr int x_cols
= X_Cols
;
365 static constexpr auto Identity() {
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);
375 std::array
<avec
<float, X_Cols
>, Y_Rows
> rows
= {}; // row-major
379 constexpr mat() = default;
381 explicit constexpr mat(const std::array
<avec
<float, X_Cols
>, Y_Rows
>& rows
) {
385 template <int Y_Rows2
, int X_Cols2
>
386 explicit constexpr mat(const mat
<Y_Rows2
, X_Cols2
>& m
) {
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
);
406 friend auto operator*(const mat
& a
, const float b
) {
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
;
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
));
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); }
436 float determinant(const T
& m
) {
437 static_assert(T::x_cols
== T::y_rows
);
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
;
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) {
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
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
;
475 for (int ax
= 0; ax
< T::x_cols
; ax
++) {
482 for (int ay
= 0; ay
< T::y_rows
; ay
++) {
488 b
.at(ax
- x_skips
, ay
- y_skips
) = a
.at(ax
, ay
);
492 const auto minor
= determinant(b
);
498 /// The matrix of cofactors.
500 auto comatrix(const T
& a
) {
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
);
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
);
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
;
537 void ForEachIntWithin(const ivec3 size
, const F
& f
) {
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)) {
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
;
560 std::vector
<vec3
> data
;
564 static Lut3
Create(const ivec3 size
) {
567 lut
.data
.resize(size
.x() * size
.y() * size
.z());
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
);
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
);
594 /// p: [0, N-1] (clamps)
595 vec3
Fetch(ivec3 p
) const {
596 const auto i
= Index(p
);
600 /// in01: [0.0, 1.0] (clamps)
601 vec3
Sample(vec3 in01
) const;
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
);
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
648 defaultSize
= ivec3({31, 15, 31}); // Y, Cb, Cr
650 return ToLut3(defaultSize
);
654 } // namespace mozilla::color
658 #endif // MOZILLA_GFX_GL_COLORSPACES_H_