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 39static 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 NULL 51}; 52 53enum { Y, U, V, A }; 54 55enum var_name { 56 VAR_DAR, 57 VAR_HSUB, VAR_VSUB, 58 VAR_IN_H, VAR_IH, 59 VAR_IN_W, VAR_IW, 60 VAR_SAR, 61 VAR_X, 62 VAR_Y, 63 VAR_H, 64 VAR_W, 65 VAR_T, 66 VARS_NB 67}; 68 69typedef struct DrawBoxContext { 70 const AVClass *class; 71 int x, y, w, h; 72 int thickness; 73 char *color_str; 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 80} DrawBoxContext; 81 82static const int NUM_EXPR_EVALS = 5; 83 84static av_cold int init(AVFilterContext *ctx) 85{ 86 DrawBoxContext *s = ctx->priv; 87 uint8_t rgba_color[4]; 88 89 if (!strcmp(s->color_str, "invert")) 90 s->invert_color = 1; 91 else if (av_parse_color(rgba_color, s->color_str, -1, ctx) < 0) 92 return AVERROR(EINVAL); 93 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]; 99 } 100 101 return 0; 102} 103 104static int query_formats(AVFilterContext *ctx) 105{ 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, 111 AV_PIX_FMT_NONE 112 }; 113 114 ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); 115 return 0; 116} 117 118static int config_input(AVFilterLink *inlink) 119{ 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; 124 char *expr; 125 int ret; 126 int i; 127 128 s->hsub = desc->log2_chroma_w; 129 s->vsub = desc->log2_chroma_h; 130 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; 142 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) 148 goto fail; 149 s->x = var_values[VAR_X] = res; 150 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) 154 goto fail; 155 s->y = var_values[VAR_Y] = res; 156 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) 160 goto fail; 161 s->w = var_values[VAR_W] = res; 162 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) 166 goto fail; 167 s->h = var_values[VAR_H] = res; 168 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) 172 goto fail; 173 s->thickness = var_values[VAR_T] = res; 174 } 175 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; 179 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); 184 } 185 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]); 189 190 return 0; 191 192fail: 193 av_log(ctx, AV_LOG_ERROR, 194 "Error when evaluating the expression '%s'.\n", 195 expr); 196 return ret; 197} 198 199static int filter_frame(AVFilterLink *inlink, AVFrame *frame) 200{ 201 DrawBoxContext *s = inlink->dst->priv; 202 int plane, x, y, xb = s->x, yb = s->y; 203 unsigned char *row[4]; 204 205 for (y = FFMAX(yb, 0); y < frame->height && y < (yb + s->h); y++) { 206 row[0] = frame->data[0] + y * frame->linesize[0]; 207 208 for (plane = 1; plane < 3; plane++) 209 row[plane] = frame->data[plane] + 210 frame->linesize[plane] * (y >> s->vsub); 211 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]; 217 } else { 218 for (x = FFMAX(xb, 0); x < xb + s->w && x < frame->width; x++) { 219 double alpha = (double)s->yuv_color[A] / 255; 220 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]; 226 } 227 } 228 } 229 } 230 231 return ff_filter_frame(inlink->dst->outputs[0], frame); 232} 233 234#define OFFSET(x) offsetof(DrawBoxContext, x) 235#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM 236 237#if CONFIG_DRAWBOX_FILTER 238 239static 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 }, 250 { NULL } 251}; 252 253AVFILTER_DEFINE_CLASS(drawbox); 254 255static const AVFilterPad drawbox_inputs[] = { 256 { 257 .name = "default", 258 .type = AVMEDIA_TYPE_VIDEO, 259 .config_props = config_input, 260 .filter_frame = filter_frame, 261 .needs_writable = 1, 262 }, 263 { NULL } 264}; 265 266static const AVFilterPad drawbox_outputs[] = { 267 { 268 .name = "default", 269 .type = AVMEDIA_TYPE_VIDEO, 270 }, 271 { NULL } 272}; 273 274AVFilter ff_vf_drawbox = { 275 .name = "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, 279 .init = init, 280 .query_formats = query_formats, 281 .inputs = drawbox_inputs, 282 .outputs = drawbox_outputs, 283 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, 284}; 285#endif /* CONFIG_DRAWBOX_FILTER */ 286 287#if CONFIG_DRAWGRID_FILTER 288static av_pure av_always_inline int pixel_belongs_to_grid(DrawBoxContext *drawgrid, int x, int y) 289{ 290 // x is horizontal (width) coord, 291 // y is vertical (height) coord 292 int x_modulo; 293 int y_modulo; 294 295 // Abstract from the offset 296 x -= drawgrid->x; 297 y -= drawgrid->y; 298 299 x_modulo = x % drawgrid->w; 300 y_modulo = y % drawgrid->h; 301 302 // If x or y got negative, fix values to preserve logics 303 if (x_modulo < 0) 304 x_modulo += drawgrid->w; 305 if (y_modulo < 0) 306 y_modulo += drawgrid->h; 307 308 return x_modulo < drawgrid->thickness // Belongs to vertical line 309 || y_modulo < drawgrid->thickness; // Belongs to horizontal line 310} 311 312static int drawgrid_filter_frame(AVFilterLink *inlink, AVFrame *frame) 313{ 314 DrawBoxContext *drawgrid = inlink->dst->priv; 315 int plane, x, y; 316 uint8_t *row[4]; 317 318 for (y = 0; y < frame->height; y++) { 319 row[0] = frame->data[0] + y * frame->linesize[0]; 320 321 for (plane = 1; plane < 3; plane++) 322 row[plane] = frame->data[plane] + 323 frame->linesize[plane] * (y >> drawgrid->vsub); 324 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]; 329 } else { 330 for (x = 0; x < frame->width; x++) { 331 double alpha = (double)drawgrid->yuv_color[A] / 255; 332 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]; 337 } 338 } 339 } 340 } 341 342 return ff_filter_frame(inlink->dst->outputs[0], frame); 343} 344 345static 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 }, 356 { NULL } 357}; 358 359AVFILTER_DEFINE_CLASS(drawgrid); 360 361static const AVFilterPad drawgrid_inputs[] = { 362 { 363 .name = "default", 364 .type = AVMEDIA_TYPE_VIDEO, 365 .config_props = config_input, 366 .filter_frame = drawgrid_filter_frame, 367 .needs_writable = 1, 368 }, 369 { NULL } 370}; 371 372static const AVFilterPad drawgrid_outputs[] = { 373 { 374 .name = "default", 375 .type = AVMEDIA_TYPE_VIDEO, 376 }, 377 { NULL } 378}; 379 380AVFilter ff_vf_drawgrid = { 381 .name = "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, 385 .init = init, 386 .query_formats = query_formats, 387 .inputs = drawgrid_inputs, 388 .outputs = drawgrid_outputs, 389 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, 390}; 391 392#endif /* CONFIG_DRAWGRID_FILTER */ 393