Text can now be aligned vertically (top, middle, bottom) and horizontally (left, center, right) relative to the background box.
Showing the changes to libavfilter (1 of 2 files). Show all
Unified · Split
| @@ -309,12 +309,14 @@ typedef struct DrawTextContext { · 308 unmodified lines | ||
| 309 | 309 | |
| 310 | 310 | int boxw; ///< the value of the boxw parameter |
| 311 | 311 | int boxh; ///< the value of the boxh parameter |
| 312 | + uint8_t *text_align; ///< the horizontal and vertical text alignment | |
| 312 | 313 | |
| 313 | 314 | TextLine *lines; ///< computed information about text lines |
| 314 | 315 | int line_count; ///< the number of text lines |
| 315 | 316 | uint32_t *tab_clusters; ///< the position of tab characters in the text |
| 316 | 317 | int tab_count; ///< the number of tab characters |
| 317 | 318 | int blank_advance64; ///< the size of the space character |
| 319 | + int tab_warning_printed; ///< ensure the tab warning to be printed only once | |
| 318 | 320 | } DrawTextContext; |
| 319 | 321 | |
| 320 | 322 | #define OFFSET(x) offsetof(DrawTextContext, x) |
| @@ -333,6 +335,7 @@ static const AVOption drawtext_options[]= { · 12 unmodified lines | ||
| 333 | 335 | {"boxborderw", "set box borders width", OFFSET(boxborderw), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS}, |
| 334 | 336 | {"line_spacing", "set line spacing in pixels", OFFSET(line_spacing), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, FLAGS}, |
| 335 | 337 | {"fontsize", "set font size", OFFSET(fontsize_expr), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS}, |
| 338 | + {"text_align", "set text alignment", OFFSET(text_align), AV_OPT_TYPE_STRING, {.str="TL"}, 0, 0, FLAGS}, | |
| 336 | 339 | {"x", "set x expression", OFFSET(x_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS}, |
| 337 | 340 | {"y", "set y expression", OFFSET(y_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS}, |
| 338 | 341 | {"boxw", "set box width", OFFSET(boxw), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS}, |
| @@ -851,6 +854,19 @@ static int string_to_array(const char *source, int *result, int result_size) · 512 unmodified lines | ||
| 851 | 854 | return counter; |
| 852 | 855 | } |
| 853 | 856 | |
| 857 | +static int validate_text_align(char* text_align) | |
| 858 | +{ | |
| 859 | + int err = 0; | |
| 860 | + if (strlen(text_align) != 2 | |
| 861 | + || strchr("LCRTMB", text_align[0]) == NULL || strchr("LCRTMB", text_align[1]) == NULL | |
| 862 | + || (strchr("TMB", text_align[0]) != NULL && strchr("LCR", text_align[1]) == NULL) | |
| 863 | + || (strchr("LCR", text_align[0]) != NULL && strchr("TMB", text_align[1]) == NULL)) { | |
| 864 | + err = AVERROR(EINVAL); | |
| 865 | + } | |
| 866 | + | |
| 867 | + return err; | |
| 868 | +} | |
| 869 | + | |
| 854 | 870 | static av_cold int init(AVFilterContext *ctx) |
| 855 | 871 | { |
| 856 | 872 | int err; |
| @@ -916,6 +932,14 @@ static av_cold int init(AVFilterContext *ctx) · 59 unmodified lines | ||
| 916 | 932 | return AVERROR(EINVAL); |
| 917 | 933 | } |
| 918 | 934 | |
| 935 | + if ((err = validate_text_align(s->text_align))) { | |
| 936 | + av_log(ctx, AV_LOG_ERROR, | |
| 937 | + "The value provided for parameter 'text_align' is not valid,\n"); | |
| 938 | + av_log(ctx, AV_LOG_ERROR, | |
| 939 | + "please specify a two characters string containing only one letter for horizontal alignment ('LCR') and one for vertical alignment ('TMB')\n"); | |
| 940 | + return err; | |
| 941 | + } | |
| 942 | + | |
| 919 | 943 | #if CONFIG_LIBFRIBIDI |
| 920 | 944 | if (s->text_shaping) |
| 921 | 945 | if ((err = shape_text(ctx)) < 0) |
| @@ -1486,13 +1510,32 @@ static int draw_glyphs(DrawTextContext *s, AVFrame *frame, · 564 unmodified lines | ||
| 1486 | 1510 | Glyph dummy = { 0 }, *glyph; |
| 1487 | 1511 | FT_Bitmap bitmap; |
| 1488 | 1512 | FT_BitmapGlyph b_glyph; |
| 1513 | + uint8_t j_center = 0, j_right = 0, j_middle = 0, j_bottom = 0; | |
| 1514 | + int line_w, offset_y = 0; | |
| 1489 | 1515 | int clip_x = 0, clip_y = 0; |
| 1490 | 1516 | |
| 1517 | + j_center = strstr(s->text_align, "C") > 0; | |
| 1518 | + j_right = strstr(s->text_align, "R") > 0; | |
| 1519 | + j_middle = strstr(s->text_align, "M") > 0; | |
| 1520 | + j_bottom = strstr(s->text_align, "B") > 0; | |
| 1521 | + | |
| 1522 | + if (j_middle) { | |
| 1523 | + offset_y = (s->box_height - metrics->height) / 2; | |
| 1524 | + } else if (j_bottom) { | |
| 1525 | + offset_y = s->box_height - metrics->height; | |
| 1526 | + } | |
| 1527 | + | |
| 1528 | + if ((j_right || j_center) && !s->tab_warning_printed && s->tab_count > 0) { | |
| 1529 | + s->tab_warning_printed = 1; | |
| 1530 | + av_log(s, AV_LOG_WARNING, "Tab characters are only supported with left horizontal alignment\n"); | |
| 1531 | + } | |
| 1532 | + | |
| 1491 | 1533 | clip_x = FFMIN(metrics->rect_x + s->box_width + s->bb_right, frame->width); |
| 1492 | 1534 | clip_y = FFMIN(metrics->rect_y + s->box_height + s->bb_bottom, frame->height); |
| 1493 | 1535 | |
| 1494 | 1536 | for (l = 0; l < s->line_count; ++l) { |
| 1495 | 1537 | TextLine *line = &s->lines[l]; |
| 1538 | + line_w = POS_CEIL(line->width64, 64); | |
| 1496 | 1539 | for (g = 0; g < line->hb_data.glyph_count; ++g) { |
| 1497 | 1540 | info = &line->glyphs[g]; |
| 1498 | 1541 | dummy.fontsize = s->fontsize; |
| @@ -1506,10 +1549,16 @@ static int draw_glyphs(DrawTextContext *s, AVFrame *frame, · 7 unmodified lines | ||
| 1506 | 1549 | b_glyph = borderw ? glyph->border_bglyph[idx] : glyph->bglyph[idx]; |
| 1507 | 1550 | bitmap = b_glyph->bitmap; |
| 1508 | 1551 | x1 = x + info->x + b_glyph->left; |
| 1509 | − y1 = y + info->y - b_glyph->top; | |
| 1552 | + y1 = y + info->y - b_glyph->top + offset_y; | |
| 1510 | 1553 | w1 = bitmap.width; |
| 1511 | 1554 | h1 = bitmap.rows; |
| 1512 | 1555 | |
| 1556 | + if (j_center) { | |
| 1557 | + x1 += (s->box_width - line_w) / 2; | |
| 1558 | + } else if (j_right) { | |
| 1559 | + x1 += s->box_width - line_w; | |
| 1560 | + } | |
| 1561 | + | |
| 1513 | 1562 | // Offset of the glyph's bitmap in the visible region |
| 1514 | 1563 | dx = dy = 0; |
| 1515 | 1564 | if (x1 < metrics->rect_x - s->bb_left) { |