1 /*****************************************************************************
2 * quartztext.c : Put text on the video, using Mac OS X Quartz Engine
3 *****************************************************************************
4 * Copyright (C) 2007, 2009 the VideoLAN team
7 * Authors: Bernie Purcell <bitmap@videolan.org>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 //////////////////////////////////////////////////////////////////////////////
26 //////////////////////////////////////////////////////////////////////////////
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
36 #include <vlc_block.h>
37 #include <vlc_filter.h>
38 #include <vlc_stream.h>
40 #include <vlc_input.h>
41 #include <vlc_strings.h>
45 // Fix ourselves ColorSync headers that gets included in ApplicationServices.
46 #define DisposeCMProfileIterateUPP(a) DisposeCMProfileIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
47 #define DisposeCMMIterateUPP(a) DisposeCMMIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
48 #define __MACHINEEXCEPTIONS__
49 #include <Carbon/Carbon.h>
51 #define DEFAULT_FONT "Arial Black"
52 #define DEFAULT_FONT_COLOR 0xffffff
53 #define DEFAULT_REL_FONT_SIZE 16
55 #define VERTICAL_MARGIN 3
56 #define HORIZONTAL_MARGIN 10
58 //////////////////////////////////////////////////////////////////////////////
60 //////////////////////////////////////////////////////////////////////////////
61 static int Create ( vlc_object_t * );
62 static void Destroy( vlc_object_t * );
64 static int LoadFontsFromAttachments( filter_t *p_filter );
66 static int RenderText( filter_t *, subpicture_region_t *,
67 subpicture_region_t * );
68 static int RenderHtml( filter_t *, subpicture_region_t *,
69 subpicture_region_t * );
71 static int GetFontSize( filter_t *p_filter );
72 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
73 CFMutableAttributedStringRef p_attrString );
75 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
76 bool b_bold, bool b_italic, bool b_underline,
77 CFRange p_range, CFMutableAttributedStringRef p_attrString );
79 //////////////////////////////////////////////////////////////////////////////
81 //////////////////////////////////////////////////////////////////////////////
83 // The preferred way to set font style information is for it to come from the
84 // subtitle file, and for it to be rendered with RenderHtml instead of
86 #define FONT_TEXT N_("Font")
87 #define FONT_LONGTEXT N_("Name for the font you want to use")
88 #define FONTSIZER_TEXT N_("Relative font size")
89 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
90 "fonts that will be rendered on the video. If absolute font size is set, "\
91 "relative size will be overriden." )
92 #define COLOR_TEXT N_("Text default color")
93 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
94 "the video. This must be an hexadecimal (like HTML colors). The first two "\
95 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
96 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
98 static const int pi_color_values[] = {
99 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
100 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
101 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
103 static const char *const ppsz_color_descriptions[] = {
104 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
105 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
106 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
108 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
109 static const char *const ppsz_sizes_text[] = {
110 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
113 set_shortname( N_("Mac Text renderer"))
114 set_description( N_("Quartz font renderer") )
115 set_category( CAT_VIDEO )
116 set_subcategory( SUBCAT_VIDEO_SUBPIC )
118 add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
120 add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, NULL, FONTSIZER_TEXT,
121 FONTSIZER_LONGTEXT, false )
122 change_integer_list( pi_sizes, ppsz_sizes_text, NULL );
123 add_integer( "quartztext-color", 0x00FFFFFF, NULL, COLOR_TEXT,
124 COLOR_LONGTEXT, false )
125 change_integer_list( pi_color_values, ppsz_color_descriptions, NULL );
126 set_capability( "text renderer", 150 )
127 add_shortcut( "text" )
128 set_callbacks( Create, Destroy )
131 typedef struct font_stack_t font_stack_t;
136 uint32_t i_color; // ARGB
138 font_stack_t *p_next;
144 uint32_t i_font_color; /* ARGB */
151 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
152 struct offscreen_bitmap_t
155 int i_bitsPerChannel;
161 //////////////////////////////////////////////////////////////////////////////
162 // filter_sys_t: quartztext local data
163 //////////////////////////////////////////////////////////////////////////////
164 // This structure is part of the video output thread descriptor.
165 // It describes the freetype specific properties of an output thread.
166 //////////////////////////////////////////////////////////////////////////////
170 uint8_t i_font_opacity;
174 ATSFontContainerRef *p_fonts;
178 //////////////////////////////////////////////////////////////////////////////
179 // Create: allocates osd-text video thread output method
180 //////////////////////////////////////////////////////////////////////////////
181 // This function allocates and initializes a Clone vout method.
182 //////////////////////////////////////////////////////////////////////////////
183 static int Create( vlc_object_t *p_this )
185 filter_t *p_filter = (filter_t *)p_this;
188 // Allocate structure
189 p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
192 p_sys->psz_font_name = var_CreateGetString( p_this, "quartztext-font" );
193 p_sys->i_font_opacity = 255;
194 p_sys->i_font_color = __MAX( __MIN( var_CreateGetInteger( p_this, "quartztext-color" ) , 0xFFFFFF ), 0 );
195 p_sys->i_font_size = GetFontSize( p_filter );
197 p_filter->pf_render_text = RenderText;
198 p_filter->pf_render_html = RenderHtml;
200 p_sys->p_fonts = NULL;
203 LoadFontsFromAttachments( p_filter );
208 //////////////////////////////////////////////////////////////////////////////
209 // Destroy: destroy Clone video thread output method
210 //////////////////////////////////////////////////////////////////////////////
211 // Clean up all data and library connections
212 //////////////////////////////////////////////////////////////////////////////
213 static void Destroy( vlc_object_t *p_this )
215 filter_t *p_filter = (filter_t *)p_this;
216 filter_sys_t *p_sys = p_filter->p_sys;
222 for( k = 0; k < p_sys->i_fonts; k++ )
224 ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
227 free( p_sys->p_fonts );
230 free( p_sys->psz_font_name );
234 //////////////////////////////////////////////////////////////////////////////
235 // Make any TTF/OTF fonts present in the attachments of the media file
236 // available to the Quartz engine for text rendering
237 //////////////////////////////////////////////////////////////////////////////
238 static int LoadFontsFromAttachments( filter_t *p_filter )
240 filter_sys_t *p_sys = p_filter->p_sys;
241 input_thread_t *p_input;
242 input_attachment_t **pp_attachments;
243 int i_attachments_cnt;
245 int rv = VLC_SUCCESS;
247 p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
251 if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
253 vlc_object_release(p_input);
258 p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
259 if(! p_sys->p_fonts )
262 for( k = 0; k < i_attachments_cnt; k++ )
264 input_attachment_t *p_attach = pp_attachments[k];
268 if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
269 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
270 ( p_attach->i_data > 0 ) &&
271 ( p_attach->p_data != NULL ) )
273 ATSFontContainerRef container;
275 if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
277 kATSFontContextLocal,
278 kATSFontFormatUnspecified,
280 kATSOptionFlagsDefault,
283 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
287 vlc_input_attachment_Delete( p_attach );
289 free( pp_attachments );
291 vlc_object_release(p_input);
296 static char *EliminateCRLF( char *psz_string )
301 for( p = psz_string; p && *p; p++ )
303 if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
305 for( q = p + 1; *q; q++ )
314 // Renders a text subpicture region into another one.
315 // It is used as pf_add_string callback in the vout method by this module
316 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
317 subpicture_region_t *p_region_in )
319 filter_sys_t *p_sys = p_filter->p_sys;
321 int i_font_alpha, i_font_size;
322 uint32_t i_font_color;
326 p_sys->i_font_size = GetFontSize( p_filter );
329 if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
330 psz_string = p_region_in->psz_text;
331 if( !psz_string || !*psz_string ) return VLC_EGENERIC;
333 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
336 if( p_region_in->p_style )
338 i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
339 i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
340 i_font_size = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
344 i_font_color = p_sys->i_font_color;
345 i_font_alpha = 255 - p_sys->i_font_opacity;
346 i_font_size = p_sys->i_font_size;
349 if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
351 if( i_font_size <= 0 )
353 msg_Warn( p_filter, "invalid fontsize, using 12" );
354 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
355 i_font_size = 12 * val.i_int / 1000;
360 p_region_out->i_x = p_region_in->i_x;
361 p_region_out->i_y = p_region_in->i_y;
363 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
367 CFStringRef p_cfString;
370 EliminateCRLF( psz_string);
371 p_cfString = CFStringCreateWithCString( NULL, psz_string, kCFStringEncodingUTF8 );
372 CFAttributedStringReplaceString( p_attrString, CFRangeMake(0, 0), p_cfString );
373 CFRelease( p_cfString );
374 len = CFAttributedStringGetLength( p_attrString );
376 setFontAttibutes( p_sys->psz_font_name, i_font_size, i_font_color, FALSE, FALSE, FALSE,
377 CFRangeMake( 0, len ), p_attrString);
379 RenderYUVA( p_filter, p_region_out, p_attrString );
381 CFRelease(p_attrString);
387 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
395 p_new = malloc( sizeof( font_stack_t ) );
399 p_new->p_next = NULL;
402 p_new->psz_name = strdup( psz_name );
404 p_new->psz_name = NULL;
406 p_new->i_size = i_size;
407 p_new->i_color = i_color;
415 font_stack_t *p_last;
417 for( p_last = *p_font;
419 p_last = p_last->p_next )
422 p_last->p_next = p_new;
427 static int PopFont( font_stack_t **p_font )
429 font_stack_t *p_last, *p_next_to_last;
431 if( !p_font || !*p_font )
434 p_next_to_last = NULL;
435 for( p_last = *p_font;
437 p_last = p_last->p_next )
439 p_next_to_last = p_last;
443 p_next_to_last->p_next = NULL;
447 free( p_last->psz_name );
453 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
456 font_stack_t *p_last;
458 if( !p_font || !*p_font )
463 p_last=p_last->p_next )
466 *psz_name = p_last->psz_name;
467 *i_size = p_last->i_size;
468 *i_color = p_last->i_color;
473 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
474 font_stack_t **p_fonts, int i_scale )
477 char *psz_fontname = NULL;
478 uint32_t i_font_color = 0xffffff;
479 int i_font_alpha = 0;
480 int i_font_size = 24;
482 // Default all attributes to the top font in the stack -- in case not
483 // all attributes are specified in the sub-font
484 if( VLC_SUCCESS == PeekFont( p_fonts,
489 psz_fontname = strdup( psz_fontname );
490 i_font_size = i_font_size * 1000 / i_scale;
492 i_font_alpha = (i_font_color >> 24) & 0xff;
493 i_font_color &= 0x00ffffff;
495 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
497 char *psz_name = xml_ReaderName( p_xml_reader );
498 char *psz_value = xml_ReaderValue( p_xml_reader );
500 if( psz_name && psz_value )
502 if( !strcasecmp( "face", psz_name ) )
504 if( psz_fontname ) free( psz_fontname );
505 psz_fontname = strdup( psz_value );
507 else if( !strcasecmp( "size", psz_name ) )
509 if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
511 int i_value = atoi( psz_value );
513 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
514 i_font_size += ( i_value * i_font_size ) / 10;
515 else if( i_value < -5 )
516 i_font_size = - i_value;
517 else if( i_value > 5 )
518 i_font_size = i_value;
521 i_font_size = atoi( psz_value );
523 else if( !strcasecmp( "color", psz_name ) &&
524 ( psz_value[0] == '#' ) )
526 i_font_color = strtol( psz_value + 1, NULL, 16 );
527 i_font_color &= 0x00ffffff;
529 else if( !strcasecmp( "alpha", psz_name ) &&
530 ( psz_value[0] == '#' ) )
532 i_font_alpha = strtol( psz_value + 1, NULL, 16 );
533 i_font_alpha &= 0xff;
539 rv = PushFont( p_fonts,
541 i_font_size * i_scale / 1000,
542 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24) );
544 free( psz_fontname );
549 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
550 bool b_bold, bool b_italic, bool b_underline,
551 CFRange p_range, CFMutableAttributedStringRef p_attrString )
553 CFStringRef p_cfString;
556 // Handle font name and size
557 p_cfString = CFStringCreateWithCString( NULL,
559 kCFStringEncodingUTF8 );
560 p_font = CTFontCreateWithName( p_cfString,
563 CFRelease( p_cfString );
564 CFAttributedStringSetAttribute( p_attrString,
566 kCTFontAttributeName,
573 _uline = kCTUnderlineStyleSingle;
575 _uline = kCTUnderlineStyleNone;
577 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
578 CFAttributedStringSetAttribute( p_attrString,
580 kCTUnderlineStyleAttributeName,
582 CFRelease( underline );
591 CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
592 CFAttributedStringSetAttribute( p_attrString,
605 CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
606 CFAttributedStringSetAttribute( p_attrString,
612 // Handle foreground colour
613 CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
614 CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
615 (float)((i_font_color & 0x0000ff00) >> 8) / 255.0,
616 (float)((i_font_color & 0x000000ff) ) / 255.0,
617 (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
618 CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
619 CGColorSpaceRelease(rgbColorSpace);
621 CFAttributedStringSetAttribute( p_attrString,
623 kCTForegroundColorAttributeName,
625 CFRelease( fg_text );
629 static void GetAttrStrFromFontStack( font_stack_t **p_fonts,
630 bool b_bold, bool b_italic, bool b_uline,
631 CFRange p_range, CFMutableAttributedStringRef p_attrString )
633 char *psz_fontname = NULL;
635 uint32_t i_font_color = 0;
637 if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
640 setFontAttibutes( psz_fontname,
643 b_bold, b_italic, b_uline,
649 static int ProcessNodes( filter_t *p_filter,
650 xml_reader_t *p_xml_reader,
651 text_style_t *p_font_style,
652 CFMutableAttributedStringRef p_attrString )
654 int rv = VLC_SUCCESS;
655 filter_sys_t *p_sys = p_filter->p_sys;
656 font_stack_t *p_fonts = NULL;
660 char *psz_node = NULL;
662 bool b_italic = false;
664 bool b_uline = false;
666 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
671 rv = PushFont( &p_fonts,
672 p_font_style->psz_fontname,
673 p_font_style->i_font_size * i_scale / 1000,
674 (p_font_style->i_font_color & 0xffffff) |
675 ((p_font_style->i_font_alpha & 0xff) << 24) );
677 if( p_font_style->i_style_flags & STYLE_BOLD )
679 if( p_font_style->i_style_flags & STYLE_ITALIC )
681 if( p_font_style->i_style_flags & STYLE_UNDERLINE )
686 rv = PushFont( &p_fonts,
687 p_sys->psz_font_name,
689 p_sys->i_font_color );
691 if( rv != VLC_SUCCESS )
694 while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) )
696 switch ( xml_ReaderNodeType( p_xml_reader ) )
698 case XML_READER_NONE:
700 case XML_READER_ENDELEM:
701 psz_node = xml_ReaderName( p_xml_reader );
705 if( !strcasecmp( "font", psz_node ) )
707 else if( !strcasecmp( "b", psz_node ) )
709 else if( !strcasecmp( "i", psz_node ) )
711 else if( !strcasecmp( "u", psz_node ) )
717 case XML_READER_STARTELEM:
718 psz_node = xml_ReaderName( p_xml_reader );
721 if( !strcasecmp( "font", psz_node ) )
722 rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
723 else if( !strcasecmp( "b", psz_node ) )
725 else if( !strcasecmp( "i", psz_node ) )
727 else if( !strcasecmp( "u", psz_node ) )
729 else if( !strcasecmp( "br", psz_node ) )
731 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
732 CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), CFSTR("\n") );
734 GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
737 CFAttributedStringReplaceAttributedString( p_attrString,
738 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
740 CFRelease( p_attrnode );
745 case XML_READER_TEXT:
746 psz_node = xml_ReaderValue( p_xml_reader );
749 CFStringRef p_cfString;
752 // Turn any multiple-whitespaces into single spaces
753 char *s = strpbrk( psz_node, "\t\r\n " );
756 int i_whitespace = strspn( s, "\t\r\n " );
758 if( i_whitespace > 1 )
761 strlen( s ) - i_whitespace + 1 );
764 s = strpbrk( s, "\t\r\n " );
768 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
769 p_cfString = CFStringCreateWithCString( NULL, psz_node, kCFStringEncodingUTF8 );
770 CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), p_cfString );
771 CFRelease( p_cfString );
772 len = CFAttributedStringGetLength( p_attrnode );
774 GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
775 CFRangeMake( 0, len ),
778 CFAttributedStringReplaceAttributedString( p_attrString,
779 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
781 CFRelease( p_attrnode );
788 while( VLC_SUCCESS == PopFont( &p_fonts ) );
793 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
794 subpicture_region_t *p_region_in )
796 int rv = VLC_SUCCESS;
797 stream_t *p_sub = NULL;
799 xml_reader_t *p_xml_reader = NULL;
801 if( !p_region_in || !p_region_in->psz_html )
804 /* Reset the default fontsize in case screen metrics have changed */
805 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
807 p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
808 (uint8_t *) p_region_in->psz_html,
809 strlen( p_region_in->psz_html ),
813 p_xml = xml_Create( p_filter );
816 bool b_karaoke = false;
818 p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
821 /* Look for Root Node */
822 if( xml_ReaderRead( p_xml_reader ) == 1 )
824 char *psz_node = xml_ReaderName( p_xml_reader );
826 if( !strcasecmp( "karaoke", psz_node ) )
828 /* We're going to have to render the text a number
829 * of times to show the progress marker on the text.
831 var_SetBool( p_filter, "text-rerender", true );
834 else if( !strcasecmp( "text", psz_node ) )
840 /* Only text and karaoke tags are supported */
841 xml_ReaderDelete( p_xml, p_xml_reader );
854 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
855 rv = ProcessNodes( p_filter, p_xml_reader,
856 p_region_in->p_style, p_attrString );
858 i_len = CFAttributedStringGetLength( p_attrString );
860 p_region_out->i_x = p_region_in->i_x;
861 p_region_out->i_y = p_region_in->i_y;
863 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
865 RenderYUVA( p_filter, p_region_out, p_attrString );
867 CFRelease(p_attrString);
869 xml_ReaderDelete( p_xml, p_xml_reader );
873 stream_Delete( p_sub );
879 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
880 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
882 offscreen_bitmap_t *p_bitmap;
883 CGContextRef p_context = NULL;
885 p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
888 p_bitmap->i_bitsPerChannel = 8;
889 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
890 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
891 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
893 p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
895 *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
897 if( p_bitmap->p_data && *pp_colorSpace )
899 p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
900 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
901 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
905 if( CGContextSetAllowsAntialiasing != NULL )
907 CGContextSetAllowsAntialiasing( p_context, true );
910 *pp_memory = p_bitmap;
916 static offscreen_bitmap_t *Compose( int i_text_align,
917 CFMutableAttributedStringRef p_attrString,
920 unsigned *pi_textblock_height )
922 offscreen_bitmap_t *p_offScreen = NULL;
923 CGColorSpaceRef p_colorSpace = NULL;
924 CGContextRef p_context = NULL;
926 p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
928 *pi_textblock_height = 0;
933 CGContextSetTextMatrix( p_context, CGAffineTransformIdentity );
935 if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
937 else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
942 // Create the framesetter with the attributed string.
943 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
947 CGMutablePathRef p_path = CGPathCreateMutable();
948 CGRect p_bounds = CGRectMake( (float)HORIZONTAL_MARGIN,
949 (float)VERTICAL_MARGIN,
950 (float)(i_width - HORIZONTAL_MARGIN*2),
951 (float)(i_height - VERTICAL_MARGIN *2));
952 CGPathAddRect( p_path, NULL, p_bounds );
954 // Create the frame and draw it into the graphics context
955 frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
957 CGPathRelease(p_path);
959 // Set up black outlining of the text --
960 CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
961 CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
968 lines = CTFrameGetLines( frame );
969 penPosition.y = i_height;
970 for (int i=0; i<CFArrayGetCount( lines ); i++)
972 CGFloat ascent, descent, leading;
974 CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
975 CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
977 // Set the outlining for this line to be dependant on the size of the line -
978 // make it about 5% of the ascent, with a minimum at 1.0
979 float f_thickness = ascent * 0.05;
980 CGContextSetLineWidth( p_context, (( f_thickness > 1.0 ) ? 1.0 : f_thickness ));
982 double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2));
983 penPosition.x = HORIZONTAL_MARGIN + penOffset;
984 penPosition.y -= ascent;
985 CGContextSetTextPosition( p_context, penPosition.x, penPosition.y );
986 CTLineDraw( line, p_context );
987 penPosition.y -= descent + leading;
990 *pi_textblock_height = i_height - penPosition.y;
994 CFRelease(framesetter);
996 CGContextFlush( p_context );
997 CGContextRelease( p_context );
999 if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
1004 static int GetFontSize( filter_t *p_filter )
1006 return p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE;
1009 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
1010 CFMutableAttributedStringRef p_attrString )
1012 offscreen_bitmap_t *p_offScreen = NULL;
1013 unsigned i_textblock_height = 0;
1015 unsigned i_width = p_filter->fmt_out.video.i_visible_width;
1016 unsigned i_height = p_filter->fmt_out.video.i_visible_height;
1017 unsigned i_text_align = p_region->i_align & 0x3;
1021 msg_Err( p_filter, "Invalid argument to RenderYUVA" );
1022 return VLC_EGENERIC;
1025 p_offScreen = Compose( i_text_align, p_attrString,
1026 i_width, i_height, &i_textblock_height );
1030 msg_Err( p_filter, "No offscreen buffer" );
1031 return VLC_EGENERIC;
1034 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1037 unsigned x, y, i_pitch;
1038 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1040 // Create a new subpicture region
1041 memset( &fmt, 0, sizeof(video_format_t) );
1042 fmt.i_chroma = VLC_CODEC_YUVA;
1044 fmt.i_width = fmt.i_visible_width = i_width;
1045 fmt.i_height = fmt.i_visible_height = __MIN( i_height, i_textblock_height + VERTICAL_MARGIN * 2);
1046 fmt.i_x_offset = fmt.i_y_offset = 0;
1048 p_region->p_picture = picture_New( fmt.i_chroma, fmt.i_width, fmt.i_height, fmt.i_aspect );
1049 if( !p_region->p_picture )
1050 return VLC_EGENERIC;
1051 p_region->fmt = fmt;
1053 p_dst_y = p_region->p_picture->Y_PIXELS;
1054 p_dst_u = p_region->p_picture->U_PIXELS;
1055 p_dst_v = p_region->p_picture->V_PIXELS;
1056 p_dst_a = p_region->p_picture->A_PIXELS;
1057 i_pitch = p_region->p_picture->A_PITCH;
1059 i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1060 for( y = 0; y < fmt.i_height; y++)
1062 for( x = 0; x < fmt.i_width; x++)
1064 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1065 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1066 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1067 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1069 i_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
1070 802 * i_blue + 4096 + 131072 ) >> 13, 235);
1071 i_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
1072 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1073 i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
1074 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1076 p_dst_y[ i_offset + x ] = i_y;
1077 p_dst_u[ i_offset + x ] = i_u;
1078 p_dst_v[ i_offset + x ] = i_v;
1079 p_dst_a[ i_offset + x ] = i_alpha;
1081 i_offset += i_pitch;
1084 free( p_offScreen->p_data );
1085 free( p_offScreen );