mirror of https://github.com/mpv-player/mpv
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:
parent
1aab037601
commit
fee6b287a5
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue