tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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_