/**
 * Copyright (c) 2026 Collabora Ltd.
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef U_YCBCR_H
#define U_YCBCR_H

/* BT.601 coefficients */
static const float util_ycbcr_bt601_coeffs[3] = {
   0.299f, 0.587f, 0.114f
};

/* BT.701 coefficients */
static const float util_ycbcr_bt709_coeffs[3] = {
   0.2126f, 0.7152f, 0.0722f
};

/* BT.2020 coefficients */
static const float util_ycbcr_bt2020_coeffs[3] = {
   0.2627f, 0.6780f, 0.0593f
};

/* SMPTE 240M coefficients */
static const float util_ycbcr_smpte240m_coeffs[3] = {
   0.2122f, 0.7013f, 0.0865f
};

static inline void
util_get_ycbcr_to_rgb_matrix(float m[3][4], const float coeffs[3])
{
   /**
    * Sets up a 3x4 matrix that computes:
    *
    * R = Y + e * Cr
    * G = Y - (a * e / b) * Cr - (c * d / b) * Cb
    * B = Y + d * Cb
    */

   float a = coeffs[0];
   float b = coeffs[1];
   float c = coeffs[2];
   float d = 2 - 2 * c;
   float e = 2 - 2 * a;
   float f = 1.0f / b;

   m[0][0] = 1; m[0][1] = 0;          m[0][2] = e;          m[0][3] = 0;
   m[1][0] = 1; m[1][1] = -c * d * f; m[1][2] = -a * e * f; m[1][3] = 0;
   m[2][0] = 1; m[2][1] = d;          m[2][2] = 0;          m[2][3] = 0;
}

static inline void
util_get_rgb_to_ycbcr_matrix(float m[3][4], const float coeffs[3])
{
   /**
    * Sets up a 3x4 matrix that computes:
    *
    * Y  = a * R + b * G + c * B
    * Cb = (B - Y) / d
    * Cr = (R - Y) / e
    */

   float a = coeffs[0];
   float b = coeffs[1];
   float c = coeffs[2];
   float d = 0.5f / (c - 1);
   float e = 0.5f / (a - 1);

   m[0][0] = a;     m[0][1] = b;     m[0][2] = c;     m[0][3] = 0;
   m[1][0] = d * a; m[1][1] = d * b; m[1][2] = 0.5f;  m[1][3] = 0;
   m[2][0] = 0.5f;  m[2][1] = e * b; m[2][2] = e * c; m[2][3] = 0;
}

static inline float
util_get_full_range_chroma_bias(unsigned bpc)
{
   return -(1 << (bpc - 1)) / ((1 << bpc) - 1.0f);
}

static inline void
util_get_full_range_coeffs(float out[3][2], const unsigned bpc[3])
{
   out[0][0] = 1; out[0][1] = 0;
   out[1][0] = 1; out[1][1] = util_get_full_range_chroma_bias(bpc[1]);
   out[2][0] = 1; out[2][1] = util_get_full_range_chroma_bias(bpc[2]);
}

static inline float
util_get_narrow_range(unsigned bpc)
{
   return 1 - 1.0f / (1 << bpc);
}

static inline float
util_get_narrow_range_luma_factor(unsigned bpc)
{
   return util_get_narrow_range(bpc) * (256.0f / 219);
}

static inline float
util_get_narrow_range_chroma_factor(unsigned bpc)
{
   return util_get_narrow_range(bpc) * (256.0f / 224);
}

static inline void
util_get_narrow_range_coeffs(float out[3][2], const unsigned bpc[3])
{
   float y_factor = util_get_narrow_range_luma_factor(bpc[0]);
   float cb_factor = util_get_narrow_range_chroma_factor(bpc[1]);
   float cr_factor = util_get_narrow_range_chroma_factor(bpc[2]);

   float y_bias = -16.0f / 219;
   float c_bias = -128.0f / 224;

   out[0][0] = y_factor;  out[0][1] = y_bias;
   out[1][0] = cb_factor; out[1][1] = c_bias;
   out[2][0] = cr_factor; out[2][1] = c_bias;
}

static inline void
util_get_identity_range_coeffs(float out[3][2])
{
   out[0][0] = out[1][0] = out[2][0] = 1.0f;
   out[0][1] = out[1][1] = out[2][1] = 0.0f;
}

static inline void
util_ycbcr_adjust_from_range(float mat[3][4],
                             const float range[3][2])
{
   for (int i = 0; i < 3; ++i) {
      mat[i][3] = range[0][1] * mat[i][0] +
                  range[1][1] * mat[i][1] +
                  range[2][1] * mat[i][2] +
                                mat[i][3];

      mat[i][0] = mat[i][0] * range[0][0];
      mat[i][1] = mat[i][1] * range[1][0];
      mat[i][2] = mat[i][2] * range[2][0];
   }
}

static inline void
util_ycbcr_adjust_to_range(float mat[3][4],
                           const float range[3][2])
{
   for (int i = 0; i < 3; ++i) {
      float tmp = 1.0f / range[i][0];
      mat[i][0] = mat[i][0] * tmp;
      mat[i][1] = mat[i][1] * tmp;
      mat[i][2] = mat[i][2] * tmp;
      mat[i][3] -= range[i][1] * tmp;
   }
}

#endif /* U_YCBCR_H */
