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 #include <TargetConditionals.h>
42 #include <CoreText/CoreText.h>
43 #include <CoreGraphics/CoreGraphics.h>
46 // Fix ourselves ColorSync headers that gets included in ApplicationServices.
47 #define DisposeCMProfileIterateUPP(a) DisposeCMProfileIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
48 #define DisposeCMMIterateUPP(a) DisposeCMMIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
49 #define __MACHINEEXCEPTIONS__
50 #include <ApplicationServices/ApplicationServices.h>
53 #define DEFAULT_FONT "Arial Black"
54 #define DEFAULT_FONT_COLOR 0xffffff
55 #define DEFAULT_REL_FONT_SIZE 16
57 #define VERTICAL_MARGIN 3
58 #define HORIZONTAL_MARGIN 10
60 //////////////////////////////////////////////////////////////////////////////
62 //////////////////////////////////////////////////////////////////////////////
63 static int Create ( vlc_object_t * );
64 static void Destroy( vlc_object_t * );
66 static int LoadFontsFromAttachments( filter_t *p_filter );
68 static int RenderText( filter_t *, subpicture_region_t *,
69 subpicture_region_t *,
70 const vlc_fourcc_t * );
71 static int RenderHtml( filter_t *, subpicture_region_t *,
72 subpicture_region_t *,
73 const vlc_fourcc_t * );
75 static int GetFontSize( filter_t *p_filter );
76 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
77 CFMutableAttributedStringRef p_attrString );
79 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
80 bool b_bold, bool b_italic, bool b_underline,
81 CFRange p_range, CFMutableAttributedStringRef p_attrString );
83 //////////////////////////////////////////////////////////////////////////////
85 //////////////////////////////////////////////////////////////////////////////
87 // The preferred way to set font style information is for it to come from the
88 // subtitle file, and for it to be rendered with RenderHtml instead of
90 #define FONT_TEXT N_("Font")
91 #define FONT_LONGTEXT N_("Name for the font you want to use")
92 #define FONTSIZER_TEXT N_("Relative font size")
93 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
94 "fonts that will be rendered on the video. If absolute font size is set, "\
95 "relative size will be overridden." )
96 #define COLOR_TEXT N_("Text default color")
97 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
98 "the video. This must be an hexadecimal (like HTML colors). The first two "\
99 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
100 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
102 static const int pi_color_values[] = {
103 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
104 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
105 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
107 static const char *const ppsz_color_descriptions[] = {
108 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
109 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
110 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
112 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
113 static const char *const ppsz_sizes_text[] = {
114 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
117 set_shortname( N_("Text renderer for Mac"))
118 set_description( N_("CoreText font renderer") )
119 set_category( CAT_VIDEO )
120 set_subcategory( SUBCAT_VIDEO_SUBPIC )
122 add_string( "quartztext-font", DEFAULT_FONT, FONT_TEXT, FONT_LONGTEXT,
124 add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, FONTSIZER_TEXT,
125 FONTSIZER_LONGTEXT, false )
126 change_integer_list( pi_sizes, ppsz_sizes_text )
127 add_integer( "quartztext-color", 0x00FFFFFF, COLOR_TEXT,
128 COLOR_LONGTEXT, false )
129 change_integer_list( pi_color_values, ppsz_color_descriptions )
130 set_capability( "text renderer", 50 )
131 add_shortcut( "text" )
132 set_callbacks( Create, Destroy )
135 typedef struct font_stack_t font_stack_t;
140 uint32_t i_color; // ARGB
142 font_stack_t *p_next;
148 uint32_t i_font_color; /* ARGB */
155 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
156 struct offscreen_bitmap_t
159 int i_bitsPerChannel;
165 //////////////////////////////////////////////////////////////////////////////
166 // filter_sys_t: quartztext local data
167 //////////////////////////////////////////////////////////////////////////////
168 // This structure is part of the video output thread descriptor.
169 // It describes the freetype specific properties of an output thread.
170 //////////////////////////////////////////////////////////////////////////////
174 uint8_t i_font_opacity;
178 #ifndef TARGET_OS_IPHONE
179 ATSFontContainerRef *p_fonts;
184 //////////////////////////////////////////////////////////////////////////////
185 // Create: allocates osd-text video thread output method
186 //////////////////////////////////////////////////////////////////////////////
187 // This function allocates and initializes a Clone vout method.
188 //////////////////////////////////////////////////////////////////////////////
189 static int Create( vlc_object_t *p_this )
191 filter_t *p_filter = (filter_t *)p_this;
194 // Allocate structure
195 p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
198 p_sys->psz_font_name = var_CreateGetString( p_this, "quartztext-font" );
199 p_sys->i_font_opacity = 255;
200 p_sys->i_font_color = __MAX( __MIN( var_CreateGetInteger( p_this, "quartztext-color" ) , 0xFFFFFF ), 0 );
201 p_sys->i_font_size = GetFontSize( p_filter );
203 p_filter->pf_render_text = RenderText;
204 p_filter->pf_render_html = RenderHtml;
206 #ifndef TARGET_OS_IPHONE
207 p_sys->p_fonts = NULL;
211 LoadFontsFromAttachments( p_filter );
216 //////////////////////////////////////////////////////////////////////////////
217 // Destroy: destroy Clone video thread output method
218 //////////////////////////////////////////////////////////////////////////////
219 // Clean up all data and library connections
220 //////////////////////////////////////////////////////////////////////////////
221 static void Destroy( vlc_object_t *p_this )
223 filter_t *p_filter = (filter_t *)p_this;
224 filter_sys_t *p_sys = p_filter->p_sys;
225 #ifndef TARGET_OS_IPHONE
230 for( k = 0; k < p_sys->i_fonts; k++ )
232 ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
235 free( p_sys->p_fonts );
238 free( p_sys->psz_font_name );
242 //////////////////////////////////////////////////////////////////////////////
243 // Make any TTF/OTF fonts present in the attachments of the media file
244 // available to the Quartz engine for text rendering
245 //////////////////////////////////////////////////////////////////////////////
246 static int LoadFontsFromAttachments( filter_t *p_filter )
248 #ifdef TARGET_OS_IPHONE
249 VLC_UNUSED(p_filter);
252 filter_sys_t *p_sys = p_filter->p_sys;
253 input_attachment_t **pp_attachments;
254 int i_attachments_cnt;
256 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
260 p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
261 if(! p_sys->p_fonts )
264 for( int k = 0; k < i_attachments_cnt; k++ )
266 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 && p_attach->p_data )
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;
285 vlc_input_attachment_Delete( p_attach );
287 free( pp_attachments );
292 static char *EliminateCRLF( char *psz_string )
297 for( p = psz_string; p && *p; p++ )
299 if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
301 for( q = p + 1; *q; q++ )
310 // Renders a text subpicture region into another one.
311 // It is used as pf_add_string callback in the vout method by this module
312 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
313 subpicture_region_t *p_region_in,
314 const vlc_fourcc_t *p_chroma_list )
316 filter_sys_t *p_sys = p_filter->p_sys;
318 int i_font_alpha, i_font_size;
319 uint32_t i_font_color;
320 bool b_bold, b_uline, b_italic;
322 b_bold = b_uline = b_italic = FALSE;
324 p_sys->i_font_size = GetFontSize( p_filter );
327 if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
328 psz_string = p_region_in->psz_text;
329 if( !psz_string || !*psz_string ) return VLC_EGENERIC;
331 if( p_region_in->p_style )
333 i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
334 i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
335 i_font_size = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 );
336 if( p_region_in->p_style->i_style_flags )
338 if( p_region_in->p_style->i_style_flags & STYLE_BOLD )
340 if( p_region_in->p_style->i_style_flags & STYLE_ITALIC )
342 if( p_region_in->p_style->i_style_flags & STYLE_UNDERLINE )
348 i_font_color = p_sys->i_font_color;
349 i_font_alpha = 255 - p_sys->i_font_opacity;
350 i_font_size = p_sys->i_font_size;
353 if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
355 if( i_font_size <= 0 )
357 msg_Warn( p_filter, "invalid fontsize, using 12" );
358 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
359 i_font_size = 12 * val.i_int / 1000;
364 p_region_out->i_x = p_region_in->i_x;
365 p_region_out->i_y = p_region_in->i_y;
367 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
371 CFStringRef p_cfString;
374 EliminateCRLF( psz_string);
375 p_cfString = CFStringCreateWithCString( NULL, psz_string, kCFStringEncodingUTF8 );
376 CFAttributedStringReplaceString( p_attrString, CFRangeMake(0, 0), p_cfString );
377 CFRelease( p_cfString );
378 len = CFAttributedStringGetLength( p_attrString );
380 setFontAttibutes( p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline,
381 CFRangeMake( 0, len ), p_attrString);
383 RenderYUVA( p_filter, p_region_out, p_attrString );
384 CFRelease( p_attrString );
391 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
399 p_new = malloc( sizeof( font_stack_t ) );
403 p_new->p_next = NULL;
406 p_new->psz_name = strdup( psz_name );
408 p_new->psz_name = NULL;
410 p_new->i_size = i_size;
411 p_new->i_color = i_color;
419 font_stack_t *p_last;
421 for( p_last = *p_font;
423 p_last = p_last->p_next )
426 p_last->p_next = p_new;
431 static int PopFont( font_stack_t **p_font )
433 font_stack_t *p_last, *p_next_to_last;
435 if( !p_font || !*p_font )
438 p_next_to_last = NULL;
439 for( p_last = *p_font;
441 p_last = p_last->p_next )
443 p_next_to_last = p_last;
447 p_next_to_last->p_next = NULL;
451 free( p_last->psz_name );
457 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
460 font_stack_t *p_last;
462 if( !p_font || !*p_font )
467 p_last=p_last->p_next )
470 *psz_name = p_last->psz_name;
471 *i_size = p_last->i_size;
472 *i_color = p_last->i_color;
477 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
478 font_stack_t **p_fonts )
481 char *psz_fontname = NULL;
482 uint32_t i_font_color = 0xffffff;
483 int i_font_alpha = 0;
484 int i_font_size = 24;
485 const char *attr, *value;
487 // Default all attributes to the top font in the stack -- in case not
488 // all attributes are specified in the sub-font
489 if( VLC_SUCCESS == PeekFont( p_fonts,
494 psz_fontname = strdup( psz_fontname );
495 i_font_size = i_font_size;
497 i_font_alpha = (i_font_color >> 24) & 0xff;
498 i_font_color &= 0x00ffffff;
500 while ( (attr = xml_ReaderNextAttr( p_xml_reader, &value )) )
502 if( !strcasecmp( "face", attr ) )
504 free( psz_fontname );
505 psz_fontname = strdup( value );
507 else if( !strcasecmp( "size", attr ) )
509 if( ( *value == '+' ) || ( *value == '-' ) )
511 int i_value = atoi( 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( value );
523 else if( !strcasecmp( "color", attr ) && ( value[0] == '#' ) )
525 i_font_color = strtol( value + 1, NULL, 16 );
526 i_font_color &= 0x00ffffff;
528 else if( !strcasecmp( "alpha", attr ) && ( value[0] == '#' ) )
530 i_font_alpha = strtol( value + 1, NULL, 16 );
531 i_font_alpha &= 0xff;
534 rv = PushFont( p_fonts,
537 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24) );
539 free( psz_fontname );
544 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
545 bool b_bold, bool b_italic, bool b_underline,
546 CFRange p_range, CFMutableAttributedStringRef p_attrString )
548 CFStringRef p_cfString;
551 // Handle font name and size
552 p_cfString = CFStringCreateWithCString( NULL,
554 kCFStringEncodingUTF8 );
555 p_font = CTFontCreateWithName( p_cfString,
558 CFRelease( p_cfString );
559 CFAttributedStringSetAttribute( p_attrString,
561 kCTFontAttributeName,
568 _uline = kCTUnderlineStyleSingle;
570 _uline = kCTUnderlineStyleNone;
572 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
573 CFAttributedStringSetAttribute( p_attrString,
575 kCTUnderlineStyleAttributeName,
577 CFRelease( underline );
586 CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
587 CFAttributedStringSetAttribute( p_attrString,
600 CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
601 CFAttributedStringSetAttribute( p_attrString,
607 // Handle foreground colour
608 CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
609 CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
610 (float)((i_font_color & 0x0000ff00) >> 8) / 255.0,
611 (float)((i_font_color & 0x000000ff) ) / 255.0,
612 (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
613 CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
614 CGColorSpaceRelease(rgbColorSpace);
616 CFAttributedStringSetAttribute( p_attrString,
618 kCTForegroundColorAttributeName,
620 CFRelease( fg_text );
624 static void GetAttrStrFromFontStack( font_stack_t **p_fonts,
625 bool b_bold, bool b_italic, bool b_uline,
626 CFRange p_range, CFMutableAttributedStringRef p_attrString )
628 char *psz_fontname = NULL;
630 uint32_t i_font_color = 0;
632 if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
635 setFontAttibutes( psz_fontname,
638 b_bold, b_italic, b_uline,
644 static int ProcessNodes( filter_t *p_filter,
645 xml_reader_t *p_xml_reader,
646 text_style_t *p_font_style,
647 CFMutableAttributedStringRef p_attrString )
649 int rv = VLC_SUCCESS;
650 filter_sys_t *p_sys = p_filter->p_sys;
651 font_stack_t *p_fonts = NULL;
656 bool b_italic = false;
658 bool b_uline = false;
662 rv = PushFont( &p_fonts,
663 p_font_style->psz_fontname,
664 p_font_style->i_font_size,
665 (p_font_style->i_font_color & 0xffffff) |
666 ((p_font_style->i_font_alpha & 0xff) << 24) );
668 if( p_font_style->i_style_flags & STYLE_BOLD )
670 if( p_font_style->i_style_flags & STYLE_ITALIC )
672 if( p_font_style->i_style_flags & STYLE_UNDERLINE )
677 rv = PushFont( &p_fonts,
678 p_sys->psz_font_name,
680 p_sys->i_font_color );
682 if( rv != VLC_SUCCESS )
685 while ( ( type = xml_ReaderNextNode( p_xml_reader, &node ) ) > 0 )
689 case XML_READER_ENDELEM:
690 if( !strcasecmp( "font", node ) )
692 else if( !strcasecmp( "b", node ) )
694 else if( !strcasecmp( "i", node ) )
696 else if( !strcasecmp( "u", node ) )
700 case XML_READER_STARTELEM:
701 if( !strcasecmp( "font", node ) )
702 rv = HandleFontAttributes( p_xml_reader, &p_fonts );
703 else if( !strcasecmp( "b", node ) )
705 else if( !strcasecmp( "i", node ) )
707 else if( !strcasecmp( "u", node ) )
709 else if( !strcasecmp( "br", node ) )
711 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
712 CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), CFSTR("\n") );
714 GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
717 CFAttributedStringReplaceAttributedString( p_attrString,
718 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
720 CFRelease( p_attrnode );
723 case XML_READER_TEXT:
725 CFStringRef p_cfString;
728 // Turn any multiple-whitespaces into single spaces
729 char *dup = strdup( node );
732 char *s = strpbrk( dup, "\t\r\n " );
735 int i_whitespace = strspn( s, "\t\r\n " );
737 if( i_whitespace > 1 )
740 strlen( s ) - i_whitespace + 1 );
743 s = strpbrk( s, "\t\r\n " );
747 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
748 p_cfString = CFStringCreateWithCString( NULL, dup, kCFStringEncodingUTF8 );
749 CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), p_cfString );
750 CFRelease( p_cfString );
751 len = CFAttributedStringGetLength( p_attrnode );
753 GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
754 CFRangeMake( 0, len ),
757 CFAttributedStringReplaceAttributedString( p_attrString,
758 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
760 CFRelease( p_attrnode );
768 while( VLC_SUCCESS == PopFont( &p_fonts ) );
773 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
774 subpicture_region_t *p_region_in,
775 const vlc_fourcc_t *p_chroma_list )
777 int rv = VLC_SUCCESS;
778 stream_t *p_sub = NULL;
780 xml_reader_t *p_xml_reader = NULL;
782 if( !p_region_in || !p_region_in->psz_html )
785 /* Reset the default fontsize in case screen metrics have changed */
786 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
788 p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
789 (uint8_t *) p_region_in->psz_html,
790 strlen( p_region_in->psz_html ),
794 p_xml = xml_Create( p_filter );
797 bool b_karaoke = false;
799 p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
802 /* Look for Root Node */
804 if( xml_ReaderNextNode( p_xml_reader, &name )
805 == XML_READER_STARTELEM )
807 if( !strcasecmp( "karaoke", name ) )
809 /* We're going to have to render the text a number
810 * of times to show the progress marker on the text.
812 var_SetBool( p_filter, "text-rerender", true );
815 else if( !strcasecmp( "text", name ) )
821 /* Only text and karaoke tags are supported */
822 msg_Dbg( p_filter, "Unsupported top-level tag "
823 "<%s> ignored.", name );
829 msg_Err( p_filter, "Malformed HTML subtitle" );
833 if( rv != VLC_SUCCESS )
835 xml_ReaderDelete( p_xml_reader );
844 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
845 rv = ProcessNodes( p_filter, p_xml_reader,
846 p_region_in->p_style, p_attrString );
848 i_len = CFAttributedStringGetLength( p_attrString );
850 p_region_out->i_x = p_region_in->i_x;
851 p_region_out->i_y = p_region_in->i_y;
853 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
855 RenderYUVA( p_filter, p_region_out, p_attrString );
857 CFRelease(p_attrString);
859 xml_ReaderDelete( p_xml_reader );
863 stream_Delete( p_sub );
869 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
870 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
872 offscreen_bitmap_t *p_bitmap;
873 CGContextRef p_context = NULL;
875 p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
878 p_bitmap->i_bitsPerChannel = 8;
879 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
880 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
881 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
883 p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
885 *pp_colorSpace = CGColorSpaceCreateDeviceRGB();
887 if( p_bitmap->p_data && *pp_colorSpace )
889 p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
890 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
891 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
895 if( CGContextSetAllowsAntialiasing != NULL )
897 CGContextSetAllowsAntialiasing( p_context, true );
900 *pp_memory = p_bitmap;
906 static offscreen_bitmap_t *Compose( int i_text_align,
907 CFMutableAttributedStringRef p_attrString,
910 unsigned *pi_textblock_height )
912 offscreen_bitmap_t *p_offScreen = NULL;
913 CGColorSpaceRef p_colorSpace = NULL;
914 CGContextRef p_context = NULL;
916 p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
918 *pi_textblock_height = 0;
923 CGContextSetTextMatrix( p_context, CGAffineTransformIdentity );
925 if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
927 else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
932 // Create the framesetter with the attributed string.
933 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
937 CGMutablePathRef p_path = CGPathCreateMutable();
938 CGRect p_bounds = CGRectMake( (float)HORIZONTAL_MARGIN,
939 (float)VERTICAL_MARGIN,
940 (float)(i_width - HORIZONTAL_MARGIN*2),
941 (float)(i_height - VERTICAL_MARGIN *2));
942 CGPathAddRect( p_path, NULL, p_bounds );
944 // Create the frame and draw it into the graphics context
945 frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
947 CGPathRelease(p_path);
949 // Set up black outlining of the text --
950 CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
951 CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
958 lines = CTFrameGetLines( frame );
959 penPosition.y = i_height;
960 for (int i=0; i<CFArrayGetCount( lines ); i++)
962 CGFloat ascent, descent, leading;
964 CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
965 CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
967 // Set the outlining for this line to be dependant on the size of the line -
968 // make it about 5% of the ascent, with a minimum at 1.0
969 float f_thickness = ascent * 0.05;
970 CGContextSetLineWidth( p_context, (( f_thickness > 1.0 ) ? 1.0 : f_thickness ));
972 double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2));
973 penPosition.x = HORIZONTAL_MARGIN + penOffset;
974 penPosition.y -= ascent;
975 CGContextSetTextPosition( p_context, penPosition.x, penPosition.y );
976 CTLineDraw( line, p_context );
977 penPosition.y -= descent + leading;
980 *pi_textblock_height = i_height - penPosition.y;
984 CFRelease(framesetter);
986 CGContextFlush( p_context );
987 CGContextRelease( p_context );
989 if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
994 static int GetFontSize( filter_t *p_filter )
996 return p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE;
999 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
1000 CFMutableAttributedStringRef p_attrString )
1002 offscreen_bitmap_t *p_offScreen = NULL;
1003 unsigned i_textblock_height = 0;
1005 unsigned i_width = p_filter->fmt_out.video.i_visible_width;
1006 unsigned i_height = p_filter->fmt_out.video.i_visible_height;
1007 unsigned i_text_align = p_region->i_align & 0x3;
1011 msg_Err( p_filter, "Invalid argument to RenderYUVA" );
1012 return VLC_EGENERIC;
1015 p_offScreen = Compose( i_text_align, p_attrString,
1016 i_width, i_height, &i_textblock_height );
1020 msg_Err( p_filter, "No offscreen buffer" );
1021 return VLC_EGENERIC;
1024 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1027 unsigned x, y, i_pitch;
1028 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1030 // Create a new subpicture region
1031 memset( &fmt, 0, sizeof(video_format_t) );
1032 fmt.i_chroma = VLC_CODEC_YUVA;
1033 fmt.i_width = fmt.i_visible_width = i_width;
1034 fmt.i_height = fmt.i_visible_height = __MIN( i_height, i_textblock_height + VERTICAL_MARGIN * 2);
1035 fmt.i_x_offset = fmt.i_y_offset = 0;
1039 p_region->p_picture = picture_NewFromFormat( &fmt );
1040 if( !p_region->p_picture )
1041 return VLC_EGENERIC;
1042 p_region->fmt = fmt;
1044 p_dst_y = p_region->p_picture->Y_PIXELS;
1045 p_dst_u = p_region->p_picture->U_PIXELS;
1046 p_dst_v = p_region->p_picture->V_PIXELS;
1047 p_dst_a = p_region->p_picture->A_PIXELS;
1048 i_pitch = p_region->p_picture->A_PITCH;
1050 i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1051 for( y = 0; y < fmt.i_height; y++)
1053 for( x = 0; x < fmt.i_width; x++)
1055 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1056 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1057 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1058 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1060 i_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
1061 802 * i_blue + 4096 + 131072 ) >> 13, 235);
1062 i_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
1063 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1064 i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
1065 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1067 p_dst_y[ i_offset + x ] = i_y;
1068 p_dst_u[ i_offset + x ] = i_u;
1069 p_dst_v[ i_offset + x ] = i_v;
1070 p_dst_a[ i_offset + x ] = i_alpha;
1072 i_offset += i_pitch;
1075 free( p_offScreen->p_data );
1076 free( p_offScreen );