Colorspaces.h (27545B)
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/. */ 4 5 #ifndef MOZILLA_GFX_GL_COLORSPACES_H_ 6 #define MOZILLA_GFX_GL_COLORSPACES_H_ 7 8 // We are going to be doing so, so many transforms, so descriptive labels are 9 // critical. 10 11 // Colorspace background info: https://hackmd.io/0wkiLmP7RWOFjcD13M870A 12 13 #include <algorithm> 14 #include <array> 15 #include <cmath> 16 #include <cstdint> 17 #include <cstdlib> 18 #include <functional> 19 #include <optional> 20 #include <ostream> 21 #include <vector> 22 23 #include "AutoMappable.h" 24 #include "mozilla/Assertions.h" 25 #include "mozilla/Attributes.h" 26 #include "mozilla/Span.h" 27 28 #ifdef DEBUG 29 # define ASSERT(EXPR) \ 30 do { \ 31 if (!(EXPR)) { \ 32 __builtin_trap(); \ 33 } \ 34 } while (false) 35 #else 36 # define ASSERT(EXPR) (void)(EXPR) 37 #endif 38 39 struct _qcms_profile; 40 typedef struct _qcms_profile qcms_profile; 41 42 namespace mozilla::color { 43 44 // - 45 46 struct YuvLumaCoeffs final { 47 float r = 0.2126; 48 float g = 0.7152; 49 float b = 0.0722; 50 51 auto Members() const { return std::tie(r, g, b); } 52 MOZ_MIXIN_DERIVE_CMP_OPS_BY_MEMBERS(YuvLumaCoeffs) 53 54 static constexpr auto Rec601() { 55 return YuvLumaCoeffs{ 56 .r = 0.299, 57 .g = 0.587, 58 .b = 0.114, 59 }; 60 } 61 62 static constexpr auto Rec709() { return YuvLumaCoeffs(); } 63 64 static constexpr auto Rec2020() { 65 return YuvLumaCoeffs{ 66 .r = 0.2627, 67 .g = 0.6780, 68 .b = 0.0593, 69 }; 70 } 71 72 static constexpr auto Gbr() { return YuvLumaCoeffs{.r = 0, .g = 1, .b = 0}; } 73 }; 74 75 struct PiecewiseGammaDesc final { 76 // tf = { k * linear | linear < b 77 // { a * pow(linear, 1/g) - (a-1) | linear >= b 78 79 // Default to Srgb 80 float a = 1.055; 81 float b = 0.04045 / 12.92; 82 float g = 2.4; 83 float k = 12.92; 84 85 auto Members() const { return std::tie(a, b, g, k); } 86 MOZ_MIXIN_DERIVE_CMP_OPS_BY_MEMBERS(PiecewiseGammaDesc) 87 88 static constexpr auto Srgb() { return PiecewiseGammaDesc(); } 89 static constexpr auto DisplayP3() { return Srgb(); } 90 91 static constexpr auto Rec709() { 92 return PiecewiseGammaDesc{ 93 .a = 1.099, 94 .b = 0.018, 95 .g = 1.0 / 0.45, // ~2.222 96 .k = 4.5, 97 }; 98 } 99 // FYI: static constexpr auto Rec2020_10bit() { return Rec709(); } 100 static constexpr auto Rec2020_12bit() { 101 return PiecewiseGammaDesc{ 102 .a = 1.0993, 103 .b = 0.0181, 104 .g = 1.0 / 0.45, // ~2.222 105 .k = 4.5, 106 }; 107 } 108 }; 109 110 struct YcbcrDesc final { 111 float y0 = 16 / 255.0; 112 float y1 = 235 / 255.0; 113 float u0 = 128 / 255.0; 114 float uPlusHalf = 240 / 255.0; 115 116 auto Members() const { return std::tie(y0, y1, u0, uPlusHalf); } 117 MOZ_MIXIN_DERIVE_CMP_OPS_BY_MEMBERS(YcbcrDesc) 118 119 static constexpr auto Narrow8() { // AKA limited/studio/tv 120 return YcbcrDesc(); 121 } 122 static constexpr auto Full8() { // AKA pc 123 return YcbcrDesc{ 124 .y0 = 0 / 255.0, 125 .y1 = 255 / 255.0, 126 .u0 = 128 / 255.0, 127 .uPlusHalf = 254 / 255.0, 128 }; 129 } 130 static constexpr auto Float() { // Best for a LUT 131 return YcbcrDesc{.y0 = 0.0, .y1 = 1.0, .u0 = 0.5, .uPlusHalf = 1.0}; 132 } 133 }; 134 135 struct Chromaticities final { 136 float rx = 0.640; 137 float ry = 0.330; 138 float gx = 0.300; 139 float gy = 0.600; 140 float bx = 0.150; 141 float by = 0.060; 142 // D65: 143 static constexpr float wx = 0.3127; 144 static constexpr float wy = 0.3290; 145 146 auto Members() const { return std::tie(rx, ry, gx, gy, bx, by); } 147 MOZ_MIXIN_DERIVE_CMP_OPS_BY_MEMBERS(Chromaticities) 148 149 // - 150 151 static constexpr auto Rec709() { // AKA limited/studio/tv 152 return Chromaticities(); 153 } 154 static constexpr auto Srgb() { return Rec709(); } 155 156 static constexpr auto Rec601_625_Pal() { 157 auto ret = Rec709(); 158 ret.gx = 0.290; 159 return ret; 160 } 161 static constexpr auto Rec601_525_Ntsc() { 162 return Chromaticities{ 163 .rx = 0.630, 164 .ry = 0.340, // r 165 .gx = 0.310, 166 .gy = 0.595, // g 167 .bx = 0.155, 168 .by = 0.070, // b 169 }; 170 } 171 static constexpr auto Rec2020() { 172 return Chromaticities{ 173 .rx = 0.708, 174 .ry = 0.292, // r 175 .gx = 0.170, 176 .gy = 0.797, // g 177 .bx = 0.131, 178 .by = 0.046, // b 179 }; 180 } 181 static constexpr auto DisplayP3() { 182 return Chromaticities{ 183 .rx = 0.680, 184 .ry = 0.320, // r 185 .gx = 0.265, 186 .gy = 0.690, // g 187 .bx = 0.150, 188 .by = 0.060, // b 189 }; 190 } 191 }; 192 193 // - 194 195 struct YuvDesc final { 196 YuvLumaCoeffs yCoeffs; 197 YcbcrDesc ycbcr; 198 199 auto Members() const { return std::tie(yCoeffs, ycbcr); } 200 MOZ_MIXIN_DERIVE_CMP_OPS_BY_MEMBERS(YuvDesc) 201 }; 202 203 struct ColorspaceDesc final { 204 Chromaticities chrom; 205 std::optional<PiecewiseGammaDesc> tf; 206 std::optional<YuvDesc> yuv; 207 208 auto Members() const { return std::tie(chrom, tf, yuv); } 209 MOZ_MIXIN_DERIVE_CMP_OPS_BY_MEMBERS(ColorspaceDesc) 210 }; 211 212 // - 213 214 } // namespace mozilla::color 215 216 #define _(X) \ 217 template <> \ 218 struct std::hash<X> : mozilla::StdHashMembers<X> {}; 219 220 _(mozilla::color::YuvLumaCoeffs) 221 _(mozilla::color::PiecewiseGammaDesc) 222 _(mozilla::color::YcbcrDesc) 223 _(mozilla::color::Chromaticities) 224 _(mozilla::color::YuvDesc) 225 _(mozilla::color::ColorspaceDesc) 226 227 #undef _ 228 229 namespace mozilla::color { 230 231 // - 232 233 template <class TT, int NN> 234 struct avec final { 235 using T = TT; 236 static constexpr auto N = NN; 237 238 std::array<T, N> data = {}; 239 240 // - 241 242 constexpr avec() = default; 243 constexpr avec(const avec&) = default; 244 245 constexpr avec(const avec<T, N - 1>& v, T a) { 246 for (int i = 0; i < N - 1; i++) { 247 data[i] = v[i]; 248 } 249 data[N - 1] = a; 250 } 251 constexpr avec(const avec<T, N - 2>& v, T a, T b) { 252 for (int i = 0; i < N - 2; i++) { 253 data[i] = v[i]; 254 } 255 data[N - 2] = a; 256 data[N - 1] = b; 257 } 258 259 MOZ_IMPLICIT constexpr avec(const std::array<T, N>& data) { 260 this->data = data; 261 } 262 263 explicit constexpr avec(const T v) { 264 for (int i = 0; i < N; i++) { 265 data[i] = v; 266 } 267 } 268 269 template <class T2, int N2> 270 explicit constexpr avec(const avec<T2, N2>& v) { 271 const auto n = std::min(N, N2); 272 for (int i = 0; i < n; i++) { 273 data[i] = static_cast<T>(v[i]); 274 } 275 } 276 277 // - 278 279 constexpr auto size() const { return data.size(); } 280 281 const auto& operator[](const size_t n) const { return data[n]; } 282 auto& operator[](const size_t n) { return data[n]; } 283 284 template <int i> 285 constexpr auto get() const { 286 return (i < N) ? data[i] : 0; 287 } 288 constexpr auto x() const { return get<0>(); } 289 constexpr auto y() const { return get<1>(); } 290 constexpr auto z() const { return get<2>(); } 291 constexpr auto w() const { return get<3>(); } 292 293 constexpr auto xyz() const { return vec3({x(), y(), z()}); } 294 295 template <int i> 296 void set(const T v) { 297 if (i < N) { 298 data[i] = v; 299 } 300 } 301 void x(const T v) { set<0>(v); } 302 void y(const T v) { set<1>(v); } 303 void z(const T v) { set<2>(v); } 304 void w(const T v) { set<3>(v); } 305 306 // - 307 308 #define _(OP) \ 309 friend avec operator OP(const avec a, const avec b) { \ 310 avec c; \ 311 for (int i = 0; i < N; i++) { \ 312 c[i] = a[i] OP b[i]; \ 313 } \ 314 return c; \ 315 } \ 316 friend avec operator OP(const avec a, const T b) { \ 317 avec c; \ 318 for (int i = 0; i < N; i++) { \ 319 c[i] = a[i] OP b; \ 320 } \ 321 return c; \ 322 } \ 323 friend avec operator OP(const T a, const avec b) { \ 324 avec c; \ 325 for (int i = 0; i < N; i++) { \ 326 c[i] = a OP b[i]; \ 327 } \ 328 return c; \ 329 } 330 _(+) 331 _(-) 332 _(*) 333 _(/) 334 #undef _ 335 336 friend bool operator==(const avec a, const avec b) { 337 bool eq = true; 338 for (int i = 0; i < N; i++) { 339 eq &= (a[i] == b[i]); 340 } 341 return eq; 342 } 343 344 friend std::ostream& operator<<(std::ostream& s, const avec& a) { 345 s << "avec("; 346 const char* delim = ""; 347 for (const auto& v : a.data) { 348 s << delim << v; 349 delim = ", "; 350 } 351 return s << ")"; 352 } 353 }; 354 using vec2 = avec<float, 2>; 355 using vec3 = avec<float, 3>; 356 using vec4 = avec<float, 4>; 357 using ivec3 = avec<int32_t, 3>; 358 using ivec4 = avec<int32_t, 4>; 359 360 template <class T, int N> 361 T dot(const avec<T, N>& a, const avec<T, N>& b) { 362 const auto c = a * b; 363 T ret = 0; 364 for (int i = 0; i < N; i++) { 365 ret += c[i]; 366 } 367 return ret; 368 } 369 370 template <class V> 371 V mix(const V& zero, const V& one, const float val) { 372 return zero * (1 - val) + one * val; 373 } 374 375 template <class T, int N> 376 auto min(const avec<T, N>& a, const avec<T, N>& b) { 377 auto ret = avec<T, N>{}; 378 for (int i = 0; i < ret.N; i++) { 379 ret[i] = std::min(a[i], b[i]); 380 } 381 return ret; 382 } 383 384 template <class T, int N> 385 auto max(const avec<T, N>& a, const avec<T, N>& b) { 386 auto ret = avec<T, N>{}; 387 for (int i = 0; i < ret.N; i++) { 388 ret[i] = std::max(a[i], b[i]); 389 } 390 return ret; 391 } 392 393 template <class T, int N> 394 auto clamp(const avec<T, N>& v, const avec<T, N>& lo, const avec<T, N>& hi) { 395 return max(lo, min(v, hi)); 396 } 397 398 template <class T, int N> 399 auto floor(const avec<T, N>& a) { 400 auto ret = avec<T, N>{}; 401 for (int i = 0; i < ret.N; i++) { 402 ret[i] = floorf(a[i]); 403 } 404 return ret; 405 } 406 407 template <class T, int N> 408 auto round(const avec<T, N>& a) { 409 auto ret = avec<T, N>{}; 410 for (int i = 0; i < ret.N; i++) { 411 ret[i] = roundf(a[i]); 412 } 413 return ret; 414 } 415 416 template <class T, int N> 417 auto abs(const avec<T, N>& a) { 418 auto ret = avec<T, N>{}; 419 for (int i = 0; i < ret.N; i++) { 420 ret[i] = std::abs(a[i]); 421 } 422 return ret; 423 } 424 425 // - 426 427 template <int Y_Rows, int X_Cols> 428 struct mat final { 429 static constexpr int y_rows = Y_Rows; 430 static constexpr int x_cols = X_Cols; 431 432 static constexpr auto Identity() { 433 auto ret = mat{}; 434 for (int i = 0; i < std::min(x_cols, y_rows); i++) { 435 ret.at(i, i) = 1; 436 } 437 return ret; 438 } 439 static constexpr auto Scale(const avec<float, std::min(x_cols, y_rows)>& v) { 440 auto ret = mat{}; 441 for (int i = 0; i < v.N; i++) { 442 ret.at(i, i) = v[i]; 443 } 444 return ret; 445 } 446 447 std::array<avec<float, X_Cols>, Y_Rows> rows = {}; // row-major 448 449 // - 450 451 constexpr mat() = default; 452 453 explicit constexpr mat(const std::array<avec<float, X_Cols>, Y_Rows>& rows) { 454 this->rows = rows; 455 } 456 457 template <int Y_Rows2, int X_Cols2> 458 explicit constexpr mat(const mat<Y_Rows2, X_Cols2>& m) { 459 *this = Identity(); 460 for (int x = 0; x < std::min(X_Cols, X_Cols2); x++) { 461 for (int y = 0; y < std::min(Y_Rows, Y_Rows2); y++) { 462 at(x, y) = m.at(x, y); 463 } 464 } 465 } 466 467 constexpr bool operator==(const mat& rhs) const { 468 return this->rows == rhs.rows; 469 } 470 constexpr bool operator!=(const mat& rhs) const { return !(*this == rhs); } 471 472 const auto& at(const int x, const int y) const { return rows.at(y)[x]; } 473 auto& at(const int x, const int y) { return rows.at(y)[x]; } 474 475 friend auto operator*(const mat& a, const avec<float, X_Cols>& b_colvec) { 476 avec<float, Y_Rows> c_colvec; 477 for (int i = 0; i < y_rows; i++) { 478 c_colvec[i] = dot(a.rows.at(i), b_colvec); 479 } 480 return c_colvec; 481 } 482 483 friend auto operator*(const mat& a, const float b) { 484 mat c; 485 for (int x = 0; x < x_cols; x++) { 486 for (int y = 0; y < y_rows; y++) { 487 c.at(x, y) = a.at(x, y) * b; 488 } 489 } 490 return c; 491 } 492 friend auto operator/(const mat& a, const float b) { return a * (1 / b); } 493 494 template <int BCols, int BRows = X_Cols> 495 friend auto operator*(const mat& a, const mat<BRows, BCols>& b) { 496 const auto bt = transpose(b); 497 const auto& b_cols = bt.rows; 498 499 mat<Y_Rows, BCols> c; 500 for (int x = 0; x < BCols; x++) { 501 for (int y = 0; y < Y_Rows; y++) { 502 c.at(x, y) = dot(a.rows.at(y), b_cols.at(x)); 503 } 504 } 505 return c; 506 } 507 508 // For e.g. similarity evaluation 509 friend auto operator-(const mat& a, const mat& b) { 510 mat c; 511 for (int y = 0; y < y_rows; y++) { 512 c.rows[y] = a.rows[y] - b.rows[y]; 513 } 514 return c; 515 } 516 }; 517 518 template <class M> 519 inline float dotDifference(const M& a, const M& b) { 520 const auto c = a - b; 521 const auto d = c * avec<float, M::x_cols>(1); 522 const auto d2 = dot(d, d); 523 return d2; 524 } 525 template <class M> 526 inline bool approx(const M& a, const M& b, const float eps = 0.0001) { 527 const auto errSquared = dotDifference(a, b); 528 return errSquared <= (eps * eps); 529 } 530 531 using mat3 = mat<3, 3>; 532 using mat4 = mat<4, 4>; 533 534 inline float determinant(const mat<1, 1>& m) { return m.at(0, 0); } 535 template <class T> 536 float determinant(const T& m) { 537 static_assert(T::x_cols == T::y_rows); 538 539 float ret = 0; 540 for (int i = 0; i < T::x_cols; i++) { 541 const auto cofact = cofactor(m, i, 0); 542 ret += m.at(i, 0) * cofact; 543 } 544 return ret; 545 } 546 547 // - 548 549 template <class T> 550 float cofactor(const T& m, const int x_col, const int y_row) { 551 ASSERT(0 <= x_col && x_col < T::x_cols); 552 ASSERT(0 <= y_row && y_row < T::y_rows); 553 554 auto cofactor = minor_val(m, x_col, y_row); 555 if ((x_col + y_row) % 2 == 1) { 556 cofactor *= -1; 557 } 558 return cofactor; 559 } 560 561 // - 562 563 // Unfortunately, can't call this `minor(...)` because there is 564 // `#define minor(dev) gnu_dev_minor (dev)` 565 // in /usr/include/x86_64-linux-gnu/sys/sysmacros.h:62 566 template <class T> 567 float minor_val(const T& a, const int skip_x, const int skip_y) { 568 ASSERT(0 <= skip_x && skip_x < T::x_cols); 569 ASSERT(0 <= skip_y && skip_y < T::y_rows); 570 571 // A minor matrix is a matrix without its x_col and y_row. 572 mat<T::y_rows - 1, T::x_cols - 1> b; 573 574 int x_skips = 0; 575 for (int ax = 0; ax < T::x_cols; ax++) { 576 if (ax == skip_x) { 577 x_skips = 1; 578 continue; 579 } 580 581 int y_skips = 0; 582 for (int ay = 0; ay < T::y_rows; ay++) { 583 if (ay == skip_y) { 584 y_skips = 1; 585 continue; 586 } 587 588 b.at(ax - x_skips, ay - y_skips) = a.at(ax, ay); 589 } 590 } 591 592 const auto minor = determinant(b); 593 return minor; 594 } 595 596 // - 597 598 /// The matrix of cofactors. 599 template <class T> 600 auto comatrix(const T& a) { 601 auto b = T{}; 602 for (int x = 0; x < T::x_cols; x++) { 603 for (int y = 0; y < T::y_rows; y++) { 604 b.at(x, y) = cofactor(a, x, y); 605 } 606 } 607 return b; 608 } 609 610 // - 611 612 template <class T> 613 auto transpose(const T& a) { 614 auto b = mat<T::x_cols, T::y_rows>{}; 615 for (int x = 0; x < T::x_cols; x++) { 616 for (int y = 0; y < T::y_rows; y++) { 617 b.at(y, x) = a.at(x, y); 618 } 619 } 620 return b; 621 } 622 623 // - 624 625 template <class T> 626 inline T inverse(const T& a) { 627 const auto det = determinant(a); 628 const auto comat = comatrix(a); 629 const auto adjugate = transpose(comat); 630 const auto inv = adjugate / det; 631 return inv; 632 } 633 634 // - 635 636 template <class F> 637 void ForEachIntWithin(const ivec3 size, const F& f) { 638 ivec3 p; 639 for (p.z(0); p.z() < size.z(); p.z(p.z() + 1)) { 640 for (p.y(0); p.y() < size.y(); p.y(p.y() + 1)) { 641 for (p.x(0); p.x() < size.x(); p.x(p.x() + 1)) { 642 f(p); 643 } 644 } 645 } 646 } 647 template <class F> 648 void ForEachSampleWithin(const ivec3 size, const F& f) { 649 const auto div = vec3(size - 1); 650 ForEachIntWithin(size, [&](const ivec3& isrc) { 651 const auto fsrc = vec3(isrc) / div; 652 f(fsrc); 653 }); 654 } 655 656 // - 657 658 struct Lut3 final { 659 ivec3 size; 660 std::vector<vec3> data; 661 662 // - 663 664 static Lut3 Create(const ivec3 size) { 665 Lut3 lut; 666 lut.size = size; 667 lut.data.resize(size.x() * size.y() * size.z()); 668 return lut; 669 } 670 671 // - 672 673 /// p: [0, N-1] (clamps) 674 size_t Index(ivec3 p) const { 675 const auto scales = ivec3({1, size.x(), size.x() * size.y()}); 676 p = max(ivec3(0), min(p, size - 1)); // clamp 677 return dot(p, scales); 678 } 679 680 // - 681 682 template <class F> 683 void SetMap(const F& dstFromSrc01) { 684 ForEachIntWithin(size, [&](const ivec3 p) { 685 const auto i = Index(p); 686 const auto src01 = vec3(p) / vec3(size - 1); 687 const auto dstVal = dstFromSrc01(src01); 688 data.at(i) = dstVal; 689 }); 690 } 691 692 // - 693 694 /// p: [0, N-1] (clamps) 695 vec3 Fetch(ivec3 p) const { 696 const auto i = Index(p); 697 return data.at(i); 698 } 699 700 /// in01: [0.0, 1.0] (clamps) 701 vec3 Sample(vec3 in01) const; 702 }; 703 704 // - 705 706 /** 707 Naively, it would be ideal to map directly from ycbcr to rgb, 708 but headroom and footroom are problematic: For e.g. narrow-range-8-bit, 709 our naive LUT would start at absolute y=0/255. However, values only start 710 at y=16/255, and depending on where your first LUT sample is, you might get 711 very poor approximations for y=16/255. 712 Further, even for full-range-8-bit, y=-0.5 is encoded as 1/255. U and v 713 aren't *as* important as y, but we should try be accurate for the min and 714 max values. Additionally, it would be embarassing to get whites/greys wrong, 715 so preserving u=0.0 should also be a goal. 716 Finally, when using non-linear transfer functions, the linear approximation of a 717 point between two samples will be fairly inaccurate. 718 We preserve min and max by choosing our input range such that min and max are 719 the endpoints of their LUT axis. 720 We preserve accuracy (at and around) mid by choosing odd sizes for dimentions. 721 722 But also, the LUT is surprisingly robust, so check if the simple version works 723 before adding complexity! 724 **/ 725 726 struct ColorspaceTransform final { 727 ColorspaceDesc srcSpace; 728 ColorspaceDesc dstSpace; 729 mat4 srcRgbTfFromSrc; 730 std::optional<PiecewiseGammaDesc> srcTf; 731 mat3 dstRgbLinFromSrcRgbLin; 732 std::optional<PiecewiseGammaDesc> dstTf; 733 mat4 dstFromDstRgbTf; 734 735 static ColorspaceTransform Create(const ColorspaceDesc& src, 736 const ColorspaceDesc& dst); 737 738 // - 739 740 vec3 DstFromSrc(vec3 src) const; 741 742 std::optional<mat4> ToMat4() const; 743 744 Lut3 ToLut3(const ivec3 size) const; 745 Lut3 ToLut3() const { 746 auto defaultSize = ivec3({31, 31, 15}); // Order of importance: G, R, B 747 if (srcSpace.yuv) { 748 defaultSize = ivec3({31, 15, 31}); // Y, Cb, Cr 749 } 750 return ToLut3(defaultSize); 751 } 752 }; 753 754 // - 755 756 struct RgbTransferTables { 757 std::vector<float> r; 758 std::vector<float> g; 759 std::vector<float> b; 760 }; 761 float GuessGamma(const std::vector<float>& vals, float exp_guess = 1.0); 762 763 static constexpr auto D65 = vec2{{0.3127, 0.3290}}; 764 static constexpr auto D50 = vec2{{0.34567, 0.35850}}; 765 mat3 XyzAFromXyzB_BradfordLinear(const vec2 xyA, const vec2 xyB); 766 767 // - 768 769 struct ColorProfileDesc { 770 // ICC profiles are phrased as PCS-from-encoded (PCS is CIEXYZ-D50) 771 // However, all of our colorspaces are D65, so let's normalize to that, 772 // even though it's a reversible transform. 773 color::mat4 rgbFromYcbcr = color::mat4::Identity(); 774 RgbTransferTables linearFromTf; 775 color::mat3 xyzd65FromLinearRgb = color::mat3::Identity(); 776 777 static ColorProfileDesc From(const ColorspaceDesc&); 778 static ColorProfileDesc From(const qcms_profile&); 779 }; 780 781 template <class C> 782 inline float SampleOutByIn(const C& outByIn, const float in) { 783 switch (outByIn.size()) { 784 case 0: 785 return in; 786 case 1: 787 return outByIn.at(0); 788 } 789 MOZ_ASSERT(outByIn.size() >= 2); 790 791 // Estimate based on nearest (first) derivative: 792 // Find the nearest point to `in` in `outByIn`. 793 const auto inId = in * (outByIn.size() - 1); 794 const auto inId0F = std::clamp(floorf(inId), 0.f, float(outByIn.size() - 2)); 795 const auto inId0 = size_t(inId0F); 796 const auto out0 = outByIn.at(inId0 + 0); 797 const auto out1 = outByIn.at(inId0 + 1); 798 const auto d_inId0 = float(1); 799 const auto d_out0 = out1 - out0; 800 const auto d_inId = inId - inId0; 801 802 const auto out = out0 + (d_out0 / d_inId0) * d_inId; 803 // printf("SampleOutByIn(%f)->%f\n", in, out); 804 return out; 805 } 806 807 template <class C> 808 inline float SampleInByOut(const C& outByIn, const float out) { 809 MOZ_ASSERT(outByIn.size() >= 2); 810 const auto begin = outByIn.begin(); 811 812 const auto out0_itr = std::lower_bound(begin + 1, outByIn.end() - 1, out) - 1; 813 814 const auto in0 = float(out0_itr - begin) / (outByIn.size() - 1); 815 const auto out0 = *out0_itr; 816 const auto d_in = float(1) / (outByIn.size() - 1); 817 const auto d_out = *(out0_itr + 1) - *out0_itr; 818 819 // printf("%f + (%f / %f) * (%f - %f)\n", in0, d_in, d_out, out, out0); 820 const auto in = in0 + (d_in / d_out) * (out - out0); 821 // printf("SampleInByOut(%f)->%f\n", out, in); 822 return in; 823 } 824 825 template <class C, class FnLessEqualT = std::less_equal<typename C::value_type>> 826 inline bool IsMonotonic(const C& vals, const FnLessEqualT& LessEqual = {}) { 827 bool ok = true; 828 const auto begin = vals.begin(); 829 for (size_t i = 1; i < vals.size(); i++) { 830 const auto itr = begin + i; 831 ok &= LessEqual(*(itr - 1), *itr); 832 // Assert(true, [&]() { 833 // return prints("[%zu]->%f <= [%zu]->%f", i-1, *(itr-1), i, *itr); 834 // }); 835 } 836 return ok; 837 } 838 839 template <class T, class I> 840 inline std::optional<I> SeekNeq(const T& ref, const I first, const I last) { 841 const auto inc = (last - first) > 0 ? 1 : -1; 842 auto itr = first; 843 while (true) { 844 if (*itr != ref) return itr; 845 if (itr == last) return {}; 846 itr += inc; 847 } 848 } 849 850 template <class T> 851 struct TwoPoints { 852 struct { 853 T x; 854 T y; 855 } p0; 856 struct { 857 T x; 858 T y; 859 } p1; 860 861 T y(const T x) const { 862 const auto dx = p1.x - p0.x; 863 const auto dy = p1.y - p0.y; 864 return p0.y + dy / dx * (x - p0.x); 865 } 866 }; 867 868 /// Fills `vals` with `x:[0..vals.size()-1] => line.y(x)`. 869 template <class T> 870 static void LinearFill(T& vals, const TwoPoints<float>& line) { 871 float x = -1; 872 for (auto& val : vals) { 873 x += 1; 874 val = line.y(x); 875 } 876 } 877 878 // - 879 880 inline void DequantizeMonotonic(const Span<float> vals) { 881 MOZ_ASSERT(IsMonotonic(vals)); 882 883 const auto first = vals.begin(); 884 const auto end = vals.end(); 885 if (first == end) return; 886 const auto last = end - 1; 887 if (first == last) return; 888 889 // Three monotonic cases: 890 // 1. [0,0,0,0] 891 // 2. [0,0,1,1] 892 // 3. [0,1,1,2] 893 894 const auto body_first = SeekNeq(*first, first, last); 895 if (!body_first) { 896 // E.g. [0,0,0,0] 897 return; 898 } 899 900 const auto body_last = SeekNeq(*last, last, *body_first); 901 if (!body_last) { 902 // E.g. [0,0,1,1] 903 // This isn't the most accurate, but close enough. 904 // print("#2: %s", to_str(vals).c_str()); 905 LinearFill(vals, { 906 {0, *first}, 907 {float(vals.size() - 1), *last}, 908 }); 909 // print(" -> %s\n", to_str(vals).c_str()); 910 return; 911 } 912 913 // E.g. [0,1,1,2] 914 // ^^^ body 915 // => f(0.5)->0.5, f(2.5)->1.5 916 // => f(x) = f(x0) + (x-x0) * (f(x1) - f(x0)) / (x1-x0) 917 // => f(x) = f(x0) + (x-x0) * dfdx 918 919 const auto head_end = *body_first; 920 const auto head = vals.subspan(0, head_end - vals.begin()); 921 const auto tail_begin = *body_last + 1; 922 const auto tail = vals.subspan(tail_begin - vals.begin()); 923 // print("head tail: %s %s\n", 924 // to_str(head).c_str(), 925 // to_str(tail).c_str()); 926 927 // const auto body = vals->subspan(head.size(), vals->size()-tail.size()); 928 auto next_part_first = head_end; 929 while (next_part_first != tail_begin) { 930 const auto part_first = next_part_first; 931 // print("part_first: %f\n", *part_first); 932 next_part_first = *SeekNeq(*part_first, part_first, tail_begin); 933 // print("next_part_first: %f\n", *next_part_first); 934 const auto part = 935 Span<float>{part_first, size_t(next_part_first - part_first)}; 936 // print("part: %s\n", to_str(part).c_str()); 937 const auto prev_part_last = part_first - 1; 938 const auto part_last = next_part_first - 1; 939 const auto line = TwoPoints<float>{ 940 {-0.5, (*prev_part_last + *part_first) / 2}, 941 {part.size() - 0.5f, (*part_last + *next_part_first) / 2}, 942 }; 943 LinearFill(part, line); 944 } 945 946 static constexpr bool INFER_HEAD_TAIL_FROM_BODY_EDGE = false; 947 // Basically ignore contents of head and tail, and infer from edges of body. 948 // print("3: %s\n", to_str(vals).c_str()); 949 if (!IsMonotonic(head, std::less<float>{})) { 950 if (!INFER_HEAD_TAIL_FROM_BODY_EDGE) { 951 LinearFill(head, 952 { 953 {0, *head.begin()}, 954 {head.size() - 0.5f, (*(head.end() - 1) + *head_end) / 2}, 955 }); 956 } else { 957 LinearFill(head, { 958 {head.size() + 0.0f, *head_end}, 959 {head.size() + 1.0f, *(head_end + 1)}, 960 }); 961 } 962 } 963 if (!IsMonotonic(tail, std::less<float>{})) { 964 if (!INFER_HEAD_TAIL_FROM_BODY_EDGE) { 965 LinearFill(tail, { 966 {-0.5, (*(tail_begin - 1) + *tail.begin()) / 2}, 967 {tail.size() - 1.0f, *(tail.end() - 1)}, 968 }); 969 } else { 970 LinearFill(tail, { 971 {-2.0f, *(tail_begin - 2)}, 972 {-1.0f, *(tail_begin - 1)}, 973 }); 974 } 975 } 976 // print("3: %s\n", to_str(vals).c_str()); 977 MOZ_ASSERT(IsMonotonic(vals, std::less<float>{})); 978 979 // Rescale, because we tend to lose range. 980 static constexpr bool RESCALE = false; 981 if (RESCALE) { 982 const auto firstv = *first; 983 const auto lastv = *last; 984 for (auto& val : vals) { 985 val = (val - firstv) / (lastv - firstv); 986 } 987 } 988 // print("4: %s\n", to_str(vals).c_str()); 989 } 990 991 template <class In, class Out> 992 static void InvertLut(const In& lut, Out* const out_invertedLut) { 993 MOZ_ASSERT(IsMonotonic(lut)); 994 auto plut = &lut; 995 auto vec = std::vector<float>{}; 996 if (!IsMonotonic(lut, std::less<float>{})) { 997 // print("Not strictly monotonic...\n"); 998 vec.assign(lut.begin(), lut.end()); 999 DequantizeMonotonic(vec); 1000 plut = &vec; 1001 // print(" Now strictly monotonic: %i: %s\n", 1002 // int(IsMonotonic(*plut, std::less<float>{})), to_str(*plut).c_str()); 1003 MOZ_ASSERT(IsMonotonic(*plut, std::less<float>{})); 1004 } 1005 MOZ_ASSERT(plut->size() >= 2); 1006 1007 auto& ret = *out_invertedLut; 1008 for (size_t i_out = 0; i_out < ret.size(); i_out++) { 1009 const auto f_out = i_out / float(ret.size() - 1); 1010 const auto f_in = SampleInByOut(*plut, f_out); 1011 ret[i_out] = f_in; 1012 } 1013 1014 MOZ_ASSERT(IsMonotonic(ret)); 1015 MOZ_ASSERT(IsMonotonic(ret, std::less<float>{})); 1016 } 1017 1018 // - 1019 1020 struct ColorProfileConversionDesc { 1021 // ICC profiles are phrased as PCS-from-encoded (PCS is CIEXYZ-D50) 1022 color::mat4 srcRgbFromSrcYuv = color::mat4::Identity(); 1023 RgbTransferTables srcLinearFromSrcTf; 1024 color::mat3 dstLinearFromSrcLinear = color::mat3::Identity(); 1025 RgbTransferTables dstTfFromDstLinear; 1026 1027 struct FromDesc { 1028 ColorProfileDesc src; 1029 ColorProfileDesc dst; 1030 }; 1031 static ColorProfileConversionDesc From(const FromDesc&); 1032 1033 vec3 DstFromSrc(const vec3 src) const { 1034 const auto srcRgb = vec3(srcRgbFromSrcYuv * vec4(src, 1)); 1035 const auto srcLinear = vec3{{ 1036 SampleOutByIn(srcLinearFromSrcTf.r, srcRgb.x()), 1037 SampleOutByIn(srcLinearFromSrcTf.g, srcRgb.y()), 1038 SampleOutByIn(srcLinearFromSrcTf.b, srcRgb.z()), 1039 }}; 1040 const auto dstLinear = dstLinearFromSrcLinear * srcLinear; 1041 const auto dstRgb = vec3{{ 1042 SampleOutByIn(dstTfFromDstLinear.r, dstLinear.x()), 1043 SampleOutByIn(dstTfFromDstLinear.g, dstLinear.y()), 1044 SampleOutByIn(dstTfFromDstLinear.b, dstLinear.z()), 1045 }}; 1046 return dstRgb; 1047 } 1048 }; 1049 1050 } // namespace mozilla::color 1051 1052 #undef ASSERT 1053 1054 #endif // MOZILLA_GFX_GL_COLORSPACES_H_