2 * Copyright (c) 2008 Affine Systems, Inc (Michael Sullivan, Bobby Impollonia)
3 * Copyright (c) 2013 Andrey Utkin <andrey.krieger.utkin gmail com>
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (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 GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 * Box and grid drawing filters. Also a nice template for a filter
25 * that needs to write in the input frame.
28 #include "libavutil/colorspace.h"
29 #include "libavutil/common.h"
30 #include "libavutil/opt.h"
31 #include "libavutil/eval.h"
32 #include "libavutil/pixdesc.h"
33 #include "libavutil/parseutils.h"
39 static const char *const var_names
[] = {
42 "in_h", "ih", ///< height of the input video
43 "in_w", "iw", ///< width of the input video
47 "h", ///< height of the rendered box
48 "w", ///< width of the rendered box
69 typedef struct DrawBoxContext
{
74 unsigned char yuv_color
[4];
75 int invert_color
; ///< invert luma color
76 int vsub
, hsub
; ///< chroma subsampling
77 char *x_expr
, *y_expr
; ///< expression for x and y
78 char *w_expr
, *h_expr
; ///< expression for width and height
79 char *t_expr
; ///< expression for thickness
82 static const int NUM_EXPR_EVALS
= 5;
84 static av_cold
int init(AVFilterContext
*ctx
)
86 DrawBoxContext
*s
= ctx
->priv
;
87 uint8_t rgba_color
[4];
89 if (!strcmp(s
->color_str
, "invert"))
91 else if (av_parse_color(rgba_color
, s
->color_str
, -1, ctx
) < 0)
92 return AVERROR(EINVAL
);
94 if (!s
->invert_color
) {
95 s
->yuv_color
[Y
] = RGB_TO_Y_CCIR(rgba_color
[0], rgba_color
[1], rgba_color
[2]);
96 s
->yuv_color
[U
] = RGB_TO_U_CCIR(rgba_color
[0], rgba_color
[1], rgba_color
[2], 0);
97 s
->yuv_color
[V
] = RGB_TO_V_CCIR(rgba_color
[0], rgba_color
[1], rgba_color
[2], 0);
98 s
->yuv_color
[A
] = rgba_color
[3];
104 static int query_formats(AVFilterContext
*ctx
)
106 static const enum AVPixelFormat pix_fmts
[] = {
107 AV_PIX_FMT_YUV444P
, AV_PIX_FMT_YUV422P
, AV_PIX_FMT_YUV420P
,
108 AV_PIX_FMT_YUV411P
, AV_PIX_FMT_YUV410P
,
109 AV_PIX_FMT_YUVJ444P
, AV_PIX_FMT_YUVJ422P
, AV_PIX_FMT_YUVJ420P
,
110 AV_PIX_FMT_YUV440P
, AV_PIX_FMT_YUVJ440P
,
114 ff_set_common_formats(ctx
, ff_make_format_list(pix_fmts
));
118 static int config_input(AVFilterLink
*inlink
)
120 AVFilterContext
*ctx
= inlink
->dst
;
121 DrawBoxContext
*s
= ctx
->priv
;
122 const AVPixFmtDescriptor
*desc
= av_pix_fmt_desc_get(inlink
->format
);
123 double var_values
[VARS_NB
], res
;
128 s
->hsub
= desc
->log2_chroma_w
;
129 s
->vsub
= desc
->log2_chroma_h
;
131 var_values
[VAR_IN_H
] = var_values
[VAR_IH
] = inlink
->h
;
132 var_values
[VAR_IN_W
] = var_values
[VAR_IW
] = inlink
->w
;
133 var_values
[VAR_SAR
] = inlink
->sample_aspect_ratio
.num
? av_q2d(inlink
->sample_aspect_ratio
) : 1;
134 var_values
[VAR_DAR
] = (double)inlink
->w
/ inlink
->h
* var_values
[VAR_SAR
];
135 var_values
[VAR_HSUB
] = s
->hsub
;
136 var_values
[VAR_VSUB
] = s
->vsub
;
137 var_values
[VAR_X
] = NAN
;
138 var_values
[VAR_Y
] = NAN
;
139 var_values
[VAR_H
] = NAN
;
140 var_values
[VAR_W
] = NAN
;
141 var_values
[VAR_T
] = NAN
;
143 for (i
= 0; i
<= NUM_EXPR_EVALS
; i
++) {
144 /* evaluate expressions, fail on last iteration */
145 if ((ret
= av_expr_parse_and_eval(&res
, (expr
= s
->x_expr
),
146 var_names
, var_values
,
147 NULL
, NULL
, NULL
, NULL
, NULL
, 0, ctx
)) < 0 && i
== NUM_EXPR_EVALS
)
149 s
->x
= var_values
[VAR_X
] = res
;
151 if ((ret
= av_expr_parse_and_eval(&res
, (expr
= s
->y_expr
),
152 var_names
, var_values
,
153 NULL
, NULL
, NULL
, NULL
, NULL
, 0, ctx
)) < 0 && i
== NUM_EXPR_EVALS
)
155 s
->y
= var_values
[VAR_Y
] = res
;
157 if ((ret
= av_expr_parse_and_eval(&res
, (expr
= s
->w_expr
),
158 var_names
, var_values
,
159 NULL
, NULL
, NULL
, NULL
, NULL
, 0, ctx
)) < 0 && i
== NUM_EXPR_EVALS
)
161 s
->w
= var_values
[VAR_W
] = res
;
163 if ((ret
= av_expr_parse_and_eval(&res
, (expr
= s
->h_expr
),
164 var_names
, var_values
,
165 NULL
, NULL
, NULL
, NULL
, NULL
, 0, ctx
)) < 0 && i
== NUM_EXPR_EVALS
)
167 s
->h
= var_values
[VAR_H
] = res
;
169 if ((ret
= av_expr_parse_and_eval(&res
, (expr
= s
->t_expr
),
170 var_names
, var_values
,
171 NULL
, NULL
, NULL
, NULL
, NULL
, 0, ctx
)) < 0 && i
== NUM_EXPR_EVALS
)
173 s
->thickness
= var_values
[VAR_T
] = res
;
176 /* if w or h are zero, use the input w/h */
177 s
->w
= (s
->w
> 0) ? s
->w
: inlink
->w
;
178 s
->h
= (s
->h
> 0) ? s
->h
: inlink
->h
;
180 /* sanity check width and height */
181 if (s
->w
< 0 || s
->h
< 0) {
182 av_log(ctx
, AV_LOG_ERROR
, "Size values less than 0 are not acceptable.\n");
183 return AVERROR(EINVAL
);
186 av_log(ctx
, AV_LOG_VERBOSE
, "x:%d y:%d w:%d h:%d color:0x%02X%02X%02X%02X\n",
187 s
->x
, s
->y
, s
->w
, s
->h
,
188 s
->yuv_color
[Y
], s
->yuv_color
[U
], s
->yuv_color
[V
], s
->yuv_color
[A
]);
193 av_log(ctx
, AV_LOG_ERROR
,
194 "Error when evaluating the expression '%s'.\n",
199 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*frame
)
201 DrawBoxContext
*s
= inlink
->dst
->priv
;
202 int plane
, x
, y
, xb
= s
->x
, yb
= s
->y
;
203 unsigned char *row
[4];
205 for (y
= FFMAX(yb
, 0); y
< frame
->height
&& y
< (yb
+ s
->h
); y
++) {
206 row
[0] = frame
->data
[0] + y
* frame
->linesize
[0];
208 for (plane
= 1; plane
< 3; plane
++)
209 row
[plane
] = frame
->data
[plane
] +
210 frame
->linesize
[plane
] * (y
>> s
->vsub
);
212 if (s
->invert_color
) {
213 for (x
= FFMAX(xb
, 0); x
< xb
+ s
->w
&& x
< frame
->width
; x
++)
214 if ((y
- yb
< s
->thickness
) || (yb
+ s
->h
- 1 - y
< s
->thickness
) ||
215 (x
- xb
< s
->thickness
) || (xb
+ s
->w
- 1 - x
< s
->thickness
))
216 row
[0][x
] = 0xff - row
[0][x
];
218 for (x
= FFMAX(xb
, 0); x
< xb
+ s
->w
&& x
< frame
->width
; x
++) {
219 double alpha
= (double)s
->yuv_color
[A
] / 255;
221 if ((y
- yb
< s
->thickness
) || (yb
+ s
->h
- 1 - y
< s
->thickness
) ||
222 (x
- xb
< s
->thickness
) || (xb
+ s
->w
- 1 - x
< s
->thickness
)) {
223 row
[0][x
] = (1 - alpha
) * row
[0][x
] + alpha
* s
->yuv_color
[Y
];
224 row
[1][x
>> s
->hsub
] = (1 - alpha
) * row
[1][x
>> s
->hsub
] + alpha
* s
->yuv_color
[U
];
225 row
[2][x
>> s
->hsub
] = (1 - alpha
) * row
[2][x
>> s
->hsub
] + alpha
* s
->yuv_color
[V
];
231 return ff_filter_frame(inlink
->dst
->outputs
[0], frame
);
234 #define OFFSET(x) offsetof(DrawBoxContext, x)
235 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
237 #if CONFIG_DRAWBOX_FILTER
239 static const AVOption drawbox_options
[] = {
240 { "x", "set horizontal position of the left box edge", OFFSET(x_expr
), AV_OPT_TYPE_STRING
, { .str
="0" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
241 { "y", "set vertical position of the top box edge", OFFSET(y_expr
), AV_OPT_TYPE_STRING
, { .str
="0" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
242 { "width", "set width of the box", OFFSET(w_expr
), AV_OPT_TYPE_STRING
, { .str
="0" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
243 { "w", "set width of the box", OFFSET(w_expr
), AV_OPT_TYPE_STRING
, { .str
="0" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
244 { "height", "set height of the box", OFFSET(h_expr
), AV_OPT_TYPE_STRING
, { .str
="0" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
245 { "h", "set height of the box", OFFSET(h_expr
), AV_OPT_TYPE_STRING
, { .str
="0" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
246 { "color", "set color of the box", OFFSET(color_str
), AV_OPT_TYPE_STRING
, { .str
= "black" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
247 { "c", "set color of the box", OFFSET(color_str
), AV_OPT_TYPE_STRING
, { .str
= "black" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
248 { "thickness", "set the box thickness", OFFSET(t_expr
), AV_OPT_TYPE_STRING
, { .str
="3" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
249 { "t", "set the box thickness", OFFSET(t_expr
), AV_OPT_TYPE_STRING
, { .str
="3" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
253 AVFILTER_DEFINE_CLASS(drawbox
);
255 static const AVFilterPad drawbox_inputs
[] = {
258 .type
= AVMEDIA_TYPE_VIDEO
,
259 .config_props
= config_input
,
260 .filter_frame
= filter_frame
,
266 static const AVFilterPad drawbox_outputs
[] = {
269 .type
= AVMEDIA_TYPE_VIDEO
,
274 AVFilter ff_vf_drawbox
= {
276 .description
= NULL_IF_CONFIG_SMALL("Draw a colored box on the input video."),
277 .priv_size
= sizeof(DrawBoxContext
),
278 .priv_class
= &drawbox_class
,
280 .query_formats
= query_formats
,
281 .inputs
= drawbox_inputs
,
282 .outputs
= drawbox_outputs
,
283 .flags
= AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC
,
285 #endif /* CONFIG_DRAWBOX_FILTER */
287 #if CONFIG_DRAWGRID_FILTER
288 static av_pure av_always_inline
int pixel_belongs_to_grid(DrawBoxContext
*drawgrid
, int x
, int y
)
290 // x is horizontal (width) coord,
291 // y is vertical (height) coord
295 // Abstract from the offset
299 x_modulo
= x
% drawgrid
->w
;
300 y_modulo
= y
% drawgrid
->h
;
302 // If x or y got negative, fix values to preserve logics
304 x_modulo
+= drawgrid
->w
;
306 y_modulo
+= drawgrid
->h
;
308 return x_modulo
< drawgrid
->thickness
// Belongs to vertical line
309 || y_modulo
< drawgrid
->thickness
; // Belongs to horizontal line
312 static int drawgrid_filter_frame(AVFilterLink
*inlink
, AVFrame
*frame
)
314 DrawBoxContext
*drawgrid
= inlink
->dst
->priv
;
318 for (y
= 0; y
< frame
->height
; y
++) {
319 row
[0] = frame
->data
[0] + y
* frame
->linesize
[0];
321 for (plane
= 1; plane
< 3; plane
++)
322 row
[plane
] = frame
->data
[plane
] +
323 frame
->linesize
[plane
] * (y
>> drawgrid
->vsub
);
325 if (drawgrid
->invert_color
) {
326 for (x
= 0; x
< frame
->width
; x
++)
327 if (pixel_belongs_to_grid(drawgrid
, x
, y
))
328 row
[0][x
] = 0xff - row
[0][x
];
330 for (x
= 0; x
< frame
->width
; x
++) {
331 double alpha
= (double)drawgrid
->yuv_color
[A
] / 255;
333 if (pixel_belongs_to_grid(drawgrid
, x
, y
)) {
334 row
[0][x
] = (1 - alpha
) * row
[0][x
] + alpha
* drawgrid
->yuv_color
[Y
];
335 row
[1][x
>> drawgrid
->hsub
] = (1 - alpha
) * row
[1][x
>> drawgrid
->hsub
] + alpha
* drawgrid
->yuv_color
[U
];
336 row
[2][x
>> drawgrid
->hsub
] = (1 - alpha
) * row
[2][x
>> drawgrid
->hsub
] + alpha
* drawgrid
->yuv_color
[V
];
342 return ff_filter_frame(inlink
->dst
->outputs
[0], frame
);
345 static const AVOption drawgrid_options
[] = {
346 { "x", "set horizontal offset", OFFSET(x_expr
), AV_OPT_TYPE_STRING
, { .str
="0" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
347 { "y", "set vertical offset", OFFSET(y_expr
), AV_OPT_TYPE_STRING
, { .str
="0" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
348 { "width", "set width of grid cell", OFFSET(w_expr
), AV_OPT_TYPE_STRING
, { .str
="0" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
349 { "w", "set width of grid cell", OFFSET(w_expr
), AV_OPT_TYPE_STRING
, { .str
="0" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
350 { "height", "set height of grid cell", OFFSET(h_expr
), AV_OPT_TYPE_STRING
, { .str
="0" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
351 { "h", "set height of grid cell", OFFSET(h_expr
), AV_OPT_TYPE_STRING
, { .str
="0" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
352 { "color", "set color of the grid", OFFSET(color_str
), AV_OPT_TYPE_STRING
, { .str
= "black" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
353 { "c", "set color of the grid", OFFSET(color_str
), AV_OPT_TYPE_STRING
, { .str
= "black" }, CHAR_MIN
, CHAR_MAX
, FLAGS
},
354 { "thickness", "set grid line thickness", OFFSET(t_expr
), AV_OPT_TYPE_STRING
, {.str
="1"}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
355 { "t", "set grid line thickness", OFFSET(t_expr
), AV_OPT_TYPE_STRING
, {.str
="1"}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
359 AVFILTER_DEFINE_CLASS(drawgrid
);
361 static const AVFilterPad drawgrid_inputs
[] = {
364 .type
= AVMEDIA_TYPE_VIDEO
,
365 .config_props
= config_input
,
366 .filter_frame
= drawgrid_filter_frame
,
372 static const AVFilterPad drawgrid_outputs
[] = {
375 .type
= AVMEDIA_TYPE_VIDEO
,
380 AVFilter ff_vf_drawgrid
= {
382 .description
= NULL_IF_CONFIG_SMALL("Draw a colored grid on the input video."),
383 .priv_size
= sizeof(DrawBoxContext
),
384 .priv_class
= &drawgrid_class
,
386 .query_formats
= query_formats
,
387 .inputs
= drawgrid_inputs
,
388 .outputs
= drawgrid_outputs
,
389 .flags
= AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC
,
392 #endif /* CONFIG_DRAWGRID_FILTER */