1 /*
2 * Copyright (c) 2022 Niklas Haas
3 * This file is part of FFmpeg.
4 *
5 * FFmpeg is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * FFmpeg is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with FFmpeg; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 /**
21 * @file
22 * filter for generating ICC profiles
23 */
24
25 #include <lcms2.h>
26
27 #include "libavutil/csp.h"
28 #include "libavutil/opt.h"
29 #include "libavutil/pixdesc.h"
30
31 #include "avfilter.h"
32 #include "fflcms2.h"
33 #include "internal.h"
34
35 typedef struct IccDetectContext {
36 const AVClass *class;
37 FFIccContext icc;
38 int force;
39 /* (cached) detected ICC profile values */
40 AVBufferRef *profile;
41 enum AVColorPrimaries profile_prim;
42 enum AVColorTransferCharacteristic profile_trc;
43 } IccDetectContext;
44
45 #define OFFSET(x) offsetof(IccDetectContext, x)
46 #define VF AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
47
48 static const AVOption iccdetect_options[] = {
49 { "force", "overwrite existing tags", OFFSET(force), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1, VF },
50 { NULL }
51 };
52
53 AVFILTER_DEFINE_CLASS(iccdetect);
54
iccdetect_uninit(AVFilterContext * avctx)55 static av_cold void iccdetect_uninit(AVFilterContext *avctx)
56 {
57 IccDetectContext *s = avctx->priv;
58 av_buffer_unref(&s->profile);
59 ff_icc_context_uninit(&s->icc);
60 }
61
iccdetect_init(AVFilterContext * avctx)62 static av_cold int iccdetect_init(AVFilterContext *avctx)
63 {
64 IccDetectContext *s = avctx->priv;
65 return ff_icc_context_init(&s->icc, avctx);
66 }
67
iccdetect_filter_frame(AVFilterLink * inlink,AVFrame * frame)68 static int iccdetect_filter_frame(AVFilterLink *inlink, AVFrame *frame)
69 {
70 AVFilterContext *avctx = inlink->dst;
71 IccDetectContext *s = avctx->priv;
72 const AVFrameSideData *sd;
73 AVColorPrimariesDesc coeffs;
74 cmsHPROFILE profile;
75 int ret;
76
77 sd = av_frame_get_side_data(frame, AV_FRAME_DATA_ICC_PROFILE);
78 if (!sd)
79 return ff_filter_frame(inlink->dst->outputs[0], frame);
80
81 if (s->profile && s->profile->data == sd->buf->data) {
82 /* No change from previous ICC profile */
83 goto done;
84 }
85
86 if ((ret = av_buffer_replace(&s->profile, sd->buf)) < 0)
87 return ret;
88 s->profile_prim = AVCOL_PRI_UNSPECIFIED;
89 s->profile_trc = AVCOL_TRC_UNSPECIFIED;
90
91 profile = cmsOpenProfileFromMemTHR(s->icc.ctx, sd->data, sd->size);
92 if (!profile)
93 return AVERROR_INVALIDDATA;
94
95 ret = ff_icc_profile_read_primaries(&s->icc, profile, &coeffs);
96 if (!ret)
97 ret = ff_icc_profile_detect_transfer(&s->icc, profile, &s->profile_trc);
98 cmsCloseProfile(profile);
99 if (ret < 0)
100 return ret;
101
102 s->profile_prim = av_csp_primaries_id_from_desc(&coeffs);
103
104 done:
105 if (s->profile_prim != AVCOL_PRI_UNSPECIFIED) {
106 if (s->force || frame->color_primaries == AVCOL_PRI_UNSPECIFIED)
107 frame->color_primaries = s->profile_prim;
108 }
109
110 if (s->profile_trc != AVCOL_TRC_UNSPECIFIED) {
111 if (s->force || frame->color_trc == AVCOL_TRC_UNSPECIFIED)
112 frame->color_trc = s->profile_trc;
113 }
114
115 return ff_filter_frame(inlink->dst->outputs[0], frame);
116 }
117
118 static const AVFilterPad iccdetect_inputs[] = {
119 {
120 .name = "default",
121 .type = AVMEDIA_TYPE_VIDEO,
122 .filter_frame = iccdetect_filter_frame,
123 },
124 };
125
126 static const AVFilterPad iccdetect_outputs[] = {
127 {
128 .name = "default",
129 .type = AVMEDIA_TYPE_VIDEO,
130 },
131 };
132
133 const AVFilter ff_vf_iccdetect = {
134 .name = "iccdetect",
135 .description = NULL_IF_CONFIG_SMALL("Detect and parse ICC profiles."),
136 .priv_size = sizeof(IccDetectContext),
137 .priv_class = &iccdetect_class,
138 .flags = AVFILTER_FLAG_METADATA_ONLY,
139 .init = &iccdetect_init,
140 .uninit = &iccdetect_uninit,
141 FILTER_INPUTS(iccdetect_inputs),
142 FILTER_OUTPUTS(iccdetect_outputs),
143 };
144