[FFmpeg-devel] [PATCH] Add drawtext filter from the libavfilter soc repo.

Måns Rullgård mans
Sat Feb 19 16:21:13 CET 2011


Stefano Sabatini <stefano.sabatini-lala at poste.it> writes:

> Updated work in progress (missing docs update, and I want to check
> better to logic of the overlay).
> -- 
> FFmpeg = Freak & Faithless Mega Philosofic Enhancing Gigant
>
> From f0986d9a358ebc819619c0a30fc94ac97be90839 Mon Sep 17 00:00:00 2001
> From: Stefano Sabatini <stefano.sabatini-lala at poste.it>
> Date: Tue, 28 Sep 2010 16:09:27 +0200
> Subject: [PATCH] lavfi: add drawtext filter
>
> Port drawtext filter by Hemanth from the libavfilter soc repo, with
> the following additions:
> * support to generic load libfreetype options
> * support to anti-aliased glyph rendering
> * support to UTF-8 text and Unicode chars rendering
> ---
>  configure                 |    6 +
>  doc/filters.texi          |   81 ++++++
>  libavfilter/Makefile      |    1 +
>  libavfilter/allfilters.c  |    1 +
>  libavfilter/vf_drawtext.c |  609 +++++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 698 insertions(+), 0 deletions(-)
>  create mode 100644 libavfilter/vf_drawtext.c
>

[...]

> diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
> new file mode 100644
> index 0000000..1eec7dd
> --- /dev/null
> +++ b/libavfilter/vf_drawtext.c

[...]

> +static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
> +{
> +    uint32_t code;
> +    uint8_t *p;
> +    int err;
> +    int y_max, y_min;
> +    DrawTextContext *dtext = ctx->priv;
> +
> +    dtext->class = &drawtext_class;
> +    av_opt_set_defaults2(dtext, 0, 0);
> +    dtext->fgcolor_string = av_strdup("black");
> +    dtext->bgcolor_string = av_strdup("white");
> +
> +    if ((err = (av_set_options_string(dtext, args, "=", ":"))) < 0) {
> +        av_log(ctx, AV_LOG_ERROR, "Error parsing options string: '%s'\n", args);
> +        return err;
> +    }
> +
> +    if (!dtext->fontfile) {
> +        av_log(ctx, AV_LOG_ERROR,
> +               "No font filename provided\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    if (dtext->textfile) {
> +        uint8_t *textbuf;
> +        size_t textbuf_size;
> +
> +        if (dtext->text) {
> +            av_log(ctx, AV_LOG_ERROR,
> +                   "Both text and text file provided. Please provide only one\n");
> +            return AVERROR(EINVAL);
> +        }
> +        if ((err = av_file_map(dtext->textfile, &textbuf, &textbuf_size, 0, ctx)) < 0) {
> +            av_log(ctx, AV_LOG_ERROR,
> +                   "The text file '%s' could not be read or is empty\n",
> +                   dtext->textfile);
> +            return err;
> +        }
> +
> +        if (!(dtext->text = av_malloc(textbuf_size+1)))
> +            return AVERROR(ENOMEM);
> +        memcpy(dtext->text, textbuf, textbuf_size);
> +        dtext->text[textbuf_size] = 0;
> +        av_file_unmap(textbuf, textbuf_size);
> +    }
> +
> +    if (!dtext->text) {
> +        av_log(ctx, AV_LOG_ERROR,
> +               "Either text or a valid file must be provided\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    if ((err = get_yuv_color(dtext->fgcolor, dtext->fgcolor_string, ctx))) {
> +        av_log(ctx, AV_LOG_ERROR,
> +               "Invalid foreground color '%s'\n", dtext->fgcolor_string);
> +        return err;
> +    }
> +
> +    if ((err = get_yuv_color(dtext->bgcolor, dtext->bgcolor_string, ctx))) {
> +        av_log(ctx, AV_LOG_ERROR,
> +               "Invalid background color '%s'\n", dtext->fgcolor_string);
> +        return err;
> +    }
> +
> +    if ((err = FT_Init_FreeType(&(dtext->library)))) {
> +        av_log(ctx, AV_LOG_ERROR,
> +               "Could not load FreeType: %s\n", FT_ERRMSG(err));
> +        return AVERROR(EINVAL);
> +    }
> +
> +    /* load the face, and set up the encoding, which is by default UTF-8 */
> +    if ((err = FT_New_Face(dtext->library, dtext->fontfile, 0, &dtext->face))) {
> +        av_log(ctx, AV_LOG_ERROR, "Could not load fontface from file '%s': %s\n",
> +               dtext->fontfile, FT_ERRMSG(err));
> +        return AVERROR(EINVAL);
> +    }
> +    if ((err = FT_Set_Pixel_Sizes(dtext->face, 0, dtext->fontsize))) {
> +        av_log(ctx, AV_LOG_ERROR, "Could not set font size to %d pixels: %s\n",
> +               dtext->fontsize, FT_ERRMSG(err));
> +        return AVERROR(EINVAL);
> +    }
> +
> +    dtext->use_kerning = FT_HAS_KERNING(dtext->face);
> +
> +    /* load and cache glyphs */
> +    y_min =  32000;
> +    y_max = -32000;
> +
> +    /* load ASCII chars */
> +    for (code = 0; code < 256; code++)
> +        load_glyph(ctx, code, &y_min, &y_max);

ASCII ends at 127.  The remainder up to 255 is latin1 aka 8859-1.

> +    /* load all the other non-ASCII chars presented in the UTF-8 sequence */
> +    for (p = dtext->text; *p; ) {
> +        GET_UTF8(code, *p++, continue;);
> +        load_glyph(ctx, code, &y_min, &y_max);
> +    }

Why do you preload the entire latin1 character set?  I think it would be
better to load glyphs on demand as they are encountered while
rendering.  That would save memory and speed up lookup (since the tree
will be shallower).  Your current version will also fail if the strftime
expansion introduces non-latin1 characters not used elsewhere in the
format string.  On-demand loading would fix that as well.

> +    dtext->text_height = y_max - y_min;
> +    dtext->baseline    = y_max;
> +
> +#if !HAVE_LOCALTIME_R
> +    av_log(ctx, AV_LOG_WARNING, "strftime() expansion unavailable!\n");
> +#endif
> +
> +    return 0;
> +}
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    /* FIXME: Add support for other formats */
> +    enum PixelFormat pix_fmts[] = {

static const

> +        PIX_FMT_YUV420P, PIX_FMT_YUV444P, PIX_FMT_YUV422P,
> +        PIX_FMT_YUV411P, PIX_FMT_YUV410P,
> +        PIX_FMT_YUV440P, PIX_FMT_NONE
> +    };
> +
> +    avfilter_set_common_formats(ctx, avfilter_make_format_list(pix_fmts));
> +    return 0;
> +}
> +
> +static int glyph_enu_free(void *opaque, void *elem)
> +{
> +    av_free(elem);
> +    return 0;
> +}
> +
> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> +    DrawTextContext *dtext = ctx->priv;
> +    av_freep(&dtext->fontfile);
> +    av_freep(&dtext->text);
> +    av_freep(&dtext->fgcolor_string);
> +    av_freep(&dtext->bgcolor_string);
> +    av_tree_enumerate(dtext->glyphs, NULL, NULL, glyph_enu_free);
> +    av_tree_destroy(dtext->glyphs);
> +    dtext->glyphs = 0;
> +    FT_Done_Face(dtext->face);
> +    FT_Done_FreeType(dtext->library);
> +}

Do those FT functions handle null pointers?


[...]

> +static int draw_text(AVFilterContext *ctx, AVFilterBufferRef *picref,
> +                     int width, int height)
> +{
> +    DrawTextContext *dtext = ctx->priv;
> +    FT_Face face = dtext->face;
> +    char *text = dtext->text;
> +    uint32_t code = 0;
> +    int x = 0, y = 0, i = 0;
> +    uint8_t *p;
> +    int str_w, str_w_max;
> +    FT_Vector delta;
> +    Glyph *glyph = NULL, *prev_glyph = NULL;
> +    Glyph dummy = { 0 };
> +    Glyph *glyph0 = av_tree_find(dtext->glyphs, &dummy, (void *)glyph_cmp, NULL);
> +
> +#if HAVE_LOCALTIME_R
> +    time_t now = time(0);
> +    struct tm ltime;
> +    size_t expanded_text_len;
> +
> +    dtext->expanded_text[0] = '\1';
> +    expanded_text_len = strftime(dtext->expanded_text, MAX_EXPANDED_TEXT_SIZE,
> +                                 text, localtime_r(&now, &ltime));
> +
> +    if (expanded_text_len == 0 && dtext->expanded_text[0] != '\0') {

This isn't bullet-proof.  The buffer contents are unspecified if the
return value is zero.  It is safe insofar an unterminated string will
never escape, but it doesn't necessarily catch all errors.  Consider
this remark merely as such.

> +        av_log(ctx, AV_LOG_WARNING,
> +               "String expanded by strftime() is too big");
> +        return AVERROR(EINVAL);
> +    }
> +    text = dtext->expanded_text;
> +#endif
> +
> +    /* measure text size and save glyphs position */
> +    str_w = str_w_max = 0;
> +    x = dtext->x;
> +    y = dtext->y;
> +
> +    for (i = 0, p = text; *p; i++) {
> +        GET_UTF8(code, *p++, continue;);
> +
> +        /* get glyph */
> +        prev_glyph = glyph;
> +        dummy.code = code;
> +        glyph = av_tree_find(dtext->glyphs, &dummy, (void *)glyph_cmp, NULL);
> +        if (!glyph)
> +            glyph = glyph0;
> +
> +        /* kerning */
> +        if (dtext->use_kerning && prev_glyph && glyph->code) {
> +            FT_Get_Kerning(dtext->face, prev_glyph->code, glyph->code,
> +                           ft_kerning_default, &delta);
> +            x += delta.x >> 6;
> +        }
> +
> +        if (x + glyph->advance >= width || code == '\n' || code == '\r') {
> +            if (code != '\n' || code != '\r')
> +                str_w_max = width - dtext->x - 1;
> +            y += dtext->text_height;
> +            x = dtext->x;
> +        }

This will double-space windows-style lines.  You could probably get away
with ignoring \r and advancing the line on \n only.  A strict solution
that also works with old-style Mac (\r only) lines is more complex, but
we don't run on such systems anyway.

> +        /* save position */
> +        dtext->positions[i].x = x + glyph->bitmap_left;
> +        dtext->positions[i].y = y - glyph->bitmap_top + dtext->baseline;
> +        x     += glyph->advance;
> +        str_w += glyph->advance;
> +    }
> +    y += dtext->text_height;
> +    if (str_w_max == 0)
> +        str_w_max = str_w;
> +
> +    if (dtext->draw_box) {
> +        /* Check if it doesn't pass the limits */
> +        str_w_max = FFMIN(str_w_max, width - dtext->x - 1);
> +        y = FFMIN(y, height - 1);
> +
> +        /* draw background */
> +        drawbox(picref, dtext->x, dtext->y, str_w_max, y-dtext->y,
> +                dtext->bgcolor, dtext->hsub, dtext->vsub);
> +    }
> +
> +    /* draw glyphs */
> +    for (i = 0, p = text; *p; i++) {
> +        Glyph dummy = { 0 };
> +        GET_UTF8(code, *p++, continue;);
> +
> +        /* skip new line char, just go to new line */
> +        if (code == '\n' || code == '\r')
> +            continue;
> +
> +        dummy.code = code;
> +        glyph = av_tree_find(dtext->glyphs, &dummy, (void *)glyph_cmp, NULL);
> +        if (!glyph)
> +            glyph = glyph0;
> +
> +        draw_glyph(picref, &glyph->bitmap,
> +                   dtext->positions[i].x, dtext->positions[i].y, width, height,
> +                   dtext->fgcolor, dtext->bgcolor, dtext->outline,
> +                   dtext->hsub, dtext->vsub);
> +
> +        /* increment pen position */
> +        x += face->glyph->advance.x >> 6;
> +    }

You might want to do something clever with the tab character.  I don't
really have a good suggestion though.

> +    return 0;
> +}

-- 
M?ns Rullg?rd
mans at mansr.com



More information about the ffmpeg-devel mailing list