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)
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 = VLC_CLIP(var_CreateGetInteger(p_this, "quartztext-color") , 0, 0xFFFFFF);
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
226 if (p_sys->p_fonts) {
227 for (int k = 0; k < p_sys->i_fonts; k++) {
228 ATSFontDeactivate(p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault);
230 free(p_sys->p_fonts);
233 free(p_sys->psz_font_name);
237 /*****************************************************************************
238 * Make any TTF/OTF fonts present in the attachments of the media file
239 * available to the Quartz engine for text rendering
240 *****************************************************************************/
241 static int LoadFontsFromAttachments(filter_t *p_filter)
243 #ifdef TARGET_OS_IPHONE
244 VLC_UNUSED(p_filter);
247 filter_sys_t *p_sys = p_filter->p_sys;
248 input_attachment_t **pp_attachments;
249 int i_attachments_cnt;
251 if (filter_GetInputAttachments(p_filter, &pp_attachments, &i_attachments_cnt))
255 p_sys->p_fonts = malloc(i_attachments_cnt * sizeof(ATSFontContainerRef));
256 if (! p_sys->p_fonts)
259 for (int k = 0; k < i_attachments_cnt; k++) {
260 input_attachment_t *p_attach = pp_attachments[k];
262 if ((!strcmp(p_attach->psz_mime, "application/x-truetype-font") || // TTF
263 !strcmp(p_attach->psz_mime, "application/x-font-otf")) && // OTF
264 p_attach->i_data > 0 && p_attach->p_data) {
265 ATSFontContainerRef container;
267 if (noErr == ATSFontActivateFromMemory(p_attach->p_data,
269 kATSFontContextLocal,
270 kATSFontFormatUnspecified,
272 kATSOptionFlagsDefault,
274 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
276 vlc_input_attachment_Delete(p_attach);
278 free(pp_attachments);
283 static char *EliminateCRLF(char *psz_string)
287 for (char * p = psz_string; p && *p; p++) {
288 if ((*p == '\r') && (*(p+1) == '\n')) {
289 for (q = p + 1; *q; q++)
298 /* Renders a text subpicture region into another one.
299 * It is used as pf_add_string callback in the vout method by this module */
300 static int RenderText(filter_t *p_filter, subpicture_region_t *p_region_out,
301 subpicture_region_t *p_region_in,
302 const vlc_fourcc_t *p_chroma_list)
304 filter_sys_t *p_sys = p_filter->p_sys;
306 int i_font_alpha, i_font_size;
307 uint32_t i_font_color;
308 bool b_bold, b_uline, b_italic;
310 b_bold = b_uline = b_italic = FALSE;
312 p_sys->i_font_size = GetFontSize(p_filter);
315 if (!p_region_in || !p_region_out)
318 psz_string = p_region_in->psz_text;
319 if (!psz_string || !*psz_string)
322 if (p_region_in->p_style) {
323 i_font_color = VLC_CLIP(p_region_in->p_style->i_font_color, 0, 0xFFFFFF);
324 i_font_alpha = VLC_CLIP(p_region_in->p_style->i_font_alpha, 0, 255);
325 i_font_size = VLC_CLIP(p_region_in->p_style->i_font_size, 0, 255);
326 if (p_region_in->p_style->i_style_flags) {
327 if (p_region_in->p_style->i_style_flags & STYLE_BOLD)
329 if (p_region_in->p_style->i_style_flags & STYLE_ITALIC)
331 if (p_region_in->p_style->i_style_flags & STYLE_UNDERLINE)
335 i_font_color = p_sys->i_font_color;
336 i_font_alpha = 255 - p_sys->i_font_opacity;
337 i_font_size = p_sys->i_font_size;
341 i_font_alpha = 255 - p_sys->i_font_opacity;
343 if (i_font_size <= 0) {
344 msg_Warn(p_filter, "invalid fontsize, using 12");
345 if (VLC_SUCCESS == var_Get(p_filter, "scale", &val))
346 i_font_size = 12 * val.i_int / 1000;
351 p_region_out->i_x = p_region_in->i_x;
352 p_region_out->i_y = p_region_in->i_y;
354 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
357 CFStringRef p_cfString;
360 EliminateCRLF(psz_string);
361 p_cfString = CFStringCreateWithCString(NULL, psz_string, kCFStringEncodingUTF8);
362 CFAttributedStringReplaceString(p_attrString, CFRangeMake(0, 0), p_cfString);
363 CFRelease(p_cfString);
364 len = CFAttributedStringGetLength(p_attrString);
366 setFontAttibutes(p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline,
367 CFRangeMake(0, len), p_attrString);
369 RenderYUVA(p_filter, p_region_out, p_attrString);
370 CFRelease(p_attrString);
377 static int PushFont(font_stack_t **p_font, const char *psz_name, int i_size,
385 p_new = malloc(sizeof(font_stack_t));
389 p_new->p_next = NULL;
392 p_new->psz_name = strdup(psz_name);
394 p_new->psz_name = NULL;
396 p_new->i_size = i_size;
397 p_new->i_color = i_color;
402 font_stack_t *p_last;
404 for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
407 p_last->p_next = p_new;
412 static int PopFont(font_stack_t **p_font)
414 font_stack_t *p_last, *p_next_to_last;
416 if (!p_font || !*p_font)
419 p_next_to_last = NULL;
420 for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
421 p_next_to_last = p_last;
424 p_next_to_last->p_next = NULL;
428 free(p_last->psz_name);
434 static int PeekFont(font_stack_t **p_font, char **psz_name, int *i_size,
437 font_stack_t *p_last;
439 if (!p_font || !*p_font)
444 p_last=p_last->p_next)
447 *psz_name = p_last->psz_name;
448 *i_size = p_last->i_size;
449 *i_color = p_last->i_color;
454 static int HandleFontAttributes(xml_reader_t *p_xml_reader,
455 font_stack_t **p_fonts)
458 char *psz_fontname = NULL;
459 uint32_t i_font_color = 0xffffff;
460 int i_font_alpha = 0;
461 int i_font_size = 24;
462 const char *attr, *value;
464 /* Default all attributes to the top font in the stack -- in case not
465 * all attributes are specified in the sub-font */
466 if (VLC_SUCCESS == PeekFont(p_fonts,
470 psz_fontname = strdup(psz_fontname);
471 i_font_size = i_font_size;
473 i_font_alpha = (i_font_color >> 24) & 0xff;
474 i_font_color &= 0x00ffffff;
476 while ((attr = xml_ReaderNextAttr(p_xml_reader, &value))) {
477 if (!strcasecmp("face", attr)) {
479 psz_fontname = strdup(value);
480 } else if (!strcasecmp("size", attr)) {
481 if ((*value == '+') || (*value == '-')) {
482 int i_value = atoi(value);
484 if ((i_value >= -5) && (i_value <= 5))
485 i_font_size += (i_value * i_font_size) / 10;
486 else if (i_value < -5)
487 i_font_size = - i_value;
488 else if (i_value > 5)
489 i_font_size = i_value;
492 i_font_size = atoi(value);
493 } else if (!strcasecmp("color", attr) && (value[0] == '#')) {
494 i_font_color = strtol(value + 1, NULL, 16);
495 i_font_color &= 0x00ffffff;
496 } else if (!strcasecmp("alpha", attr) && (value[0] == '#')) {
497 i_font_alpha = strtol(value + 1, NULL, 16);
498 i_font_alpha &= 0xff;
501 rv = PushFont(p_fonts,
504 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24));
511 static void setFontAttibutes(char *psz_fontname, int i_font_size, uint32_t i_font_color,
512 bool b_bold, bool b_italic, bool b_underline,
513 CFRange p_range, CFMutableAttributedStringRef p_attrString)
515 CFStringRef p_cfString;
518 // Handle font name and size
519 p_cfString = CFStringCreateWithCString(NULL,
521 kCFStringEncodingUTF8);
522 p_font = CTFontCreateWithName(p_cfString,
525 CFRelease(p_cfString);
526 CFAttributedStringSetAttribute(p_attrString,
528 kCTFontAttributeName,
535 _uline = kCTUnderlineStyleSingle;
537 _uline = kCTUnderlineStyleNone;
539 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
540 CFAttributedStringSetAttribute(p_attrString,
542 kCTUnderlineStyleAttributeName,
544 CFRelease(underline);
553 CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
554 CFAttributedStringSetAttribute(p_attrString,
567 CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
568 CFAttributedStringSetAttribute(p_attrString,
574 // Handle foreground colour
575 CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
576 CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
577 (float)((i_font_color & 0x0000ff00) >> 8) / 255.0,
578 (float)((i_font_color & 0x000000ff)) / 255.0,
579 (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
580 CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
581 CGColorSpaceRelease(rgbColorSpace);
583 CFAttributedStringSetAttribute(p_attrString,
585 kCTForegroundColorAttributeName,
591 static void GetAttrStrFromFontStack(font_stack_t **p_fonts,
592 bool b_bold, bool b_italic, bool b_uline,
593 CFRange p_range, CFMutableAttributedStringRef p_attrString)
595 char *psz_fontname = NULL;
597 uint32_t i_font_color = 0;
599 if (VLC_SUCCESS == PeekFont(p_fonts, &psz_fontname, &i_font_size,
601 setFontAttibutes(psz_fontname,
604 b_bold, b_italic, b_uline,
610 static int ProcessNodes(filter_t *p_filter,
611 xml_reader_t *p_xml_reader,
612 text_style_t *p_font_style,
613 CFMutableAttributedStringRef p_attrString)
615 int rv = VLC_SUCCESS;
616 filter_sys_t *p_sys = p_filter->p_sys;
617 font_stack_t *p_fonts = NULL;
622 bool b_italic = false;
624 bool b_uline = false;
627 rv = PushFont(&p_fonts,
628 p_font_style->psz_fontname,
629 p_font_style->i_font_size,
630 (p_font_style->i_font_color & 0xffffff) |
631 ((p_font_style->i_font_alpha & 0xff) << 24));
633 if (p_font_style->i_style_flags & STYLE_BOLD)
635 if (p_font_style->i_style_flags & STYLE_ITALIC)
637 if (p_font_style->i_style_flags & STYLE_UNDERLINE)
640 rv = PushFont(&p_fonts,
641 p_sys->psz_font_name,
643 p_sys->i_font_color);
645 if (rv != VLC_SUCCESS)
648 while ((type = xml_ReaderNextNode(p_xml_reader, &node)) > 0) {
650 case XML_READER_ENDELEM:
651 if (!strcasecmp("font", node))
653 else if (!strcasecmp("b", node))
655 else if (!strcasecmp("i", node))
657 else if (!strcasecmp("u", node))
661 case XML_READER_STARTELEM:
662 if (!strcasecmp("font", node))
663 rv = HandleFontAttributes(p_xml_reader, &p_fonts);
664 else if (!strcasecmp("b", node))
666 else if (!strcasecmp("i", node))
668 else if (!strcasecmp("u", node))
670 else if (!strcasecmp("br", node)) {
671 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
672 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), CFSTR("\n"));
674 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
677 CFAttributedStringReplaceAttributedString(p_attrString,
678 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
680 CFRelease(p_attrnode);
683 case XML_READER_TEXT:
685 CFStringRef p_cfString;
688 // Turn any multiple-whitespaces into single spaces
689 char *dup = strdup(node);
692 char *s = strpbrk(dup, "\t\r\n ");
695 int i_whitespace = strspn(s, "\t\r\n ");
697 if (i_whitespace > 1)
700 strlen(s) - i_whitespace + 1);
703 s = strpbrk(s, "\t\r\n ");
707 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
708 p_cfString = CFStringCreateWithCString(NULL, dup, kCFStringEncodingUTF8);
709 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), p_cfString);
710 CFRelease(p_cfString);
711 len = CFAttributedStringGetLength(p_attrnode);
713 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
717 CFAttributedStringReplaceAttributedString(p_attrString,
718 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
720 CFRelease(p_attrnode);
728 while(VLC_SUCCESS == PopFont(&p_fonts));
733 static int RenderHtml(filter_t *p_filter, subpicture_region_t *p_region_out,
734 subpicture_region_t *p_region_in,
735 const vlc_fourcc_t *p_chroma_list)
737 int rv = VLC_SUCCESS;
738 stream_t *p_sub = NULL;
740 xml_reader_t *p_xml_reader = NULL;
742 if (!p_region_in || !p_region_in->psz_html)
745 /* Reset the default fontsize in case screen metrics have changed */
746 p_filter->p_sys->i_font_size = GetFontSize(p_filter);
748 p_sub = stream_MemoryNew(VLC_OBJECT(p_filter),
749 (uint8_t *) p_region_in->psz_html,
750 strlen(p_region_in->psz_html),
753 p_xml = xml_Create(p_filter);
755 bool b_karaoke = false;
757 p_xml_reader = xml_ReaderCreate(p_xml, p_sub);
759 /* Look for Root Node */
761 if (xml_ReaderNextNode(p_xml_reader, &name)
762 == XML_READER_STARTELEM) {
763 if (!strcasecmp("karaoke", name)) {
764 /* We're going to have to render the text a number
765 * of times to show the progress marker on the text.
767 var_SetBool(p_filter, "text-rerender", true);
769 } else if (!strcasecmp("text", name))
772 /* Only text and karaoke tags are supported */
773 msg_Dbg(p_filter, "Unsupported top-level tag "
774 "<%s> ignored.", name);
778 msg_Err(p_filter, "Malformed HTML subtitle");
782 if (rv != VLC_SUCCESS) {
783 xml_ReaderDelete(p_xml_reader);
791 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
792 rv = ProcessNodes(p_filter, p_xml_reader,
793 p_region_in->p_style, p_attrString);
795 i_len = CFAttributedStringGetLength(p_attrString);
797 p_region_out->i_x = p_region_in->i_x;
798 p_region_out->i_y = p_region_in->i_y;
800 if ((rv == VLC_SUCCESS) && (i_len > 0))
801 RenderYUVA(p_filter, p_region_out, p_attrString);
803 CFRelease(p_attrString);
805 xml_ReaderDelete(p_xml_reader);
809 stream_Delete(p_sub);
815 static CGContextRef CreateOffScreenContext(int i_width, int i_height,
816 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace)
818 offscreen_bitmap_t *p_bitmap;
819 CGContextRef p_context = NULL;
821 p_bitmap = (offscreen_bitmap_t *) malloc(sizeof(offscreen_bitmap_t));
823 p_bitmap->i_bitsPerChannel = 8;
824 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
825 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
826 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
828 p_bitmap->p_data = calloc(i_height, p_bitmap->i_bytesPerRow);
830 *pp_colorSpace = CGColorSpaceCreateDeviceRGB();
832 if (p_bitmap->p_data && *pp_colorSpace)
833 p_context = CGBitmapContextCreate(p_bitmap->p_data, i_width, i_height,
834 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
835 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
838 if (CGContextSetAllowsAntialiasing != NULL)
839 CGContextSetAllowsAntialiasing(p_context, true);
841 *pp_memory = p_bitmap;
847 static offscreen_bitmap_t *Compose(int i_text_align,
848 CFMutableAttributedStringRef p_attrString,
851 unsigned *pi_textblock_height)
853 offscreen_bitmap_t *p_offScreen = NULL;
854 CGColorSpaceRef p_colorSpace = NULL;
855 CGContextRef p_context = NULL;
857 p_context = CreateOffScreenContext(i_width, i_height, &p_offScreen, &p_colorSpace);
859 *pi_textblock_height = 0;
863 CGContextSetTextMatrix(p_context, CGAffineTransformIdentity);
865 if (i_text_align == SUBPICTURE_ALIGN_RIGHT)
867 else if (i_text_align != SUBPICTURE_ALIGN_LEFT)
872 // Create the framesetter with the attributed string.
873 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
876 CGMutablePathRef p_path = CGPathCreateMutable();
877 CGRect p_bounds = CGRectMake((float)HORIZONTAL_MARGIN,
878 (float)VERTICAL_MARGIN,
879 (float)(i_width - HORIZONTAL_MARGIN*2),
880 (float)(i_height - VERTICAL_MARGIN *2));
881 CGPathAddRect(p_path, NULL, p_bounds);
883 // Create the frame and draw it into the graphics context
884 frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
886 CGPathRelease(p_path);
888 // Set up black outlining of the text --
889 CGContextSetRGBStrokeColor(p_context, 0, 0, 0, 0.5);
890 CGContextSetTextDrawingMode(p_context, kCGTextFillStroke);
896 lines = CTFrameGetLines(frame);
897 penPosition.y = i_height;
898 for (int i=0; i<CFArrayGetCount(lines); i++) {
899 CGFloat ascent, descent, leading;
901 CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
902 CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
904 // Set the outlining for this line to be dependant on the size of the line -
905 // make it about 5% of the ascent, with a minimum at 1.0
906 float f_thickness = ascent * 0.05;
907 CGContextSetLineWidth(p_context, ((f_thickness > 1.0) ? 1.0 : f_thickness));
909 double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2));
910 penPosition.x = HORIZONTAL_MARGIN + penOffset;
911 penPosition.y -= ascent;
912 CGContextSetTextPosition(p_context, penPosition.x, penPosition.y);
913 CTLineDraw(line, p_context);
914 penPosition.y -= descent + leading;
917 *pi_textblock_height = i_height - penPosition.y;
921 CFRelease(framesetter);
923 CGContextFlush(p_context);
924 CGContextRelease(p_context);
926 if (p_colorSpace) CGColorSpaceRelease(p_colorSpace);
931 static int GetFontSize(filter_t *p_filter)
933 return p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE;
936 static int RenderYUVA(filter_t *p_filter, subpicture_region_t *p_region,
937 CFMutableAttributedStringRef p_attrString)
939 offscreen_bitmap_t *p_offScreen = NULL;
940 unsigned i_textblock_height = 0;
942 unsigned i_width = p_filter->fmt_out.video.i_visible_width;
943 unsigned i_height = p_filter->fmt_out.video.i_visible_height;
944 unsigned i_text_align = p_region->i_align & 0x3;
947 msg_Err(p_filter, "Invalid argument to RenderYUVA");
951 p_offScreen = Compose(i_text_align, p_attrString,
952 i_width, i_height, &i_textblock_height);
955 msg_Err(p_filter, "No offscreen buffer");
959 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
963 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
965 // Create a new subpicture region
966 memset(&fmt, 0, sizeof(video_format_t));
967 fmt.i_chroma = VLC_CODEC_YUVA;
968 fmt.i_width = fmt.i_visible_width = i_width;
969 fmt.i_height = fmt.i_visible_height = __MIN(i_height, i_textblock_height + VERTICAL_MARGIN * 2);
970 fmt.i_x_offset = fmt.i_y_offset = 0;
974 p_region->p_picture = picture_NewFromFormat(&fmt);
975 if (!p_region->p_picture)
979 p_dst_y = p_region->p_picture->Y_PIXELS;
980 p_dst_u = p_region->p_picture->U_PIXELS;
981 p_dst_v = p_region->p_picture->V_PIXELS;
982 p_dst_a = p_region->p_picture->A_PIXELS;
983 i_pitch = p_region->p_picture->A_PITCH;
985 i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
986 for (unsigned y = 0; y < fmt.i_height; y++) {
987 for (unsigned x = 0; x < fmt.i_width; x++) {
988 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
989 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
990 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
991 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
993 i_y = (uint8_t)__MIN(abs(2104 * i_red + 4130 * i_green +
994 802 * i_blue + 4096 + 131072) >> 13, 235);
995 i_u = (uint8_t)__MIN(abs(-1214 * i_red + -2384 * i_green +
996 3598 * i_blue + 4096 + 1048576) >> 13, 240);
997 i_v = (uint8_t)__MIN(abs(3598 * i_red + -3013 * i_green +
998 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1000 p_dst_y[ i_offset + x ] = i_y;
1001 p_dst_u[ i_offset + x ] = i_u;
1002 p_dst_v[ i_offset + x ] = i_v;
1003 p_dst_a[ i_offset + x ] = i_alpha;
1005 i_offset += i_pitch;
1008 free(p_offScreen->p_data);