[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