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 #include <Carbon/Carbon.h>
50 #define DEFAULT_FONT "Arial Black"
51 #define DEFAULT_FONT_COLOR 0xffffff
52 #define DEFAULT_REL_FONT_SIZE 16
54 #define VERTICAL_MARGIN 3
55 #define HORIZONTAL_MARGIN 10
57 //////////////////////////////////////////////////////////////////////////////
59 //////////////////////////////////////////////////////////////////////////////
60 static int Create ( vlc_object_t * );
61 static void Destroy( vlc_object_t * );
63 static int LoadFontsFromAttachments( filter_t *p_filter );
65 static int RenderText( filter_t *, subpicture_region_t *,
66 subpicture_region_t * );
67 static int RenderHtml( filter_t *, subpicture_region_t *,
68 subpicture_region_t * );
70 static int GetFontSize( filter_t *p_filter );
71 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
72 CFMutableAttributedStringRef p_attrString );
74 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
75 bool b_bold, bool b_italic, bool b_underline,
76 CFRange p_range, CFMutableAttributedStringRef p_attrString );
78 //////////////////////////////////////////////////////////////////////////////
80 //////////////////////////////////////////////////////////////////////////////
82 // The preferred way to set font style information is for it to come from the
83 // subtitle file, and for it to be rendered with RenderHtml instead of
85 #define FONT_TEXT N_("Font")
86 #define FONT_LONGTEXT N_("Name for the font you want to use")
87 #define FONTSIZER_TEXT N_("Relative font size")
88 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
89 "fonts that will be rendered on the video. If absolute font size is set, "\
90 "relative size will be overriden." )
91 #define COLOR_TEXT N_("Text default color")
92 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
93 "the video. This must be an hexadecimal (like HTML colors). The first two "\
94 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
95 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
97 static const int pi_color_values[] = {
98 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
99 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
100 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
102 static const char *const ppsz_color_descriptions[] = {
103 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
104 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
105 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
107 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
108 static const char *const ppsz_sizes_text[] = {
109 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
112 set_shortname( N_("Mac Text renderer"))
113 set_description( N_("Quartz font renderer") )
114 set_category( CAT_VIDEO )
115 set_subcategory( SUBCAT_VIDEO_SUBPIC )
117 add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
119 add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, NULL, FONTSIZER_TEXT,
120 FONTSIZER_LONGTEXT, false )
121 change_integer_list( pi_sizes, ppsz_sizes_text, NULL );
122 add_integer( "quartztext-color", 0x00FFFFFF, NULL, COLOR_TEXT,
123 COLOR_LONGTEXT, false )
124 change_integer_list( pi_color_values, ppsz_color_descriptions, NULL );
125 set_capability( "text renderer", 150 )
126 add_shortcut( "text" )
127 set_callbacks( Create, Destroy )
130 typedef struct font_stack_t font_stack_t;
135 uint32_t i_color; // ARGB
137 font_stack_t *p_next;
143 uint32_t i_font_color; /* ARGB */
150 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
151 struct offscreen_bitmap_t
154 int i_bitsPerChannel;
160 //////////////////////////////////////////////////////////////////////////////
161 // filter_sys_t: quartztext local data
162 //////////////////////////////////////////////////////////////////////////////
163 // This structure is part of the video output thread descriptor.
164 // It describes the freetype specific properties of an output thread.
165 //////////////////////////////////////////////////////////////////////////////
169 uint8_t i_font_opacity;
173 ATSFontContainerRef *p_fonts;
177 //////////////////////////////////////////////////////////////////////////////
178 // Create: allocates osd-text video thread output method
179 //////////////////////////////////////////////////////////////////////////////
180 // This function allocates and initializes a Clone vout method.
181 //////////////////////////////////////////////////////////////////////////////
182 static int Create( vlc_object_t *p_this )
184 filter_t *p_filter = (filter_t *)p_this;
187 // Allocate structure
188 p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
191 p_sys->psz_font_name = var_CreateGetString( p_this, "quartztext-font" );
192 p_sys->i_font_opacity = 255;
193 p_sys->i_font_color = __MAX( __MIN( var_CreateGetInteger( p_this, "quartztext-color" ) , 0xFFFFFF ), 0 );
194 p_sys->i_font_size = GetFontSize( p_filter );
196 p_filter->pf_render_text = RenderText;
197 p_filter->pf_render_html = RenderHtml;
199 p_sys->p_fonts = NULL;
202 LoadFontsFromAttachments( p_filter );
207 //////////////////////////////////////////////////////////////////////////////
208 // Destroy: destroy Clone video thread output method
209 //////////////////////////////////////////////////////////////////////////////
210 // Clean up all data and library connections
211 //////////////////////////////////////////////////////////////////////////////
212 static void Destroy( vlc_object_t *p_this )
214 filter_t *p_filter = (filter_t *)p_this;
215 filter_sys_t *p_sys = p_filter->p_sys;
221 for( k = 0; k < p_sys->i_fonts; k++ )
223 ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
226 free( p_sys->p_fonts );
229 free( p_sys->psz_font_name );
233 //////////////////////////////////////////////////////////////////////////////
234 // Make any TTF/OTF fonts present in the attachments of the media file
235 // available to the Quartz engine for text rendering
236 //////////////////////////////////////////////////////////////////////////////
237 static int LoadFontsFromAttachments( filter_t *p_filter )
239 filter_sys_t *p_sys = p_filter->p_sys;
240 input_thread_t *p_input;
241 input_attachment_t **pp_attachments;
242 int i_attachments_cnt;
244 int rv = VLC_SUCCESS;
246 p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
250 if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
252 vlc_object_release(p_input);
257 p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
258 if(! p_sys->p_fonts )
261 for( k = 0; k < i_attachments_cnt; k++ )
263 input_attachment_t *p_attach = pp_attachments[k];
267 if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
268 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
269 ( p_attach->i_data > 0 ) &&
270 ( p_attach->p_data != NULL ) )
272 ATSFontContainerRef container;
274 if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
276 kATSFontContextLocal,
277 kATSFontFormatUnspecified,
279 kATSOptionFlagsDefault,
282 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
286 vlc_input_attachment_Delete( p_attach );
288 free( pp_attachments );
290 vlc_object_release(p_input);
295 static char *EliminateCRLF( char *psz_string )
300 for( p = psz_string; p && *p; p++ )
302 if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
304 for( q = p + 1; *q; q++ )
313 // Renders a text subpicture region into another one.
314 // It is used as pf_add_string callback in the vout method by this module
315 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
316 subpicture_region_t *p_region_in )
318 filter_sys_t *p_sys = p_filter->p_sys;
320 int i_font_alpha, i_font_size;
321 uint32_t i_font_color;
325 p_sys->i_font_size = GetFontSize( p_filter );
328 if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
329 psz_string = p_region_in->psz_text;
330 if( !psz_string || !*psz_string ) return VLC_EGENERIC;
332 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
335 if( p_region_in->p_style )
337 i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
338 i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
339 i_font_size = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
343 i_font_color = p_sys->i_font_color;
344 i_font_alpha = 255 - p_sys->i_font_opacity;
345 i_font_size = p_sys->i_font_size;
348 if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
350 if( i_font_size <= 0 )
352 msg_Warn( p_filter, "invalid fontsize, using 12" );
353 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
354 i_font_size = 12 * val.i_int / 1000;
359 p_region_out->i_x = p_region_in->i_x;
360 p_region_out->i_y = p_region_in->i_y;
362 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
366 CFStringRef p_cfString;
369 EliminateCRLF( psz_string);
370 p_cfString = CFStringCreateWithCString( NULL, psz_string, kCFStringEncodingUTF8 );
371 CFAttributedStringReplaceString( p_attrString, CFRangeMake(0, 0), p_cfString );
372 CFRelease( p_cfString );
373 len = CFAttributedStringGetLength( p_attrString );
375 setFontAttibutes( p_sys->psz_font_name, i_font_size, i_font_color, FALSE, FALSE, FALSE,
376 CFRangeMake( 0, len ), p_attrString);
378 RenderYUVA( p_filter, p_region_out, p_attrString );
380 CFRelease(p_attrString);
386 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
394 p_new = malloc( sizeof( font_stack_t ) );
398 p_new->p_next = NULL;
401 p_new->psz_name = strdup( psz_name );
403 p_new->psz_name = NULL;
405 p_new->i_size = i_size;
406 p_new->i_color = i_color;
414 font_stack_t *p_last;
416 for( p_last = *p_font;
418 p_last = p_last->p_next )
421 p_last->p_next = p_new;
426 static int PopFont( font_stack_t **p_font )
428 font_stack_t *p_last, *p_next_to_last;
430 if( !p_font || !*p_font )
433 p_next_to_last = NULL;
434 for( p_last = *p_font;
436 p_last = p_last->p_next )
438 p_next_to_last = p_last;
442 p_next_to_last->p_next = NULL;
446 free( p_last->psz_name );
452 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
455 font_stack_t *p_last;
457 if( !p_font || !*p_font )
462 p_last=p_last->p_next )
465 *psz_name = p_last->psz_name;
466 *i_size = p_last->i_size;
467 *i_color = p_last->i_color;
472 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
473 font_stack_t **p_fonts, int i_scale )
476 char *psz_fontname = NULL;
477 uint32_t i_font_color = 0xffffff;
478 int i_font_alpha = 0;
479 int i_font_size = 24;
481 // Default all attributes to the top font in the stack -- in case not
482 // all attributes are specified in the sub-font
483 if( VLC_SUCCESS == PeekFont( p_fonts,
488 psz_fontname = strdup( psz_fontname );
489 i_font_size = i_font_size * 1000 / i_scale;
491 i_font_alpha = (i_font_color >> 24) & 0xff;
492 i_font_color &= 0x00ffffff;
494 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
496 char *psz_name = xml_ReaderName( p_xml_reader );
497 char *psz_value = xml_ReaderValue( p_xml_reader );
499 if( psz_name && psz_value )
501 if( !strcasecmp( "face", psz_name ) )
503 if( psz_fontname ) free( psz_fontname );
504 psz_fontname = strdup( psz_value );
506 else if( !strcasecmp( "size", psz_name ) )
508 if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
510 int i_value = atoi( psz_value );
512 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
513 i_font_size += ( i_value * i_font_size ) / 10;
514 else if( i_value < -5 )
515 i_font_size = - i_value;
516 else if( i_value > 5 )
517 i_font_size = i_value;
520 i_font_size = atoi( psz_value );
522 else if( !strcasecmp( "color", psz_name ) &&
523 ( psz_value[0] == '#' ) )
525 i_font_color = strtol( psz_value + 1, NULL, 16 );
526 i_font_color &= 0x00ffffff;
528 else if( !strcasecmp( "alpha", psz_name ) &&
529 ( psz_value[0] == '#' ) )
531 i_font_alpha = strtol( psz_value + 1, NULL, 16 );
532 i_font_alpha &= 0xff;
538 rv = PushFont( p_fonts,
540 i_font_size * i_scale / 1000,
541 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24) );
543 free( psz_fontname );
548 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
549 bool b_bold, bool b_italic, bool b_underline,
550 CFRange p_range, CFMutableAttributedStringRef p_attrString )
552 CFStringRef p_cfString;
555 // Handle font name and size
556 p_cfString = CFStringCreateWithCString( NULL,
558 kCFStringEncodingUTF8 );
559 p_font = CTFontCreateWithName( p_cfString,
562 CFRelease( p_cfString );
563 CFAttributedStringSetAttribute( p_attrString,
565 kCTFontAttributeName,
572 _uline = kCTUnderlineStyleSingle;
574 _uline = kCTUnderlineStyleNone;
576 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
577 CFAttributedStringSetAttribute( p_attrString,
579 kCTUnderlineStyleAttributeName,
581 CFRelease( underline );
590 CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
591 CFAttributedStringSetAttribute( p_attrString,
604 CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
605 CFAttributedStringSetAttribute( p_attrString,
611 // Handle foreground colour
612 CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
613 CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
614 (float)((i_font_color & 0x0000ff00) >> 8) / 255.0,
615 (float)((i_font_color & 0x000000ff) ) / 255.0,
616 (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
617 CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
618 CGColorSpaceRelease(rgbColorSpace);
620 CFAttributedStringSetAttribute( p_attrString,
622 kCTForegroundColorAttributeName,
624 CFRelease( fg_text );
628 static void GetAttrStrFromFontStack( font_stack_t **p_fonts,
629 bool b_bold, bool b_italic, bool b_uline,
630 CFRange p_range, CFMutableAttributedStringRef p_attrString )
632 char *psz_fontname = NULL;
634 uint32_t i_font_color = 0;
636 if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
639 setFontAttibutes( psz_fontname,
642 b_bold, b_italic, b_uline,
648 static int ProcessNodes( filter_t *p_filter,
649 xml_reader_t *p_xml_reader,
650 text_style_t *p_font_style,
651 CFMutableAttributedStringRef p_attrString )
653 int rv = VLC_SUCCESS;
654 filter_sys_t *p_sys = p_filter->p_sys;
655 font_stack_t *p_fonts = NULL;
659 char *psz_node = NULL;
661 bool b_italic = false;
663 bool b_uline = false;
665 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
670 rv = PushFont( &p_fonts,
671 p_font_style->psz_fontname,
672 p_font_style->i_font_size * i_scale / 1000,
673 (p_font_style->i_font_color & 0xffffff) |
674 ((p_font_style->i_font_alpha & 0xff) << 24) );
676 if( p_font_style->i_style_flags & STYLE_BOLD )
678 if( p_font_style->i_style_flags & STYLE_ITALIC )
680 if( p_font_style->i_style_flags & STYLE_UNDERLINE )
685 rv = PushFont( &p_fonts,
686 p_sys->psz_font_name,
688 p_sys->i_font_color );
690 if( rv != VLC_SUCCESS )
693 while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) )
695 switch ( xml_ReaderNodeType( p_xml_reader ) )
697 case XML_READER_NONE:
699 case XML_READER_ENDELEM:
700 psz_node = xml_ReaderName( p_xml_reader );
704 if( !strcasecmp( "font", psz_node ) )
706 else if( !strcasecmp( "b", psz_node ) )
708 else if( !strcasecmp( "i", psz_node ) )
710 else if( !strcasecmp( "u", psz_node ) )
716 case XML_READER_STARTELEM:
717 psz_node = xml_ReaderName( p_xml_reader );
720 if( !strcasecmp( "font", psz_node ) )
721 rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
722 else if( !strcasecmp( "b", psz_node ) )
724 else if( !strcasecmp( "i", psz_node ) )
726 else if( !strcasecmp( "u", psz_node ) )
728 else if( !strcasecmp( "br", psz_node ) )
730 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
731 CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), CFSTR("\n") );
733 GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
736 CFAttributedStringReplaceAttributedString( p_attrString,
737 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
739 CFRelease( p_attrnode );
744 case XML_READER_TEXT:
745 psz_node = xml_ReaderValue( p_xml_reader );
748 CFStringRef p_cfString;
751 // Turn any multiple-whitespaces into single spaces
752 char *s = strpbrk( psz_node, "\t\r\n " );
755 int i_whitespace = strspn( s, "\t\r\n " );
757 if( i_whitespace > 1 )
760 strlen( s ) - i_whitespace + 1 );
763 s = strpbrk( s, "\t\r\n " );
767 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
768 p_cfString = CFStringCreateWithCString( NULL, psz_node, kCFStringEncodingUTF8 );
769 CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), p_cfString );
770 CFRelease( p_cfString );
771 len = CFAttributedStringGetLength( p_attrnode );
773 GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
774 CFRangeMake( 0, len ),
777 CFAttributedStringReplaceAttributedString( p_attrString,
778 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
780 CFRelease( p_attrnode );
787 while( VLC_SUCCESS == PopFont( &p_fonts ) );
792 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
793 subpicture_region_t *p_region_in )
795 int rv = VLC_SUCCESS;
796 stream_t *p_sub = NULL;
798 xml_reader_t *p_xml_reader = NULL;
800 if( !p_region_in || !p_region_in->psz_html )
803 /* Reset the default fontsize in case screen metrics have changed */
804 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
806 p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
807 (uint8_t *) p_region_in->psz_html,
808 strlen( p_region_in->psz_html ),
812 p_xml = xml_Create( p_filter );
815 bool b_karaoke = false;
817 p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
820 /* Look for Root Node */
821 if( xml_ReaderRead( p_xml_reader ) == 1 )
823 char *psz_node = xml_ReaderName( p_xml_reader );
825 if( !strcasecmp( "karaoke", psz_node ) )
827 /* We're going to have to render the text a number
828 * of times to show the progress marker on the text.
830 var_SetBool( p_filter, "text-rerender", true );
833 else if( !strcasecmp( "text", psz_node ) )
839 /* Only text and karaoke tags are supported */
840 xml_ReaderDelete( p_xml, p_xml_reader );
853 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
854 rv = ProcessNodes( p_filter, p_xml_reader,
855 p_region_in->p_style, p_attrString );
857 i_len = CFAttributedStringGetLength( p_attrString );
859 p_region_out->i_x = p_region_in->i_x;
860 p_region_out->i_y = p_region_in->i_y;
862 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
864 RenderYUVA( p_filter, p_region_out, p_attrString );
866 CFRelease(p_attrString);
868 xml_ReaderDelete( p_xml, p_xml_reader );
872 stream_Delete( p_sub );
878 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
879 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
881 offscreen_bitmap_t *p_bitmap;
882 CGContextRef p_context = NULL;
884 p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
887 p_bitmap->i_bitsPerChannel = 8;
888 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
889 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
890 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
892 p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
894 *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
896 if( p_bitmap->p_data && *pp_colorSpace )
898 p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
899 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
900 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
904 if( CGContextSetAllowsAntialiasing != NULL )
906 CGContextSetAllowsAntialiasing( p_context, true );
909 *pp_memory = p_bitmap;
915 static offscreen_bitmap_t *Compose( int i_text_align,
916 CFMutableAttributedStringRef p_attrString,
919 unsigned *pi_textblock_height )
921 offscreen_bitmap_t *p_offScreen = NULL;
922 CGColorSpaceRef p_colorSpace = NULL;
923 CGContextRef p_context = NULL;
925 p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
927 *pi_textblock_height = 0;
932 CGContextSetTextMatrix( p_context, CGAffineTransformIdentity );
934 if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
936 else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
941 // Create the framesetter with the attributed string.
942 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
946 CGMutablePathRef p_path = CGPathCreateMutable();
947 CGRect p_bounds = CGRectMake( (float)HORIZONTAL_MARGIN,
948 (float)VERTICAL_MARGIN,
949 (float)(i_width - HORIZONTAL_MARGIN*2),
950 (float)(i_height - VERTICAL_MARGIN *2));
951 CGPathAddRect( p_path, NULL, p_bounds );
953 // Create the frame and draw it into the graphics context
954 frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
956 CGPathRelease(p_path);
958 // Set up black outlining of the text --
959 CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
960 CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
967 lines = CTFrameGetLines( frame );
968 penPosition.y = i_height;
969 for (int i=0; i<CFArrayGetCount( lines ); i++)
971 CGFloat ascent, descent, leading;
973 CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
974 CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
976 // Set the outlining for this line to be dependant on the size of the line -
977 // make it about 5% of the ascent, with a minimum at 1.0
978 float f_thickness = ascent * 0.05;
979 CGContextSetLineWidth( p_context, (( f_thickness > 1.0 ) ? 1.0 : f_thickness ));
981 double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2));
982 penPosition.x = HORIZONTAL_MARGIN + penOffset;
983 penPosition.y -= ascent;
984 CGContextSetTextPosition( p_context, penPosition.x, penPosition.y );
985 CTLineDraw( line, p_context );
986 penPosition.y -= descent + leading;
989 *pi_textblock_height = i_height - penPosition.y;
993 CFRelease(framesetter);
995 CGContextFlush( p_context );
996 CGContextRelease( p_context );
998 if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
1003 static int GetFontSize( filter_t *p_filter )
1005 return p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE;
1008 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
1009 CFMutableAttributedStringRef p_attrString )
1011 offscreen_bitmap_t *p_offScreen = NULL;
1012 unsigned i_textblock_height = 0;
1014 unsigned i_width = p_filter->fmt_out.video.i_visible_width;
1015 unsigned i_height = p_filter->fmt_out.video.i_visible_height;
1016 unsigned i_text_align = p_region->i_align & 0x3;
1020 msg_Err( p_filter, "Invalid argument to RenderYUVA" );
1021 return VLC_EGENERIC;
1024 p_offScreen = Compose( i_text_align, p_attrString,
1025 i_width, i_height, &i_textblock_height );
1029 msg_Err( p_filter, "No offscreen buffer" );
1030 return VLC_EGENERIC;
1033 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1036 unsigned x, y, i_pitch;
1037 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1039 // Create a new subpicture region
1040 memset( &fmt, 0, sizeof(video_format_t) );
1041 fmt.i_chroma = VLC_CODEC_YUVA;
1043 fmt.i_width = fmt.i_visible_width = i_width;
1044 fmt.i_height = fmt.i_visible_height = __MIN( i_height, i_textblock_height + VERTICAL_MARGIN * 2);
1045 fmt.i_x_offset = fmt.i_y_offset = 0;
1047 p_region->p_picture = picture_New( fmt.i_chroma, fmt.i_width, fmt.i_height, fmt.i_aspect );
1048 if( !p_region->p_picture )
1049 return VLC_EGENERIC;
1050 p_region->fmt = fmt;
1052 p_dst_y = p_region->p_picture->Y_PIXELS;
1053 p_dst_u = p_region->p_picture->U_PIXELS;
1054 p_dst_v = p_region->p_picture->V_PIXELS;
1055 p_dst_a = p_region->p_picture->A_PIXELS;
1056 i_pitch = p_region->p_picture->A_PITCH;
1058 i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1059 for( y = 0; y < fmt.i_height; y++)
1061 for( x = 0; x < fmt.i_width; x++)
1063 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1064 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1065 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1066 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1068 i_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
1069 802 * i_blue + 4096 + 131072 ) >> 13, 235);
1070 i_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
1071 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1072 i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
1073 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1075 p_dst_y[ i_offset + x ] = i_y;
1076 p_dst_u[ i_offset + x ] = i_u;
1077 p_dst_v[ i_offset + x ] = i_v;
1078 p_dst_a[ i_offset + x ] = i_alpha;
1080 i_offset += i_pitch;
1083 free( p_offScreen->p_data );
1084 free( p_offScreen );