Browse code

drawtext: make x and y parametric

Introduce variables "E", "PHI", "PI", "main_w"/"W", "main_h"/"H",
"text_w"/"w", "text_h"/"h", "x", "y", "n" and "t" in line with
vf_overlay and refactor the code accordingly.

Luca Barbato authored on 2011/12/01 19:43:11
Showing 2 changed files
... ...
@@ -505,6 +505,32 @@ If both text and textfile are specified, an error is thrown.
505 505
 @item x, y
506 506
 The offsets where text will be drawn within the video frame.
507 507
 Relative to the top/left border of the output image.
508
+They accept expressions similar to the @ref{overlay} filter:
509
+@table @option
510
+
511
+@item x, y
512
+the computed values for @var{x} and @var{y}. They are evaluated for
513
+each new frame.
514
+
515
+@item main_w, main_h
516
+main input width and height
517
+
518
+@item W, H
519
+same as @var{main_w} and @var{main_h}
520
+
521
+@item text_w, text_h
522
+rendered text width and height
523
+
524
+@item w, h
525
+same as @var{text_w} and @var{text_h}
526
+
527
+@item n
528
+the number of frames processed, starting from 0
529
+
530
+@item t
531
+timestamp expressed in seconds, NAN if the input timestamp is unknown
532
+
533
+@end table
508 534
 
509 535
 The default value of @var{x} and @var{y} is 0.
510 536
 
... ...
@@ -1048,6 +1074,7 @@ other parameters is 0.
1048 1048
 These parameters correspond to the parameters assigned to the
1049 1049
 libopencv function @code{cvSmooth}.
1050 1050
 
1051
+@anchor{overlay}
1051 1052
 @section overlay
1052 1053
 
1053 1054
 Overlay one video on top of another.
... ...
@@ -31,7 +31,9 @@
31 31
 
32 32
 #include "libavutil/colorspace.h"
33 33
 #include "libavutil/file.h"
34
+#include "libavutil/eval.h"
34 35
 #include "libavutil/opt.h"
36
+#include "libavutil/mathematics.h"
35 37
 #include "libavutil/parseutils.h"
36 38
 #include "libavutil/pixdesc.h"
37 39
 #include "libavutil/tree.h"
... ...
@@ -45,6 +47,36 @@
45 45
 #include FT_FREETYPE_H
46 46
 #include FT_GLYPH_H
47 47
 
48
+static const char *var_names[] = {
49
+    "E",
50
+    "PHI",
51
+    "PI",
52
+    "main_w",    "W", ///< width  of the main    video
53
+    "main_h",    "H", ///< height of the main    video
54
+    "text_w",    "w", ///< width  of the overlay text
55
+    "text_h",    "h", ///< height of the overlay text
56
+    "x",
57
+    "y",
58
+    "n",              ///< number of processed frames
59
+    "t",              ///< timestamp expressed in seconds
60
+    NULL
61
+};
62
+
63
+enum var_name {
64
+    VAR_E,
65
+    VAR_PHI,
66
+    VAR_PI,
67
+    VAR_MAIN_W, VAR_MW,
68
+    VAR_MAIN_H, VAR_MH,
69
+    VAR_TEXT_W, VAR_TW,
70
+    VAR_TEXT_H, VAR_TH,
71
+    VAR_X,
72
+    VAR_Y,
73
+    VAR_N,
74
+    VAR_T,
75
+    VAR_VARS_NB
76
+};
77
+
48 78
 typedef struct {
49 79
     const AVClass *class;
50 80
     uint8_t *fontfile;              ///< font to be used
... ...
@@ -81,6 +113,10 @@ typedef struct {
81 81
     int pixel_step[4];              ///< distance in bytes between the component of each pixel
82 82
     uint8_t rgba_map[4];            ///< map RGBA offsets to the positions in the packed RGBA format
83 83
     uint8_t *box_line[4];           ///< line used for filling the box background
84
+    char   *x_expr, *y_expr;
85
+    AVExpr *x_pexpr, *y_pexpr;      ///< parsed expressions for x and y
86
+    double var_values[VAR_VARS_NB];
87
+    int draw;                       ///< set to zero to prevent drawing
84 88
 } DrawTextContext;
85 89
 
86 90
 #define OFFSET(x) offsetof(DrawTextContext, x)
... ...
@@ -94,8 +130,8 @@ static const AVOption drawtext_options[]= {
94 94
 {"shadowcolor", "set shadow color",  OFFSET(shadowcolor_string), AV_OPT_TYPE_STRING, {.str=NULL},  CHAR_MIN, CHAR_MAX },
95 95
 {"box",      "set box",              OFFSET(draw_box),           AV_OPT_TYPE_INT,    {.dbl=0},     0,        1        },
96 96
 {"fontsize", "set font size",        OFFSET(fontsize),           AV_OPT_TYPE_INT,    {.dbl=16},    1,        72       },
97
-{"x",        "set x",                OFFSET(x),                  AV_OPT_TYPE_INT,    {.dbl=0},     0,        INT_MAX  },
98
-{"y",        "set y",                OFFSET(y),                  AV_OPT_TYPE_INT,    {.dbl=0},     0,        INT_MAX  },
97
+{"x",        "set x",                OFFSET(x_expr),             AV_OPT_TYPE_STRING, {.str="0"},   CHAR_MIN, CHAR_MAX },
98
+{"y",        "set y",                OFFSET(y_expr),             AV_OPT_TYPE_STRING, {.str="0"},   CHAR_MIN, CHAR_MAX },
99 99
 {"shadowx",  "set x",                OFFSET(shadowx),            AV_OPT_TYPE_INT,    {.dbl=0},     INT_MIN,  INT_MAX  },
100 100
 {"shadowy",  "set y",                OFFSET(shadowy),            AV_OPT_TYPE_INT,    {.dbl=0},     INT_MIN,  INT_MAX  },
101 101
 {"tabsize",  "set tab size",         OFFSET(tabsize),            AV_OPT_TYPE_INT,    {.dbl=4},     0,        INT_MAX  },
... ...
@@ -374,7 +410,7 @@ static inline int is_newline(uint32_t c)
374 374
     return (c == '\n' || c == '\r' || c == '\f' || c == '\v');
375 375
 }
376 376
 
377
-static int dtext_prepare_text(AVFilterContext *ctx, int width, int height)
377
+static int dtext_prepare_text(AVFilterContext *ctx)
378 378
 {
379 379
     DrawTextContext *dtext = ctx->priv;
380 380
     uint32_t code = 0, prev_code = 0;
... ...
@@ -387,6 +423,8 @@ static int dtext_prepare_text(AVFilterContext *ctx, int width, int height)
387 387
     FT_Vector delta;
388 388
     Glyph *glyph = NULL, *prev_glyph = NULL;
389 389
     Glyph dummy = { 0 };
390
+    int width  = ctx->inputs[0]->w;
391
+    int height = ctx->inputs[0]->h;
390 392
 
391 393
 #if HAVE_LOCALTIME_R
392 394
     time_t now = time(0);
... ...
@@ -504,6 +542,27 @@ static int config_input(AVFilterLink *inlink)
504 504
     dtext->hsub = pix_desc->log2_chroma_w;
505 505
     dtext->vsub = pix_desc->log2_chroma_h;
506 506
 
507
+    dtext->var_values[VAR_E  ] = M_E;
508
+    dtext->var_values[VAR_PHI] = M_PHI;
509
+    dtext->var_values[VAR_PI ] = M_PI;
510
+
511
+    dtext->var_values[VAR_MAIN_W] =
512
+        dtext->var_values[VAR_MW] = ctx->inputs[0]->w;
513
+    dtext->var_values[VAR_MAIN_H] =
514
+        dtext->var_values[VAR_MH] = ctx->inputs[0]->h;
515
+
516
+    dtext->var_values[VAR_X] = 0;
517
+    dtext->var_values[VAR_Y] = 0;
518
+    dtext->var_values[VAR_N] = 0;
519
+    dtext->var_values[VAR_T] = NAN;
520
+
521
+
522
+    if ((ret = av_expr_parse(&dtext->x_pexpr, dtext->x_expr, var_names,
523
+                             NULL, NULL, NULL, NULL, 0, ctx)) < 0 ||
524
+        (ret = av_expr_parse(&dtext->y_pexpr, dtext->y_expr, var_names,
525
+                             NULL, NULL, NULL, NULL, 0, ctx)) < 0)
526
+        return AVERROR(EINVAL);
527
+
507 528
     if ((ret =
508 529
          ff_fill_line_with_color(dtext->box_line, dtext->pixel_step,
509 530
                                  inlink->w, dtext->boxcolor,
... ...
@@ -524,7 +583,9 @@ static int config_input(AVFilterLink *inlink)
524 524
         dtext->shadowcolor[3] = rgba[3];
525 525
     }
526 526
 
527
-    return dtext_prepare_text(ctx, ctx->inputs[0]->w, ctx->inputs[0]->h);
527
+    dtext->draw = 1;
528
+
529
+    return dtext_prepare_text(ctx);
528 530
 }
529 531
 
530 532
 #define GET_BITMAP_VAL(r, c)                                            \
... ...
@@ -697,15 +758,71 @@ static int draw_text(AVFilterContext *ctx, AVFilterBufferRef *picref,
697 697
 
698 698
 static void null_draw_slice(AVFilterLink *link, int y, int h, int slice_dir) { }
699 699
 
700
+static inline int normalize_double(int *n, double d)
701
+{
702
+    int ret = 0;
703
+
704
+    if (isnan(d)) {
705
+        ret = AVERROR(EINVAL);
706
+    } else if (d > INT_MAX || d < INT_MIN) {
707
+        *n = d > INT_MAX ? INT_MAX : INT_MIN;
708
+        ret = AVERROR(EINVAL);
709
+    } else
710
+        *n = round(d);
711
+
712
+    return ret;
713
+}
714
+
715
+static void start_frame(AVFilterLink *inlink, AVFilterBufferRef *inpicref)
716
+{
717
+    AVFilterContext *ctx = inlink->dst;
718
+    DrawTextContext *dtext = ctx->priv;
719
+
720
+    if (dtext_prepare_text(ctx) < 0) {
721
+        av_log(ctx, AV_LOG_ERROR, "Can't draw text\n");
722
+        dtext->draw = 0;
723
+    }
724
+
725
+    dtext->var_values[VAR_T] = inpicref->pts == AV_NOPTS_VALUE ?
726
+        NAN : inpicref->pts * av_q2d(inlink->time_base);
727
+    dtext->var_values[VAR_X] =
728
+        av_expr_eval(dtext->x_pexpr, dtext->var_values, NULL);
729
+    dtext->var_values[VAR_Y] =
730
+        av_expr_eval(dtext->y_pexpr, dtext->var_values, NULL);
731
+    dtext->var_values[VAR_X] =
732
+        av_expr_eval(dtext->x_pexpr, dtext->var_values, NULL);
733
+
734
+    normalize_double(&dtext->x, dtext->var_values[VAR_X]);
735
+    normalize_double(&dtext->y, dtext->var_values[VAR_Y]);
736
+
737
+    if (dtext->x < 0) dtext->x = 0;
738
+    if (dtext->y < 0) dtext->y = 0;
739
+    if ((unsigned)dtext->x + (unsigned)dtext->w > inlink->w)
740
+        dtext->x = inlink->w - dtext->w;
741
+    if ((unsigned)dtext->y + (unsigned)dtext->h > inlink->h)
742
+        dtext->y = inlink->h - dtext->h;
743
+
744
+    dtext->x &= ~((1 << dtext->hsub) - 1);
745
+    dtext->y &= ~((1 << dtext->vsub) - 1);
746
+
747
+    av_dlog(ctx, "n:%d t:%f x:%d y:%d x+w:%d y+h:%d\n",
748
+            (int)dtext->var_values[VAR_N], dtext->var_values[VAR_T],
749
+            dtext->x, dtext->y, dtext->x+dtext->w, dtext->y+dtext->h);
750
+
751
+    avfilter_start_frame(inlink->dst->outputs[0], inpicref);
752
+}
753
+
700 754
 static void end_frame(AVFilterLink *inlink)
701 755
 {
702 756
     AVFilterLink *outlink = inlink->dst->outputs[0];
703 757
     AVFilterBufferRef *picref = inlink->cur_buf;
704
-    int err = dtext_prepare_text(inlink->dst,
705
-                                 picref->video->w, picref->video->h);
706
-    if (!err)
758
+    DrawTextContext *dtext = inlink->dst->priv;
759
+
760
+    if (dtext->draw)
707 761
         draw_text(inlink->dst, picref, picref->video->w, picref->video->h);
708 762
 
763
+    dtext->var_values[VAR_N] += 1.0;
764
+
709 765
     avfilter_draw_slice(outlink, 0, picref->video->h, 1);
710 766
     avfilter_end_frame(outlink);
711 767
 }
... ...
@@ -721,7 +838,7 @@ AVFilter avfilter_vf_drawtext = {
721 721
     .inputs    = (AVFilterPad[]) {{ .name             = "default",
722 722
                                     .type             = AVMEDIA_TYPE_VIDEO,
723 723
                                     .get_video_buffer = avfilter_null_get_video_buffer,
724
-                                    .start_frame      = avfilter_null_start_frame,
724
+                                    .start_frame      = start_frame,
725 725
                                     .draw_slice       = null_draw_slice,
726 726
                                     .end_frame        = end_frame,
727 727
                                     .config_props     = config_input,