summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/input.rst4
-rw-r--r--DOCS/man/vf.rst14
-rw-r--r--player/command.c2
-rw-r--r--video/csputils.c11
-rw-r--r--video/csputils.h12
-rw-r--r--video/filter/vf_format.c4
-rw-r--r--video/mp_image.c11
-rw-r--r--video/out/opengl/video.c2
-rw-r--r--video/out/opengl/video_shaders.c90
-rw-r--r--video/out/opengl/video_shaders.h2
10 files changed, 144 insertions, 8 deletions
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst
index e0e9cf28fd..ef3ceb1fb5 100644
--- a/DOCS/man/input.rst
+++ b/DOCS/man/input.rst
@@ -1459,6 +1459,9 @@ Property list
``video-params/sig-peak``
The video file's tagged signal peak as float.
+ ``video-params/light``
+ The light type in use as a string. (Exact values subject to change.)
+
``video-params/chroma-location``
Chroma location as string. (Exact values subject to change.)
@@ -1487,6 +1490,7 @@ Property list
"primaries" MPV_FORMAT_STRING
"gamma" MPV_FORMAT_STRING
"sig-peak" MPV_FORMAT_DOUBLE
+ "light" MPV_FORMAT_STRING
"chroma-location" MPV_FORMAT_STRING
"rotate" MPV_FORMAT_INT64
"stereo-in" MPV_FORMAT_STRING
diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst
index a76e327b8a..b9a3dd41d8 100644
--- a/DOCS/man/vf.rst
+++ b/DOCS/man/vf.rst
@@ -376,6 +376,20 @@ Available mpv-only filters are:
The default of 0.0 will default to the source's nominal peak luminance.
+ ``<light>``
+ Light type of the scene. This is mostly correctly inferred based on the
+ gamma function, but it can be useful to override this when viewing raw
+ camera footage (e.g. V-Log), which is normally scene-referred instead
+ of display-referred.
+
+ Available light types are:
+
+ :auto: Automatic selection (default)
+ :display: Display-referred light (most content)
+ :hlg: Scene-referred using the HLG OOTF (e.g. HLG content)
+ :709-1886: Scene-referred using the BT709+BT1886 interaction
+ :gamma1.2: Scene-referred using a pure power OOTF (gamma=1.2)
+
``<stereo-in>``
Set the stereo mode the video is assumed to be encoded in. Takes the
same values as the ``--video-stereo-mode`` option.
diff --git a/player/command.c b/player/command.c
index 1bb38edeb9..4c15d17ca1 100644
--- a/player/command.c
+++ b/player/command.c
@@ -2696,6 +2696,8 @@ static int property_imgparams(struct mp_image_params p, int action, void *arg)
{"gamma",
SUB_PROP_STR(m_opt_choice_str(mp_csp_trc_names, p.color.gamma))},
{"sig-peak", SUB_PROP_FLOAT(p.color.sig_peak)},
+ {"light",
+ SUB_PROP_STR(m_opt_choice_str(mp_csp_light_names, p.color.light))},
{"chroma-location",
SUB_PROP_STR(m_opt_choice_str(mp_chroma_names, p.chroma_location))},
{"stereo-in",
diff --git a/video/csputils.c b/video/csputils.c
index 51019a8164..9db7bb54a9 100644
--- a/video/csputils.c
+++ b/video/csputils.c
@@ -84,6 +84,15 @@ const struct m_opt_choice_alternatives mp_csp_trc_names[] = {
{0}
};
+const struct m_opt_choice_alternatives mp_csp_light_names[] = {
+ {"auto", MP_CSP_LIGHT_AUTO},
+ {"display", MP_CSP_LIGHT_DISPLAY},
+ {"hlg", MP_CSP_LIGHT_SCENE_HLG},
+ {"709-1886", MP_CSP_LIGHT_SCENE_709_1886},
+ {"gamma1.2", MP_CSP_LIGHT_SCENE_1_2},
+ {0}
+};
+
const char *const mp_csp_equalizer_names[MP_CSP_EQ_COUNT] = {
"brightness",
"contrast",
@@ -112,6 +121,8 @@ void mp_colorspace_merge(struct mp_colorspace *orig, struct mp_colorspace *new)
orig->gamma = new->gamma;
if (!orig->sig_peak)
orig->sig_peak = new->sig_peak;
+ if (!orig->light)
+ orig->light = new->light;
}
// The short name _must_ match with what vf_stereo3d accepts (if supported).
diff --git a/video/csputils.h b/video/csputils.h
index 8e120922d7..9e81c90ebf 100644
--- a/video/csputils.h
+++ b/video/csputils.h
@@ -87,6 +87,17 @@ enum mp_csp_trc {
extern const struct m_opt_choice_alternatives mp_csp_trc_names[];
+enum mp_csp_light {
+ MP_CSP_LIGHT_AUTO,
+ MP_CSP_LIGHT_DISPLAY,
+ MP_CSP_LIGHT_SCENE_HLG,
+ MP_CSP_LIGHT_SCENE_709_1886,
+ MP_CSP_LIGHT_SCENE_1_2,
+ MP_CSP_LIGHT_COUNT
+};
+
+extern const struct m_opt_choice_alternatives mp_csp_light_names[];
+
// These constants are based on the ICC specification (Table 23) and match
// up with the API of LittleCMS, which treats them as integers.
enum mp_render_intent {
@@ -121,6 +132,7 @@ struct mp_colorspace {
enum mp_csp_levels levels;
enum mp_csp_prim primaries;
enum mp_csp_trc gamma;
+ enum mp_csp_light light;
float sig_peak; // highest relative value in signal. 0 = unknown/auto
};
diff --git a/video/filter/vf_format.c b/video/filter/vf_format.c
index 2cb6943879..ed9e4214ee 100644
--- a/video/filter/vf_format.c
+++ b/video/filter/vf_format.c
@@ -39,6 +39,7 @@ struct vf_priv_s {
int primaries;
int gamma;
float sig_peak;
+ int light;
int chroma_location;
int stereo_in;
int stereo_out;
@@ -97,6 +98,8 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
out->color.gamma = p->gamma;
if (p->sig_peak)
out->color.sig_peak = p->sig_peak;
+ if (p->light)
+ out->color.light = p->light;
if (p->chroma_location)
out->chroma_location = p->chroma_location;
if (p->stereo_in)
@@ -146,6 +149,7 @@ static const m_option_t vf_opts_fields[] = {
OPT_CHOICE_C("primaries", primaries, 0, mp_csp_prim_names),
OPT_CHOICE_C("gamma", gamma, 0, mp_csp_trc_names),
OPT_FLOAT("sig-peak", sig_peak, 0),
+ OPT_CHOICE_C("light", light, 0, mp_csp_light_names),
OPT_CHOICE_C("chroma-location", chroma_location, 0, mp_chroma_names),
OPT_CHOICE_C("stereo-in", stereo_in, 0, mp_stereo3d_names),
OPT_CHOICE_C("stereo-out", stereo_out, 0, mp_stereo3d_names),
diff --git a/video/mp_image.c b/video/mp_image.c
index ae0fc02e49..f088e18b36 100644
--- a/video/mp_image.c
+++ b/video/mp_image.c
@@ -409,6 +409,7 @@ void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src)
dst->params.color.primaries = src->params.color.primaries;
dst->params.color.gamma = src->params.color.gamma;
dst->params.color.sig_peak = src->params.color.sig_peak;
+ dst->params.color.light = src->params.color.light;
if ((dst->fmt.flags & MP_IMGFLAG_YUV) == (src->fmt.flags & MP_IMGFLAG_YUV)) {
dst->params.color.space = src->params.color.space;
dst->params.color.levels = src->params.color.levels;
@@ -688,6 +689,16 @@ void mp_image_params_guess_csp(struct mp_image_params *params)
// range as the signal peak to prevent clipping
if (!params->color.sig_peak)
params->color.sig_peak = mp_trc_nom_peak(params->color.gamma);
+
+ if (params->color.light == MP_CSP_LIGHT_AUTO) {
+ // HLG is always scene-referred (using its own OOTF), everything else
+ // we assume is display-refered by default.
+ if (params->color.gamma == MP_CSP_TRC_HLG) {
+ params->color.light = MP_CSP_LIGHT_SCENE_HLG;
+ } else {
+ params->color.light = MP_CSP_LIGHT_DISPLAY;
+ }
+ }
}
// Copy properties and data of the AVFrame into the mp_image, without taking
diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c
index cdb4383011..541e58a534 100644
--- a/video/out/opengl/video.c
+++ b/video/out/opengl/video.c
@@ -2056,6 +2056,7 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
struct mp_colorspace dst = {
.gamma = p->opts.target_trc,
.primaries = p->opts.target_prim,
+ .light = MP_CSP_LIGHT_DISPLAY,
};
if (p->use_lut_3d) {
@@ -2265,6 +2266,7 @@ static void pass_draw_osd(struct gl_video *p, int draw_flags, double pts,
static const struct mp_colorspace csp_srgb = {
.primaries = MP_CSP_PRIM_BT_709,
.gamma = MP_CSP_TRC_SRGB,
+ .light = MP_CSP_LIGHT_DISPLAY,
};
pass_colormanage(p, csp_srgb, true);
diff --git a/video/out/opengl/video_shaders.c b/video/out/opengl/video_shaders.c
index 14120d434b..f56c687903 100644
--- a/video/out/opengl/video_shaders.c
+++ b/video/out/opengl/video_shaders.c
@@ -368,6 +368,81 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
}
}
+// Apply the OOTF mapping from a given light type to display-referred light.
+// The extra peak parameter is used to scale the values before and after
+// the OOTF, and can be inferred using mp_trc_nom_peak
+void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak)
+{
+ if (light == MP_CSP_LIGHT_DISPLAY)
+ return;
+
+ GLSLF("// apply ootf\n", sc);
+ GLSLF("color.rgb *= vec3(%f);\n", peak);
+
+ switch (light)
+ {
+ case MP_CSP_LIGHT_SCENE_HLG:
+ // HLG OOTF from BT.2100, assuming a reference display with a
+ // peak of 1000 cd/m² -> gamma = 1.2
+ GLSL(float luma = dot(color.rgb, vec3(0.2627, 0.6780, 0.0593));)
+ GLSLF("color.rgb *= vec3(%f * pow(luma, 0.2));\n",
+ (1000 / MP_REF_WHITE) / pow(12, 1.2));
+ break;
+ case MP_CSP_LIGHT_SCENE_709_1886:
+ // This OOTF is defined by encoding the result as 709 and then decoding
+ // it as 1886; although this is called 709_1886 we actually use the
+ // more precise (by one decimal) values from BT.2020 instead
+ GLSL(color.rgb = mix(color.rgb * vec3(4.5),
+ vec3(1.0993) * pow(color.rgb, vec3(0.45)) - vec3(0.0993),
+ lessThan(vec3(0.0181), color.rgb));)
+ GLSL(color.rgb = pow(color.rgb, vec3(2.4));)
+ break;
+ case MP_CSP_LIGHT_SCENE_1_2:
+ GLSL(color.rgb = pow(color.rgb, vec3(1.2));)
+ break;
+ default:
+ abort();
+ }
+
+ GLSLF("color.rgb /= vec3(%f);\n", peak);
+}
+
+// Inverse of the function pass_ootf, for completeness' sake. Note that the
+// inverse OOTF for MP_CSP_LIGHT_SCENE_HLG has no analytical solution and is
+// therefore unimplemented. Care must be used to never call this function
+// in that way.(In principle, a iterative algorithm can approach
+// the solution numerically, but this is tricky and we don't really need it
+// since mpv currently only supports outputting display-referred light)
+void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak)
+{
+ if (light == MP_CSP_LIGHT_DISPLAY)
+ return;
+
+ GLSLF("// apply inverse ootf\n");
+ GLSLF("color.rgb *= vec3(%f);\n", peak);
+
+ switch (light)
+ {
+ case MP_CSP_LIGHT_SCENE_HLG:
+ // Has no analytical solution
+ abort();
+ break;
+ case MP_CSP_LIGHT_SCENE_709_1886:
+ GLSL(color.rgb = pow(color.rgb, vec3(1/2.4));)
+ GLSL(color.rgb = mix(color.rgb / vec3(4.5),
+ pow((color.rgb + vec3(0.0993)) / vec3(1.0993), vec3(1/0.45)),
+ lessThan(vec3(0.08145), color.rgb));)
+ break;
+ case MP_CSP_LIGHT_SCENE_1_2:
+ GLSL(color.rgb = pow(color.rgb, vec3(1/1.2));)
+ break;
+ default:
+ abort();
+ }
+
+ GLSLF("color.rgb /= vec3(%f);\n", peak);
+}
+
// Tone map from a known peak brightness to the range [0,1]
static void pass_tone_map(struct gl_shader_cache *sc, float ref_peak,
enum tone_mapping algo, float param)
@@ -452,20 +527,16 @@ void pass_color_map(struct gl_shader_cache *sc,
bool need_gamma = src.gamma != dst.gamma ||
src.primaries != dst.primaries ||
src_range != dst_range ||
- src.sig_peak > dst_range;
+ src.sig_peak > dst_range ||
+ src.light != dst.light;
if (need_gamma && !is_linear) {
pass_linearize(sc, src.gamma);
is_linear= true;
}
- // NOTE: When src.gamma = MP_CSP_TRC_ARIB_STD_B67, we would technically
- // need to apply the reference OOTF as part of the EOTF (which is what we
- // implement with pass_linearize), since HLG considers OOTF to be part of
- // the display's EOTF (as opposed to the camera's OETF) - although arguably
- // in our case this would be part of the ICC profile, not mpv. Either way,
- // in case somebody ends up complaining about HLG looking different from a
- // reference HLG display, this comment might be why.
+ if (src.light != dst.light)
+ pass_ootf(sc, src.light, mp_trc_nom_peak(src.gamma));
// Rescale the signal to compensate for differences in the encoding range
// and reference white level. This is necessary because of how mpv encodes
@@ -490,6 +561,9 @@ void pass_color_map(struct gl_shader_cache *sc,
GLSL(color.rgb = cms_matrix * color.rgb;)
}
+ if (src.light != dst.light)
+ pass_inverse_ootf(sc, dst.light, mp_trc_nom_peak(dst.gamma));
+
if (is_linear)
pass_delinearize(sc, dst.gamma);
}
diff --git a/video/out/opengl/video_shaders.h b/video/out/opengl/video_shaders.h
index 076a835754..207824b169 100644
--- a/video/out/opengl/video_shaders.h
+++ b/video/out/opengl/video_shaders.h
@@ -37,6 +37,8 @@ void pass_sample_oversample(struct gl_shader_cache *sc, struct scaler *scaler,
void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc);
void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc);
+void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak);
+void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak);
void pass_color_map(struct gl_shader_cache *sc,
struct mp_colorspace src, struct mp_colorspace dst,