Libav 0.7.1
|
00001 /* 00002 * Copyright (c) 2011 Stefano Sabatini 00003 * Copyright (c) 2010 S.N. Hemanth Meenakshisundaram 00004 * Copyright (c) 2003 Gustavo Sverzut Barbieri <gsbarbieri@yahoo.com.br> 00005 * 00006 * This file is part of Libav. 00007 * 00008 * Libav is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Lesser General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2.1 of the License, or (at your option) any later version. 00012 * 00013 * Libav is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Lesser General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Lesser General Public 00019 * License along with Libav; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00021 */ 00022 00029 #include <sys/time.h> 00030 #include <time.h> 00031 00032 #include "libavutil/colorspace.h" 00033 #include "libavutil/file.h" 00034 #include "libavutil/opt.h" 00035 #include "libavutil/parseutils.h" 00036 #include "libavutil/pixdesc.h" 00037 #include "libavutil/tree.h" 00038 #include "avfilter.h" 00039 #include "drawutils.h" 00040 00041 #undef time 00042 00043 #include <ft2build.h> 00044 #include <freetype/config/ftheader.h> 00045 #include FT_FREETYPE_H 00046 #include FT_GLYPH_H 00047 00048 typedef struct { 00049 const AVClass *class; 00050 uint8_t *fontfile; 00051 uint8_t *text; 00052 uint8_t *expanded_text; 00053 size_t expanded_text_size; 00054 int ft_load_flags; 00055 FT_Vector *positions; 00056 size_t nb_positions; 00057 char *textfile; 00058 unsigned int x; 00059 unsigned int y; 00060 int shadowx, shadowy; 00061 unsigned int fontsize; 00062 char *fontcolor_string; 00063 char *boxcolor_string; 00064 char *shadowcolor_string; 00065 uint8_t fontcolor[4]; 00066 uint8_t boxcolor[4]; 00067 uint8_t shadowcolor[4]; 00068 uint8_t fontcolor_rgba[4]; 00069 uint8_t boxcolor_rgba[4]; 00070 uint8_t shadowcolor_rgba[4]; 00071 00072 short int draw_box; 00073 int use_kerning; 00074 int tabsize; 00075 00076 FT_Library library; 00077 FT_Face face; 00078 struct AVTreeNode *glyphs; 00079 int hsub, vsub; 00080 int is_packed_rgb; 00081 int pixel_step[4]; 00082 uint8_t rgba_map[4]; 00083 uint8_t *box_line[4]; 00084 } DrawTextContext; 00085 00086 #define OFFSET(x) offsetof(DrawTextContext, x) 00087 00088 static const AVOption drawtext_options[]= { 00089 {"fontfile", "set font file", OFFSET(fontfile), FF_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX }, 00090 {"text", "set text", OFFSET(text), FF_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX }, 00091 {"textfile", "set text file", OFFSET(textfile), FF_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX }, 00092 {"fontcolor","set foreground color", OFFSET(fontcolor_string), FF_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX }, 00093 {"boxcolor", "set box color", OFFSET(boxcolor_string), FF_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX }, 00094 {"shadowcolor", "set shadow color", OFFSET(shadowcolor_string), FF_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX }, 00095 {"box", "set box", OFFSET(draw_box), FF_OPT_TYPE_INT, {.dbl=0}, 0, 1 }, 00096 {"fontsize", "set font size", OFFSET(fontsize), FF_OPT_TYPE_INT, {.dbl=16}, 1, 72 }, 00097 {"x", "set x", OFFSET(x), FF_OPT_TYPE_INT, {.dbl=0}, 0, INT_MAX }, 00098 {"y", "set y", OFFSET(y), FF_OPT_TYPE_INT, {.dbl=0}, 0, INT_MAX }, 00099 {"shadowx", "set x", OFFSET(shadowx), FF_OPT_TYPE_INT, {.dbl=0}, INT_MIN, INT_MAX }, 00100 {"shadowy", "set y", OFFSET(shadowy), FF_OPT_TYPE_INT, {.dbl=0}, INT_MIN, INT_MAX }, 00101 {"tabsize", "set tab size", OFFSET(tabsize), FF_OPT_TYPE_INT, {.dbl=4}, 0, INT_MAX }, 00102 00103 /* FT_LOAD_* flags */ 00104 {"ft_load_flags", "set font loading flags for libfreetype", OFFSET(ft_load_flags), FF_OPT_TYPE_FLAGS, {.dbl=FT_LOAD_DEFAULT|FT_LOAD_RENDER}, 0, INT_MAX, 0, "ft_load_flags" }, 00105 {"default", "set default", 0, FF_OPT_TYPE_CONST, {FT_LOAD_DEFAULT}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00106 {"no_scale", "set no_scale", 0, FF_OPT_TYPE_CONST, {FT_LOAD_NO_SCALE}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00107 {"no_hinting", "set no_hinting", 0, FF_OPT_TYPE_CONST, {FT_LOAD_NO_HINTING}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00108 {"render", "set render", 0, FF_OPT_TYPE_CONST, {FT_LOAD_RENDER}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00109 {"no_bitmap", "set no_bitmap", 0, FF_OPT_TYPE_CONST, {FT_LOAD_NO_BITMAP}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00110 {"vertical_layout", "set vertical_layout", 0, FF_OPT_TYPE_CONST, {FT_LOAD_VERTICAL_LAYOUT}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00111 {"force_autohint", "set force_autohint", 0, FF_OPT_TYPE_CONST, {FT_LOAD_FORCE_AUTOHINT}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00112 {"crop_bitmap", "set crop_bitmap", 0, FF_OPT_TYPE_CONST, {FT_LOAD_CROP_BITMAP}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00113 {"pedantic", "set pedantic", 0, FF_OPT_TYPE_CONST, {FT_LOAD_PEDANTIC}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00114 {"ignore_global_advance_width", "set ignore_global_advance_width", 0, FF_OPT_TYPE_CONST, {FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00115 {"no_recurse", "set no_recurse", 0, FF_OPT_TYPE_CONST, {FT_LOAD_NO_RECURSE}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00116 {"ignore_transform", "set ignore_transform", 0, FF_OPT_TYPE_CONST, {FT_LOAD_IGNORE_TRANSFORM}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00117 {"monochrome", "set monochrome", 0, FF_OPT_TYPE_CONST, {FT_LOAD_MONOCHROME}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00118 {"linear_design", "set linear_design", 0, FF_OPT_TYPE_CONST, {FT_LOAD_LINEAR_DESIGN}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00119 {"no_autohint", "set no_autohint", 0, FF_OPT_TYPE_CONST, {FT_LOAD_NO_AUTOHINT}, INT_MIN, INT_MAX, 0, "ft_load_flags" }, 00120 {NULL}, 00121 }; 00122 00123 static const char *drawtext_get_name(void *ctx) 00124 { 00125 return "drawtext"; 00126 } 00127 00128 static const AVClass drawtext_class = { 00129 "DrawTextContext", 00130 drawtext_get_name, 00131 drawtext_options 00132 }; 00133 00134 #undef __FTERRORS_H__ 00135 #define FT_ERROR_START_LIST { 00136 #define FT_ERRORDEF(e, v, s) { (e), (s) }, 00137 #define FT_ERROR_END_LIST { 0, NULL } }; 00138 00139 struct ft_error 00140 { 00141 int err; 00142 const char *err_msg; 00143 } static ft_errors[] = 00144 #include FT_ERRORS_H 00145 00146 #define FT_ERRMSG(e) ft_errors[e].err_msg 00147 00148 typedef struct { 00149 FT_Glyph *glyph; 00150 uint32_t code; 00151 FT_Bitmap bitmap; 00152 FT_BBox bbox; 00153 int advance; 00154 int bitmap_left; 00155 int bitmap_top; 00156 } Glyph; 00157 00158 static int glyph_cmp(void *key, const void *b) 00159 { 00160 const Glyph *a = key, *bb = b; 00161 int64_t diff = (int64_t)a->code - (int64_t)bb->code; 00162 return diff > 0 ? 1 : diff < 0 ? -1 : 0; 00163 } 00164 00168 static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code) 00169 { 00170 DrawTextContext *dtext = ctx->priv; 00171 Glyph *glyph; 00172 struct AVTreeNode *node = NULL; 00173 int ret; 00174 00175 /* load glyph into dtext->face->glyph */ 00176 if (FT_Load_Char(dtext->face, code, dtext->ft_load_flags)) 00177 return AVERROR(EINVAL); 00178 00179 /* save glyph */ 00180 if (!(glyph = av_mallocz(sizeof(*glyph))) || 00181 !(glyph->glyph = av_mallocz(sizeof(*glyph->glyph)))) { 00182 ret = AVERROR(ENOMEM); 00183 goto error; 00184 } 00185 glyph->code = code; 00186 00187 if (FT_Get_Glyph(dtext->face->glyph, glyph->glyph)) { 00188 ret = AVERROR(EINVAL); 00189 goto error; 00190 } 00191 00192 glyph->bitmap = dtext->face->glyph->bitmap; 00193 glyph->bitmap_left = dtext->face->glyph->bitmap_left; 00194 glyph->bitmap_top = dtext->face->glyph->bitmap_top; 00195 glyph->advance = dtext->face->glyph->advance.x >> 6; 00196 00197 /* measure text height to calculate text_height (or the maximum text height) */ 00198 FT_Glyph_Get_CBox(*glyph->glyph, ft_glyph_bbox_pixels, &glyph->bbox); 00199 00200 /* cache the newly created glyph */ 00201 if (!(node = av_mallocz(av_tree_node_size))) { 00202 ret = AVERROR(ENOMEM); 00203 goto error; 00204 } 00205 av_tree_insert(&dtext->glyphs, glyph, glyph_cmp, &node); 00206 00207 if (glyph_ptr) 00208 *glyph_ptr = glyph; 00209 return 0; 00210 00211 error: 00212 if (glyph) 00213 av_freep(&glyph->glyph); 00214 av_freep(&glyph); 00215 av_freep(&node); 00216 return ret; 00217 } 00218 00219 static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque) 00220 { 00221 int err; 00222 DrawTextContext *dtext = ctx->priv; 00223 Glyph *glyph; 00224 00225 dtext->class = &drawtext_class; 00226 av_opt_set_defaults2(dtext, 0, 0); 00227 dtext->fontcolor_string = av_strdup("black"); 00228 dtext->boxcolor_string = av_strdup("white"); 00229 dtext->shadowcolor_string = av_strdup("black"); 00230 00231 if ((err = (av_set_options_string(dtext, args, "=", ":"))) < 0) { 00232 av_log(ctx, AV_LOG_ERROR, "Error parsing options string: '%s'\n", args); 00233 return err; 00234 } 00235 00236 if (!dtext->fontfile) { 00237 av_log(ctx, AV_LOG_ERROR, "No font filename provided\n"); 00238 return AVERROR(EINVAL); 00239 } 00240 00241 if (dtext->textfile) { 00242 uint8_t *textbuf; 00243 size_t textbuf_size; 00244 00245 if (dtext->text) { 00246 av_log(ctx, AV_LOG_ERROR, 00247 "Both text and text file provided. Please provide only one\n"); 00248 return AVERROR(EINVAL); 00249 } 00250 if ((err = av_file_map(dtext->textfile, &textbuf, &textbuf_size, 0, ctx)) < 0) { 00251 av_log(ctx, AV_LOG_ERROR, 00252 "The text file '%s' could not be read or is empty\n", 00253 dtext->textfile); 00254 return err; 00255 } 00256 00257 if (!(dtext->text = av_malloc(textbuf_size+1))) 00258 return AVERROR(ENOMEM); 00259 memcpy(dtext->text, textbuf, textbuf_size); 00260 dtext->text[textbuf_size] = 0; 00261 av_file_unmap(textbuf, textbuf_size); 00262 } 00263 00264 if (!dtext->text) { 00265 av_log(ctx, AV_LOG_ERROR, 00266 "Either text or a valid file must be provided\n"); 00267 return AVERROR(EINVAL); 00268 } 00269 00270 if ((err = av_parse_color(dtext->fontcolor_rgba, dtext->fontcolor_string, -1, ctx))) { 00271 av_log(ctx, AV_LOG_ERROR, 00272 "Invalid font color '%s'\n", dtext->fontcolor_string); 00273 return err; 00274 } 00275 00276 if ((err = av_parse_color(dtext->boxcolor_rgba, dtext->boxcolor_string, -1, ctx))) { 00277 av_log(ctx, AV_LOG_ERROR, 00278 "Invalid box color '%s'\n", dtext->boxcolor_string); 00279 return err; 00280 } 00281 00282 if ((err = av_parse_color(dtext->shadowcolor_rgba, dtext->shadowcolor_string, -1, ctx))) { 00283 av_log(ctx, AV_LOG_ERROR, 00284 "Invalid shadow color '%s'\n", dtext->shadowcolor_string); 00285 return err; 00286 } 00287 00288 if ((err = FT_Init_FreeType(&(dtext->library)))) { 00289 av_log(ctx, AV_LOG_ERROR, 00290 "Could not load FreeType: %s\n", FT_ERRMSG(err)); 00291 return AVERROR(EINVAL); 00292 } 00293 00294 /* load the face, and set up the encoding, which is by default UTF-8 */ 00295 if ((err = FT_New_Face(dtext->library, dtext->fontfile, 0, &dtext->face))) { 00296 av_log(ctx, AV_LOG_ERROR, "Could not load fontface from file '%s': %s\n", 00297 dtext->fontfile, FT_ERRMSG(err)); 00298 return AVERROR(EINVAL); 00299 } 00300 if ((err = FT_Set_Pixel_Sizes(dtext->face, 0, dtext->fontsize))) { 00301 av_log(ctx, AV_LOG_ERROR, "Could not set font size to %d pixels: %s\n", 00302 dtext->fontsize, FT_ERRMSG(err)); 00303 return AVERROR(EINVAL); 00304 } 00305 00306 dtext->use_kerning = FT_HAS_KERNING(dtext->face); 00307 00308 /* load the fallback glyph with code 0 */ 00309 load_glyph(ctx, NULL, 0); 00310 00311 /* set the tabsize in pixels */ 00312 if ((err = load_glyph(ctx, &glyph, ' ') < 0)) { 00313 av_log(ctx, AV_LOG_ERROR, "Could not set tabsize.\n"); 00314 return err; 00315 } 00316 dtext->tabsize *= glyph->advance; 00317 00318 #if !HAVE_LOCALTIME_R 00319 av_log(ctx, AV_LOG_WARNING, "strftime() expansion unavailable!\n"); 00320 #endif 00321 00322 return 0; 00323 } 00324 00325 static int query_formats(AVFilterContext *ctx) 00326 { 00327 static const enum PixelFormat pix_fmts[] = { 00328 PIX_FMT_ARGB, PIX_FMT_RGBA, 00329 PIX_FMT_ABGR, PIX_FMT_BGRA, 00330 PIX_FMT_RGB24, PIX_FMT_BGR24, 00331 PIX_FMT_YUV420P, PIX_FMT_YUV444P, 00332 PIX_FMT_YUV422P, PIX_FMT_YUV411P, 00333 PIX_FMT_YUV410P, PIX_FMT_YUV440P, 00334 PIX_FMT_NONE 00335 }; 00336 00337 avfilter_set_common_formats(ctx, avfilter_make_format_list(pix_fmts)); 00338 return 0; 00339 } 00340 00341 static int glyph_enu_free(void *opaque, void *elem) 00342 { 00343 av_free(elem); 00344 return 0; 00345 } 00346 00347 static av_cold void uninit(AVFilterContext *ctx) 00348 { 00349 DrawTextContext *dtext = ctx->priv; 00350 int i; 00351 00352 av_freep(&dtext->fontfile); 00353 av_freep(&dtext->text); 00354 av_freep(&dtext->expanded_text); 00355 av_freep(&dtext->fontcolor_string); 00356 av_freep(&dtext->boxcolor_string); 00357 av_freep(&dtext->positions); 00358 av_freep(&dtext->shadowcolor_string); 00359 av_tree_enumerate(dtext->glyphs, NULL, NULL, glyph_enu_free); 00360 av_tree_destroy(dtext->glyphs); 00361 dtext->glyphs = 0; 00362 FT_Done_Face(dtext->face); 00363 FT_Done_FreeType(dtext->library); 00364 00365 for (i = 0; i < 4; i++) { 00366 av_freep(&dtext->box_line[i]); 00367 dtext->pixel_step[i] = 0; 00368 } 00369 00370 } 00371 00372 static int config_input(AVFilterLink *inlink) 00373 { 00374 DrawTextContext *dtext = inlink->dst->priv; 00375 const AVPixFmtDescriptor *pix_desc = &av_pix_fmt_descriptors[inlink->format]; 00376 int ret; 00377 00378 dtext->hsub = pix_desc->log2_chroma_w; 00379 dtext->vsub = pix_desc->log2_chroma_h; 00380 00381 if ((ret = 00382 ff_fill_line_with_color(dtext->box_line, dtext->pixel_step, 00383 inlink->w, dtext->boxcolor, 00384 inlink->format, dtext->boxcolor_rgba, 00385 &dtext->is_packed_rgb, dtext->rgba_map)) < 0) 00386 return ret; 00387 00388 if (!dtext->is_packed_rgb) { 00389 uint8_t *rgba = dtext->fontcolor_rgba; 00390 dtext->fontcolor[0] = RGB_TO_Y_CCIR(rgba[0], rgba[1], rgba[2]); 00391 dtext->fontcolor[1] = RGB_TO_U_CCIR(rgba[0], rgba[1], rgba[2], 0); 00392 dtext->fontcolor[2] = RGB_TO_V_CCIR(rgba[0], rgba[1], rgba[2], 0); 00393 dtext->fontcolor[3] = rgba[3]; 00394 rgba = dtext->shadowcolor_rgba; 00395 dtext->shadowcolor[0] = RGB_TO_Y_CCIR(rgba[0], rgba[1], rgba[2]); 00396 dtext->shadowcolor[1] = RGB_TO_U_CCIR(rgba[0], rgba[1], rgba[2], 0); 00397 dtext->shadowcolor[2] = RGB_TO_V_CCIR(rgba[0], rgba[1], rgba[2], 0); 00398 dtext->shadowcolor[3] = rgba[3]; 00399 } 00400 00401 return 0; 00402 } 00403 00404 #define GET_BITMAP_VAL(r, c) \ 00405 bitmap->pixel_mode == FT_PIXEL_MODE_MONO ? \ 00406 (bitmap->buffer[(r) * bitmap->pitch + ((c)>>3)] & (0x80 >> ((c)&7))) * 255 : \ 00407 bitmap->buffer[(r) * bitmap->pitch + (c)] 00408 00409 #define SET_PIXEL_YUV(picref, yuva_color, val, x, y, hsub, vsub) { \ 00410 luma_pos = ((x) ) + ((y) ) * picref->linesize[0]; \ 00411 alpha = yuva_color[3] * (val) * 129; \ 00412 picref->data[0][luma_pos] = (alpha * yuva_color[0] + (255*255*129 - alpha) * picref->data[0][luma_pos] ) >> 23; \ 00413 if (((x) & ((1<<(hsub)) - 1)) == 0 && ((y) & ((1<<(vsub)) - 1)) == 0) {\ 00414 chroma_pos1 = ((x) >> (hsub)) + ((y) >> (vsub)) * picref->linesize[1]; \ 00415 chroma_pos2 = ((x) >> (hsub)) + ((y) >> (vsub)) * picref->linesize[2]; \ 00416 picref->data[1][chroma_pos1] = (alpha * yuva_color[1] + (255*255*129 - alpha) * picref->data[1][chroma_pos1]) >> 23; \ 00417 picref->data[2][chroma_pos2] = (alpha * yuva_color[2] + (255*255*129 - alpha) * picref->data[2][chroma_pos2]) >> 23; \ 00418 }\ 00419 } 00420 00421 static inline int draw_glyph_yuv(AVFilterBufferRef *picref, FT_Bitmap *bitmap, unsigned int x, 00422 unsigned int y, unsigned int width, unsigned int height, 00423 const uint8_t yuva_color[4], int hsub, int vsub) 00424 { 00425 int r, c, alpha; 00426 unsigned int luma_pos, chroma_pos1, chroma_pos2; 00427 uint8_t src_val, dst_pixel[4]; 00428 00429 for (r = 0; r < bitmap->rows && r+y < height; r++) { 00430 for (c = 0; c < bitmap->width && c+x < width; c++) { 00431 /* get pixel in the picref (destination) */ 00432 dst_pixel[0] = picref->data[0][ c+x + (y+r) * picref->linesize[0]]; 00433 dst_pixel[1] = picref->data[1][((c+x) >> hsub) + ((y+r) >> vsub) * picref->linesize[1]]; 00434 dst_pixel[2] = picref->data[2][((c+x) >> hsub) + ((y+r) >> vsub) * picref->linesize[2]]; 00435 00436 /* get intensity value in the glyph bitmap (source) */ 00437 src_val = GET_BITMAP_VAL(r, c); 00438 if (!src_val) 00439 continue; 00440 00441 SET_PIXEL_YUV(picref, yuva_color, src_val, c+x, y+r, hsub, vsub); 00442 } 00443 } 00444 00445 return 0; 00446 } 00447 00448 #define SET_PIXEL_RGB(picref, rgba_color, val, x, y, pixel_step, r_off, g_off, b_off, a_off) { \ 00449 p = picref->data[0] + (x) * pixel_step + ((y) * picref->linesize[0]); \ 00450 alpha = rgba_color[3] * (val) * 129; \ 00451 *(p+r_off) = (alpha * rgba_color[0] + (255*255*129 - alpha) * *(p+r_off)) >> 23; \ 00452 *(p+g_off) = (alpha * rgba_color[1] + (255*255*129 - alpha) * *(p+g_off)) >> 23; \ 00453 *(p+b_off) = (alpha * rgba_color[2] + (255*255*129 - alpha) * *(p+b_off)) >> 23; \ 00454 } 00455 00456 static inline int draw_glyph_rgb(AVFilterBufferRef *picref, FT_Bitmap *bitmap, 00457 unsigned int x, unsigned int y, 00458 unsigned int width, unsigned int height, int pixel_step, 00459 const uint8_t rgba_color[4], const uint8_t rgba_map[4]) 00460 { 00461 int r, c, alpha; 00462 uint8_t *p; 00463 uint8_t src_val, dst_pixel[4]; 00464 00465 for (r = 0; r < bitmap->rows && r+y < height; r++) { 00466 for (c = 0; c < bitmap->width && c+x < width; c++) { 00467 /* get pixel in the picref (destination) */ 00468 dst_pixel[0] = picref->data[0][(c+x + rgba_map[0]) * pixel_step + 00469 (y+r) * picref->linesize[0]]; 00470 dst_pixel[1] = picref->data[0][(c+x + rgba_map[1]) * pixel_step + 00471 (y+r) * picref->linesize[0]]; 00472 dst_pixel[2] = picref->data[0][(c+x + rgba_map[2]) * pixel_step + 00473 (y+r) * picref->linesize[0]]; 00474 00475 /* get intensity value in the glyph bitmap (source) */ 00476 src_val = GET_BITMAP_VAL(r, c); 00477 if (!src_val) 00478 continue; 00479 00480 SET_PIXEL_RGB(picref, rgba_color, src_val, c+x, y+r, pixel_step, 00481 rgba_map[0], rgba_map[1], rgba_map[2], rgba_map[3]); 00482 } 00483 } 00484 00485 return 0; 00486 } 00487 00488 static inline void drawbox(AVFilterBufferRef *picref, unsigned int x, unsigned int y, 00489 unsigned int width, unsigned int height, 00490 uint8_t *line[4], int pixel_step[4], uint8_t color[4], 00491 int hsub, int vsub, int is_rgba_packed, uint8_t rgba_map[4]) 00492 { 00493 int i, j, alpha; 00494 00495 if (color[3] != 0xFF) { 00496 if (is_rgba_packed) { 00497 uint8_t *p; 00498 for (j = 0; j < height; j++) 00499 for (i = 0; i < width; i++) 00500 SET_PIXEL_RGB(picref, color, 255, i+x, y+j, pixel_step[0], 00501 rgba_map[0], rgba_map[1], rgba_map[2], rgba_map[3]); 00502 } else { 00503 unsigned int luma_pos, chroma_pos1, chroma_pos2; 00504 for (j = 0; j < height; j++) 00505 for (i = 0; i < width; i++) 00506 SET_PIXEL_YUV(picref, color, 255, i+x, y+j, hsub, vsub); 00507 } 00508 } else { 00509 ff_draw_rectangle(picref->data, picref->linesize, 00510 line, pixel_step, hsub, vsub, 00511 x, y, width, height); 00512 } 00513 } 00514 00515 static inline int is_newline(uint32_t c) 00516 { 00517 return (c == '\n' || c == '\r' || c == '\f' || c == '\v'); 00518 } 00519 00520 static int draw_glyphs(DrawTextContext *dtext, AVFilterBufferRef *picref, 00521 int width, int height, const uint8_t rgbcolor[4], const uint8_t yuvcolor[4], int x, int y) 00522 { 00523 char *text = HAVE_LOCALTIME_R ? dtext->expanded_text : dtext->text; 00524 uint32_t code = 0; 00525 int i; 00526 uint8_t *p; 00527 Glyph *glyph = NULL; 00528 00529 for (i = 0, p = text; *p; i++) { 00530 Glyph dummy = { 0 }; 00531 GET_UTF8(code, *p++, continue;); 00532 00533 /* skip new line chars, just go to new line */ 00534 if (code == '\n' || code == '\r' || code == '\t') 00535 continue; 00536 00537 dummy.code = code; 00538 glyph = av_tree_find(dtext->glyphs, &dummy, (void *)glyph_cmp, NULL); 00539 00540 if (glyph->bitmap.pixel_mode != FT_PIXEL_MODE_MONO && 00541 glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY) 00542 return AVERROR(EINVAL); 00543 00544 if (dtext->is_packed_rgb) { 00545 draw_glyph_rgb(picref, &glyph->bitmap, 00546 dtext->positions[i].x+x, dtext->positions[i].y+y, width, height, 00547 dtext->pixel_step[0], rgbcolor, dtext->rgba_map); 00548 } else { 00549 draw_glyph_yuv(picref, &glyph->bitmap, 00550 dtext->positions[i].x+x, dtext->positions[i].y+y, width, height, 00551 yuvcolor, dtext->hsub, dtext->vsub); 00552 } 00553 } 00554 00555 return 0; 00556 } 00557 00558 static int draw_text(AVFilterContext *ctx, AVFilterBufferRef *picref, 00559 int width, int height) 00560 { 00561 DrawTextContext *dtext = ctx->priv; 00562 uint32_t code = 0, prev_code = 0; 00563 int x = 0, y = 0, i = 0, ret; 00564 int text_height, baseline; 00565 char *text = dtext->text; 00566 uint8_t *p; 00567 int str_w = 0, len; 00568 int y_min = 32000, y_max = -32000; 00569 FT_Vector delta; 00570 Glyph *glyph = NULL, *prev_glyph = NULL; 00571 Glyph dummy = { 0 }; 00572 00573 #if HAVE_LOCALTIME_R 00574 time_t now = time(0); 00575 struct tm ltime; 00576 uint8_t *buf = dtext->expanded_text; 00577 int buf_size = dtext->expanded_text_size; 00578 00579 if (!buf) { 00580 buf_size = 2*strlen(dtext->text)+1; 00581 buf = av_malloc(buf_size); 00582 } 00583 00584 localtime_r(&now, <ime); 00585 00586 do { 00587 *buf = 1; 00588 if (strftime(buf, buf_size, dtext->text, <ime) != 0 || *buf == 0) 00589 break; 00590 buf_size *= 2; 00591 } while ((buf = av_realloc(buf, buf_size))); 00592 00593 if (!buf) 00594 return AVERROR(ENOMEM); 00595 text = dtext->expanded_text = buf; 00596 dtext->expanded_text_size = buf_size; 00597 #endif 00598 if ((len = strlen(text)) > dtext->nb_positions) { 00599 if (!(dtext->positions = 00600 av_realloc(dtext->positions, len*sizeof(*dtext->positions)))) 00601 return AVERROR(ENOMEM); 00602 dtext->nb_positions = len; 00603 } 00604 00605 x = dtext->x; 00606 y = dtext->y; 00607 00608 /* load and cache glyphs */ 00609 for (i = 0, p = text; *p; i++) { 00610 GET_UTF8(code, *p++, continue;); 00611 00612 /* get glyph */ 00613 dummy.code = code; 00614 glyph = av_tree_find(dtext->glyphs, &dummy, glyph_cmp, NULL); 00615 if (!glyph) 00616 load_glyph(ctx, &glyph, code); 00617 00618 y_min = FFMIN(glyph->bbox.yMin, y_min); 00619 y_max = FFMAX(glyph->bbox.yMax, y_max); 00620 } 00621 text_height = y_max - y_min; 00622 baseline = y_max; 00623 00624 /* compute and save position for each glyph */ 00625 glyph = NULL; 00626 for (i = 0, p = text; *p; i++) { 00627 GET_UTF8(code, *p++, continue;); 00628 00629 /* skip the \n in the sequence \r\n */ 00630 if (prev_code == '\r' && code == '\n') 00631 continue; 00632 00633 prev_code = code; 00634 if (is_newline(code)) { 00635 str_w = FFMAX(str_w, x - dtext->x); 00636 y += text_height; 00637 x = dtext->x; 00638 continue; 00639 } 00640 00641 /* get glyph */ 00642 prev_glyph = glyph; 00643 dummy.code = code; 00644 glyph = av_tree_find(dtext->glyphs, &dummy, glyph_cmp, NULL); 00645 00646 /* kerning */ 00647 if (dtext->use_kerning && prev_glyph && glyph->code) { 00648 FT_Get_Kerning(dtext->face, prev_glyph->code, glyph->code, 00649 ft_kerning_default, &delta); 00650 x += delta.x >> 6; 00651 } 00652 00653 if (x + glyph->bbox.xMax >= width) { 00654 str_w = FFMAX(str_w, x - dtext->x); 00655 y += text_height; 00656 x = dtext->x; 00657 } 00658 00659 /* save position */ 00660 dtext->positions[i].x = x + glyph->bitmap_left; 00661 dtext->positions[i].y = y - glyph->bitmap_top + baseline; 00662 if (code == '\t') x = (x / dtext->tabsize + 1)*dtext->tabsize; 00663 else x += glyph->advance; 00664 } 00665 00666 str_w = FFMIN(width - dtext->x - 1, FFMAX(str_w, x - dtext->x)); 00667 y = FFMIN(y + text_height, height - 1); 00668 00669 /* draw box */ 00670 if (dtext->draw_box) 00671 drawbox(picref, dtext->x, dtext->y, str_w, y-dtext->y, 00672 dtext->box_line, dtext->pixel_step, dtext->boxcolor, 00673 dtext->hsub, dtext->vsub, dtext->is_packed_rgb, dtext->rgba_map); 00674 00675 if (dtext->shadowx || dtext->shadowy) { 00676 if ((ret = draw_glyphs(dtext, picref, width, height, dtext->shadowcolor_rgba, 00677 dtext->shadowcolor, dtext->shadowx, dtext->shadowy)) < 0) 00678 return ret; 00679 } 00680 00681 if ((ret = draw_glyphs(dtext, picref, width, height, dtext->fontcolor_rgba, 00682 dtext->fontcolor, 0, 0)) < 0) 00683 return ret; 00684 00685 return 0; 00686 } 00687 00688 static void null_draw_slice(AVFilterLink *link, int y, int h, int slice_dir) { } 00689 00690 static void end_frame(AVFilterLink *inlink) 00691 { 00692 AVFilterLink *outlink = inlink->dst->outputs[0]; 00693 AVFilterBufferRef *picref = inlink->cur_buf; 00694 00695 draw_text(inlink->dst, picref, picref->video->w, picref->video->h); 00696 00697 avfilter_draw_slice(outlink, 0, picref->video->h, 1); 00698 avfilter_end_frame(outlink); 00699 } 00700 00701 AVFilter avfilter_vf_drawtext = { 00702 .name = "drawtext", 00703 .description = NULL_IF_CONFIG_SMALL("Draw text on top of video frames using libfreetype library."), 00704 .priv_size = sizeof(DrawTextContext), 00705 .init = init, 00706 .uninit = uninit, 00707 .query_formats = query_formats, 00708 00709 .inputs = (AVFilterPad[]) {{ .name = "default", 00710 .type = AVMEDIA_TYPE_VIDEO, 00711 .get_video_buffer = avfilter_null_get_video_buffer, 00712 .start_frame = avfilter_null_start_frame, 00713 .draw_slice = null_draw_slice, 00714 .end_frame = end_frame, 00715 .config_props = config_input, 00716 .min_perms = AV_PERM_WRITE | 00717 AV_PERM_READ, 00718 .rej_perms = AV_PERM_PRESERVE }, 00719 { .name = NULL}}, 00720 .outputs = (AVFilterPad[]) {{ .name = "default", 00721 .type = AVMEDIA_TYPE_VIDEO, }, 00722 { .name = NULL}}, 00723 };