blob: 88bb9ae5c0105f38b739a6af86cf99bda57a33ef
1 | /* |
2 | * Copyright (c) 2008 Affine Systems, Inc (Michael Sullivan, Bobby Impollonia) |
3 | * Copyright (c) 2013 Andrey Utkin <andrey.krieger.utkin gmail com> |
4 | * |
5 | * This file is part of FFmpeg. |
6 | * |
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. |
11 | * |
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. |
16 | * |
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 |
20 | */ |
21 | |
22 | /** |
23 | * @file |
24 | * Box and grid drawing filters. Also a nice template for a filter |
25 | * that needs to write in the input frame. |
26 | */ |
27 | |
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" |
34 | #include "avfilter.h" |
35 | #include "formats.h" |
36 | #include "internal.h" |
37 | #include "video.h" |
38 | |
39 | static const char *const var_names[] = { |
40 | "dar", |
41 | "hsub", "vsub", |
42 | "in_h", "ih", ///< height of the input video |
43 | "in_w", "iw", ///< width of the input video |
44 | "sar", |
45 | "x", |
46 | "y", |
47 | "h", ///< height of the rendered box |
48 | "w", ///< width of the rendered box |
49 | "t", |
50 | "max", |
51 | NULL |
52 | }; |
53 | |
54 | enum { Y, U, V, A }; |
55 | |
56 | enum var_name { |
57 | VAR_DAR, |
58 | VAR_HSUB, VAR_VSUB, |
59 | VAR_IN_H, VAR_IH, |
60 | VAR_IN_W, VAR_IW, |
61 | VAR_SAR, |
62 | VAR_X, |
63 | VAR_Y, |
64 | VAR_H, |
65 | VAR_W, |
66 | VAR_T, |
67 | VAR_MAX, |
68 | VARS_NB |
69 | }; |
70 | |
71 | typedef struct DrawBoxContext { |
72 | const AVClass *class; |
73 | int x, y, w, h; |
74 | int thickness; |
75 | char *color_str; |
76 | unsigned char yuv_color[4]; |
77 | int invert_color; ///< invert luma color |
78 | int vsub, hsub; ///< chroma subsampling |
79 | char *x_expr, *y_expr; ///< expression for x and y |
80 | char *w_expr, *h_expr; ///< expression for width and height |
81 | char *t_expr; ///< expression for thickness |
82 | int have_alpha; |
83 | } DrawBoxContext; |
84 | |
85 | static const int NUM_EXPR_EVALS = 5; |
86 | |
87 | static av_cold int init(AVFilterContext *ctx) |
88 | { |
89 | DrawBoxContext *s = ctx->priv; |
90 | uint8_t rgba_color[4]; |
91 | |
92 | if (!strcmp(s->color_str, "invert")) |
93 | s->invert_color = 1; |
94 | else if (av_parse_color(rgba_color, s->color_str, -1, ctx) < 0) |
95 | return AVERROR(EINVAL); |
96 | |
97 | if (!s->invert_color) { |
98 | s->yuv_color[Y] = RGB_TO_Y_CCIR(rgba_color[0], rgba_color[1], rgba_color[2]); |
99 | s->yuv_color[U] = RGB_TO_U_CCIR(rgba_color[0], rgba_color[1], rgba_color[2], 0); |
100 | s->yuv_color[V] = RGB_TO_V_CCIR(rgba_color[0], rgba_color[1], rgba_color[2], 0); |
101 | s->yuv_color[A] = rgba_color[3]; |
102 | } |
103 | |
104 | return 0; |
105 | } |
106 | |
107 | static int query_formats(AVFilterContext *ctx) |
108 | { |
109 | static const enum AVPixelFormat pix_fmts[] = { |
110 | AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P, |
111 | AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, |
112 | AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, |
113 | AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUVJ440P, |
114 | AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA444P, |
115 | AV_PIX_FMT_NONE |
116 | }; |
117 | AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts); |
118 | if (!fmts_list) |
119 | return AVERROR(ENOMEM); |
120 | return ff_set_common_formats(ctx, fmts_list); |
121 | } |
122 | |
123 | static int config_input(AVFilterLink *inlink) |
124 | { |
125 | AVFilterContext *ctx = inlink->dst; |
126 | DrawBoxContext *s = ctx->priv; |
127 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); |
128 | double var_values[VARS_NB], res; |
129 | char *expr; |
130 | int ret; |
131 | int i; |
132 | |
133 | s->hsub = desc->log2_chroma_w; |
134 | s->vsub = desc->log2_chroma_h; |
135 | s->have_alpha = desc->flags & AV_PIX_FMT_FLAG_ALPHA; |
136 | |
137 | var_values[VAR_IN_H] = var_values[VAR_IH] = inlink->h; |
138 | var_values[VAR_IN_W] = var_values[VAR_IW] = inlink->w; |
139 | var_values[VAR_SAR] = inlink->sample_aspect_ratio.num ? av_q2d(inlink->sample_aspect_ratio) : 1; |
140 | var_values[VAR_DAR] = (double)inlink->w / inlink->h * var_values[VAR_SAR]; |
141 | var_values[VAR_HSUB] = s->hsub; |
142 | var_values[VAR_VSUB] = s->vsub; |
143 | var_values[VAR_X] = NAN; |
144 | var_values[VAR_Y] = NAN; |
145 | var_values[VAR_H] = NAN; |
146 | var_values[VAR_W] = NAN; |
147 | var_values[VAR_T] = NAN; |
148 | |
149 | for (i = 0; i <= NUM_EXPR_EVALS; i++) { |
150 | /* evaluate expressions, fail on last iteration */ |
151 | var_values[VAR_MAX] = inlink->w; |
152 | if ((ret = av_expr_parse_and_eval(&res, (expr = s->x_expr), |
153 | var_names, var_values, |
154 | NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS) |
155 | goto fail; |
156 | s->x = var_values[VAR_X] = res; |
157 | |
158 | var_values[VAR_MAX] = inlink->h; |
159 | if ((ret = av_expr_parse_and_eval(&res, (expr = s->y_expr), |
160 | var_names, var_values, |
161 | NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS) |
162 | goto fail; |
163 | s->y = var_values[VAR_Y] = res; |
164 | |
165 | var_values[VAR_MAX] = inlink->w - s->x; |
166 | if ((ret = av_expr_parse_and_eval(&res, (expr = s->w_expr), |
167 | var_names, var_values, |
168 | NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS) |
169 | goto fail; |
170 | s->w = var_values[VAR_W] = res; |
171 | |
172 | var_values[VAR_MAX] = inlink->h - s->y; |
173 | if ((ret = av_expr_parse_and_eval(&res, (expr = s->h_expr), |
174 | var_names, var_values, |
175 | NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS) |
176 | goto fail; |
177 | s->h = var_values[VAR_H] = res; |
178 | |
179 | var_values[VAR_MAX] = INT_MAX; |
180 | if ((ret = av_expr_parse_and_eval(&res, (expr = s->t_expr), |
181 | var_names, var_values, |
182 | NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS) |
183 | goto fail; |
184 | s->thickness = var_values[VAR_T] = res; |
185 | } |
186 | |
187 | /* if w or h are zero, use the input w/h */ |
188 | s->w = (s->w > 0) ? s->w : inlink->w; |
189 | s->h = (s->h > 0) ? s->h : inlink->h; |
190 | |
191 | /* sanity check width and height */ |
192 | if (s->w < 0 || s->h < 0) { |
193 | av_log(ctx, AV_LOG_ERROR, "Size values less than 0 are not acceptable.\n"); |
194 | return AVERROR(EINVAL); |
195 | } |
196 | |
197 | av_log(ctx, AV_LOG_VERBOSE, "x:%d y:%d w:%d h:%d color:0x%02X%02X%02X%02X\n", |
198 | s->x, s->y, s->w, s->h, |
199 | s->yuv_color[Y], s->yuv_color[U], s->yuv_color[V], s->yuv_color[A]); |
200 | |
201 | return 0; |
202 | |
203 | fail: |
204 | av_log(ctx, AV_LOG_ERROR, |
205 | "Error when evaluating the expression '%s'.\n", |
206 | expr); |
207 | return ret; |
208 | } |
209 | |
210 | static int filter_frame(AVFilterLink *inlink, AVFrame *frame) |
211 | { |
212 | DrawBoxContext *s = inlink->dst->priv; |
213 | int plane, x, y, xb = s->x, yb = s->y; |
214 | unsigned char *row[4]; |
215 | |
216 | if (s->have_alpha) { |
217 | for (y = FFMAX(yb, 0); y < frame->height && y < (yb + s->h); y++) { |
218 | row[0] = frame->data[0] + y * frame->linesize[0]; |
219 | row[3] = frame->data[3] + y * frame->linesize[3]; |
220 | |
221 | for (plane = 1; plane < 3; plane++) |
222 | row[plane] = frame->data[plane] + |
223 | frame->linesize[plane] * (y >> s->vsub); |
224 | |
225 | if (s->invert_color) { |
226 | for (x = FFMAX(xb, 0); x < xb + s->w && x < frame->width; x++) |
227 | if ((y - yb < s->thickness) || (yb + s->h - 1 - y < s->thickness) || |
228 | (x - xb < s->thickness) || (xb + s->w - 1 - x < s->thickness)) |
229 | row[0][x] = 0xff - row[0][x]; |
230 | } else { |
231 | for (x = FFMAX(xb, 0); x < xb + s->w && x < frame->width; x++) { |
232 | if ((y - yb < s->thickness) || (yb + s->h - 1 - y < s->thickness) || |
233 | (x - xb < s->thickness) || (xb + s->w - 1 - x < s->thickness)) { |
234 | row[0][x ] = s->yuv_color[Y]; |
235 | row[1][x >> s->hsub] = s->yuv_color[U]; |
236 | row[2][x >> s->hsub] = s->yuv_color[V]; |
237 | row[3][x ] = s->yuv_color[A]; |
238 | } |
239 | } |
240 | } |
241 | } |
242 | } else { |
243 | for (y = FFMAX(yb, 0); y < frame->height && y < (yb + s->h); y++) { |
244 | row[0] = frame->data[0] + y * frame->linesize[0]; |
245 | |
246 | for (plane = 1; plane < 3; plane++) |
247 | row[plane] = frame->data[plane] + |
248 | frame->linesize[plane] * (y >> s->vsub); |
249 | |
250 | if (s->invert_color) { |
251 | for (x = FFMAX(xb, 0); x < xb + s->w && x < frame->width; x++) |
252 | if ((y - yb < s->thickness) || (yb + s->h - 1 - y < s->thickness) || |
253 | (x - xb < s->thickness) || (xb + s->w - 1 - x < s->thickness)) |
254 | row[0][x] = 0xff - row[0][x]; |
255 | } else { |
256 | for (x = FFMAX(xb, 0); x < xb + s->w && x < frame->width; x++) { |
257 | double alpha = (double)s->yuv_color[A] / 255; |
258 | |
259 | if ((y - yb < s->thickness) || (yb + s->h - 1 - y < s->thickness) || |
260 | (x - xb < s->thickness) || (xb + s->w - 1 - x < s->thickness)) { |
261 | row[0][x ] = (1 - alpha) * row[0][x ] + alpha * s->yuv_color[Y]; |
262 | row[1][x >> s->hsub] = (1 - alpha) * row[1][x >> s->hsub] + alpha * s->yuv_color[U]; |
263 | row[2][x >> s->hsub] = (1 - alpha) * row[2][x >> s->hsub] + alpha * s->yuv_color[V]; |
264 | } |
265 | } |
266 | } |
267 | } |
268 | } |
269 | |
270 | return ff_filter_frame(inlink->dst->outputs[0], frame); |
271 | } |
272 | |
273 | #define OFFSET(x) offsetof(DrawBoxContext, x) |
274 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM |
275 | |
276 | #if CONFIG_DRAWBOX_FILTER |
277 | |
278 | static const AVOption drawbox_options[] = { |
279 | { "x", "set horizontal position of the left box edge", OFFSET(x_expr), AV_OPT_TYPE_STRING, { .str="0" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
280 | { "y", "set vertical position of the top box edge", OFFSET(y_expr), AV_OPT_TYPE_STRING, { .str="0" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
281 | { "width", "set width of the box", OFFSET(w_expr), AV_OPT_TYPE_STRING, { .str="0" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
282 | { "w", "set width of the box", OFFSET(w_expr), AV_OPT_TYPE_STRING, { .str="0" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
283 | { "height", "set height of the box", OFFSET(h_expr), AV_OPT_TYPE_STRING, { .str="0" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
284 | { "h", "set height of the box", OFFSET(h_expr), AV_OPT_TYPE_STRING, { .str="0" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
285 | { "color", "set color of the box", OFFSET(color_str), AV_OPT_TYPE_STRING, { .str = "black" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
286 | { "c", "set color of the box", OFFSET(color_str), AV_OPT_TYPE_STRING, { .str = "black" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
287 | { "thickness", "set the box thickness", OFFSET(t_expr), AV_OPT_TYPE_STRING, { .str="3" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
288 | { "t", "set the box thickness", OFFSET(t_expr), AV_OPT_TYPE_STRING, { .str="3" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
289 | { NULL } |
290 | }; |
291 | |
292 | AVFILTER_DEFINE_CLASS(drawbox); |
293 | |
294 | static const AVFilterPad drawbox_inputs[] = { |
295 | { |
296 | .name = "default", |
297 | .type = AVMEDIA_TYPE_VIDEO, |
298 | .config_props = config_input, |
299 | .filter_frame = filter_frame, |
300 | .needs_writable = 1, |
301 | }, |
302 | { NULL } |
303 | }; |
304 | |
305 | static const AVFilterPad drawbox_outputs[] = { |
306 | { |
307 | .name = "default", |
308 | .type = AVMEDIA_TYPE_VIDEO, |
309 | }, |
310 | { NULL } |
311 | }; |
312 | |
313 | AVFilter ff_vf_drawbox = { |
314 | .name = "drawbox", |
315 | .description = NULL_IF_CONFIG_SMALL("Draw a colored box on the input video."), |
316 | .priv_size = sizeof(DrawBoxContext), |
317 | .priv_class = &drawbox_class, |
318 | .init = init, |
319 | .query_formats = query_formats, |
320 | .inputs = drawbox_inputs, |
321 | .outputs = drawbox_outputs, |
322 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, |
323 | }; |
324 | #endif /* CONFIG_DRAWBOX_FILTER */ |
325 | |
326 | #if CONFIG_DRAWGRID_FILTER |
327 | static av_pure av_always_inline int pixel_belongs_to_grid(DrawBoxContext *drawgrid, int x, int y) |
328 | { |
329 | // x is horizontal (width) coord, |
330 | // y is vertical (height) coord |
331 | int x_modulo; |
332 | int y_modulo; |
333 | |
334 | // Abstract from the offset |
335 | x -= drawgrid->x; |
336 | y -= drawgrid->y; |
337 | |
338 | x_modulo = x % drawgrid->w; |
339 | y_modulo = y % drawgrid->h; |
340 | |
341 | // If x or y got negative, fix values to preserve logics |
342 | if (x_modulo < 0) |
343 | x_modulo += drawgrid->w; |
344 | if (y_modulo < 0) |
345 | y_modulo += drawgrid->h; |
346 | |
347 | return x_modulo < drawgrid->thickness // Belongs to vertical line |
348 | || y_modulo < drawgrid->thickness; // Belongs to horizontal line |
349 | } |
350 | |
351 | static int drawgrid_filter_frame(AVFilterLink *inlink, AVFrame *frame) |
352 | { |
353 | DrawBoxContext *drawgrid = inlink->dst->priv; |
354 | int plane, x, y; |
355 | uint8_t *row[4]; |
356 | |
357 | if (drawgrid->have_alpha) { |
358 | for (y = 0; y < frame->height; y++) { |
359 | row[0] = frame->data[0] + y * frame->linesize[0]; |
360 | row[3] = frame->data[3] + y * frame->linesize[3]; |
361 | |
362 | for (plane = 1; plane < 3; plane++) |
363 | row[plane] = frame->data[plane] + |
364 | frame->linesize[plane] * (y >> drawgrid->vsub); |
365 | |
366 | if (drawgrid->invert_color) { |
367 | for (x = 0; x < frame->width; x++) |
368 | if (pixel_belongs_to_grid(drawgrid, x, y)) |
369 | row[0][x] = 0xff - row[0][x]; |
370 | } else { |
371 | for (x = 0; x < frame->width; x++) { |
372 | if (pixel_belongs_to_grid(drawgrid, x, y)) { |
373 | row[0][x ] = drawgrid->yuv_color[Y]; |
374 | row[1][x >> drawgrid->hsub] = drawgrid->yuv_color[U]; |
375 | row[2][x >> drawgrid->hsub] = drawgrid->yuv_color[V]; |
376 | row[3][x ] = drawgrid->yuv_color[A]; |
377 | } |
378 | } |
379 | } |
380 | } |
381 | } else { |
382 | for (y = 0; y < frame->height; y++) { |
383 | row[0] = frame->data[0] + y * frame->linesize[0]; |
384 | |
385 | for (plane = 1; plane < 3; plane++) |
386 | row[plane] = frame->data[plane] + |
387 | frame->linesize[plane] * (y >> drawgrid->vsub); |
388 | |
389 | if (drawgrid->invert_color) { |
390 | for (x = 0; x < frame->width; x++) |
391 | if (pixel_belongs_to_grid(drawgrid, x, y)) |
392 | row[0][x] = 0xff - row[0][x]; |
393 | } else { |
394 | for (x = 0; x < frame->width; x++) { |
395 | double alpha = (double)drawgrid->yuv_color[A] / 255; |
396 | |
397 | if (pixel_belongs_to_grid(drawgrid, x, y)) { |
398 | row[0][x ] = (1 - alpha) * row[0][x ] + alpha * drawgrid->yuv_color[Y]; |
399 | row[1][x >> drawgrid->hsub] = (1 - alpha) * row[1][x >> drawgrid->hsub] + alpha * drawgrid->yuv_color[U]; |
400 | row[2][x >> drawgrid->hsub] = (1 - alpha) * row[2][x >> drawgrid->hsub] + alpha * drawgrid->yuv_color[V]; |
401 | } |
402 | } |
403 | } |
404 | } |
405 | } |
406 | |
407 | return ff_filter_frame(inlink->dst->outputs[0], frame); |
408 | } |
409 | |
410 | static const AVOption drawgrid_options[] = { |
411 | { "x", "set horizontal offset", OFFSET(x_expr), AV_OPT_TYPE_STRING, { .str="0" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
412 | { "y", "set vertical offset", OFFSET(y_expr), AV_OPT_TYPE_STRING, { .str="0" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
413 | { "width", "set width of grid cell", OFFSET(w_expr), AV_OPT_TYPE_STRING, { .str="0" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
414 | { "w", "set width of grid cell", OFFSET(w_expr), AV_OPT_TYPE_STRING, { .str="0" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
415 | { "height", "set height of grid cell", OFFSET(h_expr), AV_OPT_TYPE_STRING, { .str="0" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
416 | { "h", "set height of grid cell", OFFSET(h_expr), AV_OPT_TYPE_STRING, { .str="0" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
417 | { "color", "set color of the grid", OFFSET(color_str), AV_OPT_TYPE_STRING, { .str = "black" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
418 | { "c", "set color of the grid", OFFSET(color_str), AV_OPT_TYPE_STRING, { .str = "black" }, CHAR_MIN, CHAR_MAX, FLAGS }, |
419 | { "thickness", "set grid line thickness", OFFSET(t_expr), AV_OPT_TYPE_STRING, {.str="1"}, CHAR_MIN, CHAR_MAX, FLAGS }, |
420 | { "t", "set grid line thickness", OFFSET(t_expr), AV_OPT_TYPE_STRING, {.str="1"}, CHAR_MIN, CHAR_MAX, FLAGS }, |
421 | { NULL } |
422 | }; |
423 | |
424 | AVFILTER_DEFINE_CLASS(drawgrid); |
425 | |
426 | static const AVFilterPad drawgrid_inputs[] = { |
427 | { |
428 | .name = "default", |
429 | .type = AVMEDIA_TYPE_VIDEO, |
430 | .config_props = config_input, |
431 | .filter_frame = drawgrid_filter_frame, |
432 | .needs_writable = 1, |
433 | }, |
434 | { NULL } |
435 | }; |
436 | |
437 | static const AVFilterPad drawgrid_outputs[] = { |
438 | { |
439 | .name = "default", |
440 | .type = AVMEDIA_TYPE_VIDEO, |
441 | }, |
442 | { NULL } |
443 | }; |
444 | |
445 | AVFilter ff_vf_drawgrid = { |
446 | .name = "drawgrid", |
447 | .description = NULL_IF_CONFIG_SMALL("Draw a colored grid on the input video."), |
448 | .priv_size = sizeof(DrawBoxContext), |
449 | .priv_class = &drawgrid_class, |
450 | .init = init, |
451 | .query_formats = query_formats, |
452 | .inputs = drawgrid_inputs, |
453 | .outputs = drawgrid_outputs, |
454 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, |
455 | }; |
456 | |
457 | #endif /* CONFIG_DRAWGRID_FILTER */ |
458 |