swscale: add ICC intent enum and option

This setting can be used to infuence the type of tone and gamut mapping used
internally when color space conversions are required. As discussed at VDD'24,
the default was set to relative colorimetric clipping, which is approximately
associative, surjective and idempotent. As such, it roundtrips well, although
it is strictly speaking not associative on out-of-gamut colors.
This commit is contained in:
Niklas Haas 2024-11-28 13:59:02 +01:00
parent 7b7c32322d
commit 45f0a7ad33
8 changed files with 62 additions and 8 deletions

View file

@ -2,6 +2,9 @@ The last version increases of all libraries were on 2024-03-07
API changes, most recent first:
2024-12-xx - xxxxxxxxxx - lsws 8.13.100 - swscale.h
Add enum SwsIntent and SwsContext.intent.
2024-12-15 - xxxxxxxxxx - lavc 61.27.100 packet.h
Add av_container_fifo_alloc_avpacket().

View file

@ -21071,7 +21071,38 @@ Set libswscale input parameters for scaling algorithms that need them. See
complete documentation. If not explicitly specified the filter applies
empty parameters.
@item intent
Set the ICC rendering intent to use when transforming between different color
spaces. It accepts the following values:
@table @samp
@item perceptual
Use a perceptually guided tone and gamut mapping curve. The exact details of
the mapping used may change at any time and should not be relied on as stable.
This intent is recommended for final viewing of image/video content in typical
viewing settings.
@item relative_colorimetric
Statically clip out-of-gamut colors using a colorimetric clipping curve which
attempts to find the colorimetrically least dissimilar in-gamut color. This
intent performs white point adaptation and black point adaptation. This is
the default. This intent is recommended wherever faithful color reproduction
is of the utmost importance, even at the cost of clipping.
@item absolute_colorimetric
Hard clip out-of-gamut colors with no attempt at white or black point
reproduction. This intent will reproduce in-gamut colors 1:1 on the output
display as they would appear on the reference display, assuming the output
display is appropriately calibrated.
@item saturation
Performs saturation mapping - that is, stretches the input color volume
directly onto the output color volume, in non-linear fashion that preserves the
original signal appearance as much as possible. This intent is recommended for
signal content evaluation, as it will not lead to any clipping. It is roughly
analogous to not performing any color mapping, although it still takes into
account the mastering display primaries and any differences in encoding TRC.
@end table
@item size, s
Set the video size. For the syntax of this option, check the

View file

@ -579,6 +579,7 @@ static int opts_equal(const SwsContext *c1, const SwsContext *c2)
c1->src_v_chr_pos == c2->src_v_chr_pos &&
c1->dst_h_chr_pos == c2->dst_h_chr_pos &&
c1->dst_v_chr_pos == c2->dst_v_chr_pos &&
c1->intent == c2->intent &&
!memcmp(c1->scaler_params, c2->scaler_params, sizeof(c1->scaler_params));
}

View file

@ -84,6 +84,12 @@ static const AVOption swscale_options[] = {
{ "threads", "number of threads", OFFSET(threads), AV_OPT_TYPE_INT, {.i64 = 1 }, .flags = VE, .unit = "threads", .max = INT_MAX },
{ "auto", "automatic selection", 0, AV_OPT_TYPE_CONST, {.i64 = 0 }, .flags = VE, .unit = "threads" },
{ "intent", "color mapping intent", OFFSET(intent), AV_OPT_TYPE_INT, { .i64 = SWS_INTENT_RELATIVE_COLORIMETRIC }, .flags = VE, .unit = "intent", .max = SWS_INTENT_NB - 1 },
{ "perceptual", "perceptual tone mapping", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_INTENT_PERCEPTUAL }, .flags = VE, .unit = "intent" },
{ "relative_colorimetric", "relative colorimetric clipping", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_INTENT_RELATIVE_COLORIMETRIC }, .flags = VE, .unit = "intent" },
{ "saturation", "saturation mapping", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_INTENT_SATURATION }, .flags = VE, .unit = "intent" },
{ "absolute_colorimetric", "absolute colorimetric clipping", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_INTENT_ABSOLUTE_COLORIMETRIC }, .flags = VE, .unit = "intent" },
{ NULL }
};

View file

@ -162,6 +162,14 @@ typedef enum SwsFlags {
SWS_ERROR_DIFFUSION = 1 << 23, ///< Set `SwsContext.dither` instead
} SwsFlags;
typedef enum SwsIntent {
SWS_INTENT_PERCEPTUAL = 0, ///< Perceptual tone mapping
SWS_INTENT_RELATIVE_COLORIMETRIC = 1, ///< Relative colorimetric clipping
SWS_INTENT_SATURATION = 2, ///< Saturation mapping
SWS_INTENT_ABSOLUTE_COLORIMETRIC = 3, ///< Absolute colorimetric clipping
SWS_INTENT_NB, ///< not part of the ABI
} SwsIntent;
/***********************************
* Context creation and management *
***********************************/
@ -226,6 +234,11 @@ typedef struct SwsContext {
int dst_v_chr_pos; ///< Destination vertical chroma position
int dst_h_chr_pos; ///< Destination horizontal chroma position
/**
* Desired ICC intent for color space conversions.
*/
int intent;
/* Remember to add new fields to graph.c:opts_equal() */
} SwsContext;

View file

@ -699,7 +699,7 @@ static_assert(offsetof(SwsInternal, redDither) + DITHER32_INT == offsetof(SwsInt
#if ARCH_X86_64
/* x86 yuv2gbrp uses the SwsInternal for yuv coefficients
if struct offsets change the asm needs to be updated too */
static_assert(offsetof(SwsInternal, yuv2rgb_y_offset) == 40332,
static_assert(offsetof(SwsInternal, yuv2rgb_y_offset) == 40348,
"yuv2rgb_y_offset must be updated in x86 asm");
#endif

View file

@ -28,7 +28,7 @@
#include "version_major.h"
#define LIBSWSCALE_VERSION_MINOR 12
#define LIBSWSCALE_VERSION_MINOR 13
#define LIBSWSCALE_VERSION_MICRO 100
#define LIBSWSCALE_VERSION_INT AV_VERSION_INT(LIBSWSCALE_VERSION_MAJOR, \

View file

@ -582,7 +582,7 @@ yuv2nv12cX_fn yuv2nv21
%if ARCH_X86_64
struc SwsInternal
.padding: resb 40332 ; offsetof(SwsInternal, yuv2rgb_y_offset)
.padding: resb 40348 ; offsetof(SwsInternal, yuv2rgb_y_offset)
.yuv2rgb_y_offset: resd 1
.yuv2rgb_y_coeff: resd 1
.yuv2rgb_v2r_coeff: resd 1