|
|
|
@@ -45,17 +45,13 @@ |
|
|
|
#include FT_FREETYPE_H |
|
|
|
#include FT_GLYPH_H |
|
|
|
|
|
|
|
#define MAX_EXPANDED_TEXT_SIZE 2048 |
|
|
|
|
|
|
|
typedef struct { |
|
|
|
const AVClass *class; |
|
|
|
char *fontfile; ///< font to be used |
|
|
|
char *text; ///< text to be drawn |
|
|
|
uint8_t *fontfile; ///< font to be used |
|
|
|
uint8_t *text; ///< text to be drawn |
|
|
|
uint8_t *text_priv; ///< used to detect whether text changed |
|
|
|
int ft_load_flags; ///< flags used for loading fonts, see FT_LOAD_* |
|
|
|
/** buffer containing the text expanded by strftime */ |
|
|
|
char expanded_text[MAX_EXPANDED_TEXT_SIZE]; |
|
|
|
/** positions for each element in the text */ |
|
|
|
FT_Vector positions[MAX_EXPANDED_TEXT_SIZE]; |
|
|
|
FT_Vector *positions; ///< positions for each element in the text |
|
|
|
char *textfile; ///< file with text to be drawn |
|
|
|
unsigned int x; ///< x position to start drawing text |
|
|
|
unsigned int y; ///< y position to start drawing text |
|
|
|
@@ -157,9 +153,10 @@ typedef struct { |
|
|
|
int bitmap_top; |
|
|
|
} Glyph; |
|
|
|
|
|
|
|
static int glyph_cmp(const Glyph *a, const Glyph *b) |
|
|
|
static int glyph_cmp(void *key, const void *b) |
|
|
|
{ |
|
|
|
int64_t diff = (int64_t)a->code - (int64_t)b->code; |
|
|
|
const Glyph *a = key, *bb = b; |
|
|
|
int64_t diff = (int64_t)a->code - (int64_t)bb->code; |
|
|
|
return diff > 0 ? 1 : diff < 0 ? -1 : 0; |
|
|
|
} |
|
|
|
|
|
|
|
@@ -169,21 +166,26 @@ static int glyph_cmp(const Glyph *a, const Glyph *b) |
|
|
|
static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code) |
|
|
|
{ |
|
|
|
DrawTextContext *dtext = ctx->priv; |
|
|
|
Glyph *glyph = av_mallocz(sizeof(Glyph)); |
|
|
|
Glyph *glyph; |
|
|
|
struct AVTreeNode *node = NULL; |
|
|
|
int ret; |
|
|
|
|
|
|
|
/* load glyph into dtext->face->glyph */ |
|
|
|
ret = FT_Load_Char(dtext->face, code, dtext->ft_load_flags); |
|
|
|
if (ret) |
|
|
|
if (FT_Load_Char(dtext->face, code, dtext->ft_load_flags)) |
|
|
|
return AVERROR(EINVAL); |
|
|
|
|
|
|
|
/* save glyph */ |
|
|
|
if (!(glyph = av_mallocz(sizeof(*glyph))) || |
|
|
|
!(glyph->glyph = av_mallocz(sizeof(*glyph->glyph)))) { |
|
|
|
ret = AVERROR(ENOMEM); |
|
|
|
goto error; |
|
|
|
} |
|
|
|
glyph->code = code; |
|
|
|
glyph->glyph = av_mallocz(sizeof(FT_Glyph)); |
|
|
|
ret = FT_Get_Glyph(dtext->face->glyph, glyph->glyph); |
|
|
|
if (ret) |
|
|
|
return AVERROR(EINVAL); |
|
|
|
|
|
|
|
if (FT_Get_Glyph(dtext->face->glyph, glyph->glyph)) { |
|
|
|
ret = AVERROR(EINVAL); |
|
|
|
goto error; |
|
|
|
} |
|
|
|
|
|
|
|
glyph->bitmap = dtext->face->glyph->bitmap; |
|
|
|
glyph->bitmap_left = dtext->face->glyph->bitmap_left; |
|
|
|
@@ -194,19 +196,29 @@ static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code) |
|
|
|
FT_Glyph_Get_CBox(*glyph->glyph, ft_glyph_bbox_pixels, &glyph->bbox); |
|
|
|
|
|
|
|
/* cache the newly created glyph */ |
|
|
|
if (!node) |
|
|
|
node = av_mallocz(av_tree_node_size); |
|
|
|
av_tree_insert(&dtext->glyphs, glyph, (void *)glyph_cmp, &node); |
|
|
|
if (!(node = av_mallocz(av_tree_node_size))) { |
|
|
|
ret = AVERROR(ENOMEM); |
|
|
|
goto error; |
|
|
|
} |
|
|
|
av_tree_insert(&dtext->glyphs, glyph, glyph_cmp, &node); |
|
|
|
|
|
|
|
if (glyph_ptr) |
|
|
|
*glyph_ptr = glyph; |
|
|
|
return 0; |
|
|
|
|
|
|
|
error: |
|
|
|
if (glyph) |
|
|
|
av_freep(&glyph->glyph); |
|
|
|
av_freep(&glyph); |
|
|
|
av_freep(&node); |
|
|
|
return ret; |
|
|
|
} |
|
|
|
|
|
|
|
static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque) |
|
|
|
{ |
|
|
|
int err; |
|
|
|
DrawTextContext *dtext = ctx->priv; |
|
|
|
Glyph *glyph; |
|
|
|
|
|
|
|
dtext->class = &drawtext_class; |
|
|
|
av_opt_set_defaults2(dtext, 0, 0); |
|
|
|
@@ -294,14 +306,15 @@ static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque) |
|
|
|
/* load the fallback glyph with code 0 */ |
|
|
|
load_glyph(ctx, NULL, 0); |
|
|
|
|
|
|
|
/* set the tabsize in pixels */ |
|
|
|
if ((err = load_glyph(ctx, &glyph, ' ') < 0)) { |
|
|
|
av_log(ctx, AV_LOG_ERROR, "Could not set tabsize.\n"); |
|
|
|
return err; |
|
|
|
} |
|
|
|
dtext->tabsize *= glyph->advance; |
|
|
|
|
|
|
|
#if !HAVE_LOCALTIME_R |
|
|
|
av_log(ctx, AV_LOG_WARNING, "strftime() expansion unavailable!\n"); |
|
|
|
#else |
|
|
|
if (strlen(dtext->text) >= MAX_EXPANDED_TEXT_SIZE) { |
|
|
|
av_log(ctx, AV_LOG_ERROR, |
|
|
|
"Impossible to print text, string is too big\n"); |
|
|
|
return AVERROR(EINVAL); |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
return 0; |
|
|
|
@@ -338,6 +351,7 @@ static av_cold void uninit(AVFilterContext *ctx) |
|
|
|
av_freep(&dtext->text); |
|
|
|
av_freep(&dtext->fontcolor_string); |
|
|
|
av_freep(&dtext->boxcolor_string); |
|
|
|
av_freep(&dtext->positions); |
|
|
|
av_freep(&dtext->shadowcolor_string); |
|
|
|
av_tree_enumerate(dtext->glyphs, NULL, NULL, glyph_enu_free); |
|
|
|
av_tree_destroy(dtext->glyphs); |
|
|
|
@@ -393,7 +407,7 @@ static int config_input(AVFilterLink *inlink) |
|
|
|
luma_pos = ((x) ) + ((y) ) * picref->linesize[0]; \ |
|
|
|
alpha = yuva_color[3] * (val) * 129; \ |
|
|
|
picref->data[0][luma_pos] = (alpha * yuva_color[0] + (255*255*129 - alpha) * picref->data[0][luma_pos] ) >> 23; \ |
|
|
|
if(((x) & ((1<<(hsub))-1))==0 && ((y) & ((1<<(vsub))-1))==0){\ |
|
|
|
if (((x) & ((1<<(hsub)) - 1)) == 0 && ((y) & ((1<<(vsub)) - 1)) == 0) {\ |
|
|
|
chroma_pos1 = ((x) >> (hsub)) + ((y) >> (vsub)) * picref->linesize[1]; \ |
|
|
|
chroma_pos2 = ((x) >> (hsub)) + ((y) >> (vsub)) * picref->linesize[2]; \ |
|
|
|
picref->data[1][chroma_pos1] = (alpha * yuva_color[1] + (255*255*129 - alpha) * picref->data[1][chroma_pos1]) >> 23; \ |
|
|
|
@@ -403,7 +417,7 @@ static int config_input(AVFilterLink *inlink) |
|
|
|
|
|
|
|
static inline int draw_glyph_yuv(AVFilterBufferRef *picref, FT_Bitmap *bitmap, unsigned int x, |
|
|
|
unsigned int y, unsigned int width, unsigned int height, |
|
|
|
unsigned char yuva_color[4], int hsub, int vsub) |
|
|
|
const uint8_t yuva_color[4], int hsub, int vsub) |
|
|
|
{ |
|
|
|
int r, c, alpha; |
|
|
|
unsigned int luma_pos, chroma_pos1, chroma_pos2; |
|
|
|
@@ -439,7 +453,7 @@ static inline int draw_glyph_yuv(AVFilterBufferRef *picref, FT_Bitmap *bitmap, u |
|
|
|
static inline int draw_glyph_rgb(AVFilterBufferRef *picref, FT_Bitmap *bitmap, |
|
|
|
unsigned int x, unsigned int y, |
|
|
|
unsigned int width, unsigned int height, int pixel_step, |
|
|
|
unsigned char rgba_color[4], uint8_t rgba_map[4]) |
|
|
|
const uint8_t rgba_color[4], const uint8_t rgba_map[4]) |
|
|
|
{ |
|
|
|
int r, c, alpha; |
|
|
|
uint8_t *p; |
|
|
|
@@ -495,10 +509,15 @@ static inline void drawbox(AVFilterBufferRef *picref, unsigned int x, unsigned i |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static inline int is_newline(uint32_t c) |
|
|
|
{ |
|
|
|
return (c == '\n' || c == '\r' || c == '\f' || c == '\v'); |
|
|
|
} |
|
|
|
|
|
|
|
static int draw_glyphs(DrawTextContext *dtext, AVFilterBufferRef *picref, |
|
|
|
int width, int height, const uint8_t rgbcolor[4], const uint8_t yuvcolor[4], int x, int y) |
|
|
|
{ |
|
|
|
char *text = HAVE_LOCALTIME_R ? dtext->expanded_text : dtext->text; |
|
|
|
char *text = dtext->text; |
|
|
|
uint32_t code = 0; |
|
|
|
int i; |
|
|
|
uint8_t *p; |
|
|
|
@@ -537,44 +556,53 @@ static int draw_text(AVFilterContext *ctx, AVFilterBufferRef *picref, |
|
|
|
int width, int height) |
|
|
|
{ |
|
|
|
DrawTextContext *dtext = ctx->priv; |
|
|
|
char *text = dtext->text; |
|
|
|
uint32_t code = 0, prev_code = 0; |
|
|
|
int x = 0, y = 0, i = 0, ret; |
|
|
|
int text_height, baseline; |
|
|
|
uint8_t *p; |
|
|
|
int str_w, str_w_max; |
|
|
|
int str_w = 0; |
|
|
|
int y_min = 32000, y_max = -32000; |
|
|
|
FT_Vector delta; |
|
|
|
Glyph *glyph = NULL, *prev_glyph = NULL; |
|
|
|
Glyph dummy = { 0 }; |
|
|
|
|
|
|
|
if (dtext->text != dtext->text_priv) { |
|
|
|
#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, <ime)); |
|
|
|
text = dtext->expanded_text; |
|
|
|
if (expanded_text_len == 0 && dtext->expanded_text[0] != '\0') { |
|
|
|
av_log(ctx, AV_LOG_ERROR, |
|
|
|
"Impossible to print text, string is too big\n"); |
|
|
|
return AVERROR(EINVAL); |
|
|
|
} |
|
|
|
time_t now = time(0); |
|
|
|
struct tm ltime; |
|
|
|
uint8_t *buf = NULL; |
|
|
|
int buflen = 2*strlen(dtext->text) + 1, len; |
|
|
|
|
|
|
|
localtime_r(&now, <ime); |
|
|
|
|
|
|
|
while ((buf = av_realloc(buf, buflen))) { |
|
|
|
*buf = 1; |
|
|
|
if ((len = strftime(buf, buflen, dtext->text, <ime)) != 0 || *buf == 0) |
|
|
|
break; |
|
|
|
buflen *= 2; |
|
|
|
} |
|
|
|
if (!buf) |
|
|
|
return AVERROR(ENOMEM); |
|
|
|
av_freep(&dtext->text); |
|
|
|
dtext->text = dtext->text_priv = buf; |
|
|
|
#else |
|
|
|
dtext->text_priv = dtext->text; |
|
|
|
#endif |
|
|
|
if (!(dtext->positions = av_realloc(dtext->positions, |
|
|
|
strlen(dtext->text)*sizeof(*dtext->positions)))) |
|
|
|
return AVERROR(ENOMEM); |
|
|
|
} |
|
|
|
|
|
|
|
str_w = str_w_max = 0; |
|
|
|
x = dtext->x; |
|
|
|
y = dtext->y; |
|
|
|
|
|
|
|
/* load and cache glyphs */ |
|
|
|
for (i = 0, p = text; *p; i++) { |
|
|
|
for (i = 0, p = dtext->text; *p; i++) { |
|
|
|
GET_UTF8(code, *p++, continue;); |
|
|
|
|
|
|
|
/* get glyph */ |
|
|
|
dummy.code = code; |
|
|
|
glyph = av_tree_find(dtext->glyphs, &dummy, (void *)glyph_cmp, NULL); |
|
|
|
glyph = av_tree_find(dtext->glyphs, &dummy, glyph_cmp, NULL); |
|
|
|
if (!glyph) |
|
|
|
load_glyph(ctx, &glyph, code); |
|
|
|
|
|
|
|
@@ -586,17 +614,25 @@ static int draw_text(AVFilterContext *ctx, AVFilterBufferRef *picref, |
|
|
|
|
|
|
|
/* compute and save position for each glyph */ |
|
|
|
glyph = NULL; |
|
|
|
for (i = 0, p = text; *p; i++) { |
|
|
|
for (i = 0, p = dtext->text; *p; i++) { |
|
|
|
GET_UTF8(code, *p++, continue;); |
|
|
|
|
|
|
|
/* skip the \n in the sequence \r\n */ |
|
|
|
if (prev_code == '\r' && code == '\n') |
|
|
|
continue; |
|
|
|
|
|
|
|
prev_code = code; |
|
|
|
if (is_newline(code)) { |
|
|
|
str_w = FFMAX(str_w, x - dtext->x); |
|
|
|
y += text_height; |
|
|
|
x = dtext->x; |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
/* get glyph */ |
|
|
|
prev_glyph = glyph; |
|
|
|
dummy.code = code; |
|
|
|
glyph = av_tree_find(dtext->glyphs, &dummy, (void *)glyph_cmp, NULL); |
|
|
|
glyph = av_tree_find(dtext->glyphs, &dummy, glyph_cmp, NULL); |
|
|
|
|
|
|
|
/* kerning */ |
|
|
|
if (dtext->use_kerning && prev_glyph && glyph->code) { |
|
|
|
@@ -605,9 +641,8 @@ static int draw_text(AVFilterContext *ctx, AVFilterBufferRef *picref, |
|
|
|
x += delta.x >> 6; |
|
|
|
} |
|
|
|
|
|
|
|
if (x + glyph->advance >= width || code == '\r' || code == '\n') { |
|
|
|
if (x + glyph->advance >= width) |
|
|
|
str_w_max = width - dtext->x - 1; |
|
|
|
if (x + glyph->bbox.xMax >= width) { |
|
|
|
str_w = FFMAX(str_w, x - dtext->x); |
|
|
|
y += text_height; |
|
|
|
x = dtext->x; |
|
|
|
} |
|
|
|
@@ -615,38 +650,27 @@ static int draw_text(AVFilterContext *ctx, AVFilterBufferRef *picref, |
|
|
|
/* save position */ |
|
|
|
dtext->positions[i].x = x + glyph->bitmap_left; |
|
|
|
dtext->positions[i].y = y - glyph->bitmap_top + baseline; |
|
|
|
if (code != '\n' && code != '\r') { |
|
|
|
int advance = glyph->advance; |
|
|
|
if (code == '\t') |
|
|
|
advance *= dtext->tabsize; |
|
|
|
x += advance; |
|
|
|
str_w += advance; |
|
|
|
} |
|
|
|
prev_code = code; |
|
|
|
if (code == '\t') x = (x / dtext->tabsize + 1)*dtext->tabsize; |
|
|
|
else x += glyph->advance; |
|
|
|
} |
|
|
|
|
|
|
|
y += text_height; |
|
|
|
if (str_w_max == 0) |
|
|
|
str_w_max = str_w; |
|
|
|
str_w = FFMIN(width - dtext->x - 1, FFMAX(str_w, x - dtext->x)); |
|
|
|
y = FFMIN(y + text_height, height - 1); |
|
|
|
|
|
|
|
/* draw box */ |
|
|
|
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, |
|
|
|
if (dtext->draw_box) |
|
|
|
drawbox(picref, dtext->x, dtext->y, str_w, y-dtext->y, |
|
|
|
dtext->box_line, dtext->pixel_step, dtext->boxcolor, |
|
|
|
dtext->hsub, dtext->vsub, dtext->is_packed_rgb, dtext->rgba_map); |
|
|
|
} |
|
|
|
|
|
|
|
if(dtext->shadowx || dtext->shadowy){ |
|
|
|
if((ret=draw_glyphs(dtext, picref, width, height, dtext->shadowcolor_rgba, dtext->shadowcolor, dtext->shadowx, dtext->shadowy))<0) |
|
|
|
if (dtext->shadowx || dtext->shadowy) { |
|
|
|
if ((ret = draw_glyphs(dtext, picref, width, height, dtext->shadowcolor_rgba, |
|
|
|
dtext->shadowcolor, dtext->shadowx, dtext->shadowy)) < 0) |
|
|
|
return ret; |
|
|
|
} |
|
|
|
|
|
|
|
if((ret=draw_glyphs(dtext, picref, width, height, dtext->fontcolor_rgba, dtext->fontcolor, 0, 0))<0) |
|
|
|
if ((ret = draw_glyphs(dtext, picref, width, height, dtext->fontcolor_rgba, |
|
|
|
dtext->fontcolor, 0, 0)) < 0) |
|
|
|
return ret; |
|
|
|
|
|
|
|
return 0; |
|
|
|
|