2 * Copyright (C) 2006 Michael Niedermayer <michaelni@gmx.at>
3 * Copyright (C) 2012 Clément Bœsch <ubitux@gmail.com>
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 #define OFFSET(x) offsetof(GEQContext, x)
46 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
48 static const AVOption geq_options[] = {
49 { "lum_expr", "set luminance expression", OFFSET(expr_str[0]), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS },
50 { "cb_expr", "set chroma blue expression", OFFSET(expr_str[1]), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS },
51 { "cr_expr", "set chroma red expression", OFFSET(expr_str[2]), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS },
52 { "alpha_expr", "set alpha expression", OFFSET(expr_str[3]), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS },
54 { "r", "set red expression", OFFSET(expr_str[6]), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS },
55 { "g", "set green expression", OFFSET(expr_str[4]), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS },
56 { "b", "set blue expression", OFFSET(expr_str[5]), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS },
60 AVFILTER_DEFINE_CLASS(geq);
62 static inline double getpix(void *priv, double x, double y, int plane)
65 GEQContext *geq = priv;
66 AVFrame *picref = geq->picref;
67 const uint8_t *src = picref->data[plane];
68 const int linesize = picref->linesize[plane];
69 const int w = picref->width >> ((plane == 1 || plane == 2) ? geq->hsub : 0);
70 const int h = picref->height >> ((plane == 1 || plane == 2) ? geq->vsub : 0);
75 xi = x = av_clipf(x, 0, w - 2);
76 yi = y = av_clipf(y, 0, h - 2);
81 return (1-y)*((1-x)*src[xi + yi * linesize] + x*src[xi + 1 + yi * linesize])
82 + y *((1-x)*src[xi + (yi+1) * linesize] + x*src[xi + 1 + (yi+1) * linesize]);
85 //TODO: cubic interpolate
86 //TODO: keep the last few frames
87 static double lum(void *priv, double x, double y) { return getpix(priv, x, y, 0); }
88 static double cb(void *priv, double x, double y) { return getpix(priv, x, y, 1); }
89 static double cr(void *priv, double x, double y) { return getpix(priv, x, y, 2); }
90 static double alpha(void *priv, double x, double y) { return getpix(priv, x, y, 3); }
92 static const char *const var_names[] = { "X", "Y", "W", "H", "N", "SW", "SH", "T", NULL };
93 enum { VAR_X, VAR_Y, VAR_W, VAR_H, VAR_N, VAR_SW, VAR_SH, VAR_T, VAR_VARS_NB };
95 static av_cold int geq_init(AVFilterContext *ctx)
97 GEQContext *geq = ctx->priv;
100 if (!geq->expr_str[0] && !geq->expr_str[4] && !geq->expr_str[5] && !geq->expr_str[6]) {
101 av_log(ctx, AV_LOG_ERROR, "A luminance or RGB expression is mandatory\n");
102 ret = AVERROR(EINVAL);
105 geq->is_rgb = !geq->expr_str[0];
107 if ((geq->expr_str[0] || geq->expr_str[1] || geq->expr_str[2]) && (geq->expr_str[4] || geq->expr_str[5] || geq->expr_str[6])) {
108 av_log(ctx, AV_LOG_ERROR, "Either YCbCr or RGB but not both must be specified\n");
109 ret = AVERROR(EINVAL);
113 if (!geq->expr_str[1] && !geq->expr_str[2]) {
114 /* No chroma at all: fallback on luma */
115 geq->expr_str[1] = av_strdup(geq->expr_str[0]);
116 geq->expr_str[2] = av_strdup(geq->expr_str[0]);
118 /* One chroma unspecified, fallback on the other */
119 if (!geq->expr_str[1]) geq->expr_str[1] = av_strdup(geq->expr_str[2]);
120 if (!geq->expr_str[2]) geq->expr_str[2] = av_strdup(geq->expr_str[1]);
123 if (!geq->expr_str[3])
124 geq->expr_str[3] = av_strdup("255");
125 if (!geq->expr_str[4])
126 geq->expr_str[4] = av_strdup("g(X,Y)");
127 if (!geq->expr_str[5])
128 geq->expr_str[5] = av_strdup("b(X,Y)");
129 if (!geq->expr_str[6])
130 geq->expr_str[6] = av_strdup("r(X,Y)");
133 (!geq->expr_str[4] || !geq->expr_str[5] || !geq->expr_str[6])
135 (!geq->expr_str[1] || !geq->expr_str[2] || !geq->expr_str[3])) {
136 ret = AVERROR(ENOMEM);
140 for (plane = 0; plane < 4; plane++) {
141 static double (*p[])(void *, double, double) = { lum, cb, cr, alpha };
142 static const char *const func2_yuv_names[] = { "lum", "cb", "cr", "alpha", "p", NULL };
143 static const char *const func2_rgb_names[] = { "g", "b", "r", "alpha", "p", NULL };
144 const char *const *func2_names = geq->is_rgb ? func2_rgb_names : func2_yuv_names;
145 double (*func2[])(void *, double, double) = { lum, cb, cr, alpha, p[plane], NULL };
147 ret = av_expr_parse(&geq->e[plane], geq->expr_str[plane < 3 && geq->is_rgb ? plane+4 : plane], var_names,
148 NULL, NULL, func2_names, func2, 0, ctx);
157 static int geq_query_formats(AVFilterContext *ctx)
159 GEQContext *geq = ctx->priv;
160 static const enum PixelFormat yuv_pix_fmts[] = {
161 AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P,
162 AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV440P,
163 AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA420P,
167 static const enum PixelFormat rgb_pix_fmts[] = {
172 ff_set_common_formats(ctx, ff_make_format_list(rgb_pix_fmts));
174 ff_set_common_formats(ctx, ff_make_format_list(yuv_pix_fmts));
178 static int geq_config_props(AVFilterLink *inlink)
180 GEQContext *geq = inlink->dst->priv;
181 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
183 geq->hsub = desc->log2_chroma_w;
184 geq->vsub = desc->log2_chroma_h;
185 geq->planes = desc->nb_components;
189 static int geq_filter_frame(AVFilterLink *inlink, AVFrame *in)
192 GEQContext *geq = inlink->dst->priv;
193 AVFilterLink *outlink = inlink->dst->outputs[0];
195 double values[VAR_VARS_NB] = {
196 [VAR_N] = inlink->frame_count,
197 [VAR_T] = in->pts == AV_NOPTS_VALUE ? NAN : in->pts * av_q2d(inlink->time_base),
201 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
204 return AVERROR(ENOMEM);
206 av_frame_copy_props(out, in);
208 for (plane = 0; plane < geq->planes && out->data[plane]; plane++) {
210 uint8_t *dst = out->data[plane];
211 const int linesize = out->linesize[plane];
212 const int w = inlink->w >> ((plane == 1 || plane == 2) ? geq->hsub : 0);
213 const int h = inlink->h >> ((plane == 1 || plane == 2) ? geq->vsub : 0);
217 values[VAR_SW] = w / (double)inlink->w;
218 values[VAR_SH] = h / (double)inlink->h;
220 for (y = 0; y < h; y++) {
222 for (x = 0; x < w; x++) {
224 dst[x] = av_expr_eval(geq->e[plane], values, geq);
230 av_frame_free(&geq->picref);
231 return ff_filter_frame(outlink, out);
234 static av_cold void geq_uninit(AVFilterContext *ctx)
237 GEQContext *geq = ctx->priv;
239 for (i = 0; i < FF_ARRAY_ELEMS(geq->e); i++)
240 av_expr_free(geq->e[i]);
243 static const AVFilterPad geq_inputs[] = {
246 .type = AVMEDIA_TYPE_VIDEO,
247 .config_props = geq_config_props,
248 .filter_frame = geq_filter_frame,
253 static const AVFilterPad geq_outputs[] = {
256 .type = AVMEDIA_TYPE_VIDEO,
261 AVFilter avfilter_vf_geq = {
263 .description = NULL_IF_CONFIG_SMALL("Apply generic equation to each pixel."),
264 .priv_size = sizeof(GEQContext),
266 .uninit = geq_uninit,
267 .query_formats = geq_query_formats,
268 .inputs = geq_inputs,
269 .outputs = geq_outputs,
270 .priv_class = &geq_class,