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/opt.h"
28 #include "libavutil/pixdesc.h"
29
30 #include "avfilter.h"
31 #include "fflcms2.h"
32 #include "internal.h"
33
34 typedef struct IccGenContext {
35 const AVClass *class;
36 FFIccContext icc;
37 /* options */
38 int color_prim;
39 int color_trc;
40 int force;
41 /* (cached) generated ICC profile */
42 cmsHPROFILE profile;
43 int profile_prim;
44 int profile_trc;
45 } IccGenContext;
46
47 #define OFFSET(x) offsetof(IccGenContext, x)
48 #define VF AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
49
50 static const AVOption iccgen_options[] = {
51 {"color_primaries", "select color primaries", OFFSET(color_prim), AV_OPT_TYPE_INT, {.i64=0}, 0, AVCOL_PRI_NB-1, VF, "color_primaries"},
52 {"auto", "infer based on frame", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, VF, "color_primaries"},
53 {"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT709}, 0, 0, VF, "color_primaries"},
54 {"bt470m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470M}, 0, 0, VF, "color_primaries"},
55 {"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470BG}, 0, 0, VF, "color_primaries"},
56 {"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE170M}, 0, 0, VF, "color_primaries"},
57 {"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE240M}, 0, 0, VF, "color_primaries"},
58 {"film", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_FILM}, 0, 0, VF, "color_primaries"},
59 {"bt2020", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT2020}, 0, 0, VF, "color_primaries"},
60 {"smpte428", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE428}, 0, 0, VF, "color_primaries"},
61 {"smpte431", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE431}, 0, 0, VF, "color_primaries"},
62 {"smpte432", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE432}, 0, 0, VF, "color_primaries"},
63 {"jedec-p22", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_JEDEC_P22}, 0, 0, VF, "color_primaries"},
64 {"ebu3213", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_EBU3213}, 0, 0, VF, "color_primaries"},
65 {"color_trc", "select color transfer", OFFSET(color_trc), AV_OPT_TYPE_INT, {.i64=0}, 0, AVCOL_TRC_NB-1, VF, "color_trc"},
66 {"auto", "infer based on frame", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, VF, "color_trc"},
67 {"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT709}, 0, 0, VF, "color_trc"},
68 {"bt470m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_GAMMA22}, 0, 0, VF, "color_trc"},
69 {"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_GAMMA28}, 0, 0, VF, "color_trc"},
70 {"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE170M}, 0, 0, VF, "color_trc"},
71 {"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE240M}, 0, 0, VF, "color_trc"},
72 {"linear", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_LINEAR}, 0, 0, VF, "color_trc"},
73 {"iec61966-2-4", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_IEC61966_2_4}, 0, 0, VF, "color_trc"},
74 {"bt1361e", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT1361_ECG}, 0, 0, VF, "color_trc"},
75 {"iec61966-2-1", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_IEC61966_2_1}, 0, 0, VF, "color_trc"},
76 {"bt2020-10", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT2020_10}, 0, 0, VF, "color_trc"},
77 {"bt2020-12", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT2020_12}, 0, 0, VF, "color_trc"},
78 {"smpte2084", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE2084}, 0, 0, VF, "color_trc"},
79 {"arib-std-b67", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_ARIB_STD_B67}, 0, 0, VF, "color_trc"},
80 { "force", "overwrite existing ICC profile", OFFSET(force), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, VF },
81 { NULL }
82 };
83
84 AVFILTER_DEFINE_CLASS(iccgen);
85
iccgen_uninit(AVFilterContext * avctx)86 static av_cold void iccgen_uninit(AVFilterContext *avctx)
87 {
88 IccGenContext *s = avctx->priv;
89 cmsCloseProfile(s->profile);
90 ff_icc_context_uninit(&s->icc);
91 }
92
iccgen_init(AVFilterContext * avctx)93 static av_cold int iccgen_init(AVFilterContext *avctx)
94 {
95 IccGenContext *s = avctx->priv;
96 return ff_icc_context_init(&s->icc, avctx);
97 }
98
iccgen_filter_frame(AVFilterLink * inlink,AVFrame * frame)99 static int iccgen_filter_frame(AVFilterLink *inlink, AVFrame *frame)
100 {
101 AVFilterContext *avctx = inlink->dst;
102 IccGenContext *s = avctx->priv;
103 enum AVColorTransferCharacteristic trc;
104 enum AVColorPrimaries prim;
105 int ret;
106
107 if (av_frame_get_side_data(frame, AV_FRAME_DATA_ICC_PROFILE)) {
108 if (s->force) {
109 av_frame_remove_side_data(frame, AV_FRAME_DATA_ICC_PROFILE);
110 } else {
111 return ff_filter_frame(inlink->dst->outputs[0], frame);
112 }
113 }
114
115 trc = s->color_trc ? s->color_trc : frame->color_trc;
116 if (trc == AVCOL_TRC_UNSPECIFIED) {
117 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
118 if (!desc)
119 return AVERROR_INVALIDDATA;
120
121 if ((desc->flags & AV_PIX_FMT_FLAG_RGB) || frame->color_range == AVCOL_RANGE_JPEG) {
122 /* Default to sRGB for RGB or full-range content */
123 trc = AVCOL_TRC_IEC61966_2_1;
124 } else {
125 /* Default to an ITU-R transfer depending on the bit-depth */
126 trc = desc->comp[0].depth >= 12 ? AVCOL_TRC_BT2020_12
127 : desc->comp[0].depth >= 10 ? AVCOL_TRC_BT2020_10
128 : AVCOL_TRC_BT709;
129 }
130 }
131
132 prim = s->color_prim ? s->color_prim : frame->color_primaries;
133 if (prim == AVCOL_PRI_UNSPECIFIED) {
134 /* Simply always default to sRGB/BT.709 primaries to avoid surprises */
135 prim = AVCOL_PRI_BT709;
136 }
137
138 if (s->profile && prim != s->profile_prim && trc != s->profile_trc) {
139 cmsCloseProfile(s->profile);
140 s->profile = NULL;
141 }
142
143 if (!s->profile) {
144 if ((ret = ff_icc_profile_generate(&s->icc, prim, trc, &s->profile)) < 0)
145 return ret;
146 s->profile_prim = prim;
147 s->profile_trc = trc;
148 }
149
150 if ((ret = ff_icc_profile_attach(&s->icc, s->profile, frame)) < 0)
151 return ret;
152
153 return ff_filter_frame(inlink->dst->outputs[0], frame);
154 }
155
156 static const AVFilterPad iccgen_inputs[] = {
157 {
158 .name = "default",
159 .type = AVMEDIA_TYPE_VIDEO,
160 .filter_frame = iccgen_filter_frame,
161 },
162 };
163
164 static const AVFilterPad iccgen_outputs[] = {
165 {
166 .name = "default",
167 .type = AVMEDIA_TYPE_VIDEO,
168 },
169 };
170
171 const AVFilter ff_vf_iccgen = {
172 .name = "iccgen",
173 .description = NULL_IF_CONFIG_SMALL("Generate and attach ICC profiles."),
174 .priv_size = sizeof(IccGenContext),
175 .priv_class = &iccgen_class,
176 .flags = AVFILTER_FLAG_METADATA_ONLY,
177 .init = &iccgen_init,
178 .uninit = &iccgen_uninit,
179 FILTER_INPUTS(iccgen_inputs),
180 FILTER_OUTPUTS(iccgen_outputs),
181 };
182