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 "config_components.h"
29
30 #include "libavutil/colorspace.h"
31 #include "libavutil/common.h"
32 #include "libavutil/opt.h"
33 #include "libavutil/eval.h"
34 #include "libavutil/pixdesc.h"
35 #include "libavutil/parseutils.h"
36 #include "libavutil/detection_bbox.h"
37 #include "avfilter.h"
38 #include "drawutils.h"
39 #include "formats.h"
40 #include "internal.h"
41 #include "video.h"
42
43 static const char *const var_names[] = {
44 "dar",
45 "hsub", "vsub",
46 "in_h", "ih", ///< height of the input video
47 "in_w", "iw", ///< width of the input video
48 "sar",
49 "x",
50 "y",
51 "h", ///< height of the rendered box
52 "w", ///< width of the rendered box
53 "t",
54 "fill",
55 NULL
56 };
57
58 enum { Y, U, V, A };
59 enum { R, G, B };
60
61 enum var_name {
62 VAR_DAR,
63 VAR_HSUB, VAR_VSUB,
64 VAR_IN_H, VAR_IH,
65 VAR_IN_W, VAR_IW,
66 VAR_SAR,
67 VAR_X,
68 VAR_Y,
69 VAR_H,
70 VAR_W,
71 VAR_T,
72 VAR_MAX,
73 VARS_NB
74 };
75
76 struct DrawBoxContext;
77
78 typedef int (*PixelBelongsToRegion)(struct DrawBoxContext *s, int x, int y);
79
80 typedef struct DrawBoxContext {
81 const AVClass *class;
82 int x, y, w, h;
83 int thickness;
84 char *color_str;
85 uint8_t rgba_map[4];
86 uint8_t rgba_color[4];
87 unsigned char yuv_color[4];
88 int invert_color; ///< invert luma color
89 int vsub, hsub; ///< chroma subsampling
90 char *x_expr, *y_expr; ///< expression for x and y
91 char *w_expr, *h_expr; ///< expression for width and height
92 char *t_expr; ///< expression for thickness
93 char *box_source_string; ///< string for box data source
94 int have_alpha;
95 int replace;
96 int step;
97 enum AVFrameSideDataType box_source;
98
99 void (*draw_region)(AVFrame *frame, struct DrawBoxContext *ctx, int left, int top, int right, int down,
100 PixelBelongsToRegion pixel_belongs_to_region);
101 } DrawBoxContext;
102
103 static const int NUM_EXPR_EVALS = 5;
104
105 #define ASSIGN_THREE_CHANNELS \
106 row[0] = frame->data[0] + y * frame->linesize[0]; \
107 row[1] = frame->data[1] + (y >> ctx->vsub) * frame->linesize[1]; \
108 row[2] = frame->data[2] + (y >> ctx->vsub) * frame->linesize[2];
109
110 #define ASSIGN_FOUR_CHANNELS \
111 ASSIGN_THREE_CHANNELS \
112 row[3] = frame->data[3] + y * frame->linesize[3];
113
draw_region(AVFrame * frame,DrawBoxContext * ctx,int left,int top,int right,int down,PixelBelongsToRegion pixel_belongs_to_region)114 static void draw_region(AVFrame *frame, DrawBoxContext *ctx, int left, int top, int right, int down,
115 PixelBelongsToRegion pixel_belongs_to_region)
116 {
117 unsigned char *row[4];
118 int x, y;
119 if (ctx->have_alpha && ctx->replace) {
120 for (y = top; y < down; y++) {
121 ASSIGN_FOUR_CHANNELS
122 if (ctx->invert_color) {
123 for (x = left; x < right; x++)
124 if (pixel_belongs_to_region(ctx, x, y))
125 row[0][x] = 0xff - row[0][x];
126 } else {
127 for (x = left; x < right; x++) {
128 if (pixel_belongs_to_region(ctx, x, y)) {
129 row[0][x ] = ctx->yuv_color[Y];
130 row[1][x >> ctx->hsub] = ctx->yuv_color[U];
131 row[2][x >> ctx->hsub] = ctx->yuv_color[V];
132 row[3][x ] = ctx->yuv_color[A];
133 }
134 }
135 }
136 }
137 } else {
138 for (y = top; y < down; y++) {
139 ASSIGN_THREE_CHANNELS
140 if (ctx->invert_color) {
141 for (x = left; x < right; x++)
142 if (pixel_belongs_to_region(ctx, x, y))
143 row[0][x] = 0xff - row[0][x];
144 } else {
145 for (x = left; x < right; x++) {
146 double alpha = (double)ctx->yuv_color[A] / 255;
147
148 if (pixel_belongs_to_region(ctx, x, y)) {
149 row[0][x ] = (1 - alpha) * row[0][x ] + alpha * ctx->yuv_color[Y];
150 row[1][x >> ctx->hsub] = (1 - alpha) * row[1][x >> ctx->hsub] + alpha * ctx->yuv_color[U];
151 row[2][x >> ctx->hsub] = (1 - alpha) * row[2][x >> ctx->hsub] + alpha * ctx->yuv_color[V];
152 }
153 }
154 }
155 }
156 }
157 }
158
159 #define ASSIGN_THREE_CHANNELS_PACKED \
160 row[0] = frame->data[0] + y * frame->linesize[0] + ctx->rgba_map[0]; \
161 row[1] = frame->data[0] + y * frame->linesize[0] + ctx->rgba_map[1]; \
162 row[2] = frame->data[0] + y * frame->linesize[0] + ctx->rgba_map[2];
163
164 #define ASSIGN_FOUR_CHANNELS_PACKED \
165 ASSIGN_THREE_CHANNELS \
166 row[3] = frame->data[0] + y * frame->linesize[0] + ctx->rgba_map[3];
167
draw_region_rgb_packed(AVFrame * frame,DrawBoxContext * ctx,int left,int top,int right,int down,PixelBelongsToRegion pixel_belongs_to_region)168 static void draw_region_rgb_packed(AVFrame *frame, DrawBoxContext *ctx, int left, int top, int right, int down,
169 PixelBelongsToRegion pixel_belongs_to_region)
170 {
171 const int C = ctx->step;
172 uint8_t *row[4];
173
174 if (ctx->have_alpha && ctx->replace) {
175 for (int y = top; y < down; y++) {
176 ASSIGN_FOUR_CHANNELS_PACKED
177 if (ctx->invert_color) {
178 for (int x = left; x < right; x++)
179 if (pixel_belongs_to_region(ctx, x, y)) {
180 row[0][x*C] = 0xff - row[0][x*C];
181 row[1][x*C] = 0xff - row[1][x*C];
182 row[2][x*C] = 0xff - row[2][x*C];
183 }
184 } else {
185 for (int x = left; x < right; x++) {
186 if (pixel_belongs_to_region(ctx, x, y)) {
187 row[0][x*C] = ctx->rgba_color[R];
188 row[1][x*C] = ctx->rgba_color[G];
189 row[2][x*C] = ctx->rgba_color[B];
190 row[3][x*C] = ctx->rgba_color[A];
191 }
192 }
193 }
194 }
195 } else {
196 for (int y = top; y < down; y++) {
197 ASSIGN_THREE_CHANNELS_PACKED
198 if (ctx->invert_color) {
199 for (int x = left; x < right; x++)
200 if (pixel_belongs_to_region(ctx, x, y)) {
201 row[0][x*C] = 0xff - row[0][x*C];
202 row[1][x*C] = 0xff - row[1][x*C];
203 row[2][x*C] = 0xff - row[2][x*C];
204 }
205 } else {
206 for (int x = left; x < right; x++) {
207 float alpha = (float)ctx->rgba_color[A] / 255.f;
208
209 if (pixel_belongs_to_region(ctx, x, y)) {
210 row[0][x*C] = (1.f - alpha) * row[0][x*C] + alpha * ctx->rgba_color[R];
211 row[1][x*C] = (1.f - alpha) * row[1][x*C] + alpha * ctx->rgba_color[G];
212 row[2][x*C] = (1.f - alpha) * row[2][x*C] + alpha * ctx->rgba_color[B];
213 }
214 }
215 }
216 }
217 }
218 }
219
box_source_string_parse(const char * box_source_string)220 static enum AVFrameSideDataType box_source_string_parse(const char *box_source_string)
221 {
222 av_assert0(box_source_string);
223 if (!strcmp(box_source_string, "side_data_detection_bboxes")) {
224 return AV_FRAME_DATA_DETECTION_BBOXES;
225 } else {
226 // will support side_data_regions_of_interest next
227 return AVERROR(EINVAL);
228 }
229 }
230
init(AVFilterContext * ctx)231 static av_cold int init(AVFilterContext *ctx)
232 {
233 DrawBoxContext *s = ctx->priv;
234
235 if (s->box_source_string) {
236 s->box_source = box_source_string_parse(s->box_source_string);
237 if ((int)s->box_source < 0) {
238 av_log(ctx, AV_LOG_ERROR, "Error box source: %s\n",s->box_source_string);
239 return AVERROR(EINVAL);
240 }
241 }
242
243 if (!strcmp(s->color_str, "invert"))
244 s->invert_color = 1;
245 else if (av_parse_color(s->rgba_color, s->color_str, -1, ctx) < 0)
246 return AVERROR(EINVAL);
247
248 if (!s->invert_color) {
249 s->yuv_color[Y] = RGB_TO_Y_CCIR(s->rgba_color[0], s->rgba_color[1], s->rgba_color[2]);
250 s->yuv_color[U] = RGB_TO_U_CCIR(s->rgba_color[0], s->rgba_color[1], s->rgba_color[2], 0);
251 s->yuv_color[V] = RGB_TO_V_CCIR(s->rgba_color[0], s->rgba_color[1], s->rgba_color[2], 0);
252 s->yuv_color[A] = s->rgba_color[3];
253 }
254
255 return 0;
256 }
257
258 static const enum AVPixelFormat pix_fmts[] = {
259 AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P,
260 AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
261 AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
262 AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUVJ440P,
263 AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA444P,
264 AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24,
265 AV_PIX_FMT_RGBA, AV_PIX_FMT_BGRA,
266 AV_PIX_FMT_ARGB, AV_PIX_FMT_ABGR,
267 AV_PIX_FMT_0RGB, AV_PIX_FMT_0BGR,
268 AV_PIX_FMT_RGB0, AV_PIX_FMT_BGR0,
269 AV_PIX_FMT_NONE
270 };
271
config_input(AVFilterLink * inlink)272 static int config_input(AVFilterLink *inlink)
273 {
274 AVFilterContext *ctx = inlink->dst;
275 DrawBoxContext *s = ctx->priv;
276 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
277 double var_values[VARS_NB], res;
278 char *expr;
279 int ret;
280 int i;
281
282 ff_fill_rgba_map(s->rgba_map, inlink->format);
283
284 if (!(desc->flags & AV_PIX_FMT_FLAG_RGB))
285 s->draw_region = draw_region;
286 else
287 s->draw_region = draw_region_rgb_packed;
288
289 s->step = av_get_padded_bits_per_pixel(desc) >> 3;
290 s->hsub = desc->log2_chroma_w;
291 s->vsub = desc->log2_chroma_h;
292 s->have_alpha = desc->flags & AV_PIX_FMT_FLAG_ALPHA;
293
294 var_values[VAR_IN_H] = var_values[VAR_IH] = inlink->h;
295 var_values[VAR_IN_W] = var_values[VAR_IW] = inlink->w;
296 var_values[VAR_SAR] = inlink->sample_aspect_ratio.num ? av_q2d(inlink->sample_aspect_ratio) : 1;
297 var_values[VAR_DAR] = (double)inlink->w / inlink->h * var_values[VAR_SAR];
298 var_values[VAR_HSUB] = s->hsub;
299 var_values[VAR_VSUB] = s->vsub;
300 var_values[VAR_X] = NAN;
301 var_values[VAR_Y] = NAN;
302 var_values[VAR_H] = NAN;
303 var_values[VAR_W] = NAN;
304 var_values[VAR_T] = NAN;
305
306 for (i = 0; i <= NUM_EXPR_EVALS; i++) {
307 /* evaluate expressions, fail on last iteration */
308 var_values[VAR_MAX] = inlink->w;
309 if ((ret = av_expr_parse_and_eval(&res, (expr = s->x_expr),
310 var_names, var_values,
311 NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS)
312 goto fail;
313 s->x = var_values[VAR_X] = res;
314
315 var_values[VAR_MAX] = inlink->h;
316 if ((ret = av_expr_parse_and_eval(&res, (expr = s->y_expr),
317 var_names, var_values,
318 NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS)
319 goto fail;
320 s->y = var_values[VAR_Y] = res;
321
322 var_values[VAR_MAX] = inlink->w - s->x;
323 if ((ret = av_expr_parse_and_eval(&res, (expr = s->w_expr),
324 var_names, var_values,
325 NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS)
326 goto fail;
327 s->w = var_values[VAR_W] = res;
328
329 var_values[VAR_MAX] = inlink->h - s->y;
330 if ((ret = av_expr_parse_and_eval(&res, (expr = s->h_expr),
331 var_names, var_values,
332 NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS)
333 goto fail;
334 s->h = var_values[VAR_H] = res;
335
336 var_values[VAR_MAX] = INT_MAX;
337 if ((ret = av_expr_parse_and_eval(&res, (expr = s->t_expr),
338 var_names, var_values,
339 NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0 && i == NUM_EXPR_EVALS)
340 goto fail;
341 s->thickness = var_values[VAR_T] = res;
342 }
343
344 /* if w or h are zero, use the input w/h */
345 s->w = (s->w > 0) ? s->w : inlink->w;
346 s->h = (s->h > 0) ? s->h : inlink->h;
347
348 /* sanity check width and height */
349 if (s->w < 0 || s->h < 0) {
350 av_log(ctx, AV_LOG_ERROR, "Size values less than 0 are not acceptable.\n");
351 return AVERROR(EINVAL);
352 }
353
354 av_log(ctx, AV_LOG_VERBOSE, "x:%d y:%d w:%d h:%d color:0x%02X%02X%02X%02X\n",
355 s->x, s->y, s->w, s->h,
356 s->yuv_color[Y], s->yuv_color[U], s->yuv_color[V], s->yuv_color[A]);
357
358 return 0;
359
360 fail:
361 av_log(ctx, AV_LOG_ERROR,
362 "Error when evaluating the expression '%s'.\n",
363 expr);
364 return ret;
365 }
366
pixel_belongs_to_box(DrawBoxContext * s,int x,int y)367 static av_pure av_always_inline int pixel_belongs_to_box(DrawBoxContext *s, int x, int y)
368 {
369 return (y - s->y < s->thickness) || (s->y + s->h - 1 - y < s->thickness) ||
370 (x - s->x < s->thickness) || (s->x + s->w - 1 - x < s->thickness);
371 }
372
filter_frame(AVFilterLink * inlink,AVFrame * frame)373 static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
374 {
375 DrawBoxContext *s = inlink->dst->priv;
376 const AVDetectionBBoxHeader *header = NULL;
377 const AVDetectionBBox *bbox;
378 AVFrameSideData *sd;
379 int loop = 1;
380
381 if (s->box_source == AV_FRAME_DATA_DETECTION_BBOXES) {
382 sd = av_frame_get_side_data(frame, AV_FRAME_DATA_DETECTION_BBOXES);
383 if (sd) {
384 header = (AVDetectionBBoxHeader *)sd->data;
385 loop = header->nb_bboxes;
386 } else {
387 av_log(s, AV_LOG_WARNING, "No detection bboxes.\n");
388 return ff_filter_frame(inlink->dst->outputs[0], frame);
389 }
390 }
391
392 for (int i = 0; i < loop; i++) {
393 if (header) {
394 bbox = av_get_detection_bbox(header, i);
395 s->y = bbox->y;
396 s->x = bbox->x;
397 s->h = bbox->h;
398 s->w = bbox->w;
399 }
400
401 s->draw_region(frame, s, FFMAX(s->x, 0), FFMAX(s->y, 0), FFMIN(s->x + s->w, frame->width),
402 FFMIN(s->y + s->h, frame->height), pixel_belongs_to_box);
403 }
404
405 return ff_filter_frame(inlink->dst->outputs[0], frame);
406 }
407
process_command(AVFilterContext * ctx,const char * cmd,const char * args,char * res,int res_len,int flags)408 static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, char *res, int res_len, int flags)
409 {
410 AVFilterLink *inlink = ctx->inputs[0];
411 DrawBoxContext *s = ctx->priv;
412 int old_x = s->x;
413 int old_y = s->y;
414 int old_w = s->w;
415 int old_h = s->h;
416 int old_t = s->thickness;
417 int old_r = s->replace;
418 int ret;
419
420 ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags);
421 if (ret < 0)
422 return ret;
423
424 ret = init(ctx);
425 if (ret < 0)
426 goto end;
427 ret = config_input(inlink);
428 end:
429 if (ret < 0) {
430 s->x = old_x;
431 s->y = old_y;
432 s->w = old_w;
433 s->h = old_h;
434 s->thickness = old_t;
435 s->replace = old_r;
436 }
437
438 return ret;
439 }
440
441 #define OFFSET(x) offsetof(DrawBoxContext, x)
442 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
443
444 #if CONFIG_DRAWBOX_FILTER
445
446 static const AVOption drawbox_options[] = {
447 { "x", "set horizontal position of the left box edge", OFFSET(x_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
448 { "y", "set vertical position of the top box edge", OFFSET(y_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
449 { "width", "set width of the box", OFFSET(w_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
450 { "w", "set width of the box", OFFSET(w_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
451 { "height", "set height of the box", OFFSET(h_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
452 { "h", "set height of the box", OFFSET(h_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
453 { "color", "set color of the box", OFFSET(color_str), AV_OPT_TYPE_STRING, { .str = "black" }, 0, 0, FLAGS },
454 { "c", "set color of the box", OFFSET(color_str), AV_OPT_TYPE_STRING, { .str = "black" }, 0, 0, FLAGS },
455 { "thickness", "set the box thickness", OFFSET(t_expr), AV_OPT_TYPE_STRING, { .str="3" }, 0, 0, FLAGS },
456 { "t", "set the box thickness", OFFSET(t_expr), AV_OPT_TYPE_STRING, { .str="3" }, 0, 0, FLAGS },
457 { "replace", "replace color & alpha", OFFSET(replace), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, FLAGS },
458 { "box_source", "use datas from bounding box in side data", OFFSET(box_source_string), AV_OPT_TYPE_STRING, { .str=NULL }, 0, 1, FLAGS },
459 { NULL }
460 };
461
462 AVFILTER_DEFINE_CLASS(drawbox);
463
464 static const AVFilterPad drawbox_inputs[] = {
465 {
466 .name = "default",
467 .type = AVMEDIA_TYPE_VIDEO,
468 .flags = AVFILTERPAD_FLAG_NEEDS_WRITABLE,
469 .config_props = config_input,
470 .filter_frame = filter_frame,
471 },
472 };
473
474 static const AVFilterPad drawbox_outputs[] = {
475 {
476 .name = "default",
477 .type = AVMEDIA_TYPE_VIDEO,
478 },
479 };
480
481 const AVFilter ff_vf_drawbox = {
482 .name = "drawbox",
483 .description = NULL_IF_CONFIG_SMALL("Draw a colored box on the input video."),
484 .priv_size = sizeof(DrawBoxContext),
485 .priv_class = &drawbox_class,
486 .init = init,
487 FILTER_INPUTS(drawbox_inputs),
488 FILTER_OUTPUTS(drawbox_outputs),
489 FILTER_PIXFMTS_ARRAY(pix_fmts),
490 .process_command = process_command,
491 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
492 };
493 #endif /* CONFIG_DRAWBOX_FILTER */
494
495 #if CONFIG_DRAWGRID_FILTER
pixel_belongs_to_grid(DrawBoxContext * drawgrid,int x,int y)496 static av_pure av_always_inline int pixel_belongs_to_grid(DrawBoxContext *drawgrid, int x, int y)
497 {
498 // x is horizontal (width) coord,
499 // y is vertical (height) coord
500 int x_modulo;
501 int y_modulo;
502
503 // Abstract from the offset
504 x -= drawgrid->x;
505 y -= drawgrid->y;
506
507 x_modulo = x % drawgrid->w;
508 y_modulo = y % drawgrid->h;
509
510 // If x or y got negative, fix values to preserve logics
511 if (x_modulo < 0)
512 x_modulo += drawgrid->w;
513 if (y_modulo < 0)
514 y_modulo += drawgrid->h;
515
516 return x_modulo < drawgrid->thickness // Belongs to vertical line
517 || y_modulo < drawgrid->thickness; // Belongs to horizontal line
518 }
519
drawgrid_filter_frame(AVFilterLink * inlink,AVFrame * frame)520 static int drawgrid_filter_frame(AVFilterLink *inlink, AVFrame *frame)
521 {
522 DrawBoxContext *drawgrid = inlink->dst->priv;
523
524 drawgrid->draw_region(frame, drawgrid, 0, 0, frame->width, frame->height, pixel_belongs_to_grid);
525
526 return ff_filter_frame(inlink->dst->outputs[0], frame);
527 }
528
529 static const AVOption drawgrid_options[] = {
530 { "x", "set horizontal offset", OFFSET(x_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
531 { "y", "set vertical offset", OFFSET(y_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
532 { "width", "set width of grid cell", OFFSET(w_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
533 { "w", "set width of grid cell", OFFSET(w_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
534 { "height", "set height of grid cell", OFFSET(h_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
535 { "h", "set height of grid cell", OFFSET(h_expr), AV_OPT_TYPE_STRING, { .str="0" }, 0, 0, FLAGS },
536 { "color", "set color of the grid", OFFSET(color_str), AV_OPT_TYPE_STRING, { .str = "black" }, 0, 0, FLAGS },
537 { "c", "set color of the grid", OFFSET(color_str), AV_OPT_TYPE_STRING, { .str = "black" }, 0, 0, FLAGS },
538 { "thickness", "set grid line thickness", OFFSET(t_expr), AV_OPT_TYPE_STRING, {.str="1"}, 0, 0, FLAGS },
539 { "t", "set grid line thickness", OFFSET(t_expr), AV_OPT_TYPE_STRING, {.str="1"}, 0, 0, FLAGS },
540 { "replace", "replace color & alpha", OFFSET(replace), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, FLAGS },
541 { NULL }
542 };
543
544 AVFILTER_DEFINE_CLASS(drawgrid);
545
546 static const AVFilterPad drawgrid_inputs[] = {
547 {
548 .name = "default",
549 .type = AVMEDIA_TYPE_VIDEO,
550 .flags = AVFILTERPAD_FLAG_NEEDS_WRITABLE,
551 .config_props = config_input,
552 .filter_frame = drawgrid_filter_frame,
553 },
554 };
555
556 static const AVFilterPad drawgrid_outputs[] = {
557 {
558 .name = "default",
559 .type = AVMEDIA_TYPE_VIDEO,
560 },
561 };
562
563 const AVFilter ff_vf_drawgrid = {
564 .name = "drawgrid",
565 .description = NULL_IF_CONFIG_SMALL("Draw a colored grid on the input video."),
566 .priv_size = sizeof(DrawBoxContext),
567 .priv_class = &drawgrid_class,
568 .init = init,
569 FILTER_INPUTS(drawgrid_inputs),
570 FILTER_OUTPUTS(drawgrid_outputs),
571 FILTER_PIXFMTS_ARRAY(pix_fmts),
572 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
573 .process_command = process_command,
574 };
575
576 #endif /* CONFIG_DRAWGRID_FILTER */
577