2 * Copyright (C) 2006 Michael Niedermayer <michaelni@gmx.at>
3 * Copyright (C) 2012 Clément Bœsch <u pkh me>
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * Generic equation change filter
25 * Originally written by Michael Niedermayer for the MPlayer project, and
26 * ported by Clément Bœsch for FFmpeg.
29 #include "libavutil/avstring.h"
30 #include "libavutil/eval.h"
31 #include "libavutil/opt.h"
32 #include "libavutil/pixdesc.h"
37 AVExpr
*e
[4]; ///< expressions for each plane
38 char *expr_str
[4+3]; ///< expression strings for each plane
39 AVFrame
*picref
; ///< current input buffer
40 int hsub
, vsub
; ///< chroma subsampling
41 int planes
; ///< number of planes
45 enum { Y
= 0, U
, V
, A
, G
, B
, R
};
47 #define OFFSET(x) offsetof(GEQContext, x)
48 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
50 static const AVOption geq_options
[] = {
51 { "lum_expr", "set luminance expression", OFFSET(expr_str
[Y
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
52 { "lum", "set luminance expression", OFFSET(expr_str
[Y
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
53 { "cb_expr", "set chroma blue expression", OFFSET(expr_str
[U
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
54 { "cb", "set chroma blue expression", OFFSET(expr_str
[U
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
55 { "cr_expr", "set chroma red expression", OFFSET(expr_str
[V
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
56 { "cr", "set chroma red expression", OFFSET(expr_str
[V
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
57 { "alpha_expr", "set alpha expression", OFFSET(expr_str
[A
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
58 { "a", "set alpha expression", OFFSET(expr_str
[A
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
59 { "red_expr", "set red expression", OFFSET(expr_str
[R
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
60 { "r", "set red expression", OFFSET(expr_str
[R
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
61 { "green_expr", "set green expression", OFFSET(expr_str
[G
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
62 { "g", "set green expression", OFFSET(expr_str
[G
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
63 { "blue_expr", "set blue expression", OFFSET(expr_str
[B
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
64 { "b", "set blue expression", OFFSET(expr_str
[B
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
68 AVFILTER_DEFINE_CLASS(geq
);
70 static inline double getpix(void *priv
, double x
, double y
, int plane
)
73 GEQContext
*geq
= priv
;
74 AVFrame
*picref
= geq
->picref
;
75 const uint8_t *src
= picref
->data
[plane
];
76 const int linesize
= picref
->linesize
[plane
];
77 const int w
= (plane
== 1 || plane
== 2) ? FF_CEIL_RSHIFT(picref
->width
, geq
->hsub
) : picref
->width
;
78 const int h
= (plane
== 1 || plane
== 2) ? FF_CEIL_RSHIFT(picref
->height
, geq
->vsub
) : picref
->height
;
83 xi
= x
= av_clipf(x
, 0, w
- 2);
84 yi
= y
= av_clipf(y
, 0, h
- 2);
89 return (1-y
)*((1-x
)*src
[xi
+ yi
* linesize
] + x
*src
[xi
+ 1 + yi
* linesize
])
90 + y
*((1-x
)*src
[xi
+ (yi
+1) * linesize
] + x
*src
[xi
+ 1 + (yi
+1) * linesize
]);
93 //TODO: cubic interpolate
94 //TODO: keep the last few frames
95 static double lum(void *priv
, double x
, double y
) { return getpix(priv
, x
, y
, 0); }
96 static double cb(void *priv
, double x
, double y
) { return getpix(priv
, x
, y
, 1); }
97 static double cr(void *priv
, double x
, double y
) { return getpix(priv
, x
, y
, 2); }
98 static double alpha(void *priv
, double x
, double y
) { return getpix(priv
, x
, y
, 3); }
100 static const char *const var_names
[] = { "X", "Y", "W", "H", "N", "SW", "SH", "T", NULL
};
101 enum { VAR_X
, VAR_Y
, VAR_W
, VAR_H
, VAR_N
, VAR_SW
, VAR_SH
, VAR_T
, VAR_VARS_NB
};
103 static av_cold
int geq_init(AVFilterContext
*ctx
)
105 GEQContext
*geq
= ctx
->priv
;
108 if (!geq
->expr_str
[Y
] && !geq
->expr_str
[G
] && !geq
->expr_str
[B
] && !geq
->expr_str
[R
]) {
109 av_log(ctx
, AV_LOG_ERROR
, "A luminance or RGB expression is mandatory\n");
110 ret
= AVERROR(EINVAL
);
113 geq
->is_rgb
= !geq
->expr_str
[Y
];
115 if ((geq
->expr_str
[Y
] || geq
->expr_str
[U
] || geq
->expr_str
[V
]) && (geq
->expr_str
[G
] || geq
->expr_str
[B
] || geq
->expr_str
[R
])) {
116 av_log(ctx
, AV_LOG_ERROR
, "Either YCbCr or RGB but not both must be specified\n");
117 ret
= AVERROR(EINVAL
);
121 if (!geq
->expr_str
[U
] && !geq
->expr_str
[V
]) {
122 /* No chroma at all: fallback on luma */
123 geq
->expr_str
[U
] = av_strdup(geq
->expr_str
[Y
]);
124 geq
->expr_str
[V
] = av_strdup(geq
->expr_str
[Y
]);
126 /* One chroma unspecified, fallback on the other */
127 if (!geq
->expr_str
[U
]) geq
->expr_str
[U
] = av_strdup(geq
->expr_str
[V
]);
128 if (!geq
->expr_str
[V
]) geq
->expr_str
[V
] = av_strdup(geq
->expr_str
[U
]);
131 if (!geq
->expr_str
[A
])
132 geq
->expr_str
[A
] = av_strdup("255");
133 if (!geq
->expr_str
[G
])
134 geq
->expr_str
[G
] = av_strdup("g(X,Y)");
135 if (!geq
->expr_str
[B
])
136 geq
->expr_str
[B
] = av_strdup("b(X,Y)");
137 if (!geq
->expr_str
[R
])
138 geq
->expr_str
[R
] = av_strdup("r(X,Y)");
141 (!geq
->expr_str
[G
] || !geq
->expr_str
[B
] || !geq
->expr_str
[R
])
143 (!geq
->expr_str
[U
] || !geq
->expr_str
[V
] || !geq
->expr_str
[A
])) {
144 ret
= AVERROR(ENOMEM
);
148 for (plane
= 0; plane
< 4; plane
++) {
149 static double (*p
[])(void *, double, double) = { lum
, cb
, cr
, alpha
};
150 static const char *const func2_yuv_names
[] = { "lum", "cb", "cr", "alpha", "p", NULL
};
151 static const char *const func2_rgb_names
[] = { "g", "b", "r", "alpha", "p", NULL
};
152 const char *const *func2_names
= geq
->is_rgb
? func2_rgb_names
: func2_yuv_names
;
153 double (*func2
[])(void *, double, double) = { lum
, cb
, cr
, alpha
, p
[plane
], NULL
};
155 ret
= av_expr_parse(&geq
->e
[plane
], geq
->expr_str
[plane
< 3 && geq
->is_rgb
? plane
+4 : plane
], var_names
,
156 NULL
, NULL
, func2_names
, func2
, 0, ctx
);
165 static int geq_query_formats(AVFilterContext
*ctx
)
167 GEQContext
*geq
= ctx
->priv
;
168 static const enum PixelFormat yuv_pix_fmts
[] = {
169 AV_PIX_FMT_YUV444P
, AV_PIX_FMT_YUV422P
, AV_PIX_FMT_YUV420P
,
170 AV_PIX_FMT_YUV411P
, AV_PIX_FMT_YUV410P
, AV_PIX_FMT_YUV440P
,
171 AV_PIX_FMT_YUVA444P
, AV_PIX_FMT_YUVA422P
, AV_PIX_FMT_YUVA420P
,
175 static const enum PixelFormat rgb_pix_fmts
[] = {
176 AV_PIX_FMT_GBRP
, AV_PIX_FMT_GBRAP
,
180 ff_set_common_formats(ctx
, ff_make_format_list(rgb_pix_fmts
));
182 ff_set_common_formats(ctx
, ff_make_format_list(yuv_pix_fmts
));
186 static int geq_config_props(AVFilterLink
*inlink
)
188 GEQContext
*geq
= inlink
->dst
->priv
;
189 const AVPixFmtDescriptor
*desc
= av_pix_fmt_desc_get(inlink
->format
);
191 geq
->hsub
= desc
->log2_chroma_w
;
192 geq
->vsub
= desc
->log2_chroma_h
;
193 geq
->planes
= desc
->nb_components
;
197 static int geq_filter_frame(AVFilterLink
*inlink
, AVFrame
*in
)
200 GEQContext
*geq
= inlink
->dst
->priv
;
201 AVFilterLink
*outlink
= inlink
->dst
->outputs
[0];
203 double values
[VAR_VARS_NB
] = {
204 [VAR_N
] = inlink
->frame_count
,
205 [VAR_T
] = in
->pts
== AV_NOPTS_VALUE
? NAN
: in
->pts
* av_q2d(inlink
->time_base
),
209 out
= ff_get_video_buffer(outlink
, outlink
->w
, outlink
->h
);
212 return AVERROR(ENOMEM
);
214 av_frame_copy_props(out
, in
);
216 for (plane
= 0; plane
< geq
->planes
&& out
->data
[plane
]; plane
++) {
218 uint8_t *dst
= out
->data
[plane
];
219 const int linesize
= out
->linesize
[plane
];
220 const int w
= (plane
== 1 || plane
== 2) ? FF_CEIL_RSHIFT(inlink
->w
, geq
->hsub
) : inlink
->w
;
221 const int h
= (plane
== 1 || plane
== 2) ? FF_CEIL_RSHIFT(inlink
->h
, geq
->vsub
) : inlink
->h
;
225 values
[VAR_SW
] = w
/ (double)inlink
->w
;
226 values
[VAR_SH
] = h
/ (double)inlink
->h
;
228 for (y
= 0; y
< h
; y
++) {
230 for (x
= 0; x
< w
; x
++) {
232 dst
[x
] = av_expr_eval(geq
->e
[plane
], values
, geq
);
238 av_frame_free(&geq
->picref
);
239 return ff_filter_frame(outlink
, out
);
242 static av_cold
void geq_uninit(AVFilterContext
*ctx
)
245 GEQContext
*geq
= ctx
->priv
;
247 for (i
= 0; i
< FF_ARRAY_ELEMS(geq
->e
); i
++)
248 av_expr_free(geq
->e
[i
]);
251 static const AVFilterPad geq_inputs
[] = {
254 .type
= AVMEDIA_TYPE_VIDEO
,
255 .config_props
= geq_config_props
,
256 .filter_frame
= geq_filter_frame
,
261 static const AVFilterPad geq_outputs
[] = {
264 .type
= AVMEDIA_TYPE_VIDEO
,
269 AVFilter ff_vf_geq
= {
271 .description
= NULL_IF_CONFIG_SMALL("Apply generic equation to each pixel."),
272 .priv_size
= sizeof(GEQContext
),
274 .uninit
= geq_uninit
,
275 .query_formats
= geq_query_formats
,
276 .inputs
= geq_inputs
,
277 .outputs
= geq_outputs
,
278 .priv_class
= &geq_class
,
279 .flags
= AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC
,