1 /*****************************************************************************
2 * freetype.c : Put text on the video, using freetype2
3 *****************************************************************************
4 * Copyright (C) 2002 - 2015 VLC authors and VideoLAN
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 /*****************************************************************************
31 *****************************************************************************/
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>
48 # undef HAVE_FONTCONFIG
49 # define HAVE_GET_FONT_BY_FAMILY_NAME
54 # undef HAVE_FONTCONFIG
55 # define HAVE_GET_FONT_BY_FAMILY_NAME
59 #ifdef HAVE_FONTCONFIG
60 # define HAVE_GET_FONT_BY_FAMILY_NAME
65 # define HAVE_GET_FONT_BY_FAMILY_NAME
70 #include "platform_fonts.h"
72 #include "text_layout.h"
74 /*****************************************************************************
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
[] = {
131 static const char *const ppsz_outline_thickness
[] = {
132 N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
136 static const int pi_text_direction
[] = {
139 static const char *const ppsz_text_direction
[] = {
140 N_("Left to right"), N_("Right to left"), N_("Auto"),
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
)
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
)
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 )
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 )
171 add_bool( "freetype-bold", false, BOLD_TEXT
, NULL
, false )
174 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
175 BG_OPACITY_TEXT
, NULL
, false )
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 )
182 add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
183 OUTLINE_OPACITY_TEXT
, NULL
, false )
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 )
189 add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT
,
191 change_integer_list( pi_outline_thickness
, ppsz_outline_thickness
)
194 add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
195 SHADOW_OPACITY_TEXT
, NULL
, false )
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 )
201 add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
202 SHADOW_ANGLE_TEXT
, NULL
, false )
204 add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
205 SHADOW_DISTANCE_TEXT
, NULL
, false )
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 )
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
)
222 set_capability( "text renderer", 100 )
223 add_shortcut( "text" )
224 set_callbacks( Create
, Destroy
)
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
,
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
);
263 offsets
.x
= ( i_text_width
- p_line
->i_width
) / 2;
267 offsets
.x
= p_textbbox
->xMin
- p_line
->bbox
.xMin
;
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
;
284 if( filter_GetInputAttachments( p_filter
, &pp_attachments
, &i_attachments_cnt
) )
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
);
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
;
310 while( 0 == FT_New_Memory_Face( p_sys
->p_library
,
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
);
323 if( asprintf( &psz_lc
, FB_NAME
"-%04d",
324 p_sys
->i_fallback_counter
++ ) < 0 )
327 if( unlikely( !psz_lc
) )
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
) )
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
) )
351 FT_Done_Face( p_face
);
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
,
373 if( unlikely( !p_temp
) )
376 FreeFamilies( p_attachments
, NULL
);
380 p_temp
->p_fonts
= p_family
->p_fonts
;
384 vlc_dictionary_insert( &p_sys
->fallback_map
, FB_LIST_ATTACHMENTS
, p_attachments
);
390 FT_Done_Face( p_face
);
395 for( int i
= k
+ 1; i
< i_attachments_cnt
; ++i
)
396 vlc_input_attachment_Delete( pp_attachments
[ i
] );
398 free( pp_attachments
);
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
,
409 FT_BBox
*p_regionbbox
, FT_BBox
*p_paddedbbox
,
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};
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
);
427 fmt
.i_visible_width
= p_regionbbox
->xMax
- p_regionbbox
->xMin
+ 4;
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
)
442 fmt
.p_palette
= p_region
->fmt
.p_palette
? p_region
->fmt
.p_palette
: malloc(sizeof(*fmt
.p_palette
));
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
);
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 ?) */
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
;
516 for( x
= 1; x
< fmt
.i_width
- 1; 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;
524 memset( p_top
, 0, fmt
.i_width
);
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
,
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
];
570 *p_a
= 255 - (255 - *p_a
) * (255 - i_an
) / 255;
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
];
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
,
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];
615 p_rgba
[3] = 255 - (255 - p_rgba
[3]) * (255 - i_an
) / 255;
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
)
629 if (a
== r
&& a
== b
&& a
== g
)
631 memset(pic
->p
->p_pixels
, a
, pic
->p
->i_visible_lines
* pic
->p
->i_pitch
);
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)
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;
663 rgba
[0] = 255 - (255 - rgba
[0]) * (255 - an
) / 255;
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
,
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
;
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
,
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
,
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 */
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
)
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
,
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 */
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
,
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
;
829 uint8_t i_a
= ch
->p_style
->i_font_alpha
;
832 switch (g
) {/* Apply font alpha ratio to shadow/outline alpha */
834 i_a
= i_a
* ch
->p_style
->i_shadow_alpha
/ 255;
835 i_color
= ch
->p_style
->i_shadow_color
;
838 i_a
= i_a
* ch
->p_style
->i_outline_alpha
/ 255;
839 i_color
= ch
->p_style
->i_outline_color
;
842 i_color
= ch
->p_style
->i_font_color
;
846 if(ch
->p_ruby
&& ch
->p_ruby
->p_laid
)
848 RenderCharAXYZ( p_filter
,
851 i_offset_x
, i_offset_y
,
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
) )
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
,
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
,
882 i
+ 1 < p_line
->i_character_count
? &ch
[1] : NULL
,
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
,
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 */
903 video_format_Init( &fmt
, i_chroma
);
905 fmt
.i_visible_width
= p_regionbbox
->xMax
- p_regionbbox
->xMin
;
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
)
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 );
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
);
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
);
985 p_sys
->p_default_style
->psz_fontname
= File_Select( DEFAULT_FONT_FILE
);
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
);
997 p_sys
->p_default_style
->psz_monofontname
= File_Select( DEFAULT_MONOSPACE_FONT_FILE
);
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
];
1035 free( p_lyt
->p_uchars
);
1036 text_style_Delete( p_lyt
->p_style
);
1038 FreeLines( p_lyt
->p_laid
);
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
);
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 */
1067 uni_char_t
*p_ucs4
= ToCharset( FREETYPE_TO_UCS
, psz_text
, &i_bytes
);
1071 const size_t i_newchars
= i_bytes
/ 4;
1072 if( SIZE_MAX
/ 4 < p_text_block
->i_count
+ i_newchars
)
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
) )
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
)
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
) )
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
)
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
) )
1099 p_text_block
->pp_ruby
= p_realloc
;
1102 memcpy( &p_text_block
->p_uchars
[p_text_block
->i_count
], p_ucs4
, i_newchars
* 4 );
1105 text_style_t
*p_mgstyle
= text_style_Duplicate( p_sys
->p_default_style
);
1106 if ( p_mgstyle
== NULL
)
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
;
1123 p_ucs4
= ToCharset( FREETYPE_TO_UCS
, psz_rt
, &i_bytes
);
1126 p_rubyblock
= malloc(sizeof(ruby_block_t
));
1129 p_rubyblock
->p_style
= text_style_Duplicate( p_mgstyle
);
1130 if( !p_rubyblock
->p_style
)
1133 free( p_rubyblock
);
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
;
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] )
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
);
1175 i_nb_char
+= AddTextAndStyles( p_filter
->p_sys
,
1177 s
->style
, p_text_block
);
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
)
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
,
1218 if( text_block
.i_count
== 0 )
1220 free( text_block
.pp_styles
);
1221 free( text_block
.p_uchars
);
1222 return VLC_EGENERIC
;
1226 int rv
= VLC_SUCCESS
;
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
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
)
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_______________
1277 * | _bbox(<paddedbbox)___ |
1278 * | | rightaligned| |
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
);
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
);
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 */
1332 // p_region_out->i_x = p_region_in->i_x;
1333 // p_region_out->i_y = p_region_in->i_y;
1336 for( const vlc_fourcc_t
*p_chroma
= p_chroma_list
; *p_chroma
!= 0; p_chroma
++ )
1339 if( *p_chroma
== VLC_CODEC_YUVP
)
1340 rv
= RenderYUVP( p_filter
, p_region_out
, text_block
.p_laid
,
1341 ®ionbbox
, &paddedbbox
, &bbox
);
1342 else if( *p_chroma
== VLC_CODEC_YUVA
)
1343 rv
= RenderAXYZ( p_filter
, p_region_out
, text_block
.p_laid
,
1344 ®ionbbox
, &paddedbbox
, &bbox
,
1350 else if( *p_chroma
== VLC_CODEC_RGBA
)
1351 rv
= RenderAXYZ( p_filter
, p_region_out
, text_block
.p_laid
,
1352 ®ionbbox
, &paddedbbox
, &bbox
,
1358 else if( *p_chroma
== VLC_CODEC_ARGB
)
1359 rv
= RenderAXYZ( p_filter
, p_region_out
, text_block
.p_laid
,
1360 ®ionbbox
, &paddedbbox
, &bbox
,
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
);
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
) );
1411 /* Init Freetype and its stroker */
1412 if( FT_Init_FreeType( &p_sys
->p_library
) )
1414 msg_Err( p_filter
, "Failed to initialize FreeType" );
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
))
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
))
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
)
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
) )
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
;
1481 #if VLC_WINSTORE_APP
1482 msg_Err( p_filter
, "Error initializing DirectWrite" );
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
) );
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
)
1503 p_sys
->pf_select
= Dummy_Select
;
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();
1516 p_filter
->pf_render
= Render
;
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
;
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 );
1551 text_style_Delete( p_sys
->p_default_style
);
1552 text_style_Delete( p_sys
->p_forced_style
);
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
);
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
);
1580 if( p_sys
->p_stroker
)
1581 FT_Stroker_Done( p_sys
->p_stroker
);
1583 FT_Done_FreeType( p_sys
->p_library
);