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>
35 #include <vlc_stream.h>
37 #include <vlc_input.h>
39 // Fix ourselves ColorSync headers that gets included in ApplicationServices.
40 #define DisposeCMProfileIterateUPP(a) DisposeCMProfileIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
41 #define DisposeCMMIterateUPP(a) DisposeCMMIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
42 #define __MACHINEEXCEPTIONS__
43 #include <ApplicationServices/ApplicationServices.h>
45 #define DEFAULT_FONT "Arial Black"
46 #define DEFAULT_FONT_COLOR 0xffffff
47 #define DEFAULT_REL_FONT_SIZE 16
49 #define VERTICAL_MARGIN 3
50 #define HORIZONTAL_MARGIN 10
52 //////////////////////////////////////////////////////////////////////////////
54 //////////////////////////////////////////////////////////////////////////////
55 static int Create ( vlc_object_t * );
56 static void Destroy( vlc_object_t * );
58 static int LoadFontsFromAttachments( filter_t *p_filter );
60 static int RenderText( filter_t *, subpicture_region_t *,
61 subpicture_region_t * );
62 static int RenderHtml( filter_t *, subpicture_region_t *,
63 subpicture_region_t * );
65 static int GetFontSize( filter_t *p_filter );
66 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
67 CFMutableAttributedStringRef p_attrString );
69 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
70 bool b_bold, bool b_italic, bool b_underline,
71 CFRange p_range, CFMutableAttributedStringRef p_attrString );
73 //////////////////////////////////////////////////////////////////////////////
75 //////////////////////////////////////////////////////////////////////////////
77 // The preferred way to set font style information is for it to come from the
78 // subtitle file, and for it to be rendered with RenderHtml instead of
80 #define FONT_TEXT N_("Font")
81 #define FONT_LONGTEXT N_("Name for the font you want to use")
82 #define FONTSIZER_TEXT N_("Relative font size")
83 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
84 "fonts that will be rendered on the video. If absolute font size is set, "\
85 "relative size will be overriden." )
86 #define COLOR_TEXT N_("Text default color")
87 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
88 "the video. This must be an hexadecimal (like HTML colors). The first two "\
89 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
90 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
92 static const int pi_color_values[] = {
93 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
94 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
95 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
97 static const char *const ppsz_color_descriptions[] = {
98 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
99 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
100 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
102 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
103 static const char *const ppsz_sizes_text[] = {
104 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
107 set_shortname( N_("Text renderer for Mac"))
108 set_description( N_("CoreText font renderer") )
109 set_category( CAT_VIDEO )
110 set_subcategory( SUBCAT_VIDEO_SUBPIC )
112 add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
114 add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, NULL, FONTSIZER_TEXT,
115 FONTSIZER_LONGTEXT, false )
116 change_integer_list( pi_sizes, ppsz_sizes_text, NULL );
117 add_integer( "quartztext-color", 0x00FFFFFF, NULL, COLOR_TEXT,
118 COLOR_LONGTEXT, false )
119 change_integer_list( pi_color_values, ppsz_color_descriptions, NULL );
120 set_capability( "text renderer", 150 )
121 add_shortcut( "text" )
122 set_callbacks( Create, Destroy )
125 typedef struct font_stack_t font_stack_t;
130 uint32_t i_color; // ARGB
132 font_stack_t *p_next;
138 uint32_t i_font_color; /* ARGB */
145 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
146 struct offscreen_bitmap_t
149 int i_bitsPerChannel;
155 //////////////////////////////////////////////////////////////////////////////
156 // filter_sys_t: quartztext local data
157 //////////////////////////////////////////////////////////////////////////////
158 // This structure is part of the video output thread descriptor.
159 // It describes the freetype specific properties of an output thread.
160 //////////////////////////////////////////////////////////////////////////////
164 uint8_t i_font_opacity;
168 ATSFontContainerRef *p_fonts;
172 //////////////////////////////////////////////////////////////////////////////
173 // Create: allocates osd-text video thread output method
174 //////////////////////////////////////////////////////////////////////////////
175 // This function allocates and initializes a Clone vout method.
176 //////////////////////////////////////////////////////////////////////////////
177 static int Create( vlc_object_t *p_this )
179 filter_t *p_filter = (filter_t *)p_this;
182 // Allocate structure
183 p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
186 p_sys->psz_font_name = var_CreateGetString( p_this, "quartztext-font" );
187 p_sys->i_font_opacity = 255;
188 p_sys->i_font_color = __MAX( __MIN( var_CreateGetInteger( p_this, "quartztext-color" ) , 0xFFFFFF ), 0 );
189 p_sys->i_font_size = GetFontSize( p_filter );
191 p_filter->pf_render_text = RenderText;
192 p_filter->pf_render_html = RenderHtml;
194 p_sys->p_fonts = NULL;
197 LoadFontsFromAttachments( p_filter );
202 //////////////////////////////////////////////////////////////////////////////
203 // Destroy: destroy Clone video thread output method
204 //////////////////////////////////////////////////////////////////////////////
205 // Clean up all data and library connections
206 //////////////////////////////////////////////////////////////////////////////
207 static void Destroy( vlc_object_t *p_this )
209 filter_t *p_filter = (filter_t *)p_this;
210 filter_sys_t *p_sys = p_filter->p_sys;
216 for( k = 0; k < p_sys->i_fonts; k++ )
218 ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
221 free( p_sys->p_fonts );
224 free( p_sys->psz_font_name );
228 //////////////////////////////////////////////////////////////////////////////
229 // Make any TTF/OTF fonts present in the attachments of the media file
230 // available to the Quartz engine for text rendering
231 //////////////////////////////////////////////////////////////////////////////
232 static int LoadFontsFromAttachments( filter_t *p_filter )
234 filter_sys_t *p_sys = p_filter->p_sys;
235 input_attachment_t **pp_attachments;
236 int i_attachments_cnt;
238 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
242 p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
243 if(! p_sys->p_fonts )
246 for( int k = 0; k < i_attachments_cnt; k++ )
248 input_attachment_t *p_attach = pp_attachments[k];
250 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
251 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
252 p_attach->i_data > 0 && p_attach->p_data )
254 ATSFontContainerRef container;
256 if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
258 kATSFontContextLocal,
259 kATSFontFormatUnspecified,
261 kATSOptionFlagsDefault,
264 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
267 vlc_input_attachment_Delete( p_attach );
269 free( pp_attachments );
274 static char *EliminateCRLF( char *psz_string )
279 for( p = psz_string; p && *p; p++ )
281 if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
283 for( q = p + 1; *q; q++ )
292 // Renders a text subpicture region into another one.
293 // It is used as pf_add_string callback in the vout method by this module
294 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
295 subpicture_region_t *p_region_in )
297 filter_sys_t *p_sys = p_filter->p_sys;
299 int i_font_alpha, i_font_size;
300 uint32_t i_font_color;
301 bool b_bold, b_uline, b_italic;
304 b_bold = b_uline = b_italic = FALSE;
306 p_sys->i_font_size = GetFontSize( p_filter );
309 if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
310 psz_string = p_region_in->psz_text;
311 if( !psz_string || !*psz_string ) return VLC_EGENERIC;
313 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
316 if( p_region_in->p_style )
318 i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
319 i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
320 i_font_size = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
321 if( p_region_in->p_style->i_style_flags )
323 if( p_region_in->p_style->i_style_flags & STYLE_BOLD )
325 if( p_region_in->p_style->i_style_flags & STYLE_ITALIC )
327 if( p_region_in->p_style->i_style_flags & STYLE_UNDERLINE )
333 i_font_color = p_sys->i_font_color;
334 i_font_alpha = 255 - p_sys->i_font_opacity;
335 i_font_size = p_sys->i_font_size;
338 if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
340 if( i_font_size <= 0 )
342 msg_Warn( p_filter, "invalid fontsize, using 12" );
343 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
344 i_font_size = 12 * val.i_int / 1000;
349 p_region_out->i_x = p_region_in->i_x;
350 p_region_out->i_y = p_region_in->i_y;
352 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
356 CFStringRef p_cfString;
359 EliminateCRLF( psz_string);
360 p_cfString = CFStringCreateWithCString( NULL, psz_string, kCFStringEncodingUTF8 );
361 CFAttributedStringReplaceString( p_attrString, CFRangeMake(0, 0), p_cfString );
362 CFRelease( p_cfString );
363 len = CFAttributedStringGetLength( p_attrString );
365 setFontAttibutes( p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline,
366 CFRangeMake( 0, len ), p_attrString);
368 RenderYUVA( p_filter, p_region_out, p_attrString );
370 CFRelease(p_attrString);
376 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
384 p_new = malloc( sizeof( font_stack_t ) );
388 p_new->p_next = NULL;
391 p_new->psz_name = strdup( psz_name );
393 p_new->psz_name = NULL;
395 p_new->i_size = i_size;
396 p_new->i_color = i_color;
404 font_stack_t *p_last;
406 for( p_last = *p_font;
408 p_last = p_last->p_next )
411 p_last->p_next = p_new;
416 static int PopFont( font_stack_t **p_font )
418 font_stack_t *p_last, *p_next_to_last;
420 if( !p_font || !*p_font )
423 p_next_to_last = NULL;
424 for( p_last = *p_font;
426 p_last = p_last->p_next )
428 p_next_to_last = p_last;
432 p_next_to_last->p_next = NULL;
436 free( p_last->psz_name );
442 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
445 font_stack_t *p_last;
447 if( !p_font || !*p_font )
452 p_last=p_last->p_next )
455 *psz_name = p_last->psz_name;
456 *i_size = p_last->i_size;
457 *i_color = p_last->i_color;
462 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
463 font_stack_t **p_fonts, int i_scale )
466 char *psz_fontname = NULL;
467 uint32_t i_font_color = 0xffffff;
468 int i_font_alpha = 0;
469 int i_font_size = 24;
471 // Default all attributes to the top font in the stack -- in case not
472 // all attributes are specified in the sub-font
473 if( VLC_SUCCESS == PeekFont( p_fonts,
478 psz_fontname = strdup( psz_fontname );
479 i_font_size = i_font_size * 1000 / i_scale;
481 i_font_alpha = (i_font_color >> 24) & 0xff;
482 i_font_color &= 0x00ffffff;
484 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
486 char *psz_name = xml_ReaderName( p_xml_reader );
487 char *psz_value = xml_ReaderValue( p_xml_reader );
489 if( psz_name && psz_value )
491 if( !strcasecmp( "face", psz_name ) )
493 free( psz_fontname );
494 psz_fontname = strdup( psz_value );
496 else if( !strcasecmp( "size", psz_name ) )
498 if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
500 int i_value = atoi( psz_value );
502 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
503 i_font_size += ( i_value * i_font_size ) / 10;
504 else if( i_value < -5 )
505 i_font_size = - i_value;
506 else if( i_value > 5 )
507 i_font_size = i_value;
510 i_font_size = atoi( psz_value );
512 else if( !strcasecmp( "color", psz_name ) &&
513 ( psz_value[0] == '#' ) )
515 i_font_color = strtol( psz_value + 1, NULL, 16 );
516 i_font_color &= 0x00ffffff;
518 else if( !strcasecmp( "alpha", psz_name ) &&
519 ( psz_value[0] == '#' ) )
521 i_font_alpha = strtol( psz_value + 1, NULL, 16 );
522 i_font_alpha &= 0xff;
528 rv = PushFont( p_fonts,
530 i_font_size * i_scale / 1000,
531 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24) );
533 free( psz_fontname );
538 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
539 bool b_bold, bool b_italic, bool b_underline,
540 CFRange p_range, CFMutableAttributedStringRef p_attrString )
542 CFStringRef p_cfString;
545 // Handle font name and size
546 p_cfString = CFStringCreateWithCString( NULL,
548 kCFStringEncodingUTF8 );
549 p_font = CTFontCreateWithName( p_cfString,
552 CFRelease( p_cfString );
553 CFAttributedStringSetAttribute( p_attrString,
555 kCTFontAttributeName,
562 _uline = kCTUnderlineStyleSingle;
564 _uline = kCTUnderlineStyleNone;
566 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
567 CFAttributedStringSetAttribute( p_attrString,
569 kCTUnderlineStyleAttributeName,
571 CFRelease( underline );
580 CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
581 CFAttributedStringSetAttribute( p_attrString,
594 CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
595 CFAttributedStringSetAttribute( p_attrString,
601 // Handle foreground colour
602 CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
603 CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
604 (float)((i_font_color & 0x0000ff00) >> 8) / 255.0,
605 (float)((i_font_color & 0x000000ff) ) / 255.0,
606 (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
607 CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
608 CGColorSpaceRelease(rgbColorSpace);
610 CFAttributedStringSetAttribute( p_attrString,
612 kCTForegroundColorAttributeName,
614 CFRelease( fg_text );
618 static void GetAttrStrFromFontStack( font_stack_t **p_fonts,
619 bool b_bold, bool b_italic, bool b_uline,
620 CFRange p_range, CFMutableAttributedStringRef p_attrString )
622 char *psz_fontname = NULL;
624 uint32_t i_font_color = 0;
626 if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
629 setFontAttibutes( psz_fontname,
632 b_bold, b_italic, b_uline,
638 static int ProcessNodes( filter_t *p_filter,
639 xml_reader_t *p_xml_reader,
640 text_style_t *p_font_style,
641 CFMutableAttributedStringRef p_attrString )
643 int rv = VLC_SUCCESS;
644 filter_sys_t *p_sys = p_filter->p_sys;
645 font_stack_t *p_fonts = NULL;
649 char *psz_node = NULL;
651 bool b_italic = false;
653 bool b_uline = false;
655 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
660 rv = PushFont( &p_fonts,
661 p_font_style->psz_fontname,
662 p_font_style->i_font_size * i_scale / 1000,
663 (p_font_style->i_font_color & 0xffffff) |
664 ((p_font_style->i_font_alpha & 0xff) << 24) );
666 if( p_font_style->i_style_flags & STYLE_BOLD )
668 if( p_font_style->i_style_flags & STYLE_ITALIC )
670 if( p_font_style->i_style_flags & STYLE_UNDERLINE )
675 rv = PushFont( &p_fonts,
676 p_sys->psz_font_name,
678 p_sys->i_font_color );
680 if( rv != VLC_SUCCESS )
683 while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) )
685 switch ( xml_ReaderNodeType( p_xml_reader ) )
687 case XML_READER_NONE:
689 case XML_READER_ENDELEM:
690 psz_node = xml_ReaderName( p_xml_reader );
694 if( !strcasecmp( "font", psz_node ) )
696 else if( !strcasecmp( "b", psz_node ) )
698 else if( !strcasecmp( "i", psz_node ) )
700 else if( !strcasecmp( "u", psz_node ) )
706 case XML_READER_STARTELEM:
707 psz_node = xml_ReaderName( p_xml_reader );
710 if( !strcasecmp( "font", psz_node ) )
711 rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
712 else if( !strcasecmp( "b", psz_node ) )
714 else if( !strcasecmp( "i", psz_node ) )
716 else if( !strcasecmp( "u", psz_node ) )
718 else if( !strcasecmp( "br", psz_node ) )
720 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
721 CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), CFSTR("\n") );
723 GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
726 CFAttributedStringReplaceAttributedString( p_attrString,
727 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
729 CFRelease( p_attrnode );
734 case XML_READER_TEXT:
735 psz_node = xml_ReaderValue( p_xml_reader );
738 CFStringRef p_cfString;
741 // Turn any multiple-whitespaces into single spaces
742 char *s = strpbrk( psz_node, "\t\r\n " );
745 int i_whitespace = strspn( s, "\t\r\n " );
747 if( i_whitespace > 1 )
750 strlen( s ) - i_whitespace + 1 );
753 s = strpbrk( s, "\t\r\n " );
757 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
758 p_cfString = CFStringCreateWithCString( NULL, psz_node, kCFStringEncodingUTF8 );
759 CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), p_cfString );
760 CFRelease( p_cfString );
761 len = CFAttributedStringGetLength( p_attrnode );
763 GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
764 CFRangeMake( 0, len ),
767 CFAttributedStringReplaceAttributedString( p_attrString,
768 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
770 CFRelease( p_attrnode );
777 while( VLC_SUCCESS == PopFont( &p_fonts ) );
782 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
783 subpicture_region_t *p_region_in )
785 int rv = VLC_SUCCESS;
786 stream_t *p_sub = NULL;
788 xml_reader_t *p_xml_reader = NULL;
790 if( !p_region_in || !p_region_in->psz_html )
793 /* Reset the default fontsize in case screen metrics have changed */
794 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
796 p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
797 (uint8_t *) p_region_in->psz_html,
798 strlen( p_region_in->psz_html ),
802 p_xml = xml_Create( p_filter );
805 bool b_karaoke = false;
807 p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
810 /* Look for Root Node */
811 if( xml_ReaderRead( p_xml_reader ) == 1 )
813 char *psz_node = xml_ReaderName( p_xml_reader );
815 if( !strcasecmp( "karaoke", psz_node ) )
817 /* We're going to have to render the text a number
818 * of times to show the progress marker on the text.
820 var_SetBool( p_filter, "text-rerender", true );
823 else if( !strcasecmp( "text", psz_node ) )
829 /* Only text and karaoke tags are supported */
830 xml_ReaderDelete( p_xml_reader );
843 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
844 rv = ProcessNodes( p_filter, p_xml_reader,
845 p_region_in->p_style, p_attrString );
847 i_len = CFAttributedStringGetLength( p_attrString );
849 p_region_out->i_x = p_region_in->i_x;
850 p_region_out->i_y = p_region_in->i_y;
852 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
854 RenderYUVA( p_filter, p_region_out, p_attrString );
856 CFRelease(p_attrString);
858 xml_ReaderDelete( p_xml_reader );
862 stream_Delete( p_sub );
868 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
869 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
871 offscreen_bitmap_t *p_bitmap;
872 CGContextRef p_context = NULL;
874 p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
877 p_bitmap->i_bitsPerChannel = 8;
878 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
879 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
880 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
882 p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
884 *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
886 if( p_bitmap->p_data && *pp_colorSpace )
888 p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
889 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
890 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
894 if( CGContextSetAllowsAntialiasing != NULL )
896 CGContextSetAllowsAntialiasing( p_context, true );
899 *pp_memory = p_bitmap;
905 static offscreen_bitmap_t *Compose( int i_text_align,
906 CFMutableAttributedStringRef p_attrString,
909 unsigned *pi_textblock_height )
911 offscreen_bitmap_t *p_offScreen = NULL;
912 CGColorSpaceRef p_colorSpace = NULL;
913 CGContextRef p_context = NULL;
915 p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
917 *pi_textblock_height = 0;
922 CGContextSetTextMatrix( p_context, CGAffineTransformIdentity );
924 if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
926 else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
931 // Create the framesetter with the attributed string.
932 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
936 CGMutablePathRef p_path = CGPathCreateMutable();
937 CGRect p_bounds = CGRectMake( (float)HORIZONTAL_MARGIN,
938 (float)VERTICAL_MARGIN,
939 (float)(i_width - HORIZONTAL_MARGIN*2),
940 (float)(i_height - VERTICAL_MARGIN *2));
941 CGPathAddRect( p_path, NULL, p_bounds );
943 // Create the frame and draw it into the graphics context
944 frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
946 CGPathRelease(p_path);
948 // Set up black outlining of the text --
949 CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
950 CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
957 lines = CTFrameGetLines( frame );
958 penPosition.y = i_height;
959 for (int i=0; i<CFArrayGetCount( lines ); i++)
961 CGFloat ascent, descent, leading;
963 CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
964 CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
966 // Set the outlining for this line to be dependant on the size of the line -
967 // make it about 5% of the ascent, with a minimum at 1.0
968 float f_thickness = ascent * 0.05;
969 CGContextSetLineWidth( p_context, (( f_thickness > 1.0 ) ? 1.0 : f_thickness ));
971 double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2));
972 penPosition.x = HORIZONTAL_MARGIN + penOffset;
973 penPosition.y -= ascent;
974 CGContextSetTextPosition( p_context, penPosition.x, penPosition.y );
975 CTLineDraw( line, p_context );
976 penPosition.y -= descent + leading;
979 *pi_textblock_height = i_height - penPosition.y;
983 CFRelease(framesetter);
985 CGContextFlush( p_context );
986 CGContextRelease( p_context );
988 if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
993 static int GetFontSize( filter_t *p_filter )
995 return p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE;
998 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
999 CFMutableAttributedStringRef p_attrString )
1001 offscreen_bitmap_t *p_offScreen = NULL;
1002 unsigned i_textblock_height = 0;
1004 unsigned i_width = p_filter->fmt_out.video.i_visible_width;
1005 unsigned i_height = p_filter->fmt_out.video.i_visible_height;
1006 unsigned i_text_align = p_region->i_align & 0x3;
1010 msg_Err( p_filter, "Invalid argument to RenderYUVA" );
1011 return VLC_EGENERIC;
1014 p_offScreen = Compose( i_text_align, p_attrString,
1015 i_width, i_height, &i_textblock_height );
1019 msg_Err( p_filter, "No offscreen buffer" );
1020 return VLC_EGENERIC;
1023 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1026 unsigned x, y, i_pitch;
1027 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1029 // Create a new subpicture region
1030 memset( &fmt, 0, sizeof(video_format_t) );
1031 fmt.i_chroma = VLC_CODEC_YUVA;
1032 fmt.i_width = fmt.i_visible_width = i_width;
1033 fmt.i_height = fmt.i_visible_height = __MIN( i_height, i_textblock_height + VERTICAL_MARGIN * 2);
1034 fmt.i_x_offset = fmt.i_y_offset = 0;
1036 p_region->p_picture = picture_NewFromFormat( &fmt );
1037 if( !p_region->p_picture )
1038 return VLC_EGENERIC;
1039 p_region->fmt = fmt;
1041 p_dst_y = p_region->p_picture->Y_PIXELS;
1042 p_dst_u = p_region->p_picture->U_PIXELS;
1043 p_dst_v = p_region->p_picture->V_PIXELS;
1044 p_dst_a = p_region->p_picture->A_PIXELS;
1045 i_pitch = p_region->p_picture->A_PITCH;
1047 i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1048 for( y = 0; y < fmt.i_height; y++)
1050 for( x = 0; x < fmt.i_width; x++)
1052 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1053 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1054 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1055 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1057 i_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
1058 802 * i_blue + 4096 + 131072 ) >> 13, 235);
1059 i_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
1060 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1061 i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
1062 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1064 p_dst_y[ i_offset + x ] = i_y;
1065 p_dst_u[ i_offset + x ] = i_u;
1066 p_dst_v[ i_offset + x ] = i_v;
1067 p_dst_a[ i_offset + x ] = i_alpha;
1069 i_offset += i_pitch;
1072 free( p_offScreen->p_data );
1073 free( p_offScreen );