macOS vout: fix forgotten ';'
[vlc.git] / modules / text_renderer / freetype / freetype.c
blob92f90c9f0b552bb549e47ebcb370ce857e013307
1 /*****************************************************************************
2 * freetype.c : Put text on the video, using freetype2
3 *****************************************************************************
4 * Copyright (C) 2002 - 2015 VLC authors and VideoLAN
5 * $Id$
7 * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8 * Gildas Bazin <gbazin@videolan.org>
9 * Bernie Purcell <bitmap@videolan.org>
10 * Jean-Baptiste Kempf <jb@videolan.org>
11 * Felix Paul Kühne <fkuehne@videolan.org>
12 * Salah-Eddin Shaban <salshaaban@gmail.com>
14 * This program is free software; you can redistribute it and/or modify it
15 * under the terms of the GNU Lesser General Public License as published by
16 * the Free Software Foundation; either version 2.1 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Lesser General Public License for more details.
24 * You should have received a copy of the GNU Lesser General Public License
25 * along with this program; if not, write to the Free Software Foundation, Inc.,
26 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27 *****************************************************************************/
29 /*****************************************************************************
30 * Preamble
31 *****************************************************************************/
33 #ifdef HAVE_CONFIG_H
34 # include "config.h"
35 #endif
36 #include <math.h>
38 #include <vlc_common.h>
39 #include <vlc_plugin.h>
40 #include <vlc_input.h> /* vlc_input_attachment_* */
41 #include <vlc_filter.h> /* filter_sys_t */
42 #include <vlc_subpicture.h>
43 #include <vlc_text_style.h> /* text_style_t*/
44 #include <vlc_charset.h>
46 /* apple stuff */
47 #ifdef __APPLE__
48 # undef HAVE_FONTCONFIG
49 # define HAVE_GET_FONT_BY_FAMILY_NAME
50 #endif
52 /* Win32 */
53 #ifdef _WIN32
54 # undef HAVE_FONTCONFIG
55 # define HAVE_GET_FONT_BY_FAMILY_NAME
56 #endif
58 /* FontConfig */
59 #ifdef HAVE_FONTCONFIG
60 # define HAVE_GET_FONT_BY_FAMILY_NAME
61 #endif
63 /* Android */
64 #ifdef __ANDROID__
65 # define HAVE_GET_FONT_BY_FAMILY_NAME
66 #endif
68 #include <assert.h>
70 #include "platform_fonts.h"
71 #include "freetype.h"
72 #include "text_layout.h"
74 /*****************************************************************************
75 * Module descriptor
76 *****************************************************************************/
77 static int Create ( vlc_object_t * );
78 static void Destroy( vlc_object_t * );
80 #define FONT_TEXT N_("Font")
81 #define MONOSPACE_FONT_TEXT N_("Monospace Font")
83 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
84 #define FONT_LONGTEXT N_("Font file for the font you want to use")
86 #define OPACITY_TEXT N_("Text opacity")
87 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
88 "text that will be rendered on the video. 0 = transparent, " \
89 "255 = totally opaque." )
90 #define COLOR_TEXT N_("Text default color")
91 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
92 "the video. This must be an hexadecimal (like HTML colors). The first two "\
93 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
94 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
96 #define BOLD_TEXT N_("Force bold")
98 #define BG_OPACITY_TEXT N_("Background opacity")
99 #define BG_COLOR_TEXT N_("Background color")
101 #define OUTLINE_OPACITY_TEXT N_("Outline opacity")
102 #define OUTLINE_COLOR_TEXT N_("Outline color")
103 #define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
105 #define SHADOW_OPACITY_TEXT N_("Shadow opacity")
106 #define SHADOW_COLOR_TEXT N_("Shadow color")
107 #define SHADOW_ANGLE_TEXT N_("Shadow angle")
108 #define SHADOW_DISTANCE_TEXT N_("Shadow distance")
110 #define TEXT_DIRECTION_TEXT N_("Text direction")
111 #define TEXT_DIRECTION_LONGTEXT N_("Paragraph base direction for the Unicode bi-directional algorithm.")
114 #define YUVP_TEXT N_("Use YUVP renderer")
115 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
116 "This option is only needed if you want to encode into DVB subtitles" )
118 static const int pi_color_values[] = {
119 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
120 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
121 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
123 static const char *const ppsz_color_descriptions[] = {
124 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
125 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
126 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
128 static const int pi_outline_thickness[] = {
129 0, 2, 4, 6,
131 static const char *const ppsz_outline_thickness[] = {
132 N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
135 #ifdef HAVE_FRIBIDI
136 static const int pi_text_direction[] = {
137 0, 1, 2,
139 static const char *const ppsz_text_direction[] = {
140 N_("Left to right"), N_("Right to left"), N_("Auto"),
142 #endif
144 vlc_module_begin ()
145 set_shortname( N_("Text renderer"))
146 set_description( N_("Freetype2 font renderer") )
147 set_category( CAT_VIDEO )
148 set_subcategory( SUBCAT_VIDEO_SUBPIC )
150 #ifdef HAVE_GET_FONT_BY_FAMILY_NAME
151 add_font("freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT)
152 add_font("freetype-monofont", DEFAULT_MONOSPACE_FAMILY,
153 MONOSPACE_FONT_TEXT, FAMILY_LONGTEXT)
154 #else
155 add_loadfile("freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT)
156 add_loadfile("freetype-monofont", DEFAULT_MONOSPACE_FONT_FILE,
157 MONOSPACE_FONT_TEXT, FONT_LONGTEXT)
158 #endif
160 /* opacity valid on 0..255, with default 255 = fully opaque */
161 add_integer_with_range( "freetype-opacity", 255, 0, 255,
162 OPACITY_TEXT, OPACITY_LONGTEXT, false )
163 change_safe()
165 /* hook to the color values list, with default 0x00ffffff = white */
166 add_rgb("freetype-color", 0x00FFFFFF, COLOR_TEXT, COLOR_LONGTEXT)
167 change_integer_list( pi_color_values, ppsz_color_descriptions )
168 change_integer_range( 0x000000, 0xFFFFFF )
169 change_safe()
171 add_bool( "freetype-bold", false, BOLD_TEXT, NULL, false )
172 change_safe()
174 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
175 BG_OPACITY_TEXT, NULL, false )
176 change_safe()
177 add_rgb("freetype-background-color", 0x00000000, BG_COLOR_TEXT, NULL)
178 change_integer_list( pi_color_values, ppsz_color_descriptions )
179 change_integer_range( 0x000000, 0xFFFFFF )
180 change_safe()
182 add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
183 OUTLINE_OPACITY_TEXT, NULL, false )
184 change_safe()
185 add_rgb("freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT, NULL)
186 change_integer_list( pi_color_values, ppsz_color_descriptions )
187 change_integer_range( 0x000000, 0xFFFFFF )
188 change_safe()
189 add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
190 NULL, false )
191 change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
192 change_safe()
194 add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
195 SHADOW_OPACITY_TEXT, NULL, false )
196 change_safe()
197 add_rgb("freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT, NULL)
198 change_integer_list( pi_color_values, ppsz_color_descriptions )
199 change_integer_range( 0x000000, 0xFFFFFF )
200 change_safe()
201 add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
202 SHADOW_ANGLE_TEXT, NULL, false )
203 change_safe()
204 add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
205 SHADOW_DISTANCE_TEXT, NULL, false )
206 change_safe()
208 add_obsolete_integer( "freetype-fontsize" );
209 add_obsolete_integer( "freetype-rel-fontsize" );
210 add_obsolete_integer( "freetype-effect" );
212 add_bool( "freetype-yuvp", false, YUVP_TEXT,
213 YUVP_LONGTEXT, true )
215 #ifdef HAVE_FRIBIDI
216 add_integer_with_range( "freetype-text-direction", 0, 0, 2, TEXT_DIRECTION_TEXT,
217 TEXT_DIRECTION_LONGTEXT, false )
218 change_integer_list( pi_text_direction, ppsz_text_direction )
219 change_safe()
220 #endif
222 set_capability( "text renderer", 100 )
223 add_shortcut( "text" )
224 set_callbacks( Create, Destroy )
225 vlc_module_end ()
227 /* */
228 static void YUVFromRGB( uint32_t i_argb,
229 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
231 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
232 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
233 int i_blue = ( i_argb & 0x000000ff );
235 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
236 802 * i_blue + 4096 + 131072 ) >> 13, 235);
237 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
238 3598 * i_blue + 4096 + 1048576) >> 13, 240);
239 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
240 -585 * i_blue + 4096 + 1048576) >> 13, 240);
242 static void RGBFromRGB( uint32_t i_argb,
243 uint8_t *pi_r, uint8_t *pi_g, uint8_t *pi_b )
245 *pi_r = ( i_argb & 0x00ff0000 ) >> 16;
246 *pi_g = ( i_argb & 0x0000ff00 ) >> 8;
247 *pi_b = ( i_argb & 0x000000ff );
250 static FT_Vector GetAlignedOffset( const line_desc_t *p_line,
251 const FT_BBox *p_textbbox,
252 int i_align )
254 FT_Vector offsets = { 0, 0 };
255 const int i_text_width = p_textbbox->xMax - p_textbbox->xMin;
256 if ( p_line->i_width < i_text_width &&
257 (i_align & SUBPICTURE_ALIGN_LEFT) == 0 )
259 /* Left offset to take into account alignment */
260 if( i_align & SUBPICTURE_ALIGN_RIGHT )
261 offsets.x = ( i_text_width - p_line->i_width );
262 else /* center */
263 offsets.x = ( i_text_width - p_line->i_width ) / 2;
265 else
267 offsets.x = p_textbbox->xMin - p_line->bbox.xMin;
269 return offsets;
272 /*****************************************************************************
273 * Make any TTF/OTF fonts present in the attachments of the media file
274 * and store them for later use by the FreeType Engine
275 *****************************************************************************/
276 static int LoadFontsFromAttachments( filter_t *p_filter )
278 filter_sys_t *p_sys = p_filter->p_sys;
279 input_attachment_t **pp_attachments;
280 int i_attachments_cnt;
281 FT_Face p_face = NULL;
282 char *psz_lc = NULL;
284 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
285 return VLC_EGENERIC;
287 p_sys->i_font_attachments = 0;
288 p_sys->pp_font_attachments = vlc_alloc( i_attachments_cnt, sizeof(*p_sys->pp_font_attachments));
289 if( !p_sys->pp_font_attachments )
291 for( int i = 0; i < i_attachments_cnt; ++i )
292 vlc_input_attachment_Delete( pp_attachments[ i ] );
293 free( pp_attachments );
294 return VLC_ENOMEM;
297 int k = 0;
298 for( ; k < i_attachments_cnt; k++ )
300 input_attachment_t *p_attach = pp_attachments[k];
302 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
303 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
304 p_attach->i_data > 0 && p_attach->p_data )
306 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
308 int i_font_idx = 0;
310 while( 0 == FT_New_Memory_Face( p_sys->p_library,
311 p_attach->p_data,
312 p_attach->i_data,
313 i_font_idx,
314 &p_face ))
317 bool b_bold = p_face->style_flags & FT_STYLE_FLAG_BOLD;
318 bool b_italic = p_face->style_flags & FT_STYLE_FLAG_ITALIC;
320 if( p_face->family_name )
321 psz_lc = ToLower( p_face->family_name );
322 else
323 if( asprintf( &psz_lc, FB_NAME"-%04d",
324 p_sys->i_fallback_counter++ ) < 0 )
325 psz_lc = NULL;
327 if( unlikely( !psz_lc ) )
328 goto error;
330 vlc_family_t *p_family =
331 vlc_dictionary_value_for_key( &p_sys->family_map, psz_lc );
333 if( p_family == kVLCDictionaryNotFound )
335 p_family = NewFamily( p_filter, psz_lc, &p_sys->p_families,
336 &p_sys->family_map, psz_lc );
338 if( unlikely( !p_family ) )
339 goto error;
342 free( psz_lc );
343 psz_lc = NULL;
345 char *psz_fontfile;
346 if( asprintf( &psz_fontfile, ":/%d",
347 p_sys->i_font_attachments - 1 ) < 0
348 || !NewFont( psz_fontfile, i_font_idx, b_bold, b_italic, p_family ) )
349 goto error;
351 FT_Done_Face( p_face );
352 p_face = NULL;
354 i_font_idx++;
357 else
359 vlc_input_attachment_Delete( p_attach );
363 free( pp_attachments );
365 /* Add font attachments to the "attachments" fallback list */
366 vlc_family_t *p_attachments = NULL;
368 for( vlc_family_t *p_family = p_sys->p_families; p_family;
369 p_family = p_family->p_next )
371 vlc_family_t *p_temp = NewFamily( p_filter, p_family->psz_name, &p_attachments,
372 NULL, NULL );
373 if( unlikely( !p_temp ) )
375 if( p_attachments )
376 FreeFamilies( p_attachments, NULL );
377 return VLC_ENOMEM;
379 else
380 p_temp->p_fonts = p_family->p_fonts;
383 if( p_attachments )
384 vlc_dictionary_insert( &p_sys->fallback_map, FB_LIST_ATTACHMENTS, p_attachments );
386 return VLC_SUCCESS;
388 error:
389 if( p_face )
390 FT_Done_Face( p_face );
392 if( psz_lc )
393 free( psz_lc );
395 for( int i = k + 1; i < i_attachments_cnt; ++i )
396 vlc_input_attachment_Delete( pp_attachments[ i ] );
398 free( pp_attachments );
399 return VLC_ENOMEM;
402 /*****************************************************************************
403 * RenderYUVP: place string in picture
404 *****************************************************************************
405 * This function merges the previously rendered freetype glyphs into a picture
406 *****************************************************************************/
407 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
408 line_desc_t *p_line,
409 FT_BBox *p_regionbbox, FT_BBox *p_paddedbbox,
410 FT_BBox *p_bbox )
412 VLC_UNUSED(p_filter);
413 VLC_UNUSED(p_paddedbbox);
414 static const uint8_t pi_gamma[16] =
415 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
416 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
418 uint8_t *p_dst;
419 video_format_t fmt;
420 int i, i_pitch;
421 unsigned int x, y;
422 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
424 /* Create a new subpicture region */
425 video_format_Init( &fmt, VLC_CODEC_YUVP );
426 fmt.i_width =
427 fmt.i_visible_width = p_regionbbox->xMax - p_regionbbox->xMin + 4;
428 fmt.i_height =
429 fmt.i_visible_height = p_regionbbox->yMax - p_regionbbox->yMin + 4;
430 const unsigned regionnum = p_region->fmt.i_sar_num;
431 const unsigned regionden = p_region->fmt.i_sar_den;
432 fmt.i_sar_num = fmt.i_sar_den = 1;
433 fmt.transfer = p_region->fmt.transfer;
434 fmt.primaries = p_region->fmt.primaries;
435 fmt.space = p_region->fmt.space;
436 fmt.mastering = p_region->fmt.mastering;
438 assert( !p_region->p_picture );
439 p_region->p_picture = picture_NewFromFormat( &fmt );
440 if( !p_region->p_picture )
441 return VLC_EGENERIC;
442 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
443 p_region->fmt = fmt;
444 fmt.i_sar_num = regionnum;
445 fmt.i_sar_den = regionden;
447 /* Calculate text color components
448 * Only use the first color */
449 const int i_alpha = p_line->p_character[0].p_style->i_font_alpha;
450 YUVFromRGB( p_line->p_character[0].p_style->i_font_color, &i_y, &i_u, &i_v );
452 /* Build palette */
453 fmt.p_palette->i_entries = 16;
454 for( i = 0; i < 8; i++ )
456 fmt.p_palette->palette[i][0] = 0;
457 fmt.p_palette->palette[i][1] = 0x80;
458 fmt.p_palette->palette[i][2] = 0x80;
459 fmt.p_palette->palette[i][3] = pi_gamma[i];
460 fmt.p_palette->palette[i][3] =
461 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
463 for( i = 8; i < fmt.p_palette->i_entries; i++ )
465 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
466 fmt.p_palette->palette[i][1] = i_u;
467 fmt.p_palette->palette[i][2] = i_v;
468 fmt.p_palette->palette[i][3] = pi_gamma[i];
469 fmt.p_palette->palette[i][3] =
470 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
473 p_dst = p_region->p_picture->Y_PIXELS;
474 i_pitch = p_region->p_picture->Y_PITCH;
476 /* Initialize the region pixels */
477 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
479 for( ; p_line != NULL; p_line = p_line->p_next )
481 FT_Vector offset = GetAlignedOffset( p_line, p_bbox, p_region->i_align );
483 for( i = 0; i < p_line->i_character_count; i++ )
485 const line_character_t *ch = &p_line->p_character[i];
486 FT_BitmapGlyph p_glyph = ch->p_glyph;
488 int i_glyph_y = offset.y + p_regionbbox->yMax - p_glyph->top + p_line->i_base_line;
489 int i_glyph_x = offset.x + p_glyph->left - p_regionbbox->xMin;
491 for( y = 0; y < p_glyph->bitmap.rows; y++ )
493 for( x = 0; x < p_glyph->bitmap.width; x++ )
495 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
496 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
497 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
503 /* Outlining (find something better than nearest neighbour filtering ?) */
504 if( 1 )
506 p_dst = p_region->p_picture->Y_PIXELS;
507 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
508 uint8_t left, current;
510 for( y = 1; y < fmt.i_height - 1; y++ )
512 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
513 p_dst += p_region->p_picture->Y_PITCH;
514 left = 0;
516 for( x = 1; x < fmt.i_width - 1; x++ )
518 current = p_dst[x];
519 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
520 p_dst[x -1 + p_region->p_picture->Y_PITCH ] + p_dst[x + p_region->p_picture->Y_PITCH] + p_dst[x + 1 + p_region->p_picture->Y_PITCH]) / 16;
521 left = current;
524 memset( p_top, 0, fmt.i_width );
527 return VLC_SUCCESS;
530 /*****************************************************************************
531 * RenderYUVA: place string in picture
532 *****************************************************************************
533 * This function merges the previously rendered freetype glyphs into a picture
534 *****************************************************************************/
535 static void FillYUVAPicture( picture_t *p_picture,
536 int i_a, int i_y, int i_u, int i_v )
538 memset( p_picture->p[0].p_pixels, i_y,
539 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
540 memset( p_picture->p[1].p_pixels, i_u,
541 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
542 memset( p_picture->p[2].p_pixels, i_v,
543 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
544 memset( p_picture->p[3].p_pixels, i_a,
545 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
548 static inline void BlendYUVAPixel( picture_t *p_picture,
549 int i_picture_x, int i_picture_y,
550 int i_a, int i_y, int i_u, int i_v,
551 int i_alpha )
553 int i_an = i_a * i_alpha / 255;
555 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
556 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
557 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
558 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
560 int i_ao = *p_a;
561 if( i_ao == 0 )
563 *p_y = i_y;
564 *p_u = i_u;
565 *p_v = i_v;
566 *p_a = i_an;
568 else
570 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
571 if( *p_a != 0 )
573 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
574 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
575 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
580 static void FillRGBAPicture( picture_t *p_picture,
581 int i_a, int i_r, int i_g, int i_b )
583 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
585 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
587 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
588 p_rgba[0] = i_r;
589 p_rgba[1] = i_g;
590 p_rgba[2] = i_b;
591 p_rgba[3] = i_a;
596 static inline void BlendRGBAPixel( picture_t *p_picture,
597 int i_picture_x, int i_picture_y,
598 int i_a, int i_r, int i_g, int i_b,
599 int i_alpha )
601 int i_an = i_a * i_alpha / 255;
603 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
605 int i_ao = p_rgba[3];
606 if( i_ao == 0 )
608 p_rgba[0] = i_r;
609 p_rgba[1] = i_g;
610 p_rgba[2] = i_b;
611 p_rgba[3] = i_an;
613 else
615 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
616 if( p_rgba[3] != 0 )
618 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
619 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
620 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
625 static void FillARGBPicture(picture_t *pic, int a, int r, int g, int b)
627 if (a == 0)
628 r = g = b = 0;
629 if (a == r && a == b && a == g)
630 { /* fast path */
631 memset(pic->p->p_pixels, a, pic->p->i_visible_lines * pic->p->i_pitch);
632 return;
635 uint_fast32_t pixel = VLC_FOURCC(a, r, g, b);
636 uint8_t *line = pic->p->p_pixels;
638 for (unsigned lines = pic->p->i_visible_lines; lines > 0; lines--)
640 uint32_t *pixels = (uint32_t *)line;
641 for (unsigned cols = pic->p->i_visible_pitch; cols > 0; cols -= 4)
642 *(pixels++) = pixel;
643 line += pic->p->i_pitch;
647 static inline void BlendARGBPixel(picture_t *pic, int pic_x, int pic_y,
648 int a, int r, int g, int b, int alpha)
650 uint8_t *rgba = &pic->p->p_pixels[pic_y * pic->p->i_pitch + 4 * pic_x];
651 int an = a * alpha / 255;
652 int ao = rgba[3];
654 if (ao == 0)
656 rgba[0] = an;
657 rgba[1] = r;
658 rgba[2] = g;
659 rgba[3] = b;
661 else
663 rgba[0] = 255 - (255 - rgba[0]) * (255 - an) / 255;
664 if (rgba[0] != 0)
666 rgba[1] = (rgba[1] * ao * (255 - an) / 255 + r * an ) / rgba[0];
667 rgba[2] = (rgba[2] * ao * (255 - an) / 255 + g * an ) / rgba[0];
668 rgba[3] = (rgba[3] * ao * (255 - an) / 255 + b * an ) / rgba[0];
673 static inline void BlendAXYZGlyph( picture_t *p_picture,
674 int i_picture_x, int i_picture_y,
675 int i_a, int i_x, int i_y, int i_z,
676 FT_BitmapGlyph p_glyph,
677 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
680 for( unsigned int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
682 for( unsigned int dx = 0; dx < p_glyph->bitmap.width; dx++ )
683 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
684 i_a, i_x, i_y, i_z,
685 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
689 static inline void BlendAXYZLine( picture_t *p_picture,
690 int i_picture_x, int i_picture_y,
691 int i_a, int i_x, int i_y, int i_z,
692 const line_character_t *p_current,
693 const line_character_t *p_next,
694 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
696 int i_line_width = p_current->p_glyph->bitmap.width;
697 if( p_next )
698 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
700 for( int dx = 0; dx < i_line_width; dx++ )
702 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
703 BlendPixel( p_picture,
704 i_picture_x + dx,
705 i_picture_y + p_current->i_line_offset + dy,
706 i_a, i_x, i_y, i_z, 0xff );
710 static inline void RenderBackground( subpicture_region_t *p_region,
711 line_desc_t *p_line_head,
712 FT_BBox *p_regionbbox,
713 FT_BBox *p_paddedbbox,
714 FT_BBox *p_textbbox,
715 picture_t *p_picture,
716 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
717 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
719 for( const line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
721 FT_Vector offset = GetAlignedOffset( p_line, p_textbbox, p_region->i_text_align );
723 FT_BBox linebgbox = p_line->bbox;
724 linebgbox.xMin += offset.x;
725 linebgbox.xMax += offset.x;
726 linebgbox.yMax += offset.y;
727 linebgbox.yMin += offset.y;
729 if( p_line->i_first_visible_char_index < 0 )
730 continue; /* only spaces */
732 /* add padding */
733 linebgbox.yMax += (p_paddedbbox->xMax - p_textbbox->xMax);
734 linebgbox.yMin -= (p_textbbox->xMin - p_paddedbbox->xMin);
735 linebgbox.xMin -= (p_textbbox->xMin - p_paddedbbox->xMin);
736 linebgbox.xMax += (p_paddedbbox->xMax - p_textbbox->xMax);
738 /* Compute lower boundary for the background
739 continue down to next line top */
740 if( p_line->p_next && p_line->p_next->i_first_visible_char_index >= 0 )
741 linebgbox.yMin = __MIN(linebgbox.yMin, p_line->bbox.yMin - (p_line->bbox.yMin - p_line->p_next->bbox.yMax));
743 /* Setup color for the background */
744 const text_style_t *p_prev_style = p_line->p_character[p_line->i_first_visible_char_index].p_style;
746 FT_BBox segmentbgbox = linebgbox;
747 int i_char_index = p_line->i_first_visible_char_index;
748 /* Compute the background for the line (identify leading/trailing space) */
749 if( i_char_index > 0 )
751 segmentbgbox.xMin = offset.x +
752 p_line->p_character[p_line->i_first_visible_char_index].bbox.xMin -
753 /* padding offset */ (p_textbbox->xMin - p_paddedbbox->xMin);
756 while( i_char_index <= p_line->i_last_visible_char_index )
758 /* find last char having the same style */
759 int i_seg_end = i_char_index;
760 while( i_seg_end + 1 <= p_line->i_last_visible_char_index &&
761 p_prev_style == p_line->p_character[i_seg_end + 1].p_style )
763 i_seg_end++;
766 /* Find right boundary for bounding box for background */
767 segmentbgbox.xMax = offset.x + p_line->p_character[i_seg_end].bbox.xMax;
768 if( i_seg_end == p_line->i_last_visible_char_index ) /* add padding on last */
769 segmentbgbox.xMax += (p_paddedbbox->xMax - p_textbbox->xMax);
771 const line_character_t *p_char = &p_line->p_character[i_char_index];
772 if( p_char->p_style->i_style_flags & STYLE_BACKGROUND )
774 uint8_t i_x, i_y, i_z;
775 ExtractComponents( p_char->b_in_karaoke ? p_char->p_style->i_karaoke_background_color :
776 p_char->p_style->i_background_color,
777 &i_x, &i_y, &i_z );
778 const uint8_t i_alpha = p_char->b_in_karaoke ? p_char->p_style->i_karaoke_background_alpha:
779 p_char->p_style->i_background_alpha;
781 /* Render the actual background */
782 if( i_alpha != STYLE_ALPHA_TRANSPARENT )
784 /* rebase and clip to SCREEN coordinates */
785 FT_BBox absbox =
787 .xMin = __MAX(0, segmentbgbox.xMin - p_regionbbox->xMin),
788 .xMax = VLC_CLIP(segmentbgbox.xMax - p_regionbbox->xMin,
789 0, p_region->fmt.i_visible_width),
790 .yMin = VLC_CLIP(p_regionbbox->yMax - segmentbgbox.yMin,
791 0, p_region->fmt.i_visible_height),
792 .yMax = __MAX(0, p_regionbbox->yMax - segmentbgbox.yMax),
795 for( int dy = absbox.yMax; dy < absbox.yMin; dy++ )
797 for( int dx = absbox.xMin; dx < absbox.xMax; dx++ )
798 BlendPixel( p_picture, dx, dy, i_alpha, i_x, i_y, i_z, 0xff );
803 segmentbgbox.xMin = segmentbgbox.xMax;
804 i_char_index = i_seg_end + 1;
805 p_prev_style = p_line->p_character->p_style;
811 static void RenderCharAXYZ( filter_t *p_filter,
812 picture_t *p_picture,
813 const line_desc_t *p_line,
814 int i_offset_x,
815 int i_offset_y,
816 int g,
817 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
818 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
820 VLC_UNUSED(p_filter);
821 /* Render all glyphs and underline/strikethrough */
822 for( int i = p_line->i_first_visible_char_index; i <= p_line->i_last_visible_char_index; i++ )
824 const line_character_t *ch = &p_line->p_character[i];
825 const FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
826 if( !p_glyph )
827 continue;
829 uint8_t i_a = ch->p_style->i_font_alpha;
831 uint32_t i_color;
832 switch (g) {/* Apply font alpha ratio to shadow/outline alpha */
833 case 0:
834 i_a = i_a * ch->p_style->i_shadow_alpha / 255;
835 i_color = ch->p_style->i_shadow_color;
836 break;
837 case 1:
838 i_a = i_a * ch->p_style->i_outline_alpha / 255;
839 i_color = ch->p_style->i_outline_color;
840 break;
841 default:
842 i_color = ch->p_style->i_font_color;
843 break;
846 if(ch->p_ruby && ch->p_ruby->p_laid)
848 RenderCharAXYZ( p_filter,
849 p_picture,
850 ch->p_ruby->p_laid,
851 i_offset_x, i_offset_y,
853 ExtractComponents,
854 BlendPixel );
857 /* Don't render if invisible or not wanted */
858 if( i_a == STYLE_ALPHA_TRANSPARENT ||
859 (g == 0 && 0 == (ch->p_style->i_style_flags & STYLE_SHADOW) ) ||
860 (g == 1 && 0 == (ch->p_style->i_style_flags & STYLE_OUTLINE) )
862 continue;
864 uint8_t i_x, i_y, i_z;
865 ExtractComponents( i_color, &i_x, &i_y, &i_z );
867 int i_glyph_y = i_offset_y - p_glyph->top;
868 int i_glyph_x = i_offset_x + p_glyph->left;
870 BlendAXYZGlyph( p_picture,
871 i_glyph_x, i_glyph_y,
872 i_a, i_x, i_y, i_z,
873 p_glyph,
874 BlendPixel );
876 /* underline/strikethrough are only rendered for the normal glyph */
877 if( g == 2 && ch->i_line_thickness > 0 )
878 BlendAXYZLine( p_picture,
879 i_glyph_x, i_glyph_y + p_glyph->top,
880 i_a, i_x, i_y, i_z,
881 &ch[0],
882 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
883 BlendPixel );
887 static inline int RenderAXYZ( filter_t *p_filter,
888 subpicture_region_t *p_region,
889 line_desc_t *p_line_head,
890 FT_BBox *p_regionbbox,
891 FT_BBox *p_paddedtextbbox,
892 FT_BBox *p_textbbox,
893 vlc_fourcc_t i_chroma,
894 const video_format_t *fmt_out,
895 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
896 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
897 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
899 filter_sys_t *p_sys = p_filter->p_sys;
901 /* Create a new subpicture region */
902 video_format_t fmt;
903 video_format_Init( &fmt, i_chroma );
904 fmt.i_width =
905 fmt.i_visible_width = p_regionbbox->xMax - p_regionbbox->xMin;
906 fmt.i_height =
907 fmt.i_visible_height = p_regionbbox->yMax - p_regionbbox->yMin;
908 const unsigned regionnum = p_region->fmt.i_sar_num;
909 const unsigned regionden = p_region->fmt.i_sar_den;
910 fmt.i_sar_num = fmt.i_sar_den = 1;
911 fmt.transfer = fmt_out->transfer;
912 fmt.primaries = fmt_out->primaries;
913 fmt.space = fmt_out->space;
914 fmt.mastering = fmt_out->mastering;
916 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
917 if( !p_region->p_picture )
918 return VLC_EGENERIC;
920 p_region->fmt = fmt;
921 p_region->fmt.i_sar_num = regionnum;
922 p_region->fmt.i_sar_den = regionden;
924 /* Initialize the picture background */
925 const text_style_t *p_style = p_sys->p_default_style;
926 uint8_t i_x, i_y, i_z;
928 if (p_region->b_noregionbg) {
929 /* Render the background just under the text */
930 FillPicture( p_picture, STYLE_ALPHA_TRANSPARENT, 0x00, 0x00, 0x00 );
931 } else {
932 /* Render background under entire subpicture block */
933 ExtractComponents( p_style->i_background_color, &i_x, &i_y, &i_z );
934 FillPicture( p_picture, p_style->i_background_alpha, i_x, i_y, i_z );
937 /* Render text's background (from decoder) if any */
938 RenderBackground(p_region, p_line_head,
939 p_regionbbox, p_paddedtextbbox, p_textbbox,
940 p_picture, ExtractComponents, BlendPixel);
942 /* Render shadow then outline and then normal glyphs */
943 for( int g = 0; g < 3; g++ )
945 /* Render all lines */
946 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
948 FT_Vector offset = GetAlignedOffset( p_line, p_textbbox, p_region->i_text_align );
950 int i_glyph_offset_y = offset.y + p_regionbbox->yMax + p_line->i_base_line;
951 int i_glyph_offset_x = offset.x - p_regionbbox->xMin;
953 RenderCharAXYZ( p_filter, p_picture, p_line,
954 i_glyph_offset_x, i_glyph_offset_y, g,
955 ExtractComponents, BlendPixel );
959 return VLC_SUCCESS;
962 static void UpdateDefaultLiveStyles( filter_t *p_filter )
964 filter_sys_t *p_sys = p_filter->p_sys;
965 text_style_t *p_style = p_sys->p_default_style;
967 p_style->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
969 p_style->i_background_alpha = var_InheritInteger( p_filter, "freetype-background-opacity" );
970 p_style->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
973 static void FillDefaultStyles( filter_t *p_filter )
975 filter_sys_t *p_sys = p_filter->p_sys;
977 p_sys->p_default_style->psz_fontname = var_InheritString( p_filter, "freetype-font" );
978 /* Set default psz_fontname */
979 if( !p_sys->p_default_style->psz_fontname || !*p_sys->p_default_style->psz_fontname )
981 free( p_sys->p_default_style->psz_fontname );
982 #ifdef HAVE_GET_FONT_BY_FAMILY_NAME
983 p_sys->p_default_style->psz_fontname = strdup( DEFAULT_FAMILY );
984 #else
985 p_sys->p_default_style->psz_fontname = File_Select( DEFAULT_FONT_FILE );
986 #endif
989 p_sys->p_default_style->psz_monofontname = var_InheritString( p_filter, "freetype-monofont" );
990 /* set default psz_monofontname */
991 if( !p_sys->p_default_style->psz_monofontname || !*p_sys->p_default_style->psz_monofontname )
993 free( p_sys->p_default_style->psz_monofontname );
994 #ifdef HAVE_GET_FONT_BY_FAMILY_NAME
995 p_sys->p_default_style->psz_monofontname = strdup( DEFAULT_MONOSPACE_FAMILY );
996 #else
997 p_sys->p_default_style->psz_monofontname = File_Select( DEFAULT_MONOSPACE_FONT_FILE );
998 #endif
1001 UpdateDefaultLiveStyles( p_filter );
1003 p_sys->p_default_style->i_font_alpha = var_InheritInteger( p_filter, "freetype-opacity" );
1005 p_sys->p_default_style->i_outline_alpha = var_InheritInteger( p_filter, "freetype-outline-opacity" );
1006 p_sys->p_default_style->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
1008 p_sys->p_default_style->i_shadow_alpha = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
1009 p_sys->p_default_style->i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
1011 p_sys->p_default_style->i_font_size = 0;
1012 p_sys->p_default_style->i_style_flags |= STYLE_SHADOW;
1013 p_sys->p_default_style->i_features |= STYLE_HAS_FLAGS;
1015 if( var_InheritBool( p_filter, "freetype-bold" ) )
1017 p_sys->p_forced_style->i_style_flags |= STYLE_BOLD;
1018 p_sys->p_forced_style->i_features |= STYLE_HAS_FLAGS;
1021 /* Apply forced styles to defaults, if any */
1022 text_style_Merge( p_sys->p_default_style, p_sys->p_forced_style, true );
1025 static void FreeRubyBlockArray( ruby_block_t **pp_array, size_t i_array )
1027 ruby_block_t *p_lyt = NULL;
1028 for( size_t i = 0; i< i_array; i++ )
1030 if( p_lyt != pp_array[i] )
1032 p_lyt = pp_array[i];
1033 if( p_lyt )
1035 free( p_lyt->p_uchars );
1036 text_style_Delete( p_lyt->p_style );
1037 if( p_lyt->p_laid )
1038 FreeLines( p_lyt->p_laid );
1039 free( p_lyt );
1043 free( pp_array );
1046 static void FreeStylesArray( text_style_t **pp_styles, size_t i_styles )
1048 text_style_t *p_style = NULL;
1049 for( size_t i = 0; i< i_styles; i++ )
1051 if( p_style != pp_styles[i] )
1053 p_style = pp_styles[i];
1054 text_style_Delete( p_style );
1057 free( pp_styles );
1060 static size_t AddTextAndStyles( filter_sys_t *p_sys,
1061 const char *psz_text, const char *psz_rt,
1062 const text_style_t *p_style,
1063 layout_text_block_t *p_text_block )
1065 /* Convert chars to unicode */
1066 size_t i_bytes;
1067 uni_char_t *p_ucs4 = ToCharset( FREETYPE_TO_UCS, psz_text, &i_bytes );
1068 if( !p_ucs4 )
1069 return 0;
1071 const size_t i_newchars = i_bytes / 4;
1072 if( SIZE_MAX / 4 < p_text_block->i_count + i_newchars )
1074 free( p_ucs4 );
1075 return 0;
1077 size_t i_realloc = (p_text_block->i_count + i_newchars) * 4;
1078 void *p_realloc = realloc( p_text_block->p_uchars, i_realloc );
1079 if( unlikely(!p_realloc) )
1080 return 0;
1081 p_text_block->p_uchars = p_realloc;
1083 /* We want one per segment shared text_style_t* per unicode character */
1084 if( SIZE_MAX / sizeof(text_style_t *) < p_text_block->i_count + i_newchars )
1085 return 0;
1086 i_realloc = (p_text_block->i_count + i_newchars) * sizeof(text_style_t *);
1087 p_realloc = realloc( p_text_block->pp_styles, i_realloc );
1088 if ( unlikely(!p_realloc) )
1089 return 0;
1090 p_text_block->pp_styles = p_realloc;
1092 /* Same for ruby text */
1093 if( SIZE_MAX / sizeof(text_segment_ruby_t *) < p_text_block->i_count + i_newchars )
1094 return 0;
1095 i_realloc = (p_text_block->i_count + i_newchars) * sizeof(text_segment_ruby_t *);
1096 p_realloc = realloc( p_text_block->pp_ruby, i_realloc );
1097 if ( unlikely(!p_realloc) )
1098 return 0;
1099 p_text_block->pp_ruby = p_realloc;
1101 /* Copy data */
1102 memcpy( &p_text_block->p_uchars[p_text_block->i_count], p_ucs4, i_newchars * 4 );
1103 free( p_ucs4 );
1105 text_style_t *p_mgstyle = text_style_Duplicate( p_sys->p_default_style );
1106 if ( p_mgstyle == NULL )
1107 return 0;
1109 if( p_style )
1110 /* Replace defaults with segment values */
1111 text_style_Merge( p_mgstyle, p_style, true );
1113 /* Overwrite any default or value with forced ones */
1114 text_style_Merge( p_mgstyle, p_sys->p_forced_style, true );
1116 /* map it to each char of that segment */
1117 for ( size_t i = 0; i < i_newchars; ++i )
1118 p_text_block->pp_styles[p_text_block->i_count + i] = p_mgstyle;
1120 ruby_block_t *p_rubyblock = NULL;
1121 if( psz_rt )
1123 p_ucs4 = ToCharset( FREETYPE_TO_UCS, psz_rt, &i_bytes );
1124 if( !p_ucs4 )
1125 return 0;
1126 p_rubyblock = malloc(sizeof(ruby_block_t));
1127 if( p_rubyblock )
1129 p_rubyblock->p_style = text_style_Duplicate( p_mgstyle );
1130 if( !p_rubyblock->p_style )
1132 free( p_ucs4 );
1133 free( p_rubyblock );
1134 return 0;
1136 p_rubyblock->p_style->i_font_size *= 0.4;
1137 p_rubyblock->p_style->f_font_relsize *= 0.4;
1138 p_rubyblock->p_uchars = p_ucs4;
1139 p_rubyblock->i_count = i_bytes / 4;
1140 p_rubyblock->p_laid = NULL;
1142 else free( p_ucs4 );
1144 for ( size_t i = 0; i < i_newchars; ++i )
1145 p_text_block->pp_ruby[p_text_block->i_count + i] = p_rubyblock;
1147 /* now safe to update total nb */
1148 p_text_block->i_count += i_newchars;
1150 return i_newchars;
1153 static size_t SegmentsToTextAndStyles( filter_t *p_filter, const text_segment_t *p_segment,
1154 layout_text_block_t *p_text_block )
1156 size_t i_nb_char = 0;
1158 for( const text_segment_t *s = p_segment; s != NULL; s = s->p_next )
1160 if( !s->psz_text || !s->psz_text[0] )
1161 continue;
1163 if( s->p_ruby )
1165 for( const text_segment_ruby_t *p_ruby = s->p_ruby;
1166 p_ruby; p_ruby = p_ruby->p_next )
1168 i_nb_char += AddTextAndStyles( p_filter->p_sys,
1169 p_ruby->psz_base, p_ruby->psz_rt,
1170 s->style, p_text_block );
1173 else
1175 i_nb_char += AddTextAndStyles( p_filter->p_sys,
1176 s->psz_text, NULL,
1177 s->style, p_text_block );
1181 return i_nb_char;
1185 * This function renders a text subpicture region into another one.
1186 * It also calculates the size needed for this string, and renders the
1187 * needed glyphs into memory. It is used as pf_add_string callback in
1188 * the vout method by this module
1190 static int Render( filter_t *p_filter, subpicture_region_t *p_region_out,
1191 subpicture_region_t *p_region_in,
1192 const vlc_fourcc_t *p_chroma_list )
1194 if( !p_region_in )
1195 return VLC_EGENERIC;
1197 filter_sys_t *p_sys = p_filter->p_sys;
1198 bool b_grid = p_region_in->b_gridmode;
1199 p_sys->i_scale = ( b_grid ) ? 100 : var_InheritInteger( p_filter, "sub-text-scale");
1201 UpdateDefaultLiveStyles( p_filter );
1204 * Update the default face to reflect changes in video size or text scaling
1206 p_sys->p_face = SelectAndLoadFace( p_filter, p_sys->p_default_style, 0 );
1207 if( !p_sys->p_face )
1209 msg_Err( p_filter, "Render(): Error loading default face" );
1210 return VLC_EGENERIC;
1213 layout_text_block_t text_block = { 0 };
1214 text_block.b_balanced = p_region_in->b_balanced_text;
1215 text_block.b_grid = p_region_in->b_gridmode;
1216 text_block.i_count = SegmentsToTextAndStyles( p_filter, p_region_in->p_text,
1217 &text_block );
1218 if( text_block.i_count == 0 )
1220 free( text_block.pp_styles );
1221 free( text_block.p_uchars );
1222 return VLC_EGENERIC;
1225 /* */
1226 int rv = VLC_SUCCESS;
1227 FT_BBox bbox;
1228 int i_max_face_height;
1230 unsigned i_max_width = p_filter->fmt_out.video.i_visible_width;
1231 if( p_region_in->i_max_width > 0 && (unsigned) p_region_in->i_max_width < i_max_width )
1232 i_max_width = p_region_in->i_max_width;
1233 else if( p_region_in->i_x > 0 && (unsigned)p_region_in->i_x < i_max_width )
1234 i_max_width -= p_region_in->i_x;
1236 unsigned i_max_height = p_filter->fmt_out.video.i_visible_height;
1237 if( p_region_in->i_max_height > 0 && (unsigned) p_region_in->i_max_height < i_max_height )
1238 i_max_height = p_region_in->i_max_height;
1239 else if( p_region_in->i_y > 0 && (unsigned)p_region_in->i_y < i_max_height )
1240 i_max_height -= p_region_in->i_y;
1242 text_block.i_max_width = i_max_width;
1243 text_block.i_max_height = i_max_height;
1244 rv = LayoutTextBlock( p_filter, &text_block, &text_block.p_laid, &bbox, &i_max_face_height );
1246 /* Don't attempt to render text that couldn't be layed out
1247 * properly. */
1248 if( !rv && text_block.i_count > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
1250 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
1251 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
1253 uint8_t i_background_opacity = var_InheritInteger( p_filter, "freetype-background-opacity" );
1254 i_background_opacity = VLC_CLIP( i_background_opacity, 0, 255 );
1255 int i_margin = (i_background_opacity > 0 && !p_region_in->b_gridmode) ? i_max_face_height / 4 : 0;
1257 if( (unsigned)i_margin * 2 >= i_max_width || (unsigned)i_margin * 2 >= i_max_height )
1258 i_margin = 0;
1260 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
1261 p_chroma_list = p_chroma_list_yuvp;
1262 else if( !p_chroma_list || *p_chroma_list == 0 )
1263 p_chroma_list = p_chroma_list_rgba;
1265 FT_BBox paddedbbox = bbox;
1266 paddedbbox.xMin -= i_margin;
1267 paddedbbox.xMax += i_margin;
1268 paddedbbox.yMin -= i_margin;
1269 paddedbbox.yMax += i_margin;
1271 FT_BBox regionbbox = paddedbbox;
1273 /* _______regionbbox_______________
1274 * | |
1275 * | |
1276 * | |
1277 * | _bbox(<paddedbbox)___ |
1278 * | | rightaligned| |
1279 * | | textlines| |
1280 * | |_____________________| |
1281 * |_______________________________|
1283 * we need at least 3 bounding boxes.
1284 * regionbbox containing the whole, including region background pixels
1285 * paddedbox an enlarged text box when for drawing text background
1286 * bbox the lines bounding box for all glyphs
1287 * For simple unstyled subs, bbox == paddedbox == regionbbox
1290 unsigned outertext_w = (regionbbox.xMax - regionbbox.xMin);
1291 if( outertext_w < (unsigned) p_region_in->i_max_width )
1293 if( p_region_in->i_text_align & SUBPICTURE_ALIGN_RIGHT )
1294 regionbbox.xMin -= (p_region_in->i_max_width - outertext_w);
1295 else if( p_region_in->i_text_align & SUBPICTURE_ALIGN_LEFT )
1296 regionbbox.xMax += (p_region_in->i_max_width - outertext_w);
1297 else
1299 regionbbox.xMin -= (p_region_in->i_max_width - outertext_w) / 2;
1300 regionbbox.xMax += (p_region_in->i_max_width - outertext_w + 1) / 2;
1304 unsigned outertext_h = (regionbbox.yMax - regionbbox.yMin);
1305 if( outertext_h < (unsigned) p_region_in->i_max_height )
1307 if( p_region_in->i_text_align & SUBPICTURE_ALIGN_TOP )
1308 regionbbox.yMin -= (p_region_in->i_max_height - outertext_h);
1309 else if( p_region_in->i_text_align & SUBPICTURE_ALIGN_BOTTOM )
1310 regionbbox.yMax += (p_region_in->i_max_height - outertext_h);
1311 else
1313 regionbbox.yMin -= (p_region_in->i_max_height - outertext_h + 1) / 2;
1314 regionbbox.yMax += (p_region_in->i_max_height - outertext_h) / 2;
1318 // unsigned bboxcolor = 0xFF000000;
1319 /* TODO 4.0. No region self BG color for VLC 3.0 API*/
1321 /* Avoid useless pixels:
1322 * reshrink/trim Region Box to padded text one,
1323 * but update offsets to keep position and have same rendering */
1324 // if( (bboxcolor & 0xFF) == 0 )
1326 p_region_out->i_x = (paddedbbox.xMin - regionbbox.xMin) + p_region_in->i_x;
1327 p_region_out->i_y = (regionbbox.yMax - paddedbbox.yMax) + p_region_in->i_y;
1328 regionbbox = paddedbbox;
1330 // else /* case where the bounding box is larger and visible */
1331 // {
1332 // p_region_out->i_x = p_region_in->i_x;
1333 // p_region_out->i_y = p_region_in->i_y;
1334 // }
1336 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
1338 rv = VLC_EGENERIC;
1339 if( *p_chroma == VLC_CODEC_YUVP )
1340 rv = RenderYUVP( p_filter, p_region_out, text_block.p_laid,
1341 &regionbbox, &paddedbbox, &bbox );
1342 else if( *p_chroma == VLC_CODEC_YUVA )
1343 rv = RenderAXYZ( p_filter, p_region_out, text_block.p_laid,
1344 &regionbbox, &paddedbbox, &bbox,
1345 VLC_CODEC_YUVA,
1346 &p_region_out->fmt,
1347 YUVFromRGB,
1348 FillYUVAPicture,
1349 BlendYUVAPixel );
1350 else if( *p_chroma == VLC_CODEC_RGBA )
1351 rv = RenderAXYZ( p_filter, p_region_out, text_block.p_laid,
1352 &regionbbox, &paddedbbox, &bbox,
1353 VLC_CODEC_RGBA,
1354 &p_region_out->fmt,
1355 RGBFromRGB,
1356 FillRGBAPicture,
1357 BlendRGBAPixel );
1358 else if( *p_chroma == VLC_CODEC_ARGB )
1359 rv = RenderAXYZ( p_filter, p_region_out, text_block.p_laid,
1360 &regionbbox, &paddedbbox, &bbox,
1361 VLC_CODEC_ARGB,
1362 &p_region_out->fmt,
1363 RGBFromRGB,
1364 FillARGBPicture,
1365 BlendARGBPixel );
1367 if( !rv )
1368 break;
1371 /* With karaoke, we're going to have to render the text a number
1372 * of times to show the progress marker on the text.
1374 if( text_block.pi_k_durations )
1375 var_SetBool( p_filter, "text-rerender", true );
1378 FreeLines( text_block.p_laid );
1380 free( text_block.p_uchars );
1381 FreeStylesArray( text_block.pp_styles, text_block.i_count );
1382 if( text_block.pp_ruby )
1383 FreeRubyBlockArray( text_block.pp_ruby, text_block.i_count );
1384 free( text_block.pi_k_durations );
1386 return rv;
1389 static void FreeFace( void *p_face, void *p_obj )
1391 VLC_UNUSED( p_obj );
1393 FT_Done_Face( ( FT_Face ) p_face );
1396 /*****************************************************************************
1397 * Create: allocates osd-text video thread output method
1398 *****************************************************************************
1399 * This function allocates and initializes a Clone vout method.
1400 *****************************************************************************/
1401 static int Create( vlc_object_t *p_this )
1403 filter_t *p_filter = ( filter_t * ) p_this;
1404 filter_sys_t *p_sys = NULL;
1406 /* Allocate structure */
1407 p_filter->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
1408 if( !p_sys )
1409 return VLC_ENOMEM;
1411 /* Init Freetype and its stroker */
1412 if( FT_Init_FreeType( &p_sys->p_library ) )
1414 msg_Err( p_filter, "Failed to initialize FreeType" );
1415 free( p_sys );
1416 return VLC_EGENERIC;
1419 if( FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker ) )
1421 msg_Err( p_filter, "Failed to create stroker for outlining" );
1422 p_sys->p_stroker = NULL;
1425 /* Dictionnaries for fonts and families */
1426 vlc_dictionary_init( &p_sys->face_map, 50 );
1427 vlc_dictionary_init( &p_sys->family_map, 50 );
1428 vlc_dictionary_init( &p_sys->fallback_map, 20 );
1430 p_sys->i_scale = 100;
1432 /* default style to apply to uncomplete segmeents styles */
1433 p_sys->p_default_style = text_style_Create( STYLE_FULLY_SET );
1434 if(unlikely(!p_sys->p_default_style))
1435 goto error;
1437 /* empty style for style overriding cases */
1438 p_sys->p_forced_style = text_style_Create( STYLE_NO_DEFAULTS );
1439 if(unlikely(!p_sys->p_forced_style))
1440 goto error;
1442 /* fills default and forced style */
1443 FillDefaultStyles( p_filter );
1446 * The following variables should not be cached, as they might be changed on-the-fly:
1447 * freetype-rel-fontsize, freetype-background-opacity, freetype-background-color,
1448 * freetype-outline-thickness, freetype-color
1452 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
1453 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
1454 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
1455 p_sys->f_shadow_vector_x = f_shadow_distance * cosf((float)(2. * M_PI) * f_shadow_angle / 360);
1456 p_sys->f_shadow_vector_y = f_shadow_distance * sinf((float)(2. * M_PI) * f_shadow_angle / 360);
1458 if( LoadFontsFromAttachments( p_filter ) == VLC_ENOMEM )
1459 goto error;
1461 #ifdef HAVE_FONTCONFIG
1462 p_sys->pf_select = Generic_Select;
1463 p_sys->pf_get_family = FontConfig_GetFamily;
1464 p_sys->pf_get_fallbacks = FontConfig_GetFallbacks;
1465 if( FontConfig_Prepare( p_filter ) )
1466 goto error;
1468 #elif defined( __APPLE__ )
1469 p_sys->pf_select = Generic_Select;
1470 p_sys->pf_get_family = CoreText_GetFamily;
1471 p_sys->pf_get_fallbacks = CoreText_GetFallbacks;
1472 #elif defined( _WIN32 )
1473 if( InitDWrite( p_filter ) == VLC_SUCCESS )
1475 p_sys->pf_get_family = DWrite_GetFamily;
1476 p_sys->pf_get_fallbacks = DWrite_GetFallbacks;
1477 p_sys->pf_select = Generic_Select;
1479 else
1481 #if VLC_WINSTORE_APP
1482 msg_Err( p_filter, "Error initializing DirectWrite" );
1483 goto error;
1484 #else
1485 msg_Warn( p_filter, "DirectWrite initialization failed. Falling back to GDI/Uniscribe" );
1486 const char *const ppsz_win32_default[] =
1487 { "Tahoma", "FangSong", "SimHei", "KaiTi" };
1488 p_sys->pf_get_family = Win32_GetFamily;
1489 p_sys->pf_get_fallbacks = Win32_GetFallbacks;
1490 p_sys->pf_select = Generic_Select;
1491 InitDefaultList( p_filter, ppsz_win32_default,
1492 sizeof( ppsz_win32_default ) / sizeof( *ppsz_win32_default ) );
1493 #endif
1495 #elif defined( __ANDROID__ )
1496 p_sys->pf_get_family = Android_GetFamily;
1497 p_sys->pf_get_fallbacks = Android_GetFallbacks;
1498 p_sys->pf_select = Generic_Select;
1500 if( Android_Prepare( p_filter ) == VLC_ENOMEM )
1501 goto error;
1502 #else
1503 p_sys->pf_select = Dummy_Select;
1504 #endif
1506 p_sys->p_face = SelectAndLoadFace( p_filter, p_sys->p_default_style, 0 );
1507 if( !p_sys->p_face )
1509 msg_Err( p_filter, "Error loading default face" );
1510 #ifdef HAVE_FONTCONFIG
1511 FontConfig_Unprepare();
1512 #endif
1513 goto error;
1516 p_filter->pf_render = Render;
1518 return VLC_SUCCESS;
1520 error:
1521 Destroy( VLC_OBJECT(p_filter) );
1522 return VLC_EGENERIC;
1525 /*****************************************************************************
1526 * Destroy: destroy Clone video thread output method
1527 *****************************************************************************
1528 * Clean up all data and library connections
1529 *****************************************************************************/
1530 static void Destroy( vlc_object_t *p_this )
1532 filter_t *p_filter = (filter_t *)p_this;
1533 filter_sys_t *p_sys = p_filter->p_sys;
1535 #if 0
1536 msg_Dbg( p_filter, "------------------" );
1537 msg_Dbg( p_filter, "p_sys->p_families:" );
1538 msg_Dbg( p_filter, "------------------" );
1539 DumpFamily( p_filter, p_sys->p_families, true, -1 );
1540 msg_Dbg( p_filter, "-----------------" );
1541 msg_Dbg( p_filter, "p_sys->family_map" );
1542 msg_Dbg( p_filter, "-----------------" );
1543 DumpDictionary( p_filter, &p_sys->family_map, false, 1 );
1544 msg_Dbg( p_filter, "-------------------" );
1545 msg_Dbg( p_filter, "p_sys->fallback_map" );
1546 msg_Dbg( p_filter, "-------------------" );
1547 DumpDictionary( p_filter, &p_sys->fallback_map, true, -1 );
1548 #endif
1550 /* Text styles */
1551 text_style_Delete( p_sys->p_default_style );
1552 text_style_Delete( p_sys->p_forced_style );
1554 /* Fonts dicts */
1555 vlc_dictionary_clear( &p_sys->fallback_map, FreeFamilies, p_filter );
1556 vlc_dictionary_clear( &p_sys->face_map, FreeFace, p_filter );
1557 vlc_dictionary_clear( &p_sys->family_map, NULL, NULL );
1558 if( p_sys->p_families )
1559 FreeFamiliesAndFonts( p_sys->p_families );
1561 /* Attachments */
1562 if( p_sys->pp_font_attachments )
1564 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1565 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
1567 free( p_sys->pp_font_attachments );
1570 #ifdef HAVE_FONTCONFIG
1571 if( p_sys->p_face != NULL )
1572 FontConfig_Unprepare();
1574 #elif defined( _WIN32 )
1575 if( p_sys->pf_get_family == DWrite_GetFamily )
1576 ReleaseDWrite( p_filter );
1577 #endif
1579 /* Freetype */
1580 if( p_sys->p_stroker )
1581 FT_Stroker_Done( p_sys->p_stroker );
1583 FT_Done_FreeType( p_sys->p_library );
1585 free( p_sys );