diff mbox series

[FFmpeg-devel,1/1] avutil/csp: create public API for colorspace structs

Message ID 20220520155313.171100-2-leo.izen@gmail.com
State New
Headers show
Series avutil/csp changes | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Leo Izen May 20, 2022, 3:53 p.m. UTC
This commit moves some of the functionality from avfilter/colorspace
into avutil/csp and exposes it as a public API so it can be used by
libavcodec and/or libavformat. It also converts those structs from
double values to AVRational to make regression testing easier and
more consistent.
---
 libavfilter/colorspace.c    | 143 ++++++++----------------------------
 libavfilter/colorspace.h    |  31 +-------
 libavfilter/fflcms2.c       |  25 ++++---
 libavfilter/fflcms2.h       |   4 +-
 libavfilter/vf_colorspace.c |  37 +++++-----
 libavfilter/vf_iccdetect.c  |   5 +-
 libavfilter/vf_tonemap.c    |  17 +----
 libavutil/Makefile          |   2 +
 libavutil/csp.c             | 121 ++++++++++++++++++++++++++++++
 libavutil/csp.h             |  49 ++++++++++++
 libavutil/version.h         |   4 +-
 11 files changed, 250 insertions(+), 188 deletions(-)
 create mode 100644 libavutil/csp.c
 create mode 100644 libavutil/csp.h

Comments

Andreas Rheinhardt May 20, 2022, 4:01 p.m. UTC | #1
Leo Izen:
> This commit moves some of the functionality from avfilter/colorspace
> into avutil/csp and exposes it as a public API so it can be used by
> libavcodec and/or libavformat. It also converts those structs from
> double values to AVRational to make regression testing easier and
> more consistent.
> ---
>  libavfilter/colorspace.c    | 143 ++++++++----------------------------
>  libavfilter/colorspace.h    |  31 +-------
>  libavfilter/fflcms2.c       |  25 ++++---
>  libavfilter/fflcms2.h       |   4 +-
>  libavfilter/vf_colorspace.c |  37 +++++-----
>  libavfilter/vf_iccdetect.c  |   5 +-
>  libavfilter/vf_tonemap.c    |  17 +----
>  libavutil/Makefile          |   2 +
>  libavutil/csp.c             | 121 ++++++++++++++++++++++++++++++
>  libavutil/csp.h             |  49 ++++++++++++
>  libavutil/version.h         |   4 +-
>  11 files changed, 250 insertions(+), 188 deletions(-)
>  create mode 100644 libavutil/csp.c
>  create mode 100644 libavutil/csp.h
> 
> diff --git a/libavfilter/colorspace.c b/libavfilter/colorspace.c
> index 8d7b882375..3d125da6aa 100644
> --- a/libavfilter/colorspace.c
> +++ b/libavfilter/colorspace.c
> @@ -65,24 +65,28 @@ void ff_matrix_mul_3x3(double dst[3][3],
>  /*
>   * see e.g. http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
>   */
> -void ff_fill_rgb2xyz_table(const struct PrimaryCoefficients *coeffs,
> -                           const struct WhitepointCoefficients *wp,
> +void ff_fill_rgb2xyz_table(const AVPrimaryCoefficients *coeffs,
> +                           const AVWhitepointCoefficients *wp,
>                             double rgb2xyz[3][3])
>  {
>      double i[3][3], sr, sg, sb, zw;
> -
> -    rgb2xyz[0][0] = coeffs->xr / coeffs->yr;
> -    rgb2xyz[0][1] = coeffs->xg / coeffs->yg;
> -    rgb2xyz[0][2] = coeffs->xb / coeffs->yb;
> +    double xr = av_q2d(coeffs->xr), yr = av_q2d(coeffs->yr);
> +    double xg = av_q2d(coeffs->xg), yg = av_q2d(coeffs->yg);
> +    double xb = av_q2d(coeffs->xb), yb = av_q2d(coeffs->yb);
> +    double xw = av_q2d(wp->xw), yw = av_q2d(wp->yw);
> +
> +    rgb2xyz[0][0] = xr / yr;
> +    rgb2xyz[0][1] = xg / yg;
> +    rgb2xyz[0][2] = xb / yb;
>      rgb2xyz[1][0] = rgb2xyz[1][1] = rgb2xyz[1][2] = 1.0;
> -    rgb2xyz[2][0] = (1.0 - coeffs->xr - coeffs->yr) / coeffs->yr;
> -    rgb2xyz[2][1] = (1.0 - coeffs->xg - coeffs->yg) / coeffs->yg;
> -    rgb2xyz[2][2] = (1.0 - coeffs->xb - coeffs->yb) / coeffs->yb;
> +    rgb2xyz[2][0] = (1.0 - xr - yr) / yr;
> +    rgb2xyz[2][1] = (1.0 - xg - yg) / yg;
> +    rgb2xyz[2][2] = (1.0 - xb - yb) / yb;
>      ff_matrix_invert_3x3(rgb2xyz, i);
> -    zw = 1.0 - wp->xw - wp->yw;
> -    sr = i[0][0] * wp->xw + i[0][1] * wp->yw + i[0][2] * zw;
> -    sg = i[1][0] * wp->xw + i[1][1] * wp->yw + i[1][2] * zw;
> -    sb = i[2][0] * wp->xw + i[2][1] * wp->yw + i[2][2] * zw;
> +    zw = 1.0 - xw - yw;
> +    sr = i[0][0] * xw + i[0][1] * yw + i[0][2] * zw;
> +    sg = i[1][0] * xw + i[1][1] * yw + i[1][2] * zw;
> +    sb = i[2][0] * xw + i[2][1] * yw + i[2][2] * zw;
>      rgb2xyz[0][0] *= sr;
>      rgb2xyz[0][1] *= sg;
>      rgb2xyz[0][2] *= sb;
> @@ -107,119 +111,32 @@ static const double gbr_matrix[3][3] =
>      { 0.5, -0.5, 0   },
>  };
>  
> -/*
> - * All constants explained in e.g. https://linuxtv.org/downloads/v4l-dvb-apis/ch02s06.html
> - * The older ones (bt470bg/m) are also explained in their respective ITU docs
> - * (e.g. https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.470-5-199802-S!!PDF-E.pdf)
> - * whereas the newer ones can typically be copied directly from wikipedia :)
> - */
> -static const struct LumaCoefficients luma_coefficients[AVCOL_SPC_NB] = {
> -    [AVCOL_SPC_FCC]        = { 0.30,   0.59,   0.11   },
> -    [AVCOL_SPC_BT470BG]    = { 0.299,  0.587,  0.114  },
> -    [AVCOL_SPC_SMPTE170M]  = { 0.299,  0.587,  0.114  },
> -    [AVCOL_SPC_BT709]      = { 0.2126, 0.7152, 0.0722 },
> -    [AVCOL_SPC_SMPTE240M]  = { 0.212,  0.701,  0.087  },
> -    [AVCOL_SPC_YCOCG]      = { 0.25,   0.5,    0.25   },
> -    [AVCOL_SPC_RGB]        = { 1,      1,      1      },
> -    [AVCOL_SPC_BT2020_NCL] = { 0.2627, 0.6780, 0.0593 },
> -    [AVCOL_SPC_BT2020_CL]  = { 0.2627, 0.6780, 0.0593 },
> -};
> -
> -const struct LumaCoefficients *ff_get_luma_coefficients(enum AVColorSpace csp)
> -{
> -    const struct LumaCoefficients *coeffs;
> -
> -    if (csp >= AVCOL_SPC_NB)
> -        return NULL;
> -    coeffs = &luma_coefficients[csp];
> -    if (!coeffs->cr)
> -        return NULL;
> -
> -    return coeffs;
> -}
> -
> -#define WP_D65 { 0.3127, 0.3290 }
> -#define WP_C   { 0.3100, 0.3160 }
> -#define WP_DCI { 0.3140, 0.3510 }
> -#define WP_E   { 1/3.0f, 1/3.0f }
> -
> -static const struct ColorPrimaries color_primaries[AVCOL_PRI_NB] = {
> -    [AVCOL_PRI_BT709]     = { WP_D65, { 0.640, 0.330, 0.300, 0.600, 0.150, 0.060 } },
> -    [AVCOL_PRI_BT470M]    = { WP_C,   { 0.670, 0.330, 0.210, 0.710, 0.140, 0.080 } },
> -    [AVCOL_PRI_BT470BG]   = { WP_D65, { 0.640, 0.330, 0.290, 0.600, 0.150, 0.060 } },
> -    [AVCOL_PRI_SMPTE170M] = { WP_D65, { 0.630, 0.340, 0.310, 0.595, 0.155, 0.070 } },
> -    [AVCOL_PRI_SMPTE240M] = { WP_D65, { 0.630, 0.340, 0.310, 0.595, 0.155, 0.070 } },
> -    [AVCOL_PRI_SMPTE428]  = { WP_E,   { 0.735, 0.265, 0.274, 0.718, 0.167, 0.009 } },
> -    [AVCOL_PRI_SMPTE431]  = { WP_DCI, { 0.680, 0.320, 0.265, 0.690, 0.150, 0.060 } },
> -    [AVCOL_PRI_SMPTE432]  = { WP_D65, { 0.680, 0.320, 0.265, 0.690, 0.150, 0.060 } },
> -    [AVCOL_PRI_FILM]      = { WP_C,   { 0.681, 0.319, 0.243, 0.692, 0.145, 0.049 } },
> -    [AVCOL_PRI_BT2020]    = { WP_D65, { 0.708, 0.292, 0.170, 0.797, 0.131, 0.046 } },
> -    [AVCOL_PRI_JEDEC_P22] = { WP_D65, { 0.630, 0.340, 0.295, 0.605, 0.155, 0.077 } },
> -};
> -
> -const struct ColorPrimaries *ff_get_color_primaries(enum AVColorPrimaries prm)
> -{
> -    const struct ColorPrimaries *p;
> -
> -    if (prm >= AVCOL_PRI_NB)
> -        return NULL;
> -    p = &color_primaries[prm];
> -    if (!p->prim.xr)
> -        return NULL;
> -
> -    return p;
> -}
> -
> -enum AVColorPrimaries ff_detect_color_primaries(const struct ColorPrimaries *prm)
> -{
> -    double delta;
> -
> -    for (enum AVColorPrimaries p = 0; p < AVCOL_PRI_NB; p++) {
> -        const struct ColorPrimaries *ref = &color_primaries[p];
> -        if (!ref->prim.xr)
> -            continue;
> -
> -        delta = fabs(prm->prim.xr - ref->prim.xr) +
> -                fabs(prm->prim.yr - ref->prim.yr) +
> -                fabs(prm->prim.yg - ref->prim.yg) +
> -                fabs(prm->prim.yg - ref->prim.yg) +
> -                fabs(prm->prim.yb - ref->prim.yb) +
> -                fabs(prm->prim.yb - ref->prim.yb) +
> -                fabs(prm->wp.xw - ref->wp.xw) +
> -                fabs(prm->wp.yw - ref->wp.yw);
> -
> -        if (delta < 0.001)
> -            return p;
> -    }
> -
> -    return AVCOL_PRI_UNSPECIFIED;
> -}
> -
> -void ff_fill_rgb2yuv_table(const struct LumaCoefficients *coeffs,
> +void ff_fill_rgb2yuv_table(const AVLumaCoefficients *coeffs,
>                             double rgb2yuv[3][3])
>  {
>      double bscale, rscale;
> +    double cr = av_q2d(coeffs->cr), cg = av_q2d(coeffs->cg), cb = av_q2d(coeffs->cb);
>  
>      // special ycgco matrix
> -    if (coeffs->cr == 0.25 && coeffs->cg == 0.5 && coeffs->cb == 0.25) {
> +    if (cr == 0.25 && cg == 0.5 && cb == 0.25) {
>          memcpy(rgb2yuv, ycgco_matrix, sizeof(double) * 9);
>          return;
> -    } else if (coeffs->cr == 1 && coeffs->cg == 1 && coeffs->cb == 1) {
> +    } else if (cr == 1 && cg == 1 && cb == 1) {
>          memcpy(rgb2yuv, gbr_matrix, sizeof(double) * 9);
>          return;
>      }
>  
> -    rgb2yuv[0][0] = coeffs->cr;
> -    rgb2yuv[0][1] = coeffs->cg;
> -    rgb2yuv[0][2] = coeffs->cb;
> -    bscale = 0.5 / (coeffs->cb - 1.0);
> -    rscale = 0.5 / (coeffs->cr - 1.0);
> -    rgb2yuv[1][0] = bscale * coeffs->cr;
> -    rgb2yuv[1][1] = bscale * coeffs->cg;
> +    rgb2yuv[0][0] = cr;
> +    rgb2yuv[0][1] = cg;
> +    rgb2yuv[0][2] = cb;
> +    bscale = 0.5 / (cb - 1.0);
> +    rscale = 0.5 / (cr - 1.0);
> +    rgb2yuv[1][0] = bscale * cr;
> +    rgb2yuv[1][1] = bscale * cg;
>      rgb2yuv[1][2] = 0.5;
>      rgb2yuv[2][0] = 0.5;
> -    rgb2yuv[2][1] = rscale * coeffs->cg;
> -    rgb2yuv[2][2] = rscale * coeffs->cb;
> +    rgb2yuv[2][1] = rscale * cg;
> +    rgb2yuv[2][2] = rscale * cb;
>  }
>  
>  double ff_determine_signal_peak(AVFrame *in)
> diff --git a/libavfilter/colorspace.h b/libavfilter/colorspace.h
> index 6959133a49..879518d242 100644
> --- a/libavfilter/colorspace.h
> +++ b/libavfilter/colorspace.h
> @@ -20,43 +20,20 @@
>  #ifndef AVFILTER_COLORSPACE_H
>  #define AVFILTER_COLORSPACE_H
>  
> +#include "libavutil/csp.h"
>  #include "libavutil/frame.h"
>  #include "libavutil/pixfmt.h"
>  
>  #define REFERENCE_WHITE 100.0f
>  
> -struct LumaCoefficients {
> -    double cr, cg, cb;
> -};
> -
> -struct PrimaryCoefficients {
> -    double xr, yr, xg, yg, xb, yb;
> -};
> -
> -struct WhitepointCoefficients {
> -    double xw, yw;
> -};
> -
> -struct ColorPrimaries {
> -    struct WhitepointCoefficients wp;
> -    struct PrimaryCoefficients prim;
> -};
> -
>  void ff_matrix_invert_3x3(const double in[3][3], double out[3][3]);
>  void ff_matrix_mul_3x3(double dst[3][3],
>                 const double src1[3][3], const double src2[3][3]);
> -void ff_fill_rgb2xyz_table(const struct PrimaryCoefficients *coeffs,
> -                           const struct WhitepointCoefficients *wp,
> +void ff_fill_rgb2xyz_table(const AVPrimaryCoefficients *coeffs,
> +                           const AVWhitepointCoefficients *wp,
>                             double rgb2xyz[3][3]);
> -
> -/* Returns AVCOL_PRI_UNSPECIFIED if no clear match can be identified */
> -enum AVColorPrimaries ff_detect_color_primaries(const struct ColorPrimaries *prm);
> -
> -const struct ColorPrimaries *ff_get_color_primaries(enum AVColorPrimaries prm);
> -const struct LumaCoefficients *ff_get_luma_coefficients(enum AVColorSpace csp);
> -void ff_fill_rgb2yuv_table(const struct LumaCoefficients *coeffs,
> +void ff_fill_rgb2yuv_table(const AVLumaCoefficients *coeffs,
>                             double rgb2yuv[3][3]);
> -
>  double ff_determine_signal_peak(AVFrame *in);
>  void ff_update_hdr_metadata(AVFrame *in, double peak);
>  
> diff --git a/libavfilter/fflcms2.c b/libavfilter/fflcms2.c
> index efc7cb5189..6a843093f7 100644
> --- a/libavfilter/fflcms2.c
> +++ b/libavfilter/fflcms2.c
> @@ -18,6 +18,7 @@
>   */
>  
>  #include "libavutil/color_utils.h"
> +#include "libavutil/csp.h"
>  
>  #include "fflcms2.h"
>  
> @@ -148,20 +149,20 @@ int ff_icc_profile_generate(FFIccContext *s,
>                              cmsHPROFILE *out_profile)
>  {
>      cmsToneCurve *tonecurve;
> -    const struct ColorPrimaries *prim;
> +    const AVColorPrimariesDesc *prim;
>      int ret;
>  
> -    if (!(prim = ff_get_color_primaries(color_prim)))
> +    if (!(prim = av_get_color_primaries(color_prim)))
>          return AVERROR_INVALIDDATA;
>      if ((ret = get_curve(s, color_trc, &tonecurve)) < 0)
>          return ret;
>  
>      *out_profile = cmsCreateRGBProfileTHR(s->ctx,
> -        &(cmsCIExyY) { prim->wp.xw, prim->wp.yw, 1.0 },
> +        &(cmsCIExyY) { av_q2d(prim->wp.xw), av_q2d(prim->wp.yw), 1.0 },
>          &(cmsCIExyYTRIPLE) {
> -            .Red    = { prim->prim.xr, prim->prim.yr, 1.0 },
> -            .Green  = { prim->prim.xg, prim->prim.yg, 1.0 },
> -            .Blue   = { prim->prim.xb, prim->prim.yb, 1.0 },
> +            .Red    = { av_q2d(prim->prim.xr), av_q2d(prim->prim.yr), 1.0 },
> +            .Green  = { av_q2d(prim->prim.xg), av_q2d(prim->prim.yg), 1.0 },
> +            .Blue   = { av_q2d(prim->prim.xb), av_q2d(prim->prim.yb), 1.0 },
>          },
>          (cmsToneCurve *[3]) { tonecurve, tonecurve, tonecurve }
>      );
> @@ -194,15 +195,15 @@ int ff_icc_profile_attach(FFIccContext *s, cmsHPROFILE profile, AVFrame *frame)
>      return 0;
>  }
>  
> -static av_always_inline void XYZ_xy(cmsCIEXYZ XYZ, double *x, double *y)
> +static av_always_inline void XYZ_xy(cmsCIEXYZ XYZ, AVRational *x, AVRational *y)
>  {
>      double k = 1.0 / (XYZ.X + XYZ.Y + XYZ.Z);
> -    *x = k * XYZ.X;
> -    *y = k * XYZ.Y;
> +    *x = av_d2q(k * XYZ.X, 30000);
> +    *y = av_d2q(k * XYZ.Y, 30000);
>  }
>  
>  int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
> -                                  struct ColorPrimaries *out_primaries)
> +                                  AVColorPrimariesDesc *out_primaries)
>  {
>      static const uint8_t testprimaries[4][3] = {
>          { 0xFF,    0,    0 }, /* red */
> @@ -211,8 +212,8 @@ int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
>          { 0xFF, 0xFF, 0xFF }, /* white */
>      };
>  
> -    struct WhitepointCoefficients *wp = &out_primaries->wp;
> -    struct PrimaryCoefficients *prim = &out_primaries->prim;
> +    AVWhitepointCoefficients *wp = &out_primaries->wp;
> +    AVPrimaryCoefficients *prim = &out_primaries->prim;
>      cmsFloat64Number prev_adapt;
>      cmsHPROFILE xyz;
>      cmsHTRANSFORM tf;
> diff --git a/libavfilter/fflcms2.h b/libavfilter/fflcms2.h
> index ad6c8c47cf..0d238c679f 100644
> --- a/libavfilter/fflcms2.h
> +++ b/libavfilter/fflcms2.h
> @@ -25,9 +25,9 @@
>  #ifndef AVFILTER_FFLCMS2_H
>  #define AVFILTER_FFLCMS2_H
>  
> +#include "libavutil/csp.h"
>  #include "libavutil/frame.h"
>  #include "libavutil/pixfmt.h"
> -#include "colorspace.h"
>  
>  #include <lcms2.h>
>  
> @@ -72,7 +72,7 @@ int ff_icc_profile_attach(FFIccContext *s, cmsHPROFILE profile, AVFrame *frame);
>   * Returns 0 on success, or a negative error code.
>   */
>  int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
> -                                  struct ColorPrimaries *out_primaries);
> +                                  AVColorPrimariesDesc *out_primaries);
>  
>  /**
>   * Attempt detecting the transfer characteristic that best approximates the
> diff --git a/libavfilter/vf_colorspace.c b/libavfilter/vf_colorspace.c
> index 3c8b3b20eb..203fff3941 100644
> --- a/libavfilter/vf_colorspace.c
> +++ b/libavfilter/vf_colorspace.c
> @@ -24,6 +24,7 @@
>   */
>  
>  #include "libavutil/avassert.h"
> +#include "libavutil/csp.h"
>  #include "libavutil/mem_internal.h"
>  #include "libavutil/opt.h"
>  #include "libavutil/pixdesc.h"
> @@ -126,7 +127,7 @@ typedef struct ColorSpaceContext {
>      unsigned rgb_sz;
>      int *dither_scratch[3][2], *dither_scratch_base[3][2];
>  
> -    const struct ColorPrimaries *in_primaries, *out_primaries;
> +    const AVColorPrimariesDesc *in_primaries, *out_primaries;
>      int lrgb2lrgb_passthrough;
>      DECLARE_ALIGNED(16, int16_t, lrgb2lrgb_coeffs)[3][3][8];
>  
> @@ -134,7 +135,7 @@ typedef struct ColorSpaceContext {
>      int rgb2rgb_passthrough;
>      int16_t *lin_lut, *delin_lut;
>  
> -    const struct LumaCoefficients *in_lumacoef, *out_lumacoef;
> +    const AVLumaCoefficients *in_lumacoef, *out_lumacoef;
>      int yuv2yuv_passthrough, yuv2yuv_fastmode;
>      DECLARE_ALIGNED(16, int16_t, yuv2rgb_coeffs)[3][3][8];
>      DECLARE_ALIGNED(16, int16_t, rgb2yuv_coeffs)[3][3][8];
> @@ -233,8 +234,8 @@ static int fill_gamma_table(ColorSpaceContext *s)
>   * This function uses the Bradford mechanism.
>   */
>  static void fill_whitepoint_conv_table(double out[3][3], enum WhitepointAdaptation wp_adapt,
> -                                       const struct WhitepointCoefficients *wp_src,
> -                                       const struct WhitepointCoefficients *wp_dst)
> +                                       const AVWhitepointCoefficients *wp_src,
> +                                       const AVWhitepointCoefficients *wp_dst)
>  {
>      static const double ma_tbl[NB_WP_ADAPT_NON_IDENTITY][3][3] = {
>          [WP_ADAPT_BRADFORD] = {
> @@ -248,18 +249,20 @@ static void fill_whitepoint_conv_table(double out[3][3], enum WhitepointAdaptati
>          },
>      };
>      const double (*ma)[3] = ma_tbl[wp_adapt];
> -    double zw_src = 1.0 - wp_src->xw - wp_src->yw;
> -    double zw_dst = 1.0 - wp_dst->xw - wp_dst->yw;
> +    double xw_src = av_q2d(wp_src->xw), yw_src = av_q2d(wp_src->yw);
> +    double xw_dst = av_q2d(wp_dst->xw), yw_dst = av_q2d(wp_dst->yw);
> +    double zw_src = 1.0 - xw_src - yw_src;
> +    double zw_dst = 1.0 - xw_dst - yw_dst;
>      double mai[3][3], fac[3][3], tmp[3][3];
>      double rs, gs, bs, rd, gd, bd;
>  
>      ff_matrix_invert_3x3(ma, mai);
> -    rs = ma[0][0] * wp_src->xw + ma[0][1] * wp_src->yw + ma[0][2] * zw_src;
> -    gs = ma[1][0] * wp_src->xw + ma[1][1] * wp_src->yw + ma[1][2] * zw_src;
> -    bs = ma[2][0] * wp_src->xw + ma[2][1] * wp_src->yw + ma[2][2] * zw_src;
> -    rd = ma[0][0] * wp_dst->xw + ma[0][1] * wp_dst->yw + ma[0][2] * zw_dst;
> -    gd = ma[1][0] * wp_dst->xw + ma[1][1] * wp_dst->yw + ma[1][2] * zw_dst;
> -    bd = ma[2][0] * wp_dst->xw + ma[2][1] * wp_dst->yw + ma[2][2] * zw_dst;
> +    rs = ma[0][0] * xw_src + ma[0][1] * yw_src + ma[0][2] * zw_src;
> +    gs = ma[1][0] * xw_src + ma[1][1] * yw_src + ma[1][2] * zw_src;
> +    bs = ma[2][0] * xw_src + ma[2][1] * yw_src + ma[2][2] * zw_src;
> +    rd = ma[0][0] * xw_dst + ma[0][1] * yw_dst + ma[0][2] * zw_dst;
> +    gd = ma[1][0] * xw_dst + ma[1][1] * yw_dst + ma[1][2] * zw_dst;
> +    bd = ma[2][0] * xw_dst + ma[2][1] * yw_dst + ma[2][2] * zw_dst;
>      fac[0][0] = rd / rs;
>      fac[1][1] = gd / gs;
>      fac[2][2] = bd / bs;
> @@ -438,7 +441,7 @@ static int create_filtergraph(AVFilterContext *ctx,
>              s->in_prm = default_prm[FFMIN(s->user_iall, CS_NB)];
>          if (s->user_iprm != AVCOL_PRI_UNSPECIFIED)
>              s->in_prm = s->user_iprm;
> -        s->in_primaries = ff_get_color_primaries(s->in_prm);
> +        s->in_primaries = av_get_color_primaries(s->in_prm);
>          if (!s->in_primaries) {
>              av_log(ctx, AV_LOG_ERROR,
>                     "Unsupported input primaries %d (%s)\n",
> @@ -446,7 +449,7 @@ static int create_filtergraph(AVFilterContext *ctx,
>              return AVERROR(EINVAL);
>          }
>          s->out_prm = out->color_primaries;
> -        s->out_primaries = ff_get_color_primaries(s->out_prm);
> +        s->out_primaries = av_get_color_primaries(s->out_prm);
>          if (!s->out_primaries) {
>              if (s->out_prm == AVCOL_PRI_UNSPECIFIED) {
>                  if (s->user_all == CS_UNSPECIFIED) {
> @@ -466,7 +469,7 @@ static int create_filtergraph(AVFilterContext *ctx,
>                                             sizeof(*s->in_primaries));
>          if (!s->lrgb2lrgb_passthrough) {
>              double rgb2xyz[3][3], xyz2rgb[3][3], rgb2rgb[3][3];
> -            const struct WhitepointCoefficients *wp_out, *wp_in;
> +            const AVWhitepointCoefficients *wp_out, *wp_in;
>  
>              wp_out = &s->out_primaries->wp;
>              wp_in = &s->in_primaries->wp;
> @@ -551,7 +554,7 @@ static int create_filtergraph(AVFilterContext *ctx,
>          s->in_rng = in->color_range;
>          if (s->user_irng != AVCOL_RANGE_UNSPECIFIED)
>              s->in_rng = s->user_irng;
> -        s->in_lumacoef = ff_get_luma_coefficients(s->in_csp);
> +        s->in_lumacoef = av_get_luma_coefficients(s->in_csp);
>          if (!s->in_lumacoef) {
>              av_log(ctx, AV_LOG_ERROR,
>                     "Unsupported input colorspace %d (%s)\n",
> @@ -564,7 +567,7 @@ static int create_filtergraph(AVFilterContext *ctx,
>      if (!s->out_lumacoef) {
>          s->out_csp = out->colorspace;
>          s->out_rng = out->color_range;
> -        s->out_lumacoef = ff_get_luma_coefficients(s->out_csp);
> +        s->out_lumacoef = av_get_luma_coefficients(s->out_csp);
>          if (!s->out_lumacoef) {
>              if (s->out_csp == AVCOL_SPC_UNSPECIFIED) {
>                  if (s->user_all == CS_UNSPECIFIED) {
> diff --git a/libavfilter/vf_iccdetect.c b/libavfilter/vf_iccdetect.c
> index fb7871f035..a62c7cad47 100644
> --- a/libavfilter/vf_iccdetect.c
> +++ b/libavfilter/vf_iccdetect.c
> @@ -24,6 +24,7 @@
>  
>  #include <lcms2.h>
>  
> +#include "libavutil/csp.h"
>  #include "libavutil/opt.h"
>  #include "libavutil/pixdesc.h"
>  
> @@ -69,7 +70,7 @@ static int iccdetect_filter_frame(AVFilterLink *inlink, AVFrame *frame)
>      AVFilterContext *avctx = inlink->dst;
>      IccDetectContext *s = avctx->priv;
>      const AVFrameSideData *sd;
> -    struct ColorPrimaries coeffs;
> +    AVColorPrimariesDesc coeffs;
>      cmsHPROFILE profile;
>      int ret;
>  
> @@ -98,7 +99,7 @@ static int iccdetect_filter_frame(AVFilterLink *inlink, AVFrame *frame)
>      if (ret < 0)
>          return ret;
>  
> -    s->profile_prim = ff_detect_color_primaries(&coeffs);
> +    s->profile_prim = av_detect_color_primaries(&coeffs);
>  
>  done:
>      if (s->profile_prim != AVCOL_PRI_UNSPECIFIED) {
> diff --git a/libavfilter/vf_tonemap.c b/libavfilter/vf_tonemap.c
> index 1285dbaa4d..5845ea4047 100644
> --- a/libavfilter/vf_tonemap.c
> +++ b/libavfilter/vf_tonemap.c
> @@ -27,6 +27,7 @@
>  #include <stdio.h>
>  #include <string.h>
>  
> +#include "libavutil/csp.h"
>  #include "libavutil/imgutils.h"
>  #include "libavutil/internal.h"
>  #include "libavutil/intreadwrite.h"
> @@ -50,16 +51,6 @@ enum TonemapAlgorithm {
>      TONEMAP_MAX,
>  };
>  
> -static const struct LumaCoefficients luma_coefficients[AVCOL_SPC_NB] = {
> -    [AVCOL_SPC_FCC]        = { 0.30,   0.59,   0.11   },
> -    [AVCOL_SPC_BT470BG]    = { 0.299,  0.587,  0.114  },
> -    [AVCOL_SPC_SMPTE170M]  = { 0.299,  0.587,  0.114  },
> -    [AVCOL_SPC_BT709]      = { 0.2126, 0.7152, 0.0722 },
> -    [AVCOL_SPC_SMPTE240M]  = { 0.212,  0.701,  0.087  },
> -    [AVCOL_SPC_BT2020_NCL] = { 0.2627, 0.6780, 0.0593 },
> -    [AVCOL_SPC_BT2020_CL]  = { 0.2627, 0.6780, 0.0593 },
> -};
> -
>  typedef struct TonemapContext {
>      const AVClass *class;
>  
> @@ -68,7 +59,7 @@ typedef struct TonemapContext {
>      double desat;
>      double peak;
>  
> -    const struct LumaCoefficients *coeffs;
> +    const AVLumaCoefficients *coeffs;
>  } TonemapContext;
>  
>  static av_cold int init(AVFilterContext *ctx)
> @@ -135,7 +126,7 @@ static void tonemap(TonemapContext *s, AVFrame *out, const AVFrame *in,
>  
>      /* desaturate to prevent unnatural colors */
>      if (s->desat > 0) {
> -        float luma = s->coeffs->cr * *r_in + s->coeffs->cg * *g_in + s->coeffs->cb * *b_in;
> +        float luma = av_q2d(s->coeffs->cr) * *r_in + av_q2d(s->coeffs->cg) * *g_in + av_q2d(s->coeffs->cb) * *b_in;
>          float overbright = FFMAX(luma - s->desat, 1e-6) / FFMAX(luma, 1e-6);
>          *r_out = MIX(*r_in, luma, overbright);
>          *g_out = MIX(*g_in, luma, overbright);
> @@ -249,7 +240,7 @@ static int filter_frame(AVFilterLink *link, AVFrame *in)
>      }
>  
>      /* load original color space even if pixel format is RGB to compute overbrights */
> -    s->coeffs = &luma_coefficients[in->colorspace];
> +    s->coeffs = av_get_luma_coefficients(in->colorspace);
>      if (s->desat > 0 && (in->colorspace == AVCOL_SPC_UNSPECIFIED || !s->coeffs)) {
>          if (in->colorspace == AVCOL_SPC_UNSPECIFIED)
>              av_log(s, AV_LOG_WARNING, "Missing color space information, ");
> diff --git a/libavutil/Makefile b/libavutil/Makefile
> index 234de62a4b..74d21a8103 100644
> --- a/libavutil/Makefile
> +++ b/libavutil/Makefile
> @@ -20,6 +20,7 @@ HEADERS = adler32.h                                                     \
>            common.h                                                      \
>            cpu.h                                                         \
>            crc.h                                                         \
> +          csp.h                                                         \
>            des.h                                                         \
>            detection_bbox.h                                              \
>            dict.h                                                        \
> @@ -113,6 +114,7 @@ OBJS = adler32.o                                                        \
>         color_utils.o                                                    \
>         cpu.o                                                            \
>         crc.o                                                            \
> +       csp.o                                                            \
>         des.o                                                            \
>         detection_bbox.o                                                 \
>         dict.o                                                           \
> diff --git a/libavutil/csp.c b/libavutil/csp.c
> new file mode 100644
> index 0000000000..0cbf310afb
> --- /dev/null
> +++ b/libavutil/csp.c
> @@ -0,0 +1,121 @@
> +/*
> + * Copyright (c) 2016 Ronald S. Bultje <rsbultje@gmail.com>
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +#include <stdlib.h>
> +
> +#include "attributes.h"
> +#include "csp.h"
> +#include "pixfmt.h"
> +#include "rational.h"
> +
> +#define AVR(d) { (int)(d * 30000), 30000 }

Does this really lead to the intended values? After all, the cast does
not round to nearest, instead it just discards the fractional part (i.e.
rounds towards zero). You should probably use (int)(d * 30000 + 0.5) to
compensate for that.

> +
> +/*
> + * All constants explained in e.g. https://linuxtv.org/downloads/v4l-dvb-apis/ch02s06.html
> + * The older ones (bt470bg/m) are also explained in their respective ITU docs
> + * (e.g. https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.470-5-199802-S!!PDF-E.pdf)
> + * whereas the newer ones can typically be copied directly from wikipedia :)
> + */
> +static const struct AVLumaCoefficients luma_coefficients[AVCOL_SPC_NB] = {
> +    [AVCOL_SPC_FCC]        = { AVR(0.30),   AVR(0.59),   AVR(0.11)   },
> +    [AVCOL_SPC_BT470BG]    = { AVR(0.299),  AVR(0.587),  AVR(0.114)  },
> +    [AVCOL_SPC_SMPTE170M]  = { AVR(0.299),  AVR(0.587),  AVR(0.114)  },
> +    [AVCOL_SPC_BT709]      = { AVR(0.2126), AVR(0.7152), AVR(0.0722) },
> +    [AVCOL_SPC_SMPTE240M]  = { AVR(0.212),  AVR(0.701),  AVR(0.087)  },
> +    [AVCOL_SPC_YCOCG]      = { AVR(0.25),   AVR(0.5),    AVR(0.25)   },
> +    [AVCOL_SPC_RGB]        = { AVR(1),      AVR(1),      AVR(1)      },
> +    [AVCOL_SPC_BT2020_NCL] = { AVR(0.2627), AVR(0.6780), AVR(0.0593) },
> +    [AVCOL_SPC_BT2020_CL]  = { AVR(0.2627), AVR(0.6780), AVR(0.0593) },
> +};
> +
> +const struct AVLumaCoefficients *av_get_luma_coefficients(enum AVColorSpace csp)
> +{
> +    const AVLumaCoefficients *coeffs;
> +
> +    if (csp >= AVCOL_SPC_NB)
> +        return NULL;
> +    coeffs = &luma_coefficients[csp];
> +    if (!coeffs->cr.num)
> +        return NULL;
> +
> +    return coeffs;
> +}
> +
> +#define WP_D65 { AVR(0.3127), AVR(0.3290) }
> +#define WP_C   { AVR(0.3100), AVR(0.3160) }
> +#define WP_DCI { AVR(0.3140), AVR(0.3510) }
> +#define WP_E   { {1, 3}, {1, 3} }
> +
> +static const AVColorPrimariesDesc color_primaries[AVCOL_PRI_NB] = {
> +    [AVCOL_PRI_BT709]     = { WP_D65, { AVR(0.640), AVR(0.330), AVR(0.300), AVR(0.600), AVR(0.150), AVR(0.060) } },
> +    [AVCOL_PRI_BT470M]    = { WP_C,   { AVR(0.670), AVR(0.330), AVR(0.210), AVR(0.710), AVR(0.140), AVR(0.080) } },
> +    [AVCOL_PRI_BT470BG]   = { WP_D65, { AVR(0.640), AVR(0.330), AVR(0.290), AVR(0.600), AVR(0.150), AVR(0.060) } },
> +    [AVCOL_PRI_SMPTE170M] = { WP_D65, { AVR(0.630), AVR(0.340), AVR(0.310), AVR(0.595), AVR(0.155), AVR(0.070) } },
> +    [AVCOL_PRI_SMPTE240M] = { WP_D65, { AVR(0.630), AVR(0.340), AVR(0.310), AVR(0.595), AVR(0.155), AVR(0.070) } },
> +    [AVCOL_PRI_SMPTE428]  = { WP_E,   { AVR(0.735), AVR(0.265), AVR(0.274), AVR(0.718), AVR(0.167), AVR(0.009) } },
> +    [AVCOL_PRI_SMPTE431]  = { WP_DCI, { AVR(0.680), AVR(0.320), AVR(0.265), AVR(0.690), AVR(0.150), AVR(0.060) } },
> +    [AVCOL_PRI_SMPTE432]  = { WP_D65, { AVR(0.680), AVR(0.320), AVR(0.265), AVR(0.690), AVR(0.150), AVR(0.060) } },
> +    [AVCOL_PRI_FILM]      = { WP_C,   { AVR(0.681), AVR(0.319), AVR(0.243), AVR(0.692), AVR(0.145), AVR(0.049) } },
> +    [AVCOL_PRI_BT2020]    = { WP_D65, { AVR(0.708), AVR(0.292), AVR(0.170), AVR(0.797), AVR(0.131), AVR(0.046) } },
> +    [AVCOL_PRI_JEDEC_P22] = { WP_D65, { AVR(0.630), AVR(0.340), AVR(0.295), AVR(0.605), AVR(0.155), AVR(0.077) } },
> +};
> +
> +const AVColorPrimariesDesc *av_get_color_primaries(enum AVColorPrimaries prm)
> +{
> +    const AVColorPrimariesDesc *p;
> +
> +    if (prm >= AVCOL_PRI_NB)
> +        return NULL;
> +    p = &color_primaries[prm];
> +    if (!p->prim.xr.num)
> +        return NULL;
> +
> +    return p;
> +}
> +
> +static av_always_inline AVRational abs_sub_q(AVRational r1, AVRational r2)
> +{
> +    AVRational diff = av_sub_q(r1, r2);
> +    return av_make_q(abs(diff.num), abs(diff.den));

The typical convention when working with AVRationals is that the
denumerator is > 0.

> +}
> +
> +enum AVColorPrimaries av_detect_color_primaries(const AVColorPrimariesDesc *prm)
> +{
> +    AVRational delta;
> +
> +    for (enum AVColorPrimaries p = 0; p < AVCOL_PRI_NB; p++) {
> +        const AVColorPrimariesDesc *ref = &color_primaries[p];
> +        if (!ref->prim.xr.num)
> +            continue;
> +
> +        delta = abs_sub_q(prm->prim.xr, ref->prim.xr);
> +        delta = av_add_q(delta, abs_sub_q(prm->prim.yr, ref->prim.yr));
> +        delta = av_add_q(delta, abs_sub_q(prm->prim.yg, ref->prim.yg));
> +        delta = av_add_q(delta, abs_sub_q(prm->prim.yg, ref->prim.yg));
> +        delta = av_add_q(delta, abs_sub_q(prm->prim.yb, ref->prim.yb));
> +        delta = av_add_q(delta, abs_sub_q(prm->prim.yb, ref->prim.yb));
> +        delta = av_add_q(delta, abs_sub_q(prm->wp.xw, ref->wp.xw));
> +        delta = av_add_q(delta, abs_sub_q(prm->wp.yw, ref->wp.yw));
> +
> +        if (av_cmp_q(delta, av_make_q(1, 1000)) < 0)
> +            return p;
> +    }
> +
> +    return AVCOL_PRI_UNSPECIFIED;
> +}
> diff --git a/libavutil/csp.h b/libavutil/csp.h
> new file mode 100644
> index 0000000000..c2eada4334
> --- /dev/null
> +++ b/libavutil/csp.h
> @@ -0,0 +1,49 @@
> +/*
> + * Copyright (c) 2016 Ronald S. Bultje <rsbultje@gmail.com>
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +#ifndef AVUTIL_CSP_H
> +#define AVUTIL_CSP_H
> +
> +#include "pixfmt.h"
> +#include "rational.h"
> +
> +typedef struct AVLumaCoefficients {
> +    AVRational cr, cg, cb;
> +} AVLumaCoefficients;
> +
> +typedef struct AVPrimaryCoefficients {
> +    AVRational xr, yr, xg, yg, xb, yb;
> +} AVPrimaryCoefficients;
> +
> +typedef struct AVWhitepointCoefficients {
> +    AVRational xw, yw;
> +} AVWhitepointCoefficients;
> +
> +typedef struct AVColorPrimariesDesc {
> +    AVWhitepointCoefficients wp;
> +    AVPrimaryCoefficients prim;
> +} AVColorPrimariesDesc;
> +
> +/* Returns AVCOL_PRI_UNSPECIFIED if no clear match can be identified */
> +enum AVColorPrimaries av_detect_color_primaries(const AVColorPrimariesDesc *prm);
> +
> +const AVColorPrimariesDesc *av_get_color_primaries(enum AVColorPrimaries prm);
> +const AVLumaCoefficients *av_get_luma_coefficients(enum AVColorSpace csp);
> +
> +#endif /* AVUTIL_CSP_H */
> diff --git a/libavutil/version.h b/libavutil/version.h
> index 6735c20090..dd7d20a9fa 100644
> --- a/libavutil/version.h
> +++ b/libavutil/version.h
> @@ -79,8 +79,8 @@
>   */
>  
>  #define LIBAVUTIL_VERSION_MAJOR  57
> -#define LIBAVUTIL_VERSION_MINOR  24
> -#define LIBAVUTIL_VERSION_MICRO 101
> +#define LIBAVUTIL_VERSION_MINOR  25
> +#define LIBAVUTIL_VERSION_MICRO 100
>  
>  #define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
>                                                 LIBAVUTIL_VERSION_MINOR, \
Leo Izen May 20, 2022, 4:06 p.m. UTC | #2
On 5/20/22 12:01, Andreas Rheinhardt wrote:
> Leo Izen:
>> This commit moves some of the functionality from avfilter/colorspace
>> into avutil/csp and exposes it as a public API so it can be used by
>> libavcodec and/or libavformat. It also converts those structs from
>> double values to AVRational to make regression testing easier and
>> more consistent.
>> ---
>> +
>> +#include <stdlib.h>
>> +
>> +#include "attributes.h"
>> +#include "csp.h"
>> +#include "pixfmt.h"
>> +#include "rational.h"
>> +
>> +#define AVR(d) { (int)(d * 30000), 30000 }
> 
> Does this really lead to the intended values? After all, the cast does
> not round to nearest, instead it just discards the fractional part (i.e.
> rounds towards zero). You should probably use (int)(d * 30000 + 0.5) to
> compensate for that.
> 

I could change it to do that. That said, I modeled this after the FIX 
macro in libavcodec/mpegaudio.h on line 60, which doesn't do that. Is 
that macro also incorrect, or is there a caveat here that makes these 
scenarios different?

- Leo Izen (thebombzen)
Andreas Rheinhardt May 20, 2022, 5:09 p.m. UTC | #3
Leo Izen:
> On 5/20/22 12:01, Andreas Rheinhardt wrote:
>> Leo Izen:
>>> This commit moves some of the functionality from avfilter/colorspace
>>> into avutil/csp and exposes it as a public API so it can be used by
>>> libavcodec and/or libavformat. It also converts those structs from
>>> double values to AVRational to make regression testing easier and
>>> more consistent.
>>> ---
>>> +
>>> +#include <stdlib.h>
>>> +
>>> +#include "attributes.h"
>>> +#include "csp.h"
>>> +#include "pixfmt.h"
>>> +#include "rational.h"
>>> +
>>> +#define AVR(d) { (int)(d * 30000), 30000 }
>>
>> Does this really lead to the intended values? After all, the cast does
>> not round to nearest, instead it just discards the fractional part (i.e.
>> rounds towards zero). You should probably use (int)(d * 30000 + 0.5) to
>> compensate for that.
>>
> 
> I could change it to do that. That said, I modeled this after the FIX
> macro in libavcodec/mpegaudio.h on line 60, which doesn't do that. Is
> that macro also incorrect, or is there a caveat here that makes these
> scenarios different?
> 

The values for mpegaudio.h are surely not rounded to nearest. I don't
know whether this is intended or not, but AFAIK the codecs using this
are not intended to be bitexact (as in: the concept of bitexactness does
not apply to these codecs because the output of a decoder is not exactly
defined anyway).

While some values are indeed rounded down when converting to double,
multiplying by 30000 seems to produce the intended values somehow. I
would nevertheless add 0.5.

- Andreas
diff mbox series

Patch

diff --git a/libavfilter/colorspace.c b/libavfilter/colorspace.c
index 8d7b882375..3d125da6aa 100644
--- a/libavfilter/colorspace.c
+++ b/libavfilter/colorspace.c
@@ -65,24 +65,28 @@  void ff_matrix_mul_3x3(double dst[3][3],
 /*
  * see e.g. http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
  */
-void ff_fill_rgb2xyz_table(const struct PrimaryCoefficients *coeffs,
-                           const struct WhitepointCoefficients *wp,
+void ff_fill_rgb2xyz_table(const AVPrimaryCoefficients *coeffs,
+                           const AVWhitepointCoefficients *wp,
                            double rgb2xyz[3][3])
 {
     double i[3][3], sr, sg, sb, zw;
-
-    rgb2xyz[0][0] = coeffs->xr / coeffs->yr;
-    rgb2xyz[0][1] = coeffs->xg / coeffs->yg;
-    rgb2xyz[0][2] = coeffs->xb / coeffs->yb;
+    double xr = av_q2d(coeffs->xr), yr = av_q2d(coeffs->yr);
+    double xg = av_q2d(coeffs->xg), yg = av_q2d(coeffs->yg);
+    double xb = av_q2d(coeffs->xb), yb = av_q2d(coeffs->yb);
+    double xw = av_q2d(wp->xw), yw = av_q2d(wp->yw);
+
+    rgb2xyz[0][0] = xr / yr;
+    rgb2xyz[0][1] = xg / yg;
+    rgb2xyz[0][2] = xb / yb;
     rgb2xyz[1][0] = rgb2xyz[1][1] = rgb2xyz[1][2] = 1.0;
-    rgb2xyz[2][0] = (1.0 - coeffs->xr - coeffs->yr) / coeffs->yr;
-    rgb2xyz[2][1] = (1.0 - coeffs->xg - coeffs->yg) / coeffs->yg;
-    rgb2xyz[2][2] = (1.0 - coeffs->xb - coeffs->yb) / coeffs->yb;
+    rgb2xyz[2][0] = (1.0 - xr - yr) / yr;
+    rgb2xyz[2][1] = (1.0 - xg - yg) / yg;
+    rgb2xyz[2][2] = (1.0 - xb - yb) / yb;
     ff_matrix_invert_3x3(rgb2xyz, i);
-    zw = 1.0 - wp->xw - wp->yw;
-    sr = i[0][0] * wp->xw + i[0][1] * wp->yw + i[0][2] * zw;
-    sg = i[1][0] * wp->xw + i[1][1] * wp->yw + i[1][2] * zw;
-    sb = i[2][0] * wp->xw + i[2][1] * wp->yw + i[2][2] * zw;
+    zw = 1.0 - xw - yw;
+    sr = i[0][0] * xw + i[0][1] * yw + i[0][2] * zw;
+    sg = i[1][0] * xw + i[1][1] * yw + i[1][2] * zw;
+    sb = i[2][0] * xw + i[2][1] * yw + i[2][2] * zw;
     rgb2xyz[0][0] *= sr;
     rgb2xyz[0][1] *= sg;
     rgb2xyz[0][2] *= sb;
@@ -107,119 +111,32 @@  static const double gbr_matrix[3][3] =
     { 0.5, -0.5, 0   },
 };
 
-/*
- * All constants explained in e.g. https://linuxtv.org/downloads/v4l-dvb-apis/ch02s06.html
- * The older ones (bt470bg/m) are also explained in their respective ITU docs
- * (e.g. https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.470-5-199802-S!!PDF-E.pdf)
- * whereas the newer ones can typically be copied directly from wikipedia :)
- */
-static const struct LumaCoefficients luma_coefficients[AVCOL_SPC_NB] = {
-    [AVCOL_SPC_FCC]        = { 0.30,   0.59,   0.11   },
-    [AVCOL_SPC_BT470BG]    = { 0.299,  0.587,  0.114  },
-    [AVCOL_SPC_SMPTE170M]  = { 0.299,  0.587,  0.114  },
-    [AVCOL_SPC_BT709]      = { 0.2126, 0.7152, 0.0722 },
-    [AVCOL_SPC_SMPTE240M]  = { 0.212,  0.701,  0.087  },
-    [AVCOL_SPC_YCOCG]      = { 0.25,   0.5,    0.25   },
-    [AVCOL_SPC_RGB]        = { 1,      1,      1      },
-    [AVCOL_SPC_BT2020_NCL] = { 0.2627, 0.6780, 0.0593 },
-    [AVCOL_SPC_BT2020_CL]  = { 0.2627, 0.6780, 0.0593 },
-};
-
-const struct LumaCoefficients *ff_get_luma_coefficients(enum AVColorSpace csp)
-{
-    const struct LumaCoefficients *coeffs;
-
-    if (csp >= AVCOL_SPC_NB)
-        return NULL;
-    coeffs = &luma_coefficients[csp];
-    if (!coeffs->cr)
-        return NULL;
-
-    return coeffs;
-}
-
-#define WP_D65 { 0.3127, 0.3290 }
-#define WP_C   { 0.3100, 0.3160 }
-#define WP_DCI { 0.3140, 0.3510 }
-#define WP_E   { 1/3.0f, 1/3.0f }
-
-static const struct ColorPrimaries color_primaries[AVCOL_PRI_NB] = {
-    [AVCOL_PRI_BT709]     = { WP_D65, { 0.640, 0.330, 0.300, 0.600, 0.150, 0.060 } },
-    [AVCOL_PRI_BT470M]    = { WP_C,   { 0.670, 0.330, 0.210, 0.710, 0.140, 0.080 } },
-    [AVCOL_PRI_BT470BG]   = { WP_D65, { 0.640, 0.330, 0.290, 0.600, 0.150, 0.060 } },
-    [AVCOL_PRI_SMPTE170M] = { WP_D65, { 0.630, 0.340, 0.310, 0.595, 0.155, 0.070 } },
-    [AVCOL_PRI_SMPTE240M] = { WP_D65, { 0.630, 0.340, 0.310, 0.595, 0.155, 0.070 } },
-    [AVCOL_PRI_SMPTE428]  = { WP_E,   { 0.735, 0.265, 0.274, 0.718, 0.167, 0.009 } },
-    [AVCOL_PRI_SMPTE431]  = { WP_DCI, { 0.680, 0.320, 0.265, 0.690, 0.150, 0.060 } },
-    [AVCOL_PRI_SMPTE432]  = { WP_D65, { 0.680, 0.320, 0.265, 0.690, 0.150, 0.060 } },
-    [AVCOL_PRI_FILM]      = { WP_C,   { 0.681, 0.319, 0.243, 0.692, 0.145, 0.049 } },
-    [AVCOL_PRI_BT2020]    = { WP_D65, { 0.708, 0.292, 0.170, 0.797, 0.131, 0.046 } },
-    [AVCOL_PRI_JEDEC_P22] = { WP_D65, { 0.630, 0.340, 0.295, 0.605, 0.155, 0.077 } },
-};
-
-const struct ColorPrimaries *ff_get_color_primaries(enum AVColorPrimaries prm)
-{
-    const struct ColorPrimaries *p;
-
-    if (prm >= AVCOL_PRI_NB)
-        return NULL;
-    p = &color_primaries[prm];
-    if (!p->prim.xr)
-        return NULL;
-
-    return p;
-}
-
-enum AVColorPrimaries ff_detect_color_primaries(const struct ColorPrimaries *prm)
-{
-    double delta;
-
-    for (enum AVColorPrimaries p = 0; p < AVCOL_PRI_NB; p++) {
-        const struct ColorPrimaries *ref = &color_primaries[p];
-        if (!ref->prim.xr)
-            continue;
-
-        delta = fabs(prm->prim.xr - ref->prim.xr) +
-                fabs(prm->prim.yr - ref->prim.yr) +
-                fabs(prm->prim.yg - ref->prim.yg) +
-                fabs(prm->prim.yg - ref->prim.yg) +
-                fabs(prm->prim.yb - ref->prim.yb) +
-                fabs(prm->prim.yb - ref->prim.yb) +
-                fabs(prm->wp.xw - ref->wp.xw) +
-                fabs(prm->wp.yw - ref->wp.yw);
-
-        if (delta < 0.001)
-            return p;
-    }
-
-    return AVCOL_PRI_UNSPECIFIED;
-}
-
-void ff_fill_rgb2yuv_table(const struct LumaCoefficients *coeffs,
+void ff_fill_rgb2yuv_table(const AVLumaCoefficients *coeffs,
                            double rgb2yuv[3][3])
 {
     double bscale, rscale;
+    double cr = av_q2d(coeffs->cr), cg = av_q2d(coeffs->cg), cb = av_q2d(coeffs->cb);
 
     // special ycgco matrix
-    if (coeffs->cr == 0.25 && coeffs->cg == 0.5 && coeffs->cb == 0.25) {
+    if (cr == 0.25 && cg == 0.5 && cb == 0.25) {
         memcpy(rgb2yuv, ycgco_matrix, sizeof(double) * 9);
         return;
-    } else if (coeffs->cr == 1 && coeffs->cg == 1 && coeffs->cb == 1) {
+    } else if (cr == 1 && cg == 1 && cb == 1) {
         memcpy(rgb2yuv, gbr_matrix, sizeof(double) * 9);
         return;
     }
 
-    rgb2yuv[0][0] = coeffs->cr;
-    rgb2yuv[0][1] = coeffs->cg;
-    rgb2yuv[0][2] = coeffs->cb;
-    bscale = 0.5 / (coeffs->cb - 1.0);
-    rscale = 0.5 / (coeffs->cr - 1.0);
-    rgb2yuv[1][0] = bscale * coeffs->cr;
-    rgb2yuv[1][1] = bscale * coeffs->cg;
+    rgb2yuv[0][0] = cr;
+    rgb2yuv[0][1] = cg;
+    rgb2yuv[0][2] = cb;
+    bscale = 0.5 / (cb - 1.0);
+    rscale = 0.5 / (cr - 1.0);
+    rgb2yuv[1][0] = bscale * cr;
+    rgb2yuv[1][1] = bscale * cg;
     rgb2yuv[1][2] = 0.5;
     rgb2yuv[2][0] = 0.5;
-    rgb2yuv[2][1] = rscale * coeffs->cg;
-    rgb2yuv[2][2] = rscale * coeffs->cb;
+    rgb2yuv[2][1] = rscale * cg;
+    rgb2yuv[2][2] = rscale * cb;
 }
 
 double ff_determine_signal_peak(AVFrame *in)
diff --git a/libavfilter/colorspace.h b/libavfilter/colorspace.h
index 6959133a49..879518d242 100644
--- a/libavfilter/colorspace.h
+++ b/libavfilter/colorspace.h
@@ -20,43 +20,20 @@ 
 #ifndef AVFILTER_COLORSPACE_H
 #define AVFILTER_COLORSPACE_H
 
+#include "libavutil/csp.h"
 #include "libavutil/frame.h"
 #include "libavutil/pixfmt.h"
 
 #define REFERENCE_WHITE 100.0f
 
-struct LumaCoefficients {
-    double cr, cg, cb;
-};
-
-struct PrimaryCoefficients {
-    double xr, yr, xg, yg, xb, yb;
-};
-
-struct WhitepointCoefficients {
-    double xw, yw;
-};
-
-struct ColorPrimaries {
-    struct WhitepointCoefficients wp;
-    struct PrimaryCoefficients prim;
-};
-
 void ff_matrix_invert_3x3(const double in[3][3], double out[3][3]);
 void ff_matrix_mul_3x3(double dst[3][3],
                const double src1[3][3], const double src2[3][3]);
-void ff_fill_rgb2xyz_table(const struct PrimaryCoefficients *coeffs,
-                           const struct WhitepointCoefficients *wp,
+void ff_fill_rgb2xyz_table(const AVPrimaryCoefficients *coeffs,
+                           const AVWhitepointCoefficients *wp,
                            double rgb2xyz[3][3]);
-
-/* Returns AVCOL_PRI_UNSPECIFIED if no clear match can be identified */
-enum AVColorPrimaries ff_detect_color_primaries(const struct ColorPrimaries *prm);
-
-const struct ColorPrimaries *ff_get_color_primaries(enum AVColorPrimaries prm);
-const struct LumaCoefficients *ff_get_luma_coefficients(enum AVColorSpace csp);
-void ff_fill_rgb2yuv_table(const struct LumaCoefficients *coeffs,
+void ff_fill_rgb2yuv_table(const AVLumaCoefficients *coeffs,
                            double rgb2yuv[3][3]);
-
 double ff_determine_signal_peak(AVFrame *in);
 void ff_update_hdr_metadata(AVFrame *in, double peak);
 
diff --git a/libavfilter/fflcms2.c b/libavfilter/fflcms2.c
index efc7cb5189..6a843093f7 100644
--- a/libavfilter/fflcms2.c
+++ b/libavfilter/fflcms2.c
@@ -18,6 +18,7 @@ 
  */
 
 #include "libavutil/color_utils.h"
+#include "libavutil/csp.h"
 
 #include "fflcms2.h"
 
@@ -148,20 +149,20 @@  int ff_icc_profile_generate(FFIccContext *s,
                             cmsHPROFILE *out_profile)
 {
     cmsToneCurve *tonecurve;
-    const struct ColorPrimaries *prim;
+    const AVColorPrimariesDesc *prim;
     int ret;
 
-    if (!(prim = ff_get_color_primaries(color_prim)))
+    if (!(prim = av_get_color_primaries(color_prim)))
         return AVERROR_INVALIDDATA;
     if ((ret = get_curve(s, color_trc, &tonecurve)) < 0)
         return ret;
 
     *out_profile = cmsCreateRGBProfileTHR(s->ctx,
-        &(cmsCIExyY) { prim->wp.xw, prim->wp.yw, 1.0 },
+        &(cmsCIExyY) { av_q2d(prim->wp.xw), av_q2d(prim->wp.yw), 1.0 },
         &(cmsCIExyYTRIPLE) {
-            .Red    = { prim->prim.xr, prim->prim.yr, 1.0 },
-            .Green  = { prim->prim.xg, prim->prim.yg, 1.0 },
-            .Blue   = { prim->prim.xb, prim->prim.yb, 1.0 },
+            .Red    = { av_q2d(prim->prim.xr), av_q2d(prim->prim.yr), 1.0 },
+            .Green  = { av_q2d(prim->prim.xg), av_q2d(prim->prim.yg), 1.0 },
+            .Blue   = { av_q2d(prim->prim.xb), av_q2d(prim->prim.yb), 1.0 },
         },
         (cmsToneCurve *[3]) { tonecurve, tonecurve, tonecurve }
     );
@@ -194,15 +195,15 @@  int ff_icc_profile_attach(FFIccContext *s, cmsHPROFILE profile, AVFrame *frame)
     return 0;
 }
 
-static av_always_inline void XYZ_xy(cmsCIEXYZ XYZ, double *x, double *y)
+static av_always_inline void XYZ_xy(cmsCIEXYZ XYZ, AVRational *x, AVRational *y)
 {
     double k = 1.0 / (XYZ.X + XYZ.Y + XYZ.Z);
-    *x = k * XYZ.X;
-    *y = k * XYZ.Y;
+    *x = av_d2q(k * XYZ.X, 30000);
+    *y = av_d2q(k * XYZ.Y, 30000);
 }
 
 int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
-                                  struct ColorPrimaries *out_primaries)
+                                  AVColorPrimariesDesc *out_primaries)
 {
     static const uint8_t testprimaries[4][3] = {
         { 0xFF,    0,    0 }, /* red */
@@ -211,8 +212,8 @@  int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
         { 0xFF, 0xFF, 0xFF }, /* white */
     };
 
-    struct WhitepointCoefficients *wp = &out_primaries->wp;
-    struct PrimaryCoefficients *prim = &out_primaries->prim;
+    AVWhitepointCoefficients *wp = &out_primaries->wp;
+    AVPrimaryCoefficients *prim = &out_primaries->prim;
     cmsFloat64Number prev_adapt;
     cmsHPROFILE xyz;
     cmsHTRANSFORM tf;
diff --git a/libavfilter/fflcms2.h b/libavfilter/fflcms2.h
index ad6c8c47cf..0d238c679f 100644
--- a/libavfilter/fflcms2.h
+++ b/libavfilter/fflcms2.h
@@ -25,9 +25,9 @@ 
 #ifndef AVFILTER_FFLCMS2_H
 #define AVFILTER_FFLCMS2_H
 
+#include "libavutil/csp.h"
 #include "libavutil/frame.h"
 #include "libavutil/pixfmt.h"
-#include "colorspace.h"
 
 #include <lcms2.h>
 
@@ -72,7 +72,7 @@  int ff_icc_profile_attach(FFIccContext *s, cmsHPROFILE profile, AVFrame *frame);
  * Returns 0 on success, or a negative error code.
  */
 int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
-                                  struct ColorPrimaries *out_primaries);
+                                  AVColorPrimariesDesc *out_primaries);
 
 /**
  * Attempt detecting the transfer characteristic that best approximates the
diff --git a/libavfilter/vf_colorspace.c b/libavfilter/vf_colorspace.c
index 3c8b3b20eb..203fff3941 100644
--- a/libavfilter/vf_colorspace.c
+++ b/libavfilter/vf_colorspace.c
@@ -24,6 +24,7 @@ 
  */
 
 #include "libavutil/avassert.h"
+#include "libavutil/csp.h"
 #include "libavutil/mem_internal.h"
 #include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
@@ -126,7 +127,7 @@  typedef struct ColorSpaceContext {
     unsigned rgb_sz;
     int *dither_scratch[3][2], *dither_scratch_base[3][2];
 
-    const struct ColorPrimaries *in_primaries, *out_primaries;
+    const AVColorPrimariesDesc *in_primaries, *out_primaries;
     int lrgb2lrgb_passthrough;
     DECLARE_ALIGNED(16, int16_t, lrgb2lrgb_coeffs)[3][3][8];
 
@@ -134,7 +135,7 @@  typedef struct ColorSpaceContext {
     int rgb2rgb_passthrough;
     int16_t *lin_lut, *delin_lut;
 
-    const struct LumaCoefficients *in_lumacoef, *out_lumacoef;
+    const AVLumaCoefficients *in_lumacoef, *out_lumacoef;
     int yuv2yuv_passthrough, yuv2yuv_fastmode;
     DECLARE_ALIGNED(16, int16_t, yuv2rgb_coeffs)[3][3][8];
     DECLARE_ALIGNED(16, int16_t, rgb2yuv_coeffs)[3][3][8];
@@ -233,8 +234,8 @@  static int fill_gamma_table(ColorSpaceContext *s)
  * This function uses the Bradford mechanism.
  */
 static void fill_whitepoint_conv_table(double out[3][3], enum WhitepointAdaptation wp_adapt,
-                                       const struct WhitepointCoefficients *wp_src,
-                                       const struct WhitepointCoefficients *wp_dst)
+                                       const AVWhitepointCoefficients *wp_src,
+                                       const AVWhitepointCoefficients *wp_dst)
 {
     static const double ma_tbl[NB_WP_ADAPT_NON_IDENTITY][3][3] = {
         [WP_ADAPT_BRADFORD] = {
@@ -248,18 +249,20 @@  static void fill_whitepoint_conv_table(double out[3][3], enum WhitepointAdaptati
         },
     };
     const double (*ma)[3] = ma_tbl[wp_adapt];
-    double zw_src = 1.0 - wp_src->xw - wp_src->yw;
-    double zw_dst = 1.0 - wp_dst->xw - wp_dst->yw;
+    double xw_src = av_q2d(wp_src->xw), yw_src = av_q2d(wp_src->yw);
+    double xw_dst = av_q2d(wp_dst->xw), yw_dst = av_q2d(wp_dst->yw);
+    double zw_src = 1.0 - xw_src - yw_src;
+    double zw_dst = 1.0 - xw_dst - yw_dst;
     double mai[3][3], fac[3][3], tmp[3][3];
     double rs, gs, bs, rd, gd, bd;
 
     ff_matrix_invert_3x3(ma, mai);
-    rs = ma[0][0] * wp_src->xw + ma[0][1] * wp_src->yw + ma[0][2] * zw_src;
-    gs = ma[1][0] * wp_src->xw + ma[1][1] * wp_src->yw + ma[1][2] * zw_src;
-    bs = ma[2][0] * wp_src->xw + ma[2][1] * wp_src->yw + ma[2][2] * zw_src;
-    rd = ma[0][0] * wp_dst->xw + ma[0][1] * wp_dst->yw + ma[0][2] * zw_dst;
-    gd = ma[1][0] * wp_dst->xw + ma[1][1] * wp_dst->yw + ma[1][2] * zw_dst;
-    bd = ma[2][0] * wp_dst->xw + ma[2][1] * wp_dst->yw + ma[2][2] * zw_dst;
+    rs = ma[0][0] * xw_src + ma[0][1] * yw_src + ma[0][2] * zw_src;
+    gs = ma[1][0] * xw_src + ma[1][1] * yw_src + ma[1][2] * zw_src;
+    bs = ma[2][0] * xw_src + ma[2][1] * yw_src + ma[2][2] * zw_src;
+    rd = ma[0][0] * xw_dst + ma[0][1] * yw_dst + ma[0][2] * zw_dst;
+    gd = ma[1][0] * xw_dst + ma[1][1] * yw_dst + ma[1][2] * zw_dst;
+    bd = ma[2][0] * xw_dst + ma[2][1] * yw_dst + ma[2][2] * zw_dst;
     fac[0][0] = rd / rs;
     fac[1][1] = gd / gs;
     fac[2][2] = bd / bs;
@@ -438,7 +441,7 @@  static int create_filtergraph(AVFilterContext *ctx,
             s->in_prm = default_prm[FFMIN(s->user_iall, CS_NB)];
         if (s->user_iprm != AVCOL_PRI_UNSPECIFIED)
             s->in_prm = s->user_iprm;
-        s->in_primaries = ff_get_color_primaries(s->in_prm);
+        s->in_primaries = av_get_color_primaries(s->in_prm);
         if (!s->in_primaries) {
             av_log(ctx, AV_LOG_ERROR,
                    "Unsupported input primaries %d (%s)\n",
@@ -446,7 +449,7 @@  static int create_filtergraph(AVFilterContext *ctx,
             return AVERROR(EINVAL);
         }
         s->out_prm = out->color_primaries;
-        s->out_primaries = ff_get_color_primaries(s->out_prm);
+        s->out_primaries = av_get_color_primaries(s->out_prm);
         if (!s->out_primaries) {
             if (s->out_prm == AVCOL_PRI_UNSPECIFIED) {
                 if (s->user_all == CS_UNSPECIFIED) {
@@ -466,7 +469,7 @@  static int create_filtergraph(AVFilterContext *ctx,
                                            sizeof(*s->in_primaries));
         if (!s->lrgb2lrgb_passthrough) {
             double rgb2xyz[3][3], xyz2rgb[3][3], rgb2rgb[3][3];
-            const struct WhitepointCoefficients *wp_out, *wp_in;
+            const AVWhitepointCoefficients *wp_out, *wp_in;
 
             wp_out = &s->out_primaries->wp;
             wp_in = &s->in_primaries->wp;
@@ -551,7 +554,7 @@  static int create_filtergraph(AVFilterContext *ctx,
         s->in_rng = in->color_range;
         if (s->user_irng != AVCOL_RANGE_UNSPECIFIED)
             s->in_rng = s->user_irng;
-        s->in_lumacoef = ff_get_luma_coefficients(s->in_csp);
+        s->in_lumacoef = av_get_luma_coefficients(s->in_csp);
         if (!s->in_lumacoef) {
             av_log(ctx, AV_LOG_ERROR,
                    "Unsupported input colorspace %d (%s)\n",
@@ -564,7 +567,7 @@  static int create_filtergraph(AVFilterContext *ctx,
     if (!s->out_lumacoef) {
         s->out_csp = out->colorspace;
         s->out_rng = out->color_range;
-        s->out_lumacoef = ff_get_luma_coefficients(s->out_csp);
+        s->out_lumacoef = av_get_luma_coefficients(s->out_csp);
         if (!s->out_lumacoef) {
             if (s->out_csp == AVCOL_SPC_UNSPECIFIED) {
                 if (s->user_all == CS_UNSPECIFIED) {
diff --git a/libavfilter/vf_iccdetect.c b/libavfilter/vf_iccdetect.c
index fb7871f035..a62c7cad47 100644
--- a/libavfilter/vf_iccdetect.c
+++ b/libavfilter/vf_iccdetect.c
@@ -24,6 +24,7 @@ 
 
 #include <lcms2.h>
 
+#include "libavutil/csp.h"
 #include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
 
@@ -69,7 +70,7 @@  static int iccdetect_filter_frame(AVFilterLink *inlink, AVFrame *frame)
     AVFilterContext *avctx = inlink->dst;
     IccDetectContext *s = avctx->priv;
     const AVFrameSideData *sd;
-    struct ColorPrimaries coeffs;
+    AVColorPrimariesDesc coeffs;
     cmsHPROFILE profile;
     int ret;
 
@@ -98,7 +99,7 @@  static int iccdetect_filter_frame(AVFilterLink *inlink, AVFrame *frame)
     if (ret < 0)
         return ret;
 
-    s->profile_prim = ff_detect_color_primaries(&coeffs);
+    s->profile_prim = av_detect_color_primaries(&coeffs);
 
 done:
     if (s->profile_prim != AVCOL_PRI_UNSPECIFIED) {
diff --git a/libavfilter/vf_tonemap.c b/libavfilter/vf_tonemap.c
index 1285dbaa4d..5845ea4047 100644
--- a/libavfilter/vf_tonemap.c
+++ b/libavfilter/vf_tonemap.c
@@ -27,6 +27,7 @@ 
 #include <stdio.h>
 #include <string.h>
 
+#include "libavutil/csp.h"
 #include "libavutil/imgutils.h"
 #include "libavutil/internal.h"
 #include "libavutil/intreadwrite.h"
@@ -50,16 +51,6 @@  enum TonemapAlgorithm {
     TONEMAP_MAX,
 };
 
-static const struct LumaCoefficients luma_coefficients[AVCOL_SPC_NB] = {
-    [AVCOL_SPC_FCC]        = { 0.30,   0.59,   0.11   },
-    [AVCOL_SPC_BT470BG]    = { 0.299,  0.587,  0.114  },
-    [AVCOL_SPC_SMPTE170M]  = { 0.299,  0.587,  0.114  },
-    [AVCOL_SPC_BT709]      = { 0.2126, 0.7152, 0.0722 },
-    [AVCOL_SPC_SMPTE240M]  = { 0.212,  0.701,  0.087  },
-    [AVCOL_SPC_BT2020_NCL] = { 0.2627, 0.6780, 0.0593 },
-    [AVCOL_SPC_BT2020_CL]  = { 0.2627, 0.6780, 0.0593 },
-};
-
 typedef struct TonemapContext {
     const AVClass *class;
 
@@ -68,7 +59,7 @@  typedef struct TonemapContext {
     double desat;
     double peak;
 
-    const struct LumaCoefficients *coeffs;
+    const AVLumaCoefficients *coeffs;
 } TonemapContext;
 
 static av_cold int init(AVFilterContext *ctx)
@@ -135,7 +126,7 @@  static void tonemap(TonemapContext *s, AVFrame *out, const AVFrame *in,
 
     /* desaturate to prevent unnatural colors */
     if (s->desat > 0) {
-        float luma = s->coeffs->cr * *r_in + s->coeffs->cg * *g_in + s->coeffs->cb * *b_in;
+        float luma = av_q2d(s->coeffs->cr) * *r_in + av_q2d(s->coeffs->cg) * *g_in + av_q2d(s->coeffs->cb) * *b_in;
         float overbright = FFMAX(luma - s->desat, 1e-6) / FFMAX(luma, 1e-6);
         *r_out = MIX(*r_in, luma, overbright);
         *g_out = MIX(*g_in, luma, overbright);
@@ -249,7 +240,7 @@  static int filter_frame(AVFilterLink *link, AVFrame *in)
     }
 
     /* load original color space even if pixel format is RGB to compute overbrights */
-    s->coeffs = &luma_coefficients[in->colorspace];
+    s->coeffs = av_get_luma_coefficients(in->colorspace);
     if (s->desat > 0 && (in->colorspace == AVCOL_SPC_UNSPECIFIED || !s->coeffs)) {
         if (in->colorspace == AVCOL_SPC_UNSPECIFIED)
             av_log(s, AV_LOG_WARNING, "Missing color space information, ");
diff --git a/libavutil/Makefile b/libavutil/Makefile
index 234de62a4b..74d21a8103 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -20,6 +20,7 @@  HEADERS = adler32.h                                                     \
           common.h                                                      \
           cpu.h                                                         \
           crc.h                                                         \
+          csp.h                                                         \
           des.h                                                         \
           detection_bbox.h                                              \
           dict.h                                                        \
@@ -113,6 +114,7 @@  OBJS = adler32.o                                                        \
        color_utils.o                                                    \
        cpu.o                                                            \
        crc.o                                                            \
+       csp.o                                                            \
        des.o                                                            \
        detection_bbox.o                                                 \
        dict.o                                                           \
diff --git a/libavutil/csp.c b/libavutil/csp.c
new file mode 100644
index 0000000000..0cbf310afb
--- /dev/null
+++ b/libavutil/csp.c
@@ -0,0 +1,121 @@ 
+/*
+ * Copyright (c) 2016 Ronald S. Bultje <rsbultje@gmail.com>
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdlib.h>
+
+#include "attributes.h"
+#include "csp.h"
+#include "pixfmt.h"
+#include "rational.h"
+
+#define AVR(d) { (int)(d * 30000), 30000 }
+
+/*
+ * All constants explained in e.g. https://linuxtv.org/downloads/v4l-dvb-apis/ch02s06.html
+ * The older ones (bt470bg/m) are also explained in their respective ITU docs
+ * (e.g. https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.470-5-199802-S!!PDF-E.pdf)
+ * whereas the newer ones can typically be copied directly from wikipedia :)
+ */
+static const struct AVLumaCoefficients luma_coefficients[AVCOL_SPC_NB] = {
+    [AVCOL_SPC_FCC]        = { AVR(0.30),   AVR(0.59),   AVR(0.11)   },
+    [AVCOL_SPC_BT470BG]    = { AVR(0.299),  AVR(0.587),  AVR(0.114)  },
+    [AVCOL_SPC_SMPTE170M]  = { AVR(0.299),  AVR(0.587),  AVR(0.114)  },
+    [AVCOL_SPC_BT709]      = { AVR(0.2126), AVR(0.7152), AVR(0.0722) },
+    [AVCOL_SPC_SMPTE240M]  = { AVR(0.212),  AVR(0.701),  AVR(0.087)  },
+    [AVCOL_SPC_YCOCG]      = { AVR(0.25),   AVR(0.5),    AVR(0.25)   },
+    [AVCOL_SPC_RGB]        = { AVR(1),      AVR(1),      AVR(1)      },
+    [AVCOL_SPC_BT2020_NCL] = { AVR(0.2627), AVR(0.6780), AVR(0.0593) },
+    [AVCOL_SPC_BT2020_CL]  = { AVR(0.2627), AVR(0.6780), AVR(0.0593) },
+};
+
+const struct AVLumaCoefficients *av_get_luma_coefficients(enum AVColorSpace csp)
+{
+    const AVLumaCoefficients *coeffs;
+
+    if (csp >= AVCOL_SPC_NB)
+        return NULL;
+    coeffs = &luma_coefficients[csp];
+    if (!coeffs->cr.num)
+        return NULL;
+
+    return coeffs;
+}
+
+#define WP_D65 { AVR(0.3127), AVR(0.3290) }
+#define WP_C   { AVR(0.3100), AVR(0.3160) }
+#define WP_DCI { AVR(0.3140), AVR(0.3510) }
+#define WP_E   { {1, 3}, {1, 3} }
+
+static const AVColorPrimariesDesc color_primaries[AVCOL_PRI_NB] = {
+    [AVCOL_PRI_BT709]     = { WP_D65, { AVR(0.640), AVR(0.330), AVR(0.300), AVR(0.600), AVR(0.150), AVR(0.060) } },
+    [AVCOL_PRI_BT470M]    = { WP_C,   { AVR(0.670), AVR(0.330), AVR(0.210), AVR(0.710), AVR(0.140), AVR(0.080) } },
+    [AVCOL_PRI_BT470BG]   = { WP_D65, { AVR(0.640), AVR(0.330), AVR(0.290), AVR(0.600), AVR(0.150), AVR(0.060) } },
+    [AVCOL_PRI_SMPTE170M] = { WP_D65, { AVR(0.630), AVR(0.340), AVR(0.310), AVR(0.595), AVR(0.155), AVR(0.070) } },
+    [AVCOL_PRI_SMPTE240M] = { WP_D65, { AVR(0.630), AVR(0.340), AVR(0.310), AVR(0.595), AVR(0.155), AVR(0.070) } },
+    [AVCOL_PRI_SMPTE428]  = { WP_E,   { AVR(0.735), AVR(0.265), AVR(0.274), AVR(0.718), AVR(0.167), AVR(0.009) } },
+    [AVCOL_PRI_SMPTE431]  = { WP_DCI, { AVR(0.680), AVR(0.320), AVR(0.265), AVR(0.690), AVR(0.150), AVR(0.060) } },
+    [AVCOL_PRI_SMPTE432]  = { WP_D65, { AVR(0.680), AVR(0.320), AVR(0.265), AVR(0.690), AVR(0.150), AVR(0.060) } },
+    [AVCOL_PRI_FILM]      = { WP_C,   { AVR(0.681), AVR(0.319), AVR(0.243), AVR(0.692), AVR(0.145), AVR(0.049) } },
+    [AVCOL_PRI_BT2020]    = { WP_D65, { AVR(0.708), AVR(0.292), AVR(0.170), AVR(0.797), AVR(0.131), AVR(0.046) } },
+    [AVCOL_PRI_JEDEC_P22] = { WP_D65, { AVR(0.630), AVR(0.340), AVR(0.295), AVR(0.605), AVR(0.155), AVR(0.077) } },
+};
+
+const AVColorPrimariesDesc *av_get_color_primaries(enum AVColorPrimaries prm)
+{
+    const AVColorPrimariesDesc *p;
+
+    if (prm >= AVCOL_PRI_NB)
+        return NULL;
+    p = &color_primaries[prm];
+    if (!p->prim.xr.num)
+        return NULL;
+
+    return p;
+}
+
+static av_always_inline AVRational abs_sub_q(AVRational r1, AVRational r2)
+{
+    AVRational diff = av_sub_q(r1, r2);
+    return av_make_q(abs(diff.num), abs(diff.den));
+}
+
+enum AVColorPrimaries av_detect_color_primaries(const AVColorPrimariesDesc *prm)
+{
+    AVRational delta;
+
+    for (enum AVColorPrimaries p = 0; p < AVCOL_PRI_NB; p++) {
+        const AVColorPrimariesDesc *ref = &color_primaries[p];
+        if (!ref->prim.xr.num)
+            continue;
+
+        delta = abs_sub_q(prm->prim.xr, ref->prim.xr);
+        delta = av_add_q(delta, abs_sub_q(prm->prim.yr, ref->prim.yr));
+        delta = av_add_q(delta, abs_sub_q(prm->prim.yg, ref->prim.yg));
+        delta = av_add_q(delta, abs_sub_q(prm->prim.yg, ref->prim.yg));
+        delta = av_add_q(delta, abs_sub_q(prm->prim.yb, ref->prim.yb));
+        delta = av_add_q(delta, abs_sub_q(prm->prim.yb, ref->prim.yb));
+        delta = av_add_q(delta, abs_sub_q(prm->wp.xw, ref->wp.xw));
+        delta = av_add_q(delta, abs_sub_q(prm->wp.yw, ref->wp.yw));
+
+        if (av_cmp_q(delta, av_make_q(1, 1000)) < 0)
+            return p;
+    }
+
+    return AVCOL_PRI_UNSPECIFIED;
+}
diff --git a/libavutil/csp.h b/libavutil/csp.h
new file mode 100644
index 0000000000..c2eada4334
--- /dev/null
+++ b/libavutil/csp.h
@@ -0,0 +1,49 @@ 
+/*
+ * Copyright (c) 2016 Ronald S. Bultje <rsbultje@gmail.com>
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_CSP_H
+#define AVUTIL_CSP_H
+
+#include "pixfmt.h"
+#include "rational.h"
+
+typedef struct AVLumaCoefficients {
+    AVRational cr, cg, cb;
+} AVLumaCoefficients;
+
+typedef struct AVPrimaryCoefficients {
+    AVRational xr, yr, xg, yg, xb, yb;
+} AVPrimaryCoefficients;
+
+typedef struct AVWhitepointCoefficients {
+    AVRational xw, yw;
+} AVWhitepointCoefficients;
+
+typedef struct AVColorPrimariesDesc {
+    AVWhitepointCoefficients wp;
+    AVPrimaryCoefficients prim;
+} AVColorPrimariesDesc;
+
+/* Returns AVCOL_PRI_UNSPECIFIED if no clear match can be identified */
+enum AVColorPrimaries av_detect_color_primaries(const AVColorPrimariesDesc *prm);
+
+const AVColorPrimariesDesc *av_get_color_primaries(enum AVColorPrimaries prm);
+const AVLumaCoefficients *av_get_luma_coefficients(enum AVColorSpace csp);
+
+#endif /* AVUTIL_CSP_H */
diff --git a/libavutil/version.h b/libavutil/version.h
index 6735c20090..dd7d20a9fa 100644
--- a/libavutil/version.h
+++ b/libavutil/version.h
@@ -79,8 +79,8 @@ 
  */
 
 #define LIBAVUTIL_VERSION_MAJOR  57
-#define LIBAVUTIL_VERSION_MINOR  24
-#define LIBAVUTIL_VERSION_MICRO 101
+#define LIBAVUTIL_VERSION_MINOR  25
+#define LIBAVUTIL_VERSION_MICRO 100
 
 #define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
                                                LIBAVUTIL_VERSION_MINOR, \