Browse Source

avfilter/drawtext: Add basic text shaping using libfribidi

Fixes ticket #3758

Reviewed-by: Andrey Utkin <andrey.krieger.utkin@gmail.com>
Signed-off-by: Michael Niedermayer <michaelni@gmx.at>
tags/n2.3
Marc Jeffreys Michael Niedermayer 11 years ago
parent
commit
a0b71e9f3e
6 changed files with 134 additions and 7 deletions
  1. +1
    -0
      Changelog
  2. +1
    -0
      RELEASE_NOTES
  3. +3
    -0
      configure
  4. +11
    -0
      doc/filters.texi
  5. +1
    -1
      libavfilter/version.h
  6. +117
    -6
      libavfilter/vf_drawtext.c

+ 1
- 0
Changelog View File

@@ -35,6 +35,7 @@ version <next>:
- LRC demuxer and muxer
- Samba protocol (via libsmbclient)
- WebM DASH Manifest muxer
- libfribidi support in drawtext


version 2.2:


+ 1
- 0
RELEASE_NOTES View File

@@ -164,6 +164,7 @@
• signalstats filter
• hqx filter (hq2x, hq3x, hq4x)
• flanger filter
• libfribidi support in drawtext

┌────────────────────────────┐
│ ⚠ Behaviour changes │


+ 3
- 0
configure View File

@@ -209,6 +209,7 @@ External library support:
--enable-libfdk-aac enable AAC de/encoding via libfdk-aac [no]
--enable-libflite enable flite (voice synthesis) support via libflite [no]
--enable-libfreetype enable libfreetype [no]
--enable-libfribidi enable libfribidi [no]
--enable-libgme enable Game Music Emu via libgme [no]
--enable-libgsm enable GSM de/encoding via libgsm [no]
--enable-libiec61883 enable iec61883 via libiec61883 [no]
@@ -1333,6 +1334,7 @@ EXTERNAL_LIBRARY_LIST="
libflite
libfontconfig
libfreetype
libfribidi
libgme
libgsm
libiec61883
@@ -4729,6 +4731,7 @@ enabled libflite && require2 libflite "flite/flite.h" flite_init $flite
enabled fontconfig && enable libfontconfig
enabled libfontconfig && require_pkg_config fontconfig "fontconfig/fontconfig.h" FcInit
enabled libfreetype && require_libfreetype
enabled libfribidi && require_pkg_config fribidi fribidi.h fribidi_version_info
enabled libgme && require libgme gme/gme.h gme_new_emu -lgme -lstdc++
enabled libgsm && { for gsm_hdr in "gsm.h" "gsm/gsm.h"; do
check_lib "${gsm_hdr}" gsm_create -lgsm && break;


+ 11
- 0
doc/filters.texi View File

@@ -3653,6 +3653,8 @@ To enable compilation of this filter, you need to configure FFmpeg with
@code{--enable-libfreetype}.
To enable default font fallback and the @var{font} option you need to
configure FFmpeg with @code{--enable-libfontconfig}.
To enable the @var{text_shaping} option, you need to configure FFmpeg with
@code{--enable-libfribidi}.

@subsection Syntax

@@ -3707,6 +3709,12 @@ This parameter is mandatory if the fontconfig support is disabled.
The font size to be used for drawing text.
The default value of @var{fontsize} is 16.

@item text_shaping
If set to 1, attempt to shape the text (for example, reverse the order of
right-to-left text and join Arabic characters) before drawing it.
Otherwise, just draw the text exactly as given.
By default 1 (if supported).

@item ft_load_flags
The flags to be used for loading the fonts.

@@ -4010,6 +4018,9 @@ For more information about libfreetype, check:
For more information about fontconfig, check:
@url{http://freedesktop.org/software/fontconfig/fontconfig-user.html}.

For more information about libfribidi, check:
@url{http://fribidi.org/}.

@section edgedetect

Detect and draw edges. The filter uses the Canny Edge Detection algorithm.


+ 1
- 1
libavfilter/version.h View File

@@ -30,7 +30,7 @@
#include "libavutil/version.h"

#define LIBAVFILTER_VERSION_MAJOR 4
#define LIBAVFILTER_VERSION_MINOR 10
#define LIBAVFILTER_VERSION_MINOR 11
#define LIBAVFILTER_VERSION_MICRO 100

#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \


+ 117
- 6
libavfilter/vf_drawtext.c View File

@@ -59,6 +59,10 @@
#include "internal.h"
#include "video.h"

#if CONFIG_LIBFRIBIDI
#include <fribidi.h>
#endif

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
@@ -182,6 +186,9 @@ typedef struct DrawTextContext {
int tc24hmax; ///< 1 if timecode is wrapped to 24 hours, 0 otherwise
int reload; ///< reload text file for each frame
int start_number; ///< starting frame number for n/frame_num var
#if CONFIG_LIBFRIBIDI
int text_shaping; ///< 1 to shape the text before drawing it
#endif
AVDictionary *metadata;
} DrawTextContext;

@@ -226,6 +233,10 @@ static const AVOption drawtext_options[]= {
{"fix_bounds", "if true, check and fix text coords to avoid clipping", OFFSET(fix_bounds), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS},
{"start_number", "start frame number for n/frame_num variable", OFFSET(start_number), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS},

#if CONFIG_LIBFRIBIDI
{"text_shaping", "attempt to shape text before drawing", OFFSET(text_shaping), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS},
#endif

/* FT_LOAD_* flags */
{ "ft_load_flags", "set font loading flags for libfreetype", OFFSET(ft_load_flags), AV_OPT_TYPE_FLAGS, { .i64 = FT_LOAD_DEFAULT }, 0, INT_MAX, FLAGS, "ft_load_flags" },
{ "default", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = FT_LOAD_DEFAULT }, .flags = FLAGS, .unit = "ft_load_flags" },
@@ -482,6 +493,99 @@ static int load_textfile(AVFilterContext *ctx)
return 0;
}

static inline int is_newline(uint32_t c)
{
return c == '\n' || c == '\r' || c == '\f' || c == '\v';
}

#if CONFIG_LIBFRIBIDI
static int shape_text(AVFilterContext *ctx)
{
DrawTextContext *s = ctx->priv;
uint8_t *tmp;
int ret = AVERROR(ENOMEM);
static const FriBidiFlags flags = FRIBIDI_FLAGS_DEFAULT |
FRIBIDI_FLAGS_ARABIC;
FriBidiChar *unicodestr = NULL;
FriBidiStrIndex len;
FriBidiParType direction = FRIBIDI_PAR_LTR;
FriBidiStrIndex line_start = 0;
FriBidiStrIndex line_end = 0;
FriBidiLevel *embedding_levels = NULL;
FriBidiArabicProp *ar_props = NULL;
FriBidiCharType *bidi_types = NULL;
FriBidiStrIndex i,j;

len = strlen(s->text);
if (!(unicodestr = av_malloc_array(len, sizeof(*unicodestr)))) {
goto out;
}
len = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8,
s->text, len, unicodestr);

bidi_types = av_malloc_array(len, sizeof(*bidi_types));
if (!bidi_types) {
goto out;
}

fribidi_get_bidi_types(unicodestr, len, bidi_types);

embedding_levels = av_malloc_array(len, sizeof(*embedding_levels));
if (!embedding_levels) {
goto out;
}

if (!fribidi_get_par_embedding_levels(bidi_types, len, &direction,
embedding_levels)) {
goto out;
}

ar_props = av_malloc_array(len, sizeof(*ar_props));
if (!ar_props) {
goto out;
}

fribidi_get_joining_types(unicodestr, len, ar_props);
fribidi_join_arabic(bidi_types, len, embedding_levels, ar_props);
fribidi_shape(flags, embedding_levels, len, ar_props, unicodestr);

for (line_end = 0, line_start = 0; line_end < len; line_end++) {
if (is_newline(unicodestr[line_end]) || line_end == len - 1) {
if (!fribidi_reorder_line(flags, bidi_types,
line_end - line_start + 1, line_start,
direction, embedding_levels, unicodestr,
NULL)) {
goto out;
}
line_start = line_end + 1;
}
}

/* Remove zero-width fill chars put in by libfribidi */
for (i = 0, j = 0; i < len; i++)
if (unicodestr[i] != FRIBIDI_CHAR_FILL)
unicodestr[j++] = unicodestr[i];
len = j;

if (!(tmp = av_realloc(s->text, (len * 4 + 1) * sizeof(*s->text)))) {
/* Use len * 4, as a unicode character can be up to 4 bytes in UTF-8 */
goto out;
}

s->text = tmp;
len = fribidi_unicode_to_charset(FRIBIDI_CHAR_SET_UTF8,
unicodestr, len, s->text);
ret = 0;

out:
av_free(unicodestr);
av_free(embedding_levels);
av_free(ar_props);
av_free(bidi_types);
return ret;
}
#endif

static av_cold int init(AVFilterContext *ctx)
{
int err;
@@ -509,6 +613,12 @@ static av_cold int init(AVFilterContext *ctx)
return err;
}

#if CONFIG_LIBFRIBIDI
if (s->text_shaping)
if ((err = shape_text(ctx)) < 0)
return err;
#endif

if (s->reload && !s->textfile)
av_log(ctx, AV_LOG_WARNING, "No file to reload\n");

@@ -617,11 +727,6 @@ static av_cold void uninit(AVFilterContext *ctx)
av_bprint_finalize(&s->expanded_text, NULL);
}

static inline int is_newline(uint32_t c)
{
return c == '\n' || c == '\r' || c == '\f' || c == '\v';
}

static int config_input(AVFilterLink *inlink)
{
AVFilterContext *ctx = inlink->dst;
@@ -1132,9 +1237,15 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
DrawTextContext *s = ctx->priv;
int ret;

if (s->reload)
if (s->reload) {
if ((ret = load_textfile(ctx)) < 0)
return ret;
#if CONFIG_LIBFRIBIDI
if (s->text_shaping)
if ((ret = shape_text(ctx)) < 0)
return ret;
#endif
}

s->var_values[VAR_N] = inlink->frame_count+s->start_number;
s->var_values[VAR_T] = frame->pts == AV_NOPTS_VALUE ?


Loading…
Cancel
Save