From fee6b287a559084062d179974816fd922dc93a4f Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Tue, 25 Jul 2017 23:17:04 +0200 Subject: vo_opengl: support embedded ICC profiles This currently only works when using lcms-based color management (--icc-profile-*). In principle, we could also support using lcms even when the user has not specified an ICC profile, by generating the profile against a fixed reference (--target-prim/--target-trc) instead. I still might do that some day, simply because 3dlut provides a higher quality conversion than our simple gamut mapping does for stuff like BT.2020, and also because it's now needed to enable embedded ICC profiles. But that would be a separate change, so preserve the status quo for now. (Besides, my opinion is still that you should be using an ICC profile if you care about colors being accurate _at all_) --- DOCS/man/options.rst | 6 +++++ video/out/opengl/lcms.c | 68 +++++++++++++++++++++++++++++++++++++++++------- video/out/opengl/lcms.h | 7 +++-- video/out/opengl/video.c | 8 ++++-- 4 files changed, 76 insertions(+), 13 deletions(-) diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 80dfdceb54..7f8c94816a 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -4851,6 +4851,12 @@ The following video options are currently all specific to ``--vo=opengl`` and The default of 2.0 is somewhat conservative and will mostly just apply to skies or directly sunlit surfaces. A setting of 0.0 disables this option. +``--use-embedded-icc-profile`` + Load the embedded ICC profile contained in media files such as PNG images. + (Default: yes). Note that this option only works when also using a display + ICC profile (``--icc-profile`` or ``--icc-profile-auto``), and also + requires LittleCMS 2 support. + ``--icc-profile=`` Load an ICC profile and use it to transform video RGB to screen output. Needs LittleCMS 2 support compiled in. This option overrides the diff --git a/video/out/opengl/lcms.c b/video/out/opengl/lcms.c index cabcb16954..557048e726 100644 --- a/video/out/opengl/lcms.c +++ b/video/out/opengl/lcms.c @@ -42,6 +42,7 @@ struct gl_lcms { void *icc_data; size_t icc_size; + struct AVBufferRef *vid_profile; char *current_profile; bool using_memory_profile; bool changed; @@ -77,6 +78,7 @@ static int validate_3dlut_size_opt(struct mp_log *log, const m_option_t *opt, #define OPT_BASE_STRUCT struct mp_icc_opts const struct m_sub_options mp_icc_conf = { .opts = (const m_option_t[]) { + OPT_FLAG("use-embedded-icc-profile", use_embedded, 0), OPT_STRING("icc-profile", profile, M_OPT_FILE), OPT_FLAG("icc-profile-auto", profile_auto, 0), OPT_STRING("icc-cache-dir", cache_dir, M_OPT_FILE), @@ -92,6 +94,7 @@ const struct m_sub_options mp_icc_conf = { .defaults = &(const struct mp_icc_opts) { .size_str = "64x64x64", .intent = INTENT_RELATIVE_COLORIMETRIC, + .use_embedded = true, }, }; @@ -129,11 +132,18 @@ static void load_profile(struct gl_lcms *p) p->current_profile = talloc_strdup(p, p->opts->profile); } +static void gl_lcms_destructor(void *ptr) +{ + struct gl_lcms *p = ptr; + av_buffer_unref(&p->vid_profile); +} + struct gl_lcms *gl_lcms_init(void *talloc_ctx, struct mp_log *log, struct mpv_global *global, struct mp_icc_opts *opts) { struct gl_lcms *p = talloc_ptrtype(talloc_ctx, p); + talloc_set_destructor(p, gl_lcms_destructor); *p = (struct gl_lcms) { .global = global, .log = log, @@ -184,12 +194,25 @@ bool gl_lcms_set_memory_profile(struct gl_lcms *p, bstr profile) return true; } +// Guards against NULL and uses bstr_equals to short-circuit some special cases +static bool vid_profile_eq(struct AVBufferRef *a, struct AVBufferRef *b) +{ + if (!a || !b) + return a == b; + + return bstr_equals((struct bstr){ a->data, a->size }, + (struct bstr){ b->data, b->size }); +} + // Return whether the profile or config has changed since the last time it was // retrieved. If it has changed, gl_lcms_get_lut3d() should be called. bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, - enum mp_csp_trc trc) + enum mp_csp_trc trc, struct AVBufferRef *vid_profile) { - return p->changed || p->current_prim != prim || p->current_trc != trc; + if (p->changed || p->current_prim != prim || p->current_trc != trc) + return true; + + return !vid_profile_eq(p->vid_profile, vid_profile); } // Whether a profile is set. (gl_lcms_get_lut3d() is expected to return a lut, @@ -203,6 +226,19 @@ static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms, cmsHPROFILE disp_profile, enum mp_csp_prim prim, enum mp_csp_trc trc) { + if (p->opts->use_embedded && p->vid_profile) { + // Try using the embedded ICC profile + cmsHPROFILE prof = cmsOpenProfileFromMemTHR(cms, p->vid_profile->data, + p->vid_profile->size); + if (prof) { + MP_VERBOSE(p, "Using embedded ICC profile.\n"); + return prof; + } + + // Otherwise, warn the user and generate the profile as usual + MP_WARN(p, "Video contained an invalid ICC profile! Ignoring..\n"); + } + // The input profile for the transformation is dependent on the video // primaries and transfer characteristics struct mp_csp_primaries csp = mp_get_csp_primaries(prim); @@ -306,7 +342,8 @@ static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms, } bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, - enum mp_csp_prim prim, enum mp_csp_trc trc) + enum mp_csp_prim prim, enum mp_csp_trc trc, + struct AVBufferRef *vid_profile) { int s_r, s_g, s_b; bool result = false; @@ -315,6 +352,16 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, p->current_prim = prim; p->current_trc = trc; + // We need to hold on to a reference to the video's ICC profile for as long + // as we still need to perform equality checking, so generate a new + // reference here + av_buffer_unref(&p->vid_profile); + if (vid_profile) { + p->vid_profile = av_buffer_ref(vid_profile); + if (!p->vid_profile) + abort(); + } + if (!parse_3dlut_size(p->opts->size_str, &s_r, &s_g, &s_b)) return false; @@ -342,6 +389,8 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, abort(); av_sha_init(sha, 256); av_sha_update(sha, cache_info, strlen(cache_info)); + if (vid_profile) + av_sha_update(sha, vid_profile->data, vid_profile->size); av_sha_update(sha, p->icc_data, p->icc_size); av_sha_final(sha, hash); av_free(sha); @@ -378,19 +427,19 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, if (!profile) goto error_exit; - cmsHPROFILE vid_profile = get_vid_profile(p, cms, profile, prim, trc); - if (!vid_profile) { + cmsHPROFILE vid_hprofile = get_vid_profile(p, cms, profile, prim, trc); + if (!vid_hprofile) { cmsCloseProfile(profile); goto error_exit; } - cmsHTRANSFORM trafo = cmsCreateTransformTHR(cms, vid_profile, TYPE_RGB_16, + cmsHTRANSFORM trafo = cmsCreateTransformTHR(cms, vid_hprofile, TYPE_RGB_16, profile, TYPE_RGB_16, p->opts->intent, cmsFLAGS_HIGHRESPRECALC | cmsFLAGS_BLACKPOINTCOMPENSATION); cmsCloseProfile(profile); - cmsCloseProfile(vid_profile); + cmsCloseProfile(vid_hprofile); if (!trafo) goto error_exit; @@ -461,7 +510,7 @@ void gl_lcms_update_options(struct gl_lcms *p) { } bool gl_lcms_set_memory_profile(struct gl_lcms *p, bstr profile) {return false;} bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, - enum mp_csp_trc trc) + enum mp_csp_trc trc, struct AVBufferRef *vid_profile) { return false; } @@ -472,7 +521,8 @@ bool gl_lcms_has_profile(struct gl_lcms *p) } bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, - enum mp_csp_prim prim, enum mp_csp_trc trc) + enum mp_csp_prim prim, enum mp_csp_trc trc, + struct AVBufferRef *vid_profile) { return false; } diff --git a/video/out/opengl/lcms.h b/video/out/opengl/lcms.h index 0c0a959e82..20faa8dd5e 100644 --- a/video/out/opengl/lcms.h +++ b/video/out/opengl/lcms.h @@ -4,10 +4,12 @@ #include #include #include "misc/bstr.h" +#include extern const struct m_sub_options mp_icc_conf; struct mp_icc_opts { + int use_embedded; char *profile; int profile_auto; char *cache_dir; @@ -32,8 +34,9 @@ void gl_lcms_update_options(struct gl_lcms *p); bool gl_lcms_set_memory_profile(struct gl_lcms *p, bstr profile); bool gl_lcms_has_profile(struct gl_lcms *p); bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **, - enum mp_csp_prim prim, enum mp_csp_trc trc); + enum mp_csp_prim prim, enum mp_csp_trc trc, + struct AVBufferRef *vid_profile); bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, - enum mp_csp_trc trc); + enum mp_csp_trc trc, struct AVBufferRef *vid_profile); #endif diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c index 14d7d68171..ecb0cba183 100644 --- a/video/out/opengl/video.c +++ b/video/out/opengl/video.c @@ -593,7 +593,11 @@ static bool gl_video_get_lut3d(struct gl_video *p, enum mp_csp_prim prim, if (!p->use_lut_3d) return false; - if (p->lut_3d_texture && !gl_lcms_has_changed(p->cms, prim, trc)) + struct AVBufferRef *icc = NULL; + if (p->image.mpi) + icc = p->image.mpi->icc_profile; + + if (p->lut_3d_texture && !gl_lcms_has_changed(p->cms, prim, trc, icc)) return true; // GLES3 doesn't provide filtered 16 bit integer textures @@ -606,7 +610,7 @@ static bool gl_video_get_lut3d(struct gl_video *p, enum mp_csp_prim prim, } struct lut3d *lut3d = NULL; - if (!fmt || !gl_lcms_get_lut3d(p->cms, &lut3d, prim, trc) || !lut3d) { + if (!fmt || !gl_lcms_get_lut3d(p->cms, &lut3d, prim, trc, icc) || !lut3d) { p->use_lut_3d = false; return false; } -- cgit v1.2.3