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_)
This commit is contained in:
Niklas Haas 2017-07-25 23:17:04 +02:00
parent 1aab037601
commit fee6b287a5
4 changed files with 76 additions and 13 deletions

View File

@ -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=<file>``
Load an ICC profile and use it to transform video RGB to screen output.
Needs LittleCMS 2 support compiled in. This option overrides the

View File

@ -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;
}

View File

@ -4,10 +4,12 @@
#include <stddef.h>
#include <stdbool.h>
#include "misc/bstr.h"
#include <libavutil/buffer.h>
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

View File

@ -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;
}