• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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