[FFmpeg-devel] [PATCH 1/4] lavfi/vf_libplacebo: switch to new gamut mapping API

Niklas Haas ffmpeg at haasn.xyz
Mon May 22 11:35:43 EEST 2023


On Sun, 21 May 2023 14:24:35 +0200 Niklas Haas <ffmpeg at haasn.xyz> wrote:
> From: Niklas Haas <git at haasn.dev>
> 
> Upstream deprecated the old ad-hoc, enum/intent-based gamut mapping API
> and added a new API based on colorimetrically accurate gamut mapping
> functions.
> 
> The relevant change for us is the addition of several new modes, as well
> as deprecation of the old options. Update the documentation accordingly.
> ---
>  doc/filters.texi            | 47 ++++++++++----------
>  libavfilter/vf_libplacebo.c | 86 +++++++++++++++++++++++++++++--------
>  2 files changed, 92 insertions(+), 41 deletions(-)
> 
> diff --git a/doc/filters.texi b/doc/filters.texi
> index ddc8529bbe9..97023d5f2e8 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -16353,37 +16353,36 @@ gamut-mapping when dealing with mismatches between wide-gamut or HDR content.
>  In general, libplacebo relies on accurate source tagging and mastering display
>  gamut information to produce the best results.
>  @table @option
> - at item intent
> -Rendering intent to use when adapting between different primary color gamuts
> -(after tone-mapping).
> - at table @samp
> - at item perceptual
> -Perceptual gamut mapping. Currently equivalent to relative colorimetric.
> - at item relative
> -Relative colorimetric. This is the default.
> - at item absolute
> -Absolute colorimetric.
> - at item saturation
> -Saturation mapping. Forcibly stretches the source gamut to the target gamut.
> - at end table
> -
>  @item gamut_mode
>  How to handle out-of-gamut colors that can occur as a result of colorimetric
>  gamut mapping.
>  @table @samp
>  @item clip
> -Do nothing, simply clip out-of-range colors to the RGB volume. This is the
> -default.
> - at item warn
> -Highlight out-of-gamut pixels (by coloring them pink).
> - at item darken
> -Linearly reduces content brightness to preserves saturated details, followed by
> -clipping the remaining out-of-gamut colors. As the name implies, this makes
> -everything darker, but provides a good balance between preserving details and
> -colors.
> +Do nothing, simply clip out-of-range colors to the RGB volume. Low quality but
> +extremely fast.
> + at item perceptual
> +Perceptually soft-clip colors to the gamut volume. This is the default.
> + at item relative
> +Relative colorimetric hard-clip. Similar to @code{perceptual} but without
> +the soft knee.
> + at item saturation
> +Saturation mapping, maps primaries directly to primaries in RGB space.
> +Not recommended except for artificial computer graphics for which a bright,
> +saturated display is desired.
> + at item absolute
> +Absolute colorimetric hard-clip. Performs no adjustment of the white point.
>  @item desaturate
>  Hard-desaturates out-of-gamut colors towards white, while preserving the
> -luminance. Has a tendency to shift colors.
> +luminance. Has a tendency to distort the visual appearance of bright objects.
> + at item darken
> +Linearly reduces content brightness to preserves saturated details, followed by
> +clipping the remaining out-of-gamut colors.
> + at item warn
> +Highlight out-of-gamut pixels (by inverting/marking them).
> + at item linear
> +Linearly reduces chromaticity of the entire image to make it fit within the
> +target color volume. Be careful when using this on BT.2020 sources without
> +proper mastering metadata, as doing so will lead to excessive desaturation.
>  @end table
>  
>  @item tonemapping
> diff --git a/libavfilter/vf_libplacebo.c b/libavfilter/vf_libplacebo.c
> index f26d0126beb..09bb3dfac86 100644
> --- a/libavfilter/vf_libplacebo.c
> +++ b/libavfilter/vf_libplacebo.c
> @@ -56,6 +56,19 @@ enum {
>      TONE_MAP_COUNT,
>  };
>  
> +enum {
> +    GAMUT_MAP_CLIP,
> +    GAMUT_MAP_PERCEPTUAL,
> +    GAMUT_MAP_RELATIVE,
> +    GAMUT_MAP_SATURATION,
> +    GAMUT_MAP_ABSOLUTE,
> +    GAMUT_MAP_DESATURATE,
> +    GAMUT_MAP_DARKEN,
> +    GAMUT_MAP_HIGHLIGHT,
> +    GAMUT_MAP_LINEAR,
> +    GAMUT_MAP_COUNT,
> +};
> +
>  static const char *const var_names[] = {
>      "in_w", "iw",   ///< width  of the input video frame
>      "in_h", "ih",   ///< height of the input video frame
> @@ -186,7 +199,6 @@ typedef struct LibplaceboContext {
>  
>      /* pl_color_map_params */
>      struct pl_color_map_params color_map_params;
> -    int intent;
>      int gamut_mode;
>      int tonemapping;
>      float tonemapping_param;
> @@ -202,6 +214,7 @@ typedef struct LibplaceboContext {
>      int gamut_warning;
>      int gamut_clipping;
>      int force_icc_lut;
> +    int intent;
>  #endif
>  
>      /* pl_dither_params */
> @@ -272,6 +285,34 @@ static const struct pl_tone_map_function *pl_get_tonemapping_func(int tm) {
>      }
>  }
>  
> +static void set_gamut_mode(struct pl_color_map_params *p, int gamut_mode)
> +{
> +    switch (gamut_mode) {
> +#if PL_API_VER >= 269
> +    case GAMUT_MAP_CLIP:       p->gamut_mapping = &pl_gamut_map_clip; return;
> +    case GAMUT_MAP_PERCEPTUAL: p->gamut_mapping = &pl_gamut_map_perceptual; return;
> +    case GAMUT_MAP_RELATIVE:   p->gamut_mapping = &pl_gamut_map_relative; return;
> +    case GAMUT_MAP_SATURATION: p->gamut_mapping = &pl_gamut_map_saturation; return;
> +    case GAMUT_MAP_ABSOLUTE:   p->gamut_mapping = &pl_gamut_map_absolute; return;
> +    case GAMUT_MAP_DESATURATE: p->gamut_mapping = &pl_gamut_map_desaturate; return;
> +    case GAMUT_MAP_DARKEN:     p->gamut_mapping = &pl_gamut_map_darken; return;
> +    case GAMUT_MAP_HIGHLIGHT:  p->gamut_mapping = &pl_gamut_map_highlight; return;
> +    case GAMUT_MAP_LINEAR:     p->gamut_mapping = &pl_gamut_map_linear; return;
> +#else
> +    case GAMUT_MAP_RELATIVE:   p->intent = PL_INTENT_RELATIVE_COLORIMETRIC; return;
> +    case GAMUT_MAP_SATURATION: p->intent = PL_INTENT_SATURATION; return;
> +    case GAMUT_MAP_ABSOLUTE:   p->intent = PL_INTENT_ABSOLUTE_COLORIMETRIC; return;
> +    case GAMUT_MAP_DESATURATE: p->gamut_mode = PL_GAMUT_DESATURATE; return;
> +    case GAMUT_MAP_DARKEN:     p->gamut_mode = PL_GAMUT_DARKEN; return;
> +    case GAMUT_MAP_HIGHLIGHT:  p->gamut_mode = PL_GAMUT_WARN; return;
> +    /* Use defaults for all other cases */
> +    default: return;
> +#endif
> +    }
> +
> +    av_assert0(0);
> +};
> +
>  static int parse_shader(AVFilterContext *avctx, const void *shader, size_t len)
>  {
>      LibplaceboContext *s = avctx->priv;
> @@ -317,7 +358,7 @@ static int update_settings(AVFilterContext *ctx)
>      int err = 0;
>      LibplaceboContext *s = ctx->priv;
>      enum pl_tone_map_mode tonemapping_mode = s->tonemapping_mode;
> -    enum pl_gamut_mode gamut_mode = s->gamut_mode;
> +    int gamut_mode = s->gamut_mode;
>      uint8_t color_rgba[4];
>  
>      RET(av_parse_color(color_rgba, s->fillcolor, -1, s));
> @@ -336,10 +377,16 @@ static int update_settings(AVFilterContext *ctx)
>          }
>      }
>  
> +    switch (s->intent) {
> +    case PL_INTENT_SATURATION:            gamut_mode = GAMUT_MAP_SATURATION; break;
> +    case PL_INTENT_RELATIVE_COLORIMETRIC: gamut_mode = GAMUT_MAP_RELATIVE; break;
> +    case PL_INTENT_ABSOLUTE_COLORIMETRIC: gamut_mode = GAMUT_MAP_ABSOLUTE; break;
> +    }
> +
>      if (s->gamut_warning)
> -        gamut_mode = PL_GAMUT_WARN;
> +        gamut_mode = GAMUT_MAP_HIGHLIGHT;
>      if (s->gamut_clipping)
> -        gamut_mode = PL_GAMUT_DESATURATE;
> +        gamut_mode = GAMUT_MAP_DESATURATE;
>  #endif
>  
>      s->deband_params = *pl_deband_params(
> @@ -366,8 +413,6 @@ static int update_settings(AVFilterContext *ctx)
>      );
>  
>      s->color_map_params = *pl_color_map_params(
> -        .intent = s->intent,
> -        .gamut_mode = gamut_mode,
>          .tone_mapping_function = pl_get_tonemapping_func(s->tonemapping),
>          .tone_mapping_param = s->tonemapping_param,
>          .tone_mapping_mode = tonemapping_mode,
> @@ -376,6 +421,8 @@ static int update_settings(AVFilterContext *ctx)
>          .lut_size = s->tonemapping_lut_size,
>      );
>  
> +    set_gamut_mode(&s->color_map_params, gamut_mode);
> +
>      s->dither_params = *pl_dither_params(
>          .method = s->dithering,
>          .lut_size = s->dither_lut_size,
> @@ -1143,16 +1190,16 @@ static const AVOption libplacebo_options[] = {
>      { "scene_threshold_high", "Scene change high threshold", OFFSET(scene_high), AV_OPT_TYPE_FLOAT, {.dbl = 10.0}, -1.0, 100.0, DYNAMIC },
>      { "overshoot", "Tone-mapping overshoot margin", OFFSET(overshoot), AV_OPT_TYPE_FLOAT, {.dbl = 0.05}, 0.0, 1.0, DYNAMIC },
>  
> -    { "intent", "Rendering intent", OFFSET(intent), AV_OPT_TYPE_INT, {.i64 = PL_INTENT_RELATIVE_COLORIMETRIC}, 0, 3, DYNAMIC, "intent" },
> -        { "perceptual", "Perceptual", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_PERCEPTUAL}, 0, 0, STATIC, "intent" },
> -        { "relative", "Relative colorimetric", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_RELATIVE_COLORIMETRIC}, 0, 0, STATIC, "intent" },
> -        { "absolute", "Absolute colorimetric", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_ABSOLUTE_COLORIMETRIC}, 0, 0, STATIC, "intent" },
> -        { "saturation", "Saturation mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_SATURATION}, 0, 0, STATIC, "intent" },
> -    { "gamut_mode", "Gamut-mapping mode", OFFSET(gamut_mode), AV_OPT_TYPE_INT, {.i64 = PL_GAMUT_CLIP}, 0, PL_GAMUT_MODE_COUNT - 1, DYNAMIC, "gamut_mode" },
> -        { "clip", "Hard-clip gamut boundary", 0, AV_OPT_TYPE_CONST, {.i64 = PL_GAMUT_CLIP}, 0, 0, STATIC, "gamut_mode" },
> -        { "warn", "Highlight out-of-gamut colors", 0, AV_OPT_TYPE_CONST, {.i64 = PL_GAMUT_WARN}, 0, 0, STATIC, "gamut_mode" },
> -        { "darken", "Darken image to fit gamut", 0, AV_OPT_TYPE_CONST, {.i64 = PL_GAMUT_DARKEN}, 0, 0, STATIC, "gamut_mode" },
> -        { "desaturate", "Colorimetrically desaturate colors", 0, AV_OPT_TYPE_CONST, {.i64 = PL_GAMUT_DESATURATE}, 0, 0, STATIC, "gamut_mode" },
> +    { "gamut_mode", "Gamut-mapping mode", OFFSET(gamut_mode), AV_OPT_TYPE_INT, {.i64 = GAMUT_MAP_PERCEPTUAL}, 0, GAMUT_MAP_COUNT - 1, DYNAMIC, "gamut_mode" },
> +        { "clip", "Hard-clip (RGB per-channel)", 0, AV_OPT_TYPE_CONST, {.i64 = GAMUT_MAP_CLIP}, 0, 0, STATIC, "gamut_mode" },
> +        { "perceptual", "Colorimetric soft clipping", 0, AV_OPT_TYPE_CONST, {.i64 = GAMUT_MAP_PERCEPTUAL}, 0, 0, STATIC, "gamut_mode" },
> +        { "relative", "Relative colorimetric clipping", 0, AV_OPT_TYPE_CONST, {.i64 = GAMUT_MAP_RELATIVE}, 0, 0, STATIC, "gamut_mode" },
> +        { "saturation", "Saturation mapping (RGB -> RGB)", 0, AV_OPT_TYPE_CONST, {.i64 = GAMUT_MAP_SATURATION}, 0, 0, STATIC, "gamut_mode" },
> +        { "absolute", "Absolute colorimetric clipping", 0, AV_OPT_TYPE_CONST, {.i64 = GAMUT_MAP_ABSOLUTE}, 0, 0, STATIC, "gamut_mode" },
> +        { "desaturate", "Colorimetrically desaturate colors towards white", 0, AV_OPT_TYPE_CONST, {.i64 = GAMUT_MAP_DESATURATE}, 0, 0, STATIC, "gamut_mode" },
> +        { "darken", "Colorimetric clip with bias towards darkening image to fit gamut", 0, AV_OPT_TYPE_CONST, {.i64 = GAMUT_MAP_DARKEN}, 0, 0, STATIC, "gamut_mode" },
> +        { "warn", "Highlight out-of-gamut colors", 0, AV_OPT_TYPE_CONST, {.i64 = GAMUT_MAP_HIGHLIGHT}, 0, 0, STATIC, "gamut_mode" },
> +        { "linear", "Linearly reduce chromaticity to fit gamut", 0, AV_OPT_TYPE_CONST, {.i64 = GAMUT_MAP_LINEAR}, 0, 0, STATIC, "gamut_mode" },
>      { "tonemapping", "Tone-mapping algorithm", OFFSET(tonemapping), AV_OPT_TYPE_INT, {.i64 = TONE_MAP_AUTO}, 0, TONE_MAP_COUNT - 1, DYNAMIC, "tonemap" },
>          { "auto", "Automatic selection", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_AUTO}, 0, 0, STATIC, "tonemap" },
>          { "clip", "No tone mapping (clip", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_CLIP}, 0, 0, STATIC, "tonemap" },
> @@ -1184,7 +1231,12 @@ static const AVOption libplacebo_options[] = {
>      { "desaturation_strength", "Desaturation strength", OFFSET(desat_str), AV_OPT_TYPE_FLOAT, {.dbl = -1.0}, -1.0, 1.0, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
>      { "desaturation_exponent", "Desaturation exponent", OFFSET(desat_exp), AV_OPT_TYPE_FLOAT, {.dbl = -1.0}, -1.0, 10.0, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
>      { "gamut_warning", "Highlight out-of-gamut colors", OFFSET(gamut_warning), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
> -    { "gamut_clipping", "Enable colorimetric gamut clipping", OFFSET(gamut_clipping), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
> +    { "gamut_clipping", "Enable desaturating colorimetric gamut clipping", OFFSET(gamut_clipping), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
> +    { "intent", "Rendering intent", OFFSET(intent), AV_OPT_TYPE_INT, {.i64 = PL_INTENT_PERCEPTUAL}, 0, 3, DYNAMIC | AV_OPT_FLAG_DEPRECATED, "intent" },
> +        { "perceptual", "Perceptual", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_PERCEPTUAL}, 0, 0, STATIC, "intent" },
> +        { "relative", "Relative colorimetric", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_RELATIVE_COLORIMETRIC}, 0, 0, STATIC, "intent" },
> +        { "absolute", "Absolute colorimetric", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_ABSOLUTE_COLORIMETRIC}, 0, 0, STATIC, "intent" },
> +        { "saturation", "Saturation mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_SATURATION}, 0, 0, STATIC, "intent" },
>  #endif
>  
>      { "dithering", "Dither method to use", OFFSET(dithering), AV_OPT_TYPE_INT, {.i64 = PL_DITHER_BLUE_NOISE}, -1, PL_DITHER_METHOD_COUNT - 1, DYNAMIC, "dither" },
> -- 
> 2.40.1
> 

Merged as d637f20f057586a0a3...877ccaf776c92866e


More information about the ffmpeg-devel mailing list