History

avfilter/vf_drawtext: improve glyph shaping and positioning

- text is now shaped using libharfbuz - glyphs position is now accurate to 1/4 pixel in both directions - the default line height is now the one defined in the font Adds libharfbuzz dependency.

yethie committed 3 years ago

Commit 1eeb59a1 parent ba00ed33 files · +592 −280Browse files

Unified · Split

3 files
  1. configure +4 −1
  2. doc/filters.texi +5 −3
  3. libavfilter/vf_drawtext.c +583 −276
configure+4−1
@@ -236,6 +236,7 @@ External library support: · 235 unmodified lines
236 --enable-libfontconfig enable libfontconfig, useful for drawtext filter [no]236 --enable-libfontconfig enable libfontconfig, useful for drawtext filter [no]
237 --enable-libfreetype enable libfreetype, needed for drawtext filter [no]237 --enable-libfreetype enable libfreetype, needed for drawtext filter [no]
238 --enable-libfribidi enable libfribidi, improves drawtext filter [no]238 --enable-libfribidi enable libfribidi, improves drawtext filter [no]
239 --enable-libharfbuzz enable libharfbuzz, needed for drawtext filter [no]
239 --enable-libglslang enable GLSL->SPIRV compilation via libglslang [no]240 --enable-libglslang enable GLSL->SPIRV compilation via libglslang [no]
240 --enable-libgme enable Game Music Emu via libgme [no]241 --enable-libgme enable Game Music Emu via libgme [no]
241 --enable-libgsm enable GSM de/encoding via libgsm [no]242 --enable-libgsm enable GSM de/encoding via libgsm [no]
@@ -1859,6 +1860,7 @@ EXTERNAL_LIBRARY_LIST=" · 1617 unmodified lines
1859 libfontconfig1860 libfontconfig
1860 libfreetype1861 libfreetype
1861 libfribidi1862 libfribidi
1863 libharfbuzz
1862 libglslang1864 libglslang
1863 libgme1865 libgme
1864 libgsm1866 libgsm
@@ -3726,7 +3728,7 @@ dilation_opencl_filter_deps="opencl" · 1861 unmodified lines
3726dnn_classify_filter_select="dnn"3728dnn_classify_filter_select="dnn"
3727dnn_detect_filter_select="dnn"3729dnn_detect_filter_select="dnn"
3728dnn_processing_filter_select="dnn"3730dnn_processing_filter_select="dnn"
3729drawtext_filter_deps="libfreetype"3731drawtext_filter_deps="libfreetype libharfbuzz"
3730drawtext_filter_suggest="libfontconfig libfribidi"3732drawtext_filter_suggest="libfontconfig libfribidi"
3731elbg_filter_deps="avcodec"3733elbg_filter_deps="avcodec"
3732eq_filter_deps="gpl"3734eq_filter_deps="gpl"
@@ -6693,6 +6695,7 @@ enabled fontconfig && enable libfontconfig · 2960 unmodified lines
6693enabled libfontconfig && require_pkg_config libfontconfig fontconfig "fontconfig/fontconfig.h" FcInit6695enabled libfontconfig && require_pkg_config libfontconfig fontconfig "fontconfig/fontconfig.h" FcInit
6694enabled libfreetype && require_pkg_config libfreetype freetype2 "ft2build.h FT_FREETYPE_H" FT_Init_FreeType6696enabled libfreetype && require_pkg_config libfreetype freetype2 "ft2build.h FT_FREETYPE_H" FT_Init_FreeType
6695enabled libfribidi && require_pkg_config libfribidi fribidi fribidi.h fribidi_version_info6697enabled libfribidi && require_pkg_config libfribidi fribidi fribidi.h fribidi_version_info
6698enabled libharfbuzz && require_pkg_config libharfbuzz harfbuzz hb.h hb_buffer_create
6696enabled libglslang && { check_lib spirv_compiler glslang/Include/glslang_c_interface.h glslang_initialize_process \6699enabled libglslang && { check_lib spirv_compiler glslang/Include/glslang_c_interface.h glslang_initialize_process \
6697 -lglslang -lMachineIndependent -lOSDependent -lHLSL -lOGLCompiler -lGenericCodeGen \6700 -lglslang -lMachineIndependent -lOSDependent -lHLSL -lOGLCompiler -lGenericCodeGen \
6698 -lSPVRemapper -lSPIRV -lSPIRV-Tools-opt -lSPIRV-Tools -lpthread -lstdc++ -lm ||6701 -lSPVRemapper -lSPIRV -lSPIRV-Tools-opt -lSPIRV-Tools -lpthread -lstdc++ -lm ||
doc/filters.texi+5−3
@@ -12344,7 +12344,7 @@ Draw a text string or text from a specified file on top of a video, using the · 12343 unmodified lines
12344libfreetype library.12344libfreetype library.
12345 12345
12346To enable compilation of this filter, you need to configure FFmpeg with12346To enable compilation of this filter, you need to configure FFmpeg with
12347@code{--enable-libfreetype}.12347@code{--enable-libfreetype} and @code{--enable-libharfbuzz}.
12348To enable default font fallback and the @var{font} option you need to12348To enable default font fallback and the @var{font} option you need to
12349configure FFmpeg with @code{--enable-libfontconfig}.12349configure FFmpeg with @code{--enable-libfontconfig}.
12350To enable the @var{text_shaping} option, you need to configure FFmpeg with12350To enable the @var{text_shaping} option, you need to configure FFmpeg with
@@ -12372,8 +12372,7 @@ option, check the @ref{color syntax,,"Color" section in the ffmpeg-utils manual, · 21 unmodified lines
12372The default value of @var{boxcolor} is "white".12372The default value of @var{boxcolor} is "white".
12373 12373
12374@item line_spacing12374@item line_spacing
12375Set the line spacing in pixels of the border to be drawn around the box using @var{box}.12375Set the line spacing in pixels. The default value of @var{line_spacing} is 0.
12376The default value of @var{line_spacing} is 0.
12377 12376
12378@item borderw12377@item borderw
12379Set the width of the border to be drawn around the text using @var{bordercolor}.12378Set the width of the border to be drawn around the text using @var{bordercolor}.
@@ -12873,6 +12872,9 @@ For more information about fontconfig, check: · 493 unmodified lines
12873For more information about libfribidi, check:12872For more information about libfribidi, check:
12874@url{http://fribidi.org/}.12873@url{http://fribidi.org/}.
12875 12874
12875For more information about libharfbuzz, check:
12876@url{https://github.com/harfbuzz/harfbuzz}.
12877
12876@section edgedetect12878@section edgedetect
12877 12879
12878Detect and draw edges. The filter uses the Canny Edge Detection algorithm.12880Detect and draw edges. The filter uses the Canny Edge Detection algorithm.
libavfilter/vf_drawtext.c+583−276
@@ -1,4 +1,5 @@
1/*1/*
2 * Copyright (c) 2023 Francesco Carusi
2 * Copyright (c) 2011 Stefano Sabatini3 * Copyright (c) 2011 Stefano Sabatini
3 * Copyright (c) 2010 S.N. Hemanth Meenakshisundaram4 * Copyright (c) 2010 S.N. Hemanth Meenakshisundaram
4 * Copyright (c) 2003 Gustavo Sverzut Barbieri <gsbarbieri@yahoo.com.br>5 * Copyright (c) 2003 Gustavo Sverzut Barbieri <gsbarbieri@yahoo.com.br>
@@ -72,10 +73,16 @@ · 67 unmodified lines
72#include FT_GLYPH_H73#include FT_GLYPH_H
73#include FT_STROKER_H74#include FT_STROKER_H
74 75
76#include <hb.h>
77#include <hb-ft.h>
78
79// Ceiling operation for positive integers division
80#define POS_CEIL(x, y) ((x)/(y) + ((x)%(y) != 0))
81
75static const char *const var_names[] = {82static const char *const var_names[] = {
76 "dar",83 "dar",
77 "hsub", "vsub",84 "hsub", "vsub",
78 "line_h", "lh", ///< line height, same as max_glyph_h85 "line_h", "lh", ///< line height
79 "main_h", "h", "H", ///< height of the input video86 "main_h", "h", "H", ///< height of the input video
80 "main_w", "w", "W", ///< width of the input video87 "main_w", "w", "W", ///< width of the input video
81 "max_glyph_a", "ascent", ///< max glyph ascender88 "max_glyph_a", "ascent", ///< max glyph ascender
@@ -156,6 +163,72 @@ enum expansion_mode { · 74 unmodified lines
156 EXP_STRFTIME,163 EXP_STRFTIME,
157};164};
158 165
166typedef struct HarfbuzzData {
167 hb_buffer_t* buf;
168 hb_font_t* font;
169 unsigned int glyph_count;
170 hb_glyph_info_t* glyph_info;
171 hb_glyph_position_t* glyph_pos;
172} HarfbuzzData;
173
174/** Information about a single glyph in a text line */
175typedef struct GlyphInfo {
176 uint32_t code; ///< the glyph code point
177 int x; ///< the x position of the glyph
178 int y; ///< the y position of the glyph
179 int shift_x64; ///< the horizontal shift of the glyph in 26.6 units
180 int shift_y64; ///< the vertical shift of the glyph in 26.6 units
181} GlyphInfo;
182
183/** Information about a single line of text */
184typedef struct TextLine {
185 int offset_left64; ///< offset between the origin and
186 /// the leftmost pixel of the first glyph
187 int offset_right64; ///< maximum offset between the origin and
188 /// the rightmost pixel of the last glyph
189 int width64; ///< width of the line
190 HarfbuzzData hb_data; ///< libharfbuzz data of this text line
191 GlyphInfo* glyphs; ///< array of glyphs in this text line
192 int cluster_offset; ///< the offset at which this line begins
193} TextLine;
194
195/** A glyph as loaded and rendered using libfreetype */
196typedef struct Glyph {
197 FT_Glyph glyph;
198 FT_Glyph border_glyph;
199 uint32_t code;
200 unsigned int fontsize;
201 /** Glyph bitmaps with 1/4 pixel precision in both directions */
202 FT_BitmapGlyph bglyph[16];
203 /** Outlined glyph bitmaps with 1/4 pixel precision in both directions */
204 FT_BitmapGlyph border_bglyph[16];
205 FT_BBox bbox;
206} Glyph;
207
208/** Global text metrics */
209typedef struct TextMetrics {
210 int offset_top64; ///< ascender amount of the first line (in 26.6 units)
211 int offset_bottom64; ///< descender amount of the last line (in 26.6 units)
212 int offset_left64; ///< maximum offset between the origin and
213 /// the leftmost pixel of the first glyph
214 /// of each line (in 26.6 units)
215 int offset_right64; ///< maximum offset between the origin and
216 /// the rightmost pixel of the last glyph
217 /// of each line (in 26.6 units)
218 int line_height64; ///< the font-defined line height
219 int width; ///< width of the longest line - ceil(width64/64)
220 int height; ///< total height of the text - ceil(height64/64)
221
222 int min_y64; ///< minimum value of bbox.yMin among glyphs (in 26.6 units)
223 int max_y64; ///< maximum value of bbox.yMax among glyphs (in 26.6 units)
224 int min_x64; ///< minimum value of bbox.xMin among glyphs (in 26.6 units)
225 int max_x64; ///< maximum value of bbox.xMax among glyphs (in 26.6 units)
226
227 // Position of the background box (without borders)
228 int rect_x; ///< x position of the box
229 int rect_y; ///< y position of the box
230} TextMetrics;
231
159typedef struct DrawTextContext {232typedef struct DrawTextContext {
160 const AVClass *class;233 const AVClass *class;
161 int exp_mode; ///< expansion mode to use for the text234 int exp_mode; ///< expansion mode to use for the text
@@ -169,11 +242,9 @@ typedef struct DrawTextContext { · 7 unmodified lines
169 uint8_t *fontcolor_expr; ///< fontcolor expression to evaluate242 uint8_t *fontcolor_expr; ///< fontcolor expression to evaluate
170 AVBPrint expanded_fontcolor; ///< used to contain the expanded fontcolor spec243 AVBPrint expanded_fontcolor; ///< used to contain the expanded fontcolor spec
171 int ft_load_flags; ///< flags used for loading fonts, see FT_LOAD_*244 int ft_load_flags; ///< flags used for loading fonts, see FT_LOAD_*
172 FT_Vector *positions; ///< positions for each element in the text
173 size_t nb_positions; ///< number of elements of positions array
174 char *textfile; ///< file with text to be drawn245 char *textfile; ///< file with text to be drawn
175 int x; ///< x position to start drawing text246 double x; ///< x position to start drawing text
176 int y; ///< y position to start drawing text247 double y; ///< y position to start drawing text
177 int max_glyph_w; ///< max glyph width248 int max_glyph_w; ///< max glyph width
178 int max_glyph_h; ///< max glyph height249 int max_glyph_h; ///< max glyph height
179 int shadowx, shadowy;250 int shadowx, shadowy;
@@ -186,7 +257,10 @@ typedef struct DrawTextContext { · 6 unmodified lines
186 int line_spacing; ///< lines spacing in pixels257 int line_spacing; ///< lines spacing in pixels
187 short int draw_box; ///< draw box around text - true or false258 short int draw_box; ///< draw box around text - true or false
188 int boxborderw; ///< box border width259 int boxborderw; ///< box border width
189 int use_kerning; ///< font kerning is used - true/false260 int bb_top; ///< the size of the top box border
261 int bb_right; ///< the size of the right box border
262 int bb_bottom; ///< the size of the bottom box border
263 int bb_left; ///< the size of the left box border
190 int tabsize; ///< tab size264 int tabsize; ///< tab size
191 int fix_bounds; ///< do we let it go out of frame bounds - t/f265 int fix_bounds; ///< do we let it go out of frame bounds - t/f
192 266
@@ -221,6 +295,12 @@ typedef struct DrawTextContext { · 28 unmodified lines
221 int text_shaping; ///< 1 to shape the text before drawing it295 int text_shaping; ///< 1 to shape the text before drawing it
222#endif296#endif
223 AVDictionary *metadata;297 AVDictionary *metadata;
298
299 TextLine *lines; ///< computed information about text lines
300 int line_count; ///< the number of text lines
301 uint32_t *tab_clusters; ///< the position of tab characters in the text
302 int tab_count; ///< the number of tab characters
303 int blank_advance64; ///< the size of the space character
224} DrawTextContext;304} DrawTextContext;
225 305
226#define OFFSET(x) offsetof(DrawTextContext, x)306#define OFFSET(x) offsetof(DrawTextContext, x)
@@ -236,7 +316,7 @@ static const AVOption drawtext_options[]= { · 9 unmodified lines
236 {"bordercolor", "set border color", OFFSET(bordercolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, FLAGS},316 {"bordercolor", "set border color", OFFSET(bordercolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, FLAGS},
237 {"shadowcolor", "set shadow color", OFFSET(shadowcolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, FLAGS},317 {"shadowcolor", "set shadow color", OFFSET(shadowcolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, FLAGS},
238 {"box", "set box", OFFSET(draw_box), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},318 {"box", "set box", OFFSET(draw_box), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
239 {"boxborderw", "set box borders width", OFFSET(boxborderw), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, FLAGS},319 {"boxborderw", "set box borders width", OFFSET(boxborderw), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS},
240 {"line_spacing", "set line spacing in pixels", OFFSET(line_spacing), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, FLAGS},320 {"line_spacing", "set line spacing in pixels", OFFSET(line_spacing), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, FLAGS},
241 {"fontsize", "set font size", OFFSET(fontsize_expr), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS},321 {"fontsize", "set font size", OFFSET(fontsize_expr), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS},
242 {"x", "set x expression", OFFSET(x_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS},322 {"x", "set x expression", OFFSET(x_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS},
@@ -305,19 +385,6 @@ static const struct ft_error { · 62 unmodified lines
305 385
306#define FT_ERRMSG(e) ft_errors[e].err_msg386#define FT_ERRMSG(e) ft_errors[e].err_msg
307 387
308typedef struct Glyph {
309 FT_Glyph glyph;
310 FT_Glyph border_glyph;
311 uint32_t code;
312 unsigned int fontsize;
313 FT_Bitmap bitmap; ///< array holding bitmaps of font
314 FT_Bitmap border_bitmap; ///< array holding bitmaps of font border
315 FT_BBox bbox;
316 int advance;
317 int bitmap_left;
318 int bitmap_top;
319} Glyph;
320
321static int glyph_cmp(const void *key, const void *b)388static int glyph_cmp(const void *key, const void *b)
322{389{
323 const Glyph *a = key, *bb = b;390 const Glyph *a = key, *bb = b;
@@ -329,77 +396,6 @@ static int glyph_cmp(const void *key, const void *b) · 5 unmodified lines
329 return FFDIFFSIGN((int64_t)a->fontsize, (int64_t)bb->fontsize);396 return FFDIFFSIGN((int64_t)a->fontsize, (int64_t)bb->fontsize);
330}397}
331 398
332/**
333 * Load glyphs corresponding to the UTF-32 codepoint code.
334 */
335static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code)
336{
337 DrawTextContext *s = ctx->priv;
338 FT_BitmapGlyph bitmapglyph;
339 Glyph *glyph;
340 struct AVTreeNode *node = NULL;
341 int ret;
342
343 /* load glyph into s->face->glyph */
344 if (FT_Load_Char(s->face, code, s->ft_load_flags))
345 return AVERROR(EINVAL);
346
347 glyph = av_mallocz(sizeof(*glyph));
348 if (!glyph) {
349 ret = AVERROR(ENOMEM);
350 goto error;
351 }
352 glyph->code = code;
353 glyph->fontsize = s->fontsize;
354
355 if (FT_Get_Glyph(s->face->glyph, &glyph->glyph)) {
356 ret = AVERROR(EINVAL);
357 goto error;
358 }
359 if (s->borderw) {
360 glyph->border_glyph = glyph->glyph;
361 if (FT_Glyph_StrokeBorder(&glyph->border_glyph, s->stroker, 0, 0) ||
362 FT_Glyph_To_Bitmap(&glyph->border_glyph, FT_RENDER_MODE_NORMAL, 0, 1)) {
363 ret = AVERROR_EXTERNAL;
364 goto error;
365 }
366 bitmapglyph = (FT_BitmapGlyph) glyph->border_glyph;
367 glyph->border_bitmap = bitmapglyph->bitmap;
368 }
369 if (FT_Glyph_To_Bitmap(&glyph->glyph, FT_RENDER_MODE_NORMAL, 0, 1)) {
370 ret = AVERROR_EXTERNAL;
371 goto error;
372 }
373 bitmapglyph = (FT_BitmapGlyph) glyph->glyph;
374
375 glyph->bitmap = bitmapglyph->bitmap;
376 glyph->bitmap_left = bitmapglyph->left;
377 glyph->bitmap_top = bitmapglyph->top;
378 glyph->advance = s->face->glyph->advance.x >> 6;
379
380 /* measure text height to calculate text_height (or the maximum text height) */
381 FT_Glyph_Get_CBox(glyph->glyph, ft_glyph_bbox_pixels, &glyph->bbox);
382
383 /* cache the newly created glyph */
384 if (!(node = av_tree_node_alloc())) {
385 ret = AVERROR(ENOMEM);
386 goto error;
387 }
388 av_tree_insert(&s->glyphs, glyph, glyph_cmp, &node);
389
390 if (glyph_ptr)
391 *glyph_ptr = glyph;
392 return 0;
393
394error:
395 if (glyph)
396 av_freep(&glyph->glyph);
397
398 av_freep(&glyph);
399 av_freep(&node);
400 return ret;
401}
402
403static av_cold int set_fontsize(AVFilterContext *ctx, unsigned int fontsize)399static av_cold int set_fontsize(AVFilterContext *ctx, unsigned int fontsize)
404{400{
405 int err;401 int err;
@@ -718,11 +714,114 @@ static enum AVFrameSideDataType text_source_string_parse(const char *text_source · 312 unmodified lines
718 }714 }
719}715}
720 716
717static inline int get_subpixel_idx(int shift_x64, int shift_y64)
718{
719 int idx = (shift_x64 >> 2) + (shift_y64 >> 4);
720 return idx;
721}
722
723// Loads and (optionally) renders a glyph
724static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code, int8_t shift_x64, int8_t shift_y64)
725{
726 DrawTextContext *s = ctx->priv;
727 Glyph dummy = { 0 };
728 Glyph *glyph;
729 FT_Vector shift;
730 struct AVTreeNode *node = NULL;
731 int ret = 0;
732
733 /* get glyph */
734 dummy.code = code;
735 dummy.fontsize = s->fontsize;
736 glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
737 if (!glyph) {
738 if (FT_Load_Glyph(s->face, code, s->ft_load_flags)) {
739 return AVERROR(EINVAL);
740 }
741 glyph = av_mallocz(sizeof(*glyph));
742 if (!glyph) {
743 ret = AVERROR(ENOMEM);
744 goto error;
745 }
746 glyph->code = code;
747 glyph->fontsize = s->fontsize;
748 if (FT_Get_Glyph(s->face->glyph, &glyph->glyph)) {
749 ret = AVERROR(EINVAL);
750 goto error;
751 }
752 if (s->borderw) {
753 glyph->border_glyph = glyph->glyph;
754 if (FT_Glyph_StrokeBorder(&glyph->border_glyph, s->stroker, 0, 0)) {
755 ret = AVERROR_EXTERNAL;
756 goto error;
757 }
758 }
759 /* measure text height to calculate text_height (or the maximum text height) */
760 FT_Glyph_Get_CBox(glyph->glyph, FT_GLYPH_BBOX_SUBPIXELS, &glyph->bbox);
761
762 /* cache the newly created glyph */
763 if (!(node = av_tree_node_alloc())) {
764 ret = AVERROR(ENOMEM);
765 goto error;
766 }
767 av_tree_insert(&s->glyphs, glyph, glyph_cmp, &node);
768 } else {
769 if (s->borderw && !glyph->border_glyph) {
770 glyph->border_glyph = glyph->glyph;
771 if (FT_Glyph_StrokeBorder(&glyph->border_glyph, s->stroker, 0, 0)) {
772 ret = AVERROR_EXTERNAL;
773 goto error;
774 }
775 }
776 }
777
778 // Check if a bitmap is needed
779 if (shift_x64 >= 0 && shift_y64 >= 0) {
780 // Get the bitmap subpixel index (0 -> 15)
781 int idx = get_subpixel_idx(shift_x64, shift_y64);
782 shift.x = shift_x64;
783 shift.y = shift_y64;
784
785 if (!glyph->bglyph[idx]) {
786 FT_Glyph tmp_glyph = glyph->glyph;
787 if (FT_Glyph_To_Bitmap(&tmp_glyph, FT_RENDER_MODE_NORMAL, &shift, 0)) {
788 ret = AVERROR_EXTERNAL;
789 goto error;
790 }
791 glyph->bglyph[idx] = (FT_BitmapGlyph)tmp_glyph;
792 if (glyph->bglyph[idx]->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
793 av_log(ctx, AV_LOG_ERROR, "Monocromatic (1bpp) fonts are not supported.\n");
794 ret = AVERROR(EINVAL);
795 goto error;
796 }
797 }
798 if (s->borderw && !glyph->border_bglyph[idx]) {
799 FT_Glyph tmp_glyph = glyph->border_glyph;
800 if (FT_Glyph_To_Bitmap(&tmp_glyph, FT_RENDER_MODE_NORMAL, &shift, 0)) {
801 ret = AVERROR_EXTERNAL;
802 goto error;
803 }
804 glyph->border_bglyph[idx] = (FT_BitmapGlyph)tmp_glyph;
805 }
806 }
807 if (glyph_ptr) {
808 *glyph_ptr = glyph;
809 }
810 return 0;
811
812error:
813 if (glyph && glyph->glyph)
814 FT_Done_Glyph(glyph->glyph);
815
816 av_freep(&glyph);
817 av_freep(&node);
818 return ret;
819}
820
721static av_cold int init(AVFilterContext *ctx)821static av_cold int init(AVFilterContext *ctx)
722{822{
723 int err;823 int err;
724 DrawTextContext *s = ctx->priv;824 DrawTextContext *s = ctx->priv;
725 Glyph *glyph;
726 825
727 av_expr_free(s->fontsize_pexpr);826 av_expr_free(s->fontsize_pexpr);
728 s->fontsize_pexpr = NULL;827 s->fontsize_pexpr = NULL;
@@ -811,17 +910,8 @@ static av_cold int init(AVFilterContext *ctx) · 82 unmodified lines
811 FT_STROKER_LINEJOIN_ROUND, 0);910 FT_STROKER_LINEJOIN_ROUND, 0);
812 }911 }
813 912
814 s->use_kerning = FT_HAS_KERNING(s->face);
815
816 /* load the fallback glyph with code 0 */913 /* load the fallback glyph with code 0 */
817 load_glyph(ctx, NULL, 0);914 load_glyph(ctx, NULL, 0, 0, 0);
818
819 /* set the tabsize in pixels */
820 if ((err = load_glyph(ctx, &glyph, ' ')) < 0) {
821 av_log(ctx, AV_LOG_ERROR, "Could not set tabsize.\n");
822 return err;
823 }
824 s->tabsize *= glyph->advance;
825 915
826 if (s->exp_mode == EXP_STRFTIME &&916 if (s->exp_mode == EXP_STRFTIME &&
827 (strchr(s->text, '%') || strchr(s->text, '\\')))917 (strchr(s->text, '%') || strchr(s->text, '\\')))
@@ -844,6 +934,14 @@ static int glyph_enu_free(void *opaque, void *elem) · 16 unmodified lines
844 934
845 FT_Done_Glyph(glyph->glyph);935 FT_Done_Glyph(glyph->glyph);
846 FT_Done_Glyph(glyph->border_glyph);936 FT_Done_Glyph(glyph->border_glyph);
937 for (int t = 0; t < 16; ++t) {
938 if (glyph->bglyph[t] != NULL) {
939 FT_Done_Glyph((FT_Glyph)glyph->bglyph[t]);
940 }
941 if (glyph->border_bglyph[t] != NULL) {
942 FT_Done_Glyph((FT_Glyph)glyph->border_bglyph[t]);
943 }
944 }
847 av_free(elem);945 av_free(elem);
848 return 0;946 return 0;
849}947}
@@ -859,9 +957,6 @@ static av_cold void uninit(AVFilterContext *ctx) · 9 unmodified lines
859 957
860 s->x_pexpr = s->y_pexpr = s->a_pexpr = s->fontsize_pexpr = NULL;958 s->x_pexpr = s->y_pexpr = s->a_pexpr = s->fontsize_pexpr = NULL;
861 959
862 av_freep(&s->positions);
863 s->nb_positions = 0;
864
865 av_tree_enumerate(s->glyphs, NULL, NULL, glyph_enu_free);960 av_tree_enumerate(s->glyphs, NULL, NULL, glyph_enu_free);
866 av_tree_destroy(s->glyphs);961 av_tree_destroy(s->glyphs);
867 s->glyphs = NULL;962 s->glyphs = NULL;
@@ -1325,91 +1420,283 @@ static int expand_text(AVFilterContext *ctx, char *text, AVBPrint *bp) · 457 unmodified lines
1325 return 0;1420 return 0;
1326}1421}
1327 1422
1423static void update_color_with_alpha(DrawTextContext *s, FFDrawColor *color, const FFDrawColor incolor)
1424{
1425 *color = incolor;
1426 color->rgba[3] = (color->rgba[3] * s->alpha) / 255;
1427 ff_draw_color(&s->dc, color, color->rgba);
1428}
1429
1430static void update_alpha(DrawTextContext *s)
1431{
1432 double alpha = av_expr_eval(s->a_pexpr, s->var_values, &s->prng);
1433
1434 if (isnan(alpha))
1435 return;
1436
1437 if (alpha >= 1.0)
1438 s->alpha = 255;
1439 else if (alpha <= 0)
1440 s->alpha = 0;
1441 else
1442 s->alpha = 256 * alpha;
1443}
1444
1328static int draw_glyphs(DrawTextContext *s, AVFrame *frame,1445static int draw_glyphs(DrawTextContext *s, AVFrame *frame,
1329 int width, int height,
1330 FFDrawColor *color,1446 FFDrawColor *color,
1447 TextMetrics *metrics,
1331 int x, int y, int borderw)1448 int x, int y, int borderw)
1332{1449{
1333 char *text = s->expanded_text.str;1450 int g, l, x1, y1, w1, h1, idx;
1334 uint32_t code = 0;1451 int dx = 0, dy = 0, pdx = 0;
1335 int i, x1, y1;1452 GlyphInfo *info;
1336 uint8_t *p;1453 Glyph dummy = { 0 }, *glyph;
1337 Glyph *glyph = NULL;1454 FT_Bitmap bitmap;
1338 1455 FT_BitmapGlyph b_glyph;
1339 for (i = 0, p = text; *p; i++) {1456 int clip_x = 0, clip_y = 0;
1340 FT_Bitmap bitmap;1457
1341 Glyph dummy = { 0 };1458 clip_x = FFMIN(metrics->rect_x + metrics->width + s->bb_right, frame->width);
1342 GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_invalid;);1459 clip_y = FFMIN(metrics->rect_y + metrics->height + s->bb_bottom, frame->height);
1343continue_on_invalid:1460
1461 for (l = 0; l < s->line_count; ++l) {
1462 TextLine *line = &s->lines[l];
1463 for (g = 0; g < line->hb_data.glyph_count; ++g) {
1464 info = &line->glyphs[g];
1465 dummy.fontsize = s->fontsize;
1466 dummy.code = info->code;
1467 glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
1468 if (!glyph) {
1469 return AVERROR(EINVAL);
1470 }
1344 1471
1345 /* skip new line chars, just go to new line */1472 idx = get_subpixel_idx(info->shift_x64, info->shift_y64);
1346 if (code == '\n' || code == '\r' || code == '\t')1473 b_glyph = borderw ? glyph->border_bglyph[idx] : glyph->bglyph[idx];
1347 continue;1474 bitmap = b_glyph->bitmap;
1475 x1 = x + info->x + b_glyph->left;
1476 y1 = y + info->y - b_glyph->top;
1477 w1 = bitmap.width;
1478 h1 = bitmap.rows;
1479
1480 // Offset of the glyph's bitmap in the visible region
1481 dx = dy = 0;
1482 if (x1 < metrics->rect_x - s->bb_left) {
1483 dx = metrics->rect_x - s->bb_left - x1;
1484 x1 = metrics->rect_x - s->bb_left;
1485 }
1486 if (y1 < metrics->rect_y - s->bb_top) {
1487 dy = metrics->rect_y - s->bb_top - y1;
1488 y1 = metrics->rect_y - s->bb_top;
1489 }
1348 1490
1349 dummy.code = code;1491 // check if the glyph is empty or out of the clipping region
1350 dummy.fontsize = s->fontsize;1492 if (dx >= w1 || dy >= h1 || x1 >= clip_x || y1 >= clip_y) {
1351 glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);1493 continue;
1494 }
1352 1495
1353 bitmap = borderw ? glyph->border_bitmap : glyph->bitmap;1496 pdx = dx + dy * bitmap.pitch;
1497 w1 = FFMIN(clip_x - x1, w1 - dx);
1498 h1 = FFMIN(clip_y - y1, h1 - dy);
1354 1499
1355 if (glyph->bitmap.pixel_mode != FT_PIXEL_MODE_MONO &&1500 ff_blend_mask(&s->dc, color, frame->data, frame->linesize, clip_x, clip_y,
1356 glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)1501 bitmap.buffer + pdx, bitmap.pitch, w1, h1, 3, 0, x1, y1);
1357 return AVERROR(EINVAL);1502 }
1503 }
1358 1504
1359 x1 = s->positions[i].x+s->x+x - borderw;1505 return 0;
1360 y1 = s->positions[i].y+s->y+y - borderw;1506}
1361 1507
1362 ff_blend_mask(&s->dc, color,1508// Shapes a line of text using libharfbuzz
1363 frame->data, frame->linesize, width, height,1509static int shape_text_hb(DrawTextContext *s, HarfbuzzData* hb, const char* text, int textLen)
1364 bitmap.buffer, bitmap.pitch,1510{
1365 bitmap.width, bitmap.rows,1511 hb->buf = hb_buffer_create();
1366 bitmap.pixel_mode == FT_PIXEL_MODE_MONO ? 0 : 3,1512 if(!hb_buffer_allocation_successful(hb->buf)) {
1367 0, x1, y1);1513 return AVERROR(ENOMEM);
1368 }1514 }
1515 hb_buffer_set_direction(hb->buf, HB_DIRECTION_LTR);
1516 hb_buffer_set_script(hb->buf, HB_SCRIPT_LATIN);
1517 hb_buffer_set_language(hb->buf, hb_language_from_string("en", -1));
1518 hb_buffer_guess_segment_properties(hb->buf);
1519 hb->font = hb_ft_font_create(s->face, NULL);
1520 if(hb->font == NULL) {
1521 return AVERROR(ENOMEM);
1522 }
1523 hb_ft_font_set_funcs(hb->font);
1524 hb_buffer_add_utf8(hb->buf, text, textLen, 0, -1);
1525 hb_shape(hb->font, hb->buf, NULL, 0);
1526 hb->glyph_info = hb_buffer_get_glyph_infos(hb->buf, &hb->glyph_count);
1527 hb->glyph_pos = hb_buffer_get_glyph_positions(hb->buf, &hb->glyph_count);
1369 1528
1370 return 0;1529 return 0;
1371}1530}
1372 1531
1373 1532static void hb_destroy(HarfbuzzData *hb)
1374static void update_color_with_alpha(DrawTextContext *s, FFDrawColor *color, const FFDrawColor incolor)
1375{1533{
1376 *color = incolor;1534 hb_buffer_destroy(hb->buf);
1377 color->rgba[3] = (color->rgba[3] * s->alpha) / 255;1535 hb_font_destroy(hb->font);
1378 ff_draw_color(&s->dc, color, color->rgba);1536 hb->buf = NULL;
1537 hb->font = NULL;
1538 hb->glyph_info = NULL;
1539 hb->glyph_pos = NULL;
1379}1540}
1380 1541
1381static void update_alpha(DrawTextContext *s)1542static int measure_text(AVFilterContext *ctx, TextMetrics *metrics)
1382{1543{
1383 double alpha = av_expr_eval(s->a_pexpr, s->var_values, &s->prng);1544 DrawTextContext *s = ctx->priv;
1545 char *text = s->expanded_text.str;
1546 char *textdup = NULL, *start = NULL;
1547 int num_chars = 0;
1548 int width64 = 0, w64 = 0;
1549 int cur_min_y64 = 0, first_max_y64 = -32000;
1550 int first_min_x64 = 32000, last_max_x64 = -32000;
1551 int min_y64 = 32000, max_y64 = -32000, min_x64 = 32000, max_x64 = -32000;
1552 int line_count = 0;
1553 uint32_t code = 0;
1554 Glyph *glyph = NULL;
1555 int height64;
1556
1557 int i, tab_idx = 0, last_tab_idx = 0, line_offset = 0;
1558 char* p;
1559 int ret = 0;
1560
1561 // Count the lines and the tab characters
1562 s->tab_count = 0;
1563 for (i = 0, p = text; 1; i++) {
1564 GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_failed;);
1565continue_on_failed:
1566 if (is_newline(code) || code == 0) {
1567 ++line_count;
1568 if (code == 0) {
1569 break;
1570 }
1571 } else if (code == '\t') {
1572 ++s->tab_count;
1573 }
1574 }
1384 1575
1385 if (isnan(alpha))1576 // Evaluate the width of the space character if needed to replace tabs
1386 return;1577 if (s->tab_count > 0 && !s->blank_advance64) {
1578 HarfbuzzData hb_data;
1579 ret = shape_text_hb(s, &hb_data, " ", 1);
1580 if(ret != 0) {
1581 goto done;
1582 }
1583 s->blank_advance64 = hb_data.glyph_pos[0].x_advance;
1584 hb_destroy(&hb_data);
1585 }
1387 1586
1388 if (alpha >= 1.0)1587 s->line_count = line_count;
1389 s->alpha = 255;1588 s->lines = av_mallocz(line_count * sizeof(TextLine));
1390 else if (alpha <= 0)1589 s->tab_clusters = av_mallocz(s->tab_count * sizeof(uint32_t));
1391 s->alpha = 0;1590 for (i = 0; i < s->tab_count; ++i) {
1392 else1591 s->tab_clusters[i] = -1;
1393 s->alpha = 256 * alpha;1592 }
1593
1594 start = textdup = av_strdup(text);
1595 if (textdup == NULL) {
1596 ret = AVERROR(ENOMEM);
1597 goto done;
1598 }
1599 line_count = 0;
1600 for (i = 0, p = textdup; 1; i++) {
1601 if (*p == '\t') {
1602 s->tab_clusters[tab_idx++] = i;
1603 *p = ' ';
1604 }
1605 GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_failed2;);
1606continue_on_failed2:
1607 if (is_newline(code) || code == 0) {
1608 TextLine *cur_line = &s->lines[line_count];
1609 HarfbuzzData *hb = &cur_line->hb_data;
1610 cur_line->cluster_offset = line_offset;
1611 ret = shape_text_hb(s, hb, start, num_chars);
1612 if (ret != 0) {
1613 goto done;
1614 }
1615 w64 = 0;
1616 cur_min_y64 = 32000;
1617 for (int t = 0; t < hb->glyph_count; ++t) {
1618 uint8_t is_tab = last_tab_idx < s->tab_count &&
1619 hb->glyph_info[t].cluster == s->tab_clusters[last_tab_idx] - line_offset;
1620 if (is_tab) {
1621 ++last_tab_idx;
1622 }
1623 ret = load_glyph(ctx, &glyph, hb->glyph_info[t].codepoint, -1, -1);
1624 if (ret != 0) {
1625 goto done;
1626 }
1627 if (line_count == 0) {
1628 first_max_y64 = FFMAX(glyph->bbox.yMax, first_max_y64);
1629 }
1630 if (t == 0) {
1631 cur_line->offset_left64 = glyph->bbox.xMin;
1632 first_min_x64 = FFMIN(glyph->bbox.xMin, first_min_x64);
1633 }
1634 if (t == hb->glyph_count - 1) {
1635 w64 += glyph->bbox.xMax;
1636 last_max_x64 = FFMAX(glyph->bbox.xMax, last_max_x64);
1637 cur_line->offset_right64 = glyph->bbox.xMax;
1638 } else {
1639 if (is_tab) {
1640 int size = s->blank_advance64 * s->tabsize;
1641 w64 = (w64 / size + 1) * size;
1642 } else {
1643 w64 += hb->glyph_pos[t].x_advance;
1644 }
1645 }
1646 cur_min_y64 = FFMIN(glyph->bbox.yMin, cur_min_y64);
1647 min_y64 = FFMIN(glyph->bbox.yMin, min_y64);
1648 max_y64 = FFMAX(glyph->bbox.yMax, max_y64);
1649 min_x64 = FFMIN(glyph->bbox.xMin, min_x64);
1650 max_x64 = FFMAX(glyph->bbox.xMax, max_x64);
1651 }
1652
1653 cur_line->width64 = w64;
1654
1655 av_log(s, AV_LOG_DEBUG, " Line: %d -- glyphs count: %d - width64: %d - offset_left64: %d - offset_right64: %d)\n",
1656 line_count, hb->glyph_count, cur_line->width64, cur_line->offset_left64, cur_line->offset_right64);
1657
1658 if (w64 > width64) {
1659 width64 = w64;
1660 }
1661 num_chars = -1;
1662 start = p;
1663 ++line_count;
1664 line_offset = i + 1;
1665 }
1666
1667 if (code == 0) break;
1668 ++num_chars;
1669 }
1670
1671 metrics->line_height64 = s->face->size->metrics.height;
1672
1673 metrics->width = POS_CEIL(width64, 64);
1674 height64 = (metrics->line_height64 + s->line_spacing * 64) *
1675 (FFMAX(0, line_count - 1)) + first_max_y64 - cur_min_y64;
1676 metrics->height = POS_CEIL(height64, 64);
1677
1678 metrics->offset_top64 = first_max_y64;
1679 metrics->offset_right64 = last_max_x64;
1680 metrics->offset_bottom64 = cur_min_y64;
1681 metrics->offset_left64 = first_min_x64;
1682 metrics->min_x64 = min_x64;
1683 metrics->min_y64 = min_y64;
1684 metrics->max_x64 = max_x64;
1685 metrics->max_y64 = max_y64;
1686
1687done:
1688 av_free(textdup);
1689 return ret;
1394}1690}
1395 1691
1396static int draw_text(AVFilterContext *ctx, AVFrame *frame,1692static int draw_text(AVFilterContext *ctx, AVFrame *frame)
1397 int width, int height)
1398{1693{
1399 DrawTextContext *s = ctx->priv;1694 DrawTextContext *s = ctx->priv;
1400 AVFilterLink *inlink = ctx->inputs[0];1695 AVFilterLink *inlink = ctx->inputs[0];
1401 1696 int x = 0, y = 0, ret;
1402 uint32_t code = 0, prev_code = 0;1697 int shift_x64, shift_y64;
1403 int x = 0, y = 0, i = 0, ret;1698 int x64, y64;
1404 int max_text_line_w = 0, len;1699 Glyph *glyph = NULL;
1405 int box_w, box_h;
1406 char *text;
1407 uint8_t *p;
1408 int y_min = 32000, y_max = -32000;
1409 int x_min = 32000, x_max = -32000;
1410 FT_Vector delta;
1411 Glyph *glyph = NULL, *prev_glyph = NULL;
1412 Glyph dummy = { 0 };
1413 1700
1414 time_t now = time(0);1701 time_t now = time(0);
1415 struct tm ltime;1702 struct tm ltime;
@@ -1420,6 +1707,14 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame, · 4 unmodified lines
1420 FFDrawColor bordercolor;1707 FFDrawColor bordercolor;
1421 FFDrawColor boxcolor;1708 FFDrawColor boxcolor;
1422 1709
1710 int width = frame->width;
1711 int height = frame->height;
1712 int rec_x = 0, rec_y = 0, rec_width = 0, rec_height = 0;
1713 int is_outside = 0;
1714 int last_tab_idx = 0;
1715
1716 TextMetrics metrics;
1717
1423 av_bprint_clear(bp);1718 av_bprint_clear(bp);
1424 1719
1425 if (s->basetime != AV_NOPTS_VALUE)1720 if (s->basetime != AV_NOPTS_VALUE)
@@ -1448,13 +1743,6 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame, · 22 unmodified lines
1448 1743
1449 if (!av_bprint_is_complete(bp))1744 if (!av_bprint_is_complete(bp))
1450 return AVERROR(ENOMEM);1745 return AVERROR(ENOMEM);
1451 text = s->expanded_text.str;
1452 if ((len = s->expanded_text.len) > s->nb_positions) {
1453 if (!(s->positions =
1454 av_realloc(s->positions, len*sizeof(*s->positions))))
1455 return AVERROR(ENOMEM);
1456 s->nb_positions = len;
1457 }
1458 1746
1459 if (s->fontcolor_expr[0]) {1747 if (s->fontcolor_expr[0]) {
1460 /* If expression is set, evaluate and replace the static value */1748 /* If expression is set, evaluate and replace the static value */
@@ -1470,85 +1758,26 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame, · 9 unmodified lines
1470 ff_draw_color(&s->dc, &s->fontcolor, s->fontcolor.rgba);1758 ff_draw_color(&s->dc, &s->fontcolor, s->fontcolor.rgba);
1471 }1759 }
1472 1760
1473 x = 0;1761 if ((ret = update_fontsize(ctx)) < 0) {
1474 y = 0;
1475
1476 if ((ret = update_fontsize(ctx)) < 0)
1477 return ret;1762 return ret;
1478
1479 /* load and cache glyphs */
1480 for (i = 0, p = text; *p; i++) {
1481 GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_invalid;);
1482continue_on_invalid:
1483
1484 /* get glyph */
1485 dummy.code = code;
1486 dummy.fontsize = s->fontsize;
1487 glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
1488 if (!glyph) {
1489 ret = load_glyph(ctx, &glyph, code);
1490 if (ret < 0)
1491 return ret;
1492 }
1493
1494 y_min = FFMIN(glyph->bbox.yMin, y_min);
1495 y_max = FFMAX(glyph->bbox.yMax, y_max);
1496 x_min = FFMIN(glyph->bbox.xMin, x_min);
1497 x_max = FFMAX(glyph->bbox.xMax, x_max);
1498 }1763 }
1499 s->max_glyph_h = y_max - y_min;
1500 s->max_glyph_w = x_max - x_min;
1501
1502 /* compute and save position for each glyph */
1503 glyph = NULL;
1504 for (i = 0, p = text; *p; i++) {
1505 GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_invalid2;);
1506continue_on_invalid2:
1507
1508 /* skip the \n in the sequence \r\n */
1509 if (prev_code == '\r' && code == '\n')
1510 continue;
1511
1512 prev_code = code;
1513 if (is_newline(code)) {
1514
1515 max_text_line_w = FFMAX(max_text_line_w, x);
1516 y += s->max_glyph_h + s->line_spacing;
1517 x = 0;
1518 continue;
1519 }
1520
1521 /* get glyph */
1522 prev_glyph = glyph;
1523 dummy.code = code;
1524 dummy.fontsize = s->fontsize;
1525 glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
1526
1527 /* kerning */
1528 if (s->use_kerning && prev_glyph && glyph->code) {
1529 FT_Get_Kerning(s->face, prev_glyph->code, glyph->code,
1530 ft_kerning_default, &delta);
1531 x += delta.x >> 6;
1532 }
1533 1764
1534 /* save position */1765 if ((ret = measure_text(ctx, &metrics)) < 0) {
1535 s->positions[i].x = x + glyph->bitmap_left;1766 return ret;
1536 s->positions[i].y = y - glyph->bitmap_top + y_max;
1537 if (code == '\t') x = (x / s->tabsize + 1)*s->tabsize;
1538 else x += glyph->advance;
1539 }1767 }
1540 1768
1541 max_text_line_w = FFMAX(x, max_text_line_w);1769 s->max_glyph_h = POS_CEIL(metrics.max_y64 - metrics.min_y64, 64);
1770 s->max_glyph_w = POS_CEIL(metrics.max_x64 - metrics.min_x64, 64);
1542 1771
1543 s->var_values[VAR_TW] = s->var_values[VAR_TEXT_W] = max_text_line_w;1772 s->var_values[VAR_TW] = s->var_values[VAR_TEXT_W] = metrics.width;
1544 s->var_values[VAR_TH] = s->var_values[VAR_TEXT_H] = y + s->max_glyph_h;1773 s->var_values[VAR_TH] = s->var_values[VAR_TEXT_H] = metrics.height;
1545 1774
1546 s->var_values[VAR_MAX_GLYPH_W] = s->max_glyph_w;1775 s->var_values[VAR_MAX_GLYPH_W] = s->max_glyph_w;
1547 s->var_values[VAR_MAX_GLYPH_H] = s->max_glyph_h;1776 s->var_values[VAR_MAX_GLYPH_H] = s->max_glyph_h;
1548 s->var_values[VAR_MAX_GLYPH_A] = s->var_values[VAR_ASCENT ] = y_max;1777 s->var_values[VAR_MAX_GLYPH_A] = s->var_values[VAR_ASCENT] = POS_CEIL(metrics.max_y64, 64);
1549 s->var_values[VAR_MAX_GLYPH_D] = s->var_values[VAR_DESCENT] = y_min;1778 s->var_values[VAR_MAX_GLYPH_D] = s->var_values[VAR_DESCENT] = POS_CEIL(metrics.min_y64, 64);
1550 1779
1551 s->var_values[VAR_LINE_H] = s->var_values[VAR_LH] = s->max_glyph_h;1780 s->var_values[VAR_LINE_H] = s->var_values[VAR_LH] = metrics.line_height64 / 64.;
1552 1781
1553 if (s->text_source == AV_FRAME_DATA_DETECTION_BBOXES) {1782 if (s->text_source == AV_FRAME_DATA_DETECTION_BBOXES) {
1554 s->var_values[VAR_X] = s->x;1783 s->var_values[VAR_X] = s->x;
@@ -1566,56 +1795,139 @@ continue_on_invalid2: · 11 unmodified lines
1566 update_color_with_alpha(s, &bordercolor, s->bordercolor);1795 update_color_with_alpha(s, &bordercolor, s->bordercolor);
1567 update_color_with_alpha(s, &boxcolor , s->boxcolor );1796 update_color_with_alpha(s, &boxcolor , s->boxcolor );
1568 1797
1569 box_w = max_text_line_w;1798 if (s->draw_box && s->boxborderw) {
1570 box_h = y + s->max_glyph_h;1799 s->bb_top = s->bb_right = s->bb_bottom = s->bb_left = s->boxborderw;
1800 } else {
1801 s->bb_top = s->bb_right = s->bb_bottom = s->bb_left = 0;
1802 }
1571 1803
1572 if (s->fix_bounds) {1804 if (s->fix_bounds) {
1573
1574 /* calculate footprint of text effects */1805 /* calculate footprint of text effects */
1575 int boxoffset = s->draw_box ? FFMAX(s->boxborderw, 0) : 0;
1576 int borderoffset = s->borderw ? FFMAX(s->borderw, 0) : 0;1806 int borderoffset = s->borderw ? FFMAX(s->borderw, 0) : 0;
1577 1807
1578 int offsetleft = FFMAX3(boxoffset, borderoffset,1808 int offsetleft = FFMAX3(FFMAX(s->bb_left, 0), borderoffset,
1579 (s->shadowx < 0 ? FFABS(s->shadowx) : 0));1809 (s->shadowx < 0 ? FFABS(s->shadowx) : 0));
1580 int offsettop = FFMAX3(boxoffset, borderoffset,1810 int offsettop = FFMAX3(FFMAX(s->bb_top, 0), borderoffset,
1581 (s->shadowy < 0 ? FFABS(s->shadowy) : 0));1811 (s->shadowy < 0 ? FFABS(s->shadowy) : 0));
1582 1812 int offsetright = FFMAX3(FFMAX(s->bb_right, 0), borderoffset,
1583 int offsetright = FFMAX3(boxoffset, borderoffset,
1584 (s->shadowx > 0 ? s->shadowx : 0));1813 (s->shadowx > 0 ? s->shadowx : 0));
1585 int offsetbottom = FFMAX3(boxoffset, borderoffset,1814 int offsetbottom = FFMAX3(FFMAX(s->bb_bottom, 0), borderoffset,
1586 (s->shadowy > 0 ? s->shadowy : 0));1815 (s->shadowy > 0 ? s->shadowy : 0));
1587 1816
1588
1589 if (s->x - offsetleft < 0) s->x = offsetleft;1817 if (s->x - offsetleft < 0) s->x = offsetleft;
1590 if (s->y - offsettop < 0) s->y = offsettop;1818 if (s->y - offsettop < 0) s->y = offsettop;
1591 1819
1592 if (s->x + box_w + offsetright > width)1820 if (s->x + metrics.width + offsetright > width)
1593 s->x = FFMAX(width - box_w - offsetright, 0);1821 s->x = FFMAX(width - metrics.width - offsetright, 0);
1594 if (s->y + box_h + offsetbottom > height)1822 if (s->y + metrics.height + offsetbottom > height)
1595 s->y = FFMAX(height - box_h - offsetbottom, 0);1823 s->y = FFMAX(height - metrics.height - offsetbottom, 0);
1596 }1824 }
1597 1825
1598 /* draw box */1826 x = 0;
1599 if (s->draw_box)1827 y = 0;
1600 ff_blend_rectangle(&s->dc, &boxcolor,1828 x64 = (int)(s->x * 64.);
1601 frame->data, frame->linesize, width, height,1829 y64 = (int)(s->y * 64. + metrics.offset_top64);
1602 s->x - s->boxborderw, s->y - s->boxborderw,1830
1603 box_w + s->boxborderw * 2, box_h + s->boxborderw * 2);1831 for (int l = 0; l < s->line_count; ++l) {
1832 TextLine *line = &s->lines[l];
1833 HarfbuzzData *hb = &line->hb_data;
1834 line->glyphs = av_mallocz(hb->glyph_count * sizeof(GlyphInfo));
1835
1836 for (int t = 0; t < hb->glyph_count; ++t) {
1837 GlyphInfo *g_info = &line->glyphs[t];
1838 uint8_t is_tab = last_tab_idx < s->tab_count &&
1839 hb->glyph_info[t].cluster == s->tab_clusters[last_tab_idx] - line->cluster_offset;
1840 int true_x, true_y;
1841 if (is_tab) {
1842 ++last_tab_idx;
1843 }
1844 true_x = x + hb->glyph_pos[t].x_offset;
1845 true_y = y + hb->glyph_pos[t].y_offset;
1846 shift_x64 = (((x64 + true_x) >> 4) & 0b0011) << 4;
1847 shift_y64 = ((4 - (((y64 + true_y) >> 4) & 0b0011)) & 0b0011) << 4;
1848
1849 ret = load_glyph(ctx, &glyph, hb->glyph_info[t].codepoint, shift_x64, shift_y64);
1850 if (ret != 0) {
1851 return ret;
1852 }
1853 g_info->code = hb->glyph_info[t].codepoint;
1854 g_info->x = (x64 + true_x) >> 6;
1855 g_info->y = ((y64 + true_y) >> 6) + (shift_y64 > 0 ? 1 : 0);
1856 g_info->shift_x64 = shift_x64;
1857 g_info->shift_y64 = shift_y64;
1858
1859 if (!is_tab) {
1860 x += hb->glyph_pos[t].x_advance;
1861 } else {
1862 int size = s->blank_advance64 * s->tabsize;
1863 x = (x / size + 1) * size;
1864 }
1865 y += hb->glyph_pos[t].y_advance;
1866 }
1604 1867
1605 if (s->shadowx || s->shadowy) {1868 y += metrics.line_height64 + s->line_spacing * 64;
1606 if ((ret = draw_glyphs(s, frame, width, height,1869 x = 0;
1607 &shadowcolor, s->shadowx, s->shadowy, 0)) < 0)
1608 return ret;
1609 }1870 }
1610 1871
1611 if (s->borderw) {1872 metrics.rect_x = s->x;
1612 if ((ret = draw_glyphs(s, frame, width, height,1873 metrics.rect_y = s->y;
1613 &bordercolor, 0, 0, s->borderw)) < 0)1874
1875 if (!s->draw_box) {
1876 // Create a border for the clipping region to take into account subpixel
1877 // errors in text measurement and effects.
1878 int borderoffset = s->borderw ? FFMAX(s->borderw, 0) : 0;
1879 s->bb_left = borderoffset + (s->shadowx < 0 ? FFABS(s->shadowx) : 0) + 1;
1880 s->bb_top = borderoffset + (s->shadowy < 0 ? FFABS(s->shadowy) : 0) + 1;
1881 s->bb_right = borderoffset + (s->shadowx > 0 ? s->shadowx : 0) + 1;
1882 s->bb_bottom = borderoffset + (s->shadowy > 0 ? s->shadowy : 0) + 1;
1883 }
1884
1885 /* Check if the whole box is out of the frame */
1886 is_outside = metrics.rect_x - s->bb_left >= width ||
1887 metrics.rect_y - s->bb_top >= height ||
1888 metrics.rect_x + metrics.width + s->bb_right <= 0 ||
1889 metrics.rect_y + metrics.height + s->bb_bottom <= 0;
1890
1891 if (!is_outside) {
1892 /* draw box */
1893 if (s->draw_box) {
1894 rec_x = metrics.rect_x - s->bb_left;
1895 rec_y = metrics.rect_y - s->bb_top;
1896 rec_width = metrics.width + s->bb_right + s->bb_left;
1897 rec_height = metrics.height + s->bb_bottom + s->bb_top;
1898 ff_blend_rectangle(&s->dc, &boxcolor,
1899 frame->data, frame->linesize, width, height,
1900 rec_x, rec_y, rec_width, rec_height);
1901 }
1902
1903 if (s->shadowx || s->shadowy) {
1904 if ((ret = draw_glyphs(s, frame, &shadowcolor, &metrics,
1905 s->shadowx, s->shadowy, s->borderw)) < 0) {
1906 return ret;
1907 }
1908 }
1909
1910 if (s->borderw) {
1911 if ((ret = draw_glyphs(s, frame, &bordercolor, &metrics,
1912 0, 0, s->borderw)) < 0) {
1913 return ret;
1914 }
1915 }
1916
1917 if ((ret = draw_glyphs(s, frame, &fontcolor, &metrics, 0,
1918 0, 0)) < 0) {
1614 return ret;1919 return ret;
1920 }
1615 }1921 }
1616 if ((ret = draw_glyphs(s, frame, width, height,1922
1617 &fontcolor, 0, 0, 0)) < 0)1923 // FREE data structures
1618 return ret;1924 for (int l = 0; l < s->line_count; ++l) {
1925 TextLine *line = &s->lines[l];
1926 av_freep(&line->glyphs);
1927 hb_destroy(&line->hb_data);
1928 }
1929 av_freep(&s->lines);
1930 av_freep(&s->tab_clusters);
1619 1931
1620 return 0;1932 return 0;
1621}1933}
@@ -1691,14 +2003,9 @@ FF_ENABLE_DEPRECATION_WARNINGS · 69 unmodified lines
1691 s->x = bbox->x;2003 s->x = bbox->x;
1692 s->y = bbox->y - s->fontsize;2004 s->y = bbox->y - s->fontsize;
1693 }2005 }
1694 draw_text(ctx, frame, frame->width, frame->height);2006 draw_text(ctx, frame);
1695 }2007 }
1696 2008
1697 av_log(ctx, AV_LOG_DEBUG, "n:%d t:%f text_w:%d text_h:%d x:%d y:%d\n",
1698 (int)s->var_values[VAR_N], s->var_values[VAR_T],
1699 (int)s->var_values[VAR_TEXT_W], (int)s->var_values[VAR_TEXT_H],
1700 s->x, s->y);
1701
1702 return ff_filter_frame(outlink, frame);2009 return ff_filter_frame(outlink, frame);
1703}2010}
1704 2011

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t