1 /*****************************************************************************
2 * quartztext.c : Put text on the video, using Mac OS X Quartz Engine
3 *****************************************************************************
4 * Copyright (C) 2007, 2009, 2012 VLC authors and VideoLAN
7 * Authors: Bernie Purcell <bitmap@videolan.org>
8 * Pierre d'Herbemont <pdherbemont # videolan dot>
9 * Felix Paul Kühne <fkuehne # videolan # org>
11 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2.1 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this program; if not, write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
26 /*****************************************************************************
28 *****************************************************************************/
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_stream.h>
38 #include <vlc_input.h>
39 #include <vlc_filter.h>
41 #include <TargetConditionals.h>
44 #include <CoreText/CoreText.h>
45 #include <CoreGraphics/CoreGraphics.h>
48 // Fix ourselves ColorSync headers that gets included in ApplicationServices.
49 #define DisposeCMProfileIterateUPP(a) DisposeCMProfileIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
50 #define DisposeCMMIterateUPP(a) DisposeCMMIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
51 #define __MACHINEEXCEPTIONS__
52 #include <ApplicationServices/ApplicationServices.h>
55 #define DEFAULT_FONT "Helvetica-Neue"
56 #define DEFAULT_FONT_COLOR 0xffffff
57 #define DEFAULT_REL_FONT_SIZE 16
59 #define VERTICAL_MARGIN 3
60 #define HORIZONTAL_MARGIN 10
62 /*****************************************************************************
64 *****************************************************************************/
65 static int Create (vlc_object_t *);
66 static void Destroy(vlc_object_t *);
68 static int LoadFontsFromAttachments(filter_t *p_filter);
70 static int RenderText(filter_t *, subpicture_region_t *,
71 subpicture_region_t *,
72 const vlc_fourcc_t *);
73 static int RenderHtml(filter_t *, subpicture_region_t *,
74 subpicture_region_t *,
75 const vlc_fourcc_t *);
77 static int GetFontSize(filter_t *p_filter);
78 static int RenderYUVA(filter_t *p_filter, subpicture_region_t *p_region,
79 CFMutableAttributedStringRef p_attrString);
81 static void setFontAttibutes(char *psz_fontname, int i_font_size, uint32_t i_font_color,
82 bool b_bold, bool b_italic, bool b_underline, bool b_halfwidth,
83 CFRange p_range, CFMutableAttributedStringRef p_attrString);
85 /*****************************************************************************
87 *****************************************************************************/
89 /* The preferred way to set font style information is for it to come from the
90 * subtitle file, and for it to be rendered with RenderHtml instead of
92 #define FONT_TEXT N_("Font")
93 #define FONT_LONGTEXT N_("Name for the font you want to use")
94 #define FONTSIZER_TEXT N_("Relative font size")
95 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
96 "fonts that will be rendered on the video. If absolute font size is set, "\
97 "relative size will be overridden.")
98 #define COLOR_TEXT N_("Text default color")
99 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
100 "the video. This must be an hexadecimal (like HTML colors). The first two "\
101 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
102 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white")
104 static const int pi_color_values[] = {
105 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
106 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
107 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
109 static const char *const ppsz_color_names[] = {
110 "black", "gray", "silver", "white", "maroon",
111 "red", "fuchsia", "yellow", "olive", "green",
112 "teal", "lime", "purple", "navy", "blue", "aqua" };
114 static const char *const ppsz_color_descriptions[] = {
115 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
116 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
117 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
119 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
120 static const char *const ppsz_sizes_text[] = {
121 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
124 set_shortname(N_("Text renderer for Mac"))
125 set_description(N_("CoreText font renderer"))
126 set_category(CAT_VIDEO)
127 set_subcategory(SUBCAT_VIDEO_SUBPIC)
129 add_string("quartztext-font", DEFAULT_FONT, FONT_TEXT, FONT_LONGTEXT,
131 add_integer("quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, FONTSIZER_TEXT,
132 FONTSIZER_LONGTEXT, false)
133 change_integer_list(pi_sizes, ppsz_sizes_text)
134 add_integer("quartztext-color", 0x00FFFFFF, COLOR_TEXT,
135 COLOR_LONGTEXT, false)
136 change_integer_list(pi_color_values, ppsz_color_descriptions)
137 set_capability("text renderer", 50)
139 set_callbacks(Create, Destroy)
142 typedef struct font_stack_t font_stack_t;
147 uint32_t i_color; // ARGB
149 font_stack_t *p_next;
155 uint32_t i_font_color; /* ARGB */
162 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
163 struct offscreen_bitmap_t
166 int i_bitsPerChannel;
172 /*****************************************************************************
173 * filter_sys_t: quartztext local data
174 *****************************************************************************
175 * This structure is part of the video output thread descriptor.
176 * It describes the freetype specific properties of an output thread.
177 *****************************************************************************/
181 uint8_t i_font_opacity;
185 #ifndef TARGET_OS_IPHONE
186 ATSFontContainerRef *p_fonts;
191 /*****************************************************************************
192 * Create: allocates osd-text video thread output method
193 *****************************************************************************
194 * This function allocates and initializes a Clone vout method.
195 *****************************************************************************/
196 static int Create(vlc_object_t *p_this)
198 filter_t *p_filter = (filter_t *)p_this;
201 // Allocate structure
202 p_filter->p_sys = p_sys = malloc(sizeof(filter_sys_t));
205 p_sys->psz_font_name = var_CreateGetString(p_this, "quartztext-font");
206 p_sys->i_font_opacity = 255;
207 p_sys->i_font_color = VLC_CLIP(var_CreateGetInteger(p_this, "quartztext-color") , 0, 0xFFFFFF);
208 p_sys->i_font_size = GetFontSize(p_filter);
210 p_filter->pf_render_text = RenderText;
211 p_filter->pf_render_html = RenderHtml;
213 #ifndef TARGET_OS_IPHONE
214 p_sys->p_fonts = NULL;
218 LoadFontsFromAttachments(p_filter);
223 /*****************************************************************************
224 * Destroy: destroy Clone video thread output method
225 *****************************************************************************
226 * Clean up all data and library connections
227 *****************************************************************************/
228 static void Destroy(vlc_object_t *p_this)
230 filter_t *p_filter = (filter_t *)p_this;
231 filter_sys_t *p_sys = p_filter->p_sys;
232 #ifndef TARGET_OS_IPHONE
233 if (p_sys->p_fonts) {
234 for (int k = 0; k < p_sys->i_fonts; k++) {
235 ATSFontDeactivate(p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault);
237 free(p_sys->p_fonts);
240 free(p_sys->psz_font_name);
244 /*****************************************************************************
245 * Make any TTF/OTF fonts present in the attachments of the media file
246 * available to the Quartz engine for text rendering
247 *****************************************************************************/
248 static int LoadFontsFromAttachments(filter_t *p_filter)
250 #ifdef TARGET_OS_IPHONE
251 VLC_UNUSED(p_filter);
254 filter_sys_t *p_sys = p_filter->p_sys;
255 input_attachment_t **pp_attachments;
256 int i_attachments_cnt;
258 if (filter_GetInputAttachments(p_filter, &pp_attachments, &i_attachments_cnt))
262 p_sys->p_fonts = malloc(i_attachments_cnt * sizeof(ATSFontContainerRef));
263 if (! p_sys->p_fonts)
266 for (int k = 0; k < i_attachments_cnt; k++) {
267 input_attachment_t *p_attach = pp_attachments[k];
269 if ((!strcmp(p_attach->psz_mime, "application/x-truetype-font") || // TTF
270 !strcmp(p_attach->psz_mime, "application/x-font-otf")) && // OTF
271 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,
281 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
283 vlc_input_attachment_Delete(p_attach);
285 free(pp_attachments);
290 static char *EliminateCRLF(char *psz_string)
294 for (char * p = psz_string; p && *p; p++) {
295 if ((*p == '\r') && (*(p+1) == '\n')) {
296 for (q = p + 1; *q; q++)
305 /* Renders a text subpicture region into another one.
306 * It is used as pf_add_string callback in the vout method by this module */
307 static int RenderText(filter_t *p_filter, subpicture_region_t *p_region_out,
308 subpicture_region_t *p_region_in,
309 const vlc_fourcc_t *p_chroma_list)
311 filter_sys_t *p_sys = p_filter->p_sys;
314 uint32_t i_font_color;
315 bool b_bold, b_uline, b_italic, b_halfwidth;
317 b_bold = b_uline = b_italic = b_halfwidth = FALSE;
318 VLC_UNUSED(p_chroma_list);
320 p_sys->i_font_size = GetFontSize(p_filter);
323 if (!p_region_in || !p_region_out)
326 psz_string = p_region_in->psz_text;
327 if (!psz_string || !*psz_string)
330 if (p_region_in->p_style) {
331 i_font_color = VLC_CLIP(p_region_in->p_style->i_font_color, 0, 0xFFFFFF);
332 i_font_size = VLC_CLIP(p_region_in->p_style->i_font_size, 0, 255);
333 if (p_region_in->p_style->i_style_flags) {
334 if (p_region_in->p_style->i_style_flags & STYLE_BOLD)
336 if (p_region_in->p_style->i_style_flags & STYLE_ITALIC)
338 if (p_region_in->p_style->i_style_flags & STYLE_UNDERLINE)
340 if (p_region_in->p_style->i_style_flags & STYLE_HALFWIDTH)
344 i_font_color = p_sys->i_font_color;
345 i_font_size = p_sys->i_font_size;
348 if (i_font_size <= 0) {
349 msg_Warn(p_filter, "invalid fontsize, using 12");
350 if (VLC_SUCCESS == var_Get(p_filter, "scale", &val))
351 i_font_size = 12 * val.i_int / 1000;
356 p_region_out->i_x = p_region_in->i_x;
357 p_region_out->i_y = p_region_in->i_y;
359 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
362 CFStringRef p_cfString;
365 EliminateCRLF(psz_string);
366 p_cfString = CFStringCreateWithCString(NULL, psz_string, kCFStringEncodingUTF8);
367 CFAttributedStringReplaceString(p_attrString, CFRangeMake(0, 0), p_cfString);
368 CFRelease(p_cfString);
369 len = CFAttributedStringGetLength(p_attrString);
371 setFontAttibutes(p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline, b_halfwidth,
372 CFRangeMake(0, len), p_attrString);
374 RenderYUVA(p_filter, p_region_out, p_attrString);
375 CFRelease(p_attrString);
382 static int PushFont(font_stack_t **p_font, const char *psz_name, int i_size,
390 p_new = malloc(sizeof(font_stack_t));
394 p_new->p_next = NULL;
397 p_new->psz_name = strdup(psz_name);
399 p_new->psz_name = NULL;
401 p_new->i_size = i_size;
402 p_new->i_color = i_color;
407 font_stack_t *p_last;
409 for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
412 p_last->p_next = p_new;
417 static int PopFont(font_stack_t **p_font)
419 font_stack_t *p_last, *p_next_to_last;
421 if (!p_font || !*p_font)
424 p_next_to_last = NULL;
425 for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
426 p_next_to_last = p_last;
429 p_next_to_last->p_next = NULL;
433 free(p_last->psz_name);
439 static int PeekFont(font_stack_t **p_font, char **psz_name, int *i_size,
442 font_stack_t *p_last;
444 if (!p_font || !*p_font)
449 p_last=p_last->p_next)
452 *psz_name = p_last->psz_name;
453 *i_size = p_last->i_size;
454 *i_color = p_last->i_color;
459 static int HandleFontAttributes(xml_reader_t *p_xml_reader,
460 font_stack_t **p_fonts)
463 char *psz_fontname = NULL;
464 uint32_t i_font_color = 0xffffff;
465 int i_font_alpha = 0;
466 int i_font_size = 24;
467 const char *attr, *value;
469 /* Default all attributes to the top font in the stack -- in case not
470 * all attributes are specified in the sub-font */
471 if (VLC_SUCCESS == PeekFont(p_fonts,
475 psz_fontname = strdup(psz_fontname);
476 i_font_size = i_font_size;
478 i_font_alpha = (i_font_color >> 24) & 0xff;
479 i_font_color &= 0x00ffffff;
481 while ((attr = xml_ReaderNextAttr(p_xml_reader, &value))) {
482 if (!strcasecmp("face", attr)) {
484 psz_fontname = strdup(value);
485 } else if (!strcasecmp("size", attr)) {
486 if ((*value == '+') || (*value == '-')) {
487 int i_value = atoi(value);
489 if ((i_value >= -5) && (i_value <= 5))
490 i_font_size += (i_value * i_font_size) / 10;
491 else if (i_value < -5)
492 i_font_size = - i_value;
493 else if (i_value > 5)
494 i_font_size = i_value;
497 i_font_size = atoi(value);
498 } else if (!strcasecmp("color", attr)) {
499 if (value[0] == '#') {
500 i_font_color = strtol(value + 1, NULL, 16);
501 i_font_color &= 0x00ffffff;
503 /* color detection fallback */
504 unsigned int count = sizeof(ppsz_color_names);
505 for (unsigned x = 0; x < count; x++) {
506 if (!strcmp(value, ppsz_color_names[x])) {
507 i_font_color = pi_color_values[x];
512 } else if (!strcasecmp("alpha", attr) && (value[0] == '#')) {
513 i_font_alpha = strtol(value + 1, NULL, 16);
514 i_font_alpha &= 0xff;
517 rv = PushFont(p_fonts,
520 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24));
527 static void setFontAttibutes(char *psz_fontname, int i_font_size, uint32_t i_font_color,
528 bool b_bold, bool b_italic, bool b_underline, bool b_halfwidth,
529 CFRange p_range, CFMutableAttributedStringRef p_attrString)
531 CFStringRef p_cfString;
534 int i_font_width = b_halfwidth ? i_font_size / 2 : i_font_size;
535 CGAffineTransform trans = CGAffineTransformMakeScale((float)i_font_width
538 // fallback on default
540 psz_fontname = (char *)DEFAULT_FONT;
542 p_cfString = CFStringCreateWithCString(kCFAllocatorDefault,
544 kCFStringEncodingUTF8);
545 p_font = CTFontCreateWithName(p_cfString,
548 CFRelease(p_cfString);
549 CFAttributedStringSetAttribute(p_attrString,
551 kCTFontAttributeName,
558 _uline = kCTUnderlineStyleSingle;
560 _uline = kCTUnderlineStyleNone;
562 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
563 CFAttributedStringSetAttribute(p_attrString,
565 kCTUnderlineStyleAttributeName,
567 CFRelease(underline);
576 CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
577 CFAttributedStringSetAttribute(p_attrString,
590 CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
591 CFAttributedStringSetAttribute(p_attrString,
597 // fetch invalid colors
598 if (i_font_color == 0xFFFFFFFF)
599 i_font_color = 0x00FFFFFF;
601 // Handle foreground color
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,
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,
628 setFontAttibutes(psz_fontname,
631 b_bold, b_italic, b_uline, FALSE,
637 static int ProcessNodes(filter_t *p_filter,
638 xml_reader_t *p_xml_reader,
639 text_style_t *p_font_style,
640 CFMutableAttributedStringRef p_attrString)
642 int rv = VLC_SUCCESS;
643 filter_sys_t *p_sys = p_filter->p_sys;
644 font_stack_t *p_fonts = NULL;
649 bool b_italic = false;
651 bool b_uline = false;
654 rv = PushFont(&p_fonts,
655 p_font_style->psz_fontname,
656 p_font_style->i_font_size,
657 (p_font_style->i_font_color & 0xffffff) |
658 ((p_font_style->i_font_alpha & 0xff) << 24));
660 if (p_font_style->i_style_flags & STYLE_BOLD)
662 if (p_font_style->i_style_flags & STYLE_ITALIC)
664 if (p_font_style->i_style_flags & STYLE_UNDERLINE)
667 rv = PushFont(&p_fonts,
668 p_sys->psz_font_name,
670 p_sys->i_font_color);
672 if (rv != VLC_SUCCESS)
675 while ((type = xml_ReaderNextNode(p_xml_reader, &node)) > 0) {
677 case XML_READER_ENDELEM:
678 if (!strcasecmp("font", node))
680 else if (!strcasecmp("b", node))
682 else if (!strcasecmp("i", node))
684 else if (!strcasecmp("u", node))
688 case XML_READER_STARTELEM:
689 if (!strcasecmp("font", node))
690 rv = HandleFontAttributes(p_xml_reader, &p_fonts);
691 else if (!strcasecmp("b", node))
693 else if (!strcasecmp("i", node))
695 else if (!strcasecmp("u", node))
697 else if (!strcasecmp("br", node)) {
698 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
699 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), CFSTR("\n"));
701 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
704 CFAttributedStringReplaceAttributedString(p_attrString,
705 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
707 CFRelease(p_attrnode);
710 case XML_READER_TEXT:
712 CFStringRef p_cfString;
715 // Turn any multiple-whitespaces into single spaces
716 char *dup = strdup(node);
719 char *s = strpbrk(dup, "\t\r\n ");
722 int i_whitespace = strspn(s, "\t\r\n ");
724 if (i_whitespace > 1)
727 strlen(s) - i_whitespace + 1);
730 s = strpbrk(s, "\t\r\n ");
734 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
735 p_cfString = CFStringCreateWithCString(NULL, dup, kCFStringEncodingUTF8);
736 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), p_cfString);
737 CFRelease(p_cfString);
738 len = CFAttributedStringGetLength(p_attrnode);
740 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
744 CFAttributedStringReplaceAttributedString(p_attrString,
745 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
747 CFRelease(p_attrnode);
755 while(VLC_SUCCESS == PopFont(&p_fonts));
760 static int RenderHtml(filter_t *p_filter, subpicture_region_t *p_region_out,
761 subpicture_region_t *p_region_in,
762 const vlc_fourcc_t *p_chroma_list)
764 int rv = VLC_SUCCESS;
765 stream_t *p_sub = NULL;
767 xml_reader_t *p_xml_reader = NULL;
768 VLC_UNUSED(p_chroma_list);
770 if (!p_region_in || !p_region_in->psz_html)
773 /* Reset the default fontsize in case screen metrics have changed */
774 p_filter->p_sys->i_font_size = GetFontSize(p_filter);
776 p_sub = stream_MemoryNew(VLC_OBJECT(p_filter),
777 (uint8_t *) p_region_in->psz_html,
778 strlen(p_region_in->psz_html),
781 p_xml = xml_Create(p_filter);
783 p_xml_reader = xml_ReaderCreate(p_xml, p_sub);
785 /* Look for Root Node */
787 if (xml_ReaderNextNode(p_xml_reader, &name)
788 == XML_READER_STARTELEM) {
789 if (!strcasecmp("karaoke", name)) {
790 /* We're going to have to render the text a number
791 * of times to show the progress marker on the text.
793 var_SetBool(p_filter, "text-rerender", true);
794 } else if (strcasecmp("text", name)) {
795 /* Only text and karaoke tags are supported */
796 msg_Dbg(p_filter, "Unsupported top-level tag "
797 "<%s> ignored.", name);
801 msg_Err(p_filter, "Malformed HTML subtitle");
805 if (rv != VLC_SUCCESS) {
806 xml_ReaderDelete(p_xml_reader);
814 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
815 rv = ProcessNodes(p_filter, p_xml_reader,
816 p_region_in->p_style, p_attrString);
818 i_len = CFAttributedStringGetLength(p_attrString);
820 p_region_out->i_x = p_region_in->i_x;
821 p_region_out->i_y = p_region_in->i_y;
823 if ((rv == VLC_SUCCESS) && (i_len > 0))
824 RenderYUVA(p_filter, p_region_out, p_attrString);
826 CFRelease(p_attrString);
828 xml_ReaderDelete(p_xml_reader);
832 stream_Delete(p_sub);
838 static CGContextRef CreateOffScreenContext(int i_width, int i_height,
839 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace)
841 offscreen_bitmap_t *p_bitmap;
842 CGContextRef p_context = NULL;
844 p_bitmap = (offscreen_bitmap_t *) malloc(sizeof(offscreen_bitmap_t));
846 p_bitmap->i_bitsPerChannel = 8;
847 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
848 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
849 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
851 p_bitmap->p_data = calloc(i_height, p_bitmap->i_bytesPerRow);
853 *pp_colorSpace = CGColorSpaceCreateDeviceRGB();
855 if (p_bitmap->p_data && *pp_colorSpace)
856 p_context = CGBitmapContextCreate(p_bitmap->p_data, i_width, i_height,
857 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
858 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
861 if (CGContextSetAllowsAntialiasing != NULL)
862 CGContextSetAllowsAntialiasing(p_context, true);
864 *pp_memory = p_bitmap;
870 static offscreen_bitmap_t *Compose( subpicture_region_t *p_region,
871 CFMutableAttributedStringRef p_attrString,
874 unsigned *pi_textblock_height)
876 offscreen_bitmap_t *p_offScreen = NULL;
877 CGColorSpaceRef p_colorSpace = NULL;
878 CGContextRef p_context = NULL;
880 p_context = CreateOffScreenContext(i_width, i_height, &p_offScreen, &p_colorSpace);
882 *pi_textblock_height = 0;
886 CGContextSetTextMatrix(p_context, CGAffineTransformIdentity);
888 if (p_region->i_align & SUBPICTURE_ALIGN_RIGHT)
890 else if ((p_region->i_align & SUBPICTURE_ALIGN_LEFT) == 0)
895 // Create the framesetter with the attributed string.
896 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
899 CGMutablePathRef p_path = CGPathCreateMutable();
900 CGRect p_bounds = CGRectMake((float)HORIZONTAL_MARGIN,
901 (float)VERTICAL_MARGIN,
902 (float)(i_width - HORIZONTAL_MARGIN*2),
903 (float)(i_height - VERTICAL_MARGIN *2));
904 CGPathAddRect(p_path, NULL, p_bounds);
906 // Create the frame and draw it into the graphics context
907 frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
909 CGPathRelease(p_path);
911 // Set up black outlining of the text --
912 CGContextSetRGBStrokeColor(p_context, 0, 0, 0, 0.5);
913 CGContextSetTextDrawingMode(p_context, kCGTextFillStroke);
919 lines = CTFrameGetLines(frame);
920 penPosition.y = i_height;
921 for (int i=0; i<CFArrayGetCount(lines); i++) {
922 CGFloat ascent, descent, leading;
924 CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
925 CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
927 // Set the outlining for this line to be dependant on the size of the line -
928 // make it about 5% of the ascent, with a minimum at 1.0
929 float f_thickness = ascent * 0.05;
930 CGContextSetLineWidth(p_context, ((f_thickness > 1.0) ? 1.0 : f_thickness));
932 double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2));
933 penPosition.x = HORIZONTAL_MARGIN + penOffset;
934 if (horiz_flush == 0.0)
935 penPosition.x = p_region->i_x;
936 penPosition.y -= ascent;
937 CGContextSetTextPosition(p_context, penPosition.x, penPosition.y);
938 CTLineDraw(line, p_context);
939 penPosition.y -= descent + leading;
942 *pi_textblock_height = i_height - penPosition.y;
946 CFRelease(framesetter);
948 CGContextFlush(p_context);
949 CGContextRelease(p_context);
951 if (p_colorSpace) CGColorSpaceRelease(p_colorSpace);
956 static int GetFontSize(filter_t *p_filter)
960 int i_ratio = var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" );
962 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
966 msg_Warn( p_filter, "invalid fontsize, using 12" );
972 static int RenderYUVA(filter_t *p_filter, subpicture_region_t *p_region,
973 CFMutableAttributedStringRef p_attrString)
975 offscreen_bitmap_t *p_offScreen = NULL;
976 unsigned i_textblock_height = 0;
978 unsigned i_width = p_filter->fmt_out.video.i_visible_width;
979 unsigned i_height = p_filter->fmt_out.video.i_visible_height;
982 msg_Err(p_filter, "Invalid argument to RenderYUVA");
986 p_offScreen = Compose( p_region, p_attrString,
987 i_width, i_height, &i_textblock_height);
990 msg_Err(p_filter, "No offscreen buffer");
994 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
998 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1000 // Create a new subpicture region
1001 memset(&fmt, 0, sizeof(video_format_t));
1002 fmt.i_chroma = VLC_CODEC_YUVA;
1003 fmt.i_width = fmt.i_visible_width = i_width;
1004 fmt.i_height = fmt.i_visible_height = __MIN(i_height, i_textblock_height + VERTICAL_MARGIN * 2);
1005 fmt.i_x_offset = fmt.i_y_offset = 0;
1009 p_region->p_picture = picture_NewFromFormat(&fmt);
1010 if (!p_region->p_picture) {
1011 free(p_offScreen->p_data);
1013 return VLC_EGENERIC;
1015 p_region->fmt = fmt;
1017 p_dst_y = p_region->p_picture->Y_PIXELS;
1018 p_dst_u = p_region->p_picture->U_PIXELS;
1019 p_dst_v = p_region->p_picture->V_PIXELS;
1020 p_dst_a = p_region->p_picture->A_PIXELS;
1021 i_pitch = p_region->p_picture->A_PITCH;
1023 i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1024 for (unsigned y = 0; y < fmt.i_height; y++) {
1025 for (unsigned x = 0; x < fmt.i_width; x++) {
1026 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1027 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1028 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1029 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1031 i_y = (uint8_t)__MIN(abs(2104 * i_red + 4130 * i_green +
1032 802 * i_blue + 4096 + 131072) >> 13, 235);
1033 i_u = (uint8_t)__MIN(abs(-1214 * i_red + -2384 * i_green +
1034 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1035 i_v = (uint8_t)__MIN(abs(3598 * i_red + -3013 * i_green +
1036 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1038 p_dst_y[ i_offset + x ] = i_y;
1039 p_dst_u[ i_offset + x ] = i_u;
1040 p_dst_v[ i_offset + x ] = i_v;
1041 p_dst_a[ i_offset + x ] = i_alpha;
1043 i_offset += i_pitch;
1046 free(p_offScreen->p_data);