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,
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;
317 b_bold = b_uline = b_italic = 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)
342 i_font_color = p_sys->i_font_color;
343 i_font_size = p_sys->i_font_size;
346 if (i_font_size <= 0) {
347 msg_Warn(p_filter, "invalid fontsize, using 12");
348 if (VLC_SUCCESS == var_Get(p_filter, "scale", &val))
349 i_font_size = 12 * val.i_int / 1000;
354 p_region_out->i_x = p_region_in->i_x;
355 p_region_out->i_y = p_region_in->i_y;
357 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
360 CFStringRef p_cfString;
363 EliminateCRLF(psz_string);
364 p_cfString = CFStringCreateWithCString(NULL, psz_string, kCFStringEncodingUTF8);
365 CFAttributedStringReplaceString(p_attrString, CFRangeMake(0, 0), p_cfString);
366 CFRelease(p_cfString);
367 len = CFAttributedStringGetLength(p_attrString);
369 setFontAttibutes(p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline,
370 CFRangeMake(0, len), p_attrString);
372 RenderYUVA(p_filter, p_region_out, p_attrString);
373 CFRelease(p_attrString);
380 static int PushFont(font_stack_t **p_font, const char *psz_name, int i_size,
388 p_new = malloc(sizeof(font_stack_t));
392 p_new->p_next = NULL;
395 p_new->psz_name = strdup(psz_name);
397 p_new->psz_name = NULL;
399 p_new->i_size = i_size;
400 p_new->i_color = i_color;
405 font_stack_t *p_last;
407 for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
410 p_last->p_next = p_new;
415 static int PopFont(font_stack_t **p_font)
417 font_stack_t *p_last, *p_next_to_last;
419 if (!p_font || !*p_font)
422 p_next_to_last = NULL;
423 for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
424 p_next_to_last = p_last;
427 p_next_to_last->p_next = NULL;
431 free(p_last->psz_name);
437 static int PeekFont(font_stack_t **p_font, char **psz_name, int *i_size,
440 font_stack_t *p_last;
442 if (!p_font || !*p_font)
447 p_last=p_last->p_next)
450 *psz_name = p_last->psz_name;
451 *i_size = p_last->i_size;
452 *i_color = p_last->i_color;
457 static int HandleFontAttributes(xml_reader_t *p_xml_reader,
458 font_stack_t **p_fonts)
461 char *psz_fontname = NULL;
462 uint32_t i_font_color = 0xffffff;
463 int i_font_alpha = 0;
464 int i_font_size = 24;
465 const char *attr, *value;
467 /* Default all attributes to the top font in the stack -- in case not
468 * all attributes are specified in the sub-font */
469 if (VLC_SUCCESS == PeekFont(p_fonts,
473 psz_fontname = strdup(psz_fontname);
474 i_font_size = i_font_size;
476 i_font_alpha = (i_font_color >> 24) & 0xff;
477 i_font_color &= 0x00ffffff;
479 while ((attr = xml_ReaderNextAttr(p_xml_reader, &value))) {
480 if (!strcasecmp("face", attr)) {
482 psz_fontname = strdup(value);
483 } else if (!strcasecmp("size", attr)) {
484 if ((*value == '+') || (*value == '-')) {
485 int i_value = atoi(value);
487 if ((i_value >= -5) && (i_value <= 5))
488 i_font_size += (i_value * i_font_size) / 10;
489 else if (i_value < -5)
490 i_font_size = - i_value;
491 else if (i_value > 5)
492 i_font_size = i_value;
495 i_font_size = atoi(value);
496 } else if (!strcasecmp("color", attr)) {
497 if (value[0] == '#') {
498 i_font_color = strtol(value + 1, NULL, 16);
499 i_font_color &= 0x00ffffff;
501 /* color detection fallback */
502 unsigned int count = sizeof(ppsz_color_names);
503 for (unsigned x = 0; x < count; x++) {
504 if (!strcmp(value, ppsz_color_names[x])) {
505 i_font_color = pi_color_values[x];
510 } else if (!strcasecmp("alpha", attr) && (value[0] == '#')) {
511 i_font_alpha = strtol(value + 1, NULL, 16);
512 i_font_alpha &= 0xff;
515 rv = PushFont(p_fonts,
518 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24));
525 static void setFontAttibutes(char *psz_fontname, int i_font_size, uint32_t i_font_color,
526 bool b_bold, bool b_italic, bool b_underline,
527 CFRange p_range, CFMutableAttributedStringRef p_attrString)
529 CFStringRef p_cfString;
532 // fallback on default
534 psz_fontname = (char *)DEFAULT_FONT;
536 p_cfString = CFStringCreateWithCString(kCFAllocatorDefault,
538 kCFStringEncodingUTF8);
539 p_font = CTFontCreateWithName(p_cfString,
542 CFRelease(p_cfString);
543 CFAttributedStringSetAttribute(p_attrString,
545 kCTFontAttributeName,
552 _uline = kCTUnderlineStyleSingle;
554 _uline = kCTUnderlineStyleNone;
556 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
557 CFAttributedStringSetAttribute(p_attrString,
559 kCTUnderlineStyleAttributeName,
561 CFRelease(underline);
570 CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
571 CFAttributedStringSetAttribute(p_attrString,
584 CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
585 CFAttributedStringSetAttribute(p_attrString,
591 // fetch invalid colors
592 if (i_font_color == 0xFFFFFFFF)
593 i_font_color = 0x00FFFFFF;
595 // Handle foreground color
596 CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
597 CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
598 (float)((i_font_color & 0x0000ff00) >> 8) / 255.0,
599 (float)((i_font_color & 0x000000ff)) / 255.0,
600 (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
601 CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
602 CGColorSpaceRelease(rgbColorSpace);
604 CFAttributedStringSetAttribute(p_attrString,
606 kCTForegroundColorAttributeName,
612 static void GetAttrStrFromFontStack(font_stack_t **p_fonts,
613 bool b_bold, bool b_italic, bool b_uline,
614 CFRange p_range, CFMutableAttributedStringRef p_attrString)
616 char *psz_fontname = NULL;
618 uint32_t i_font_color = 0;
620 if (VLC_SUCCESS == PeekFont(p_fonts, &psz_fontname, &i_font_size,
622 setFontAttibutes(psz_fontname,
625 b_bold, b_italic, b_uline,
631 static int ProcessNodes(filter_t *p_filter,
632 xml_reader_t *p_xml_reader,
633 text_style_t *p_font_style,
634 CFMutableAttributedStringRef p_attrString)
636 int rv = VLC_SUCCESS;
637 filter_sys_t *p_sys = p_filter->p_sys;
638 font_stack_t *p_fonts = NULL;
643 bool b_italic = false;
645 bool b_uline = false;
648 rv = PushFont(&p_fonts,
649 p_font_style->psz_fontname,
650 p_font_style->i_font_size,
651 (p_font_style->i_font_color & 0xffffff) |
652 ((p_font_style->i_font_alpha & 0xff) << 24));
654 if (p_font_style->i_style_flags & STYLE_BOLD)
656 if (p_font_style->i_style_flags & STYLE_ITALIC)
658 if (p_font_style->i_style_flags & STYLE_UNDERLINE)
661 rv = PushFont(&p_fonts,
662 p_sys->psz_font_name,
664 p_sys->i_font_color);
666 if (rv != VLC_SUCCESS)
669 while ((type = xml_ReaderNextNode(p_xml_reader, &node)) > 0) {
671 case XML_READER_ENDELEM:
672 if (!strcasecmp("font", node))
674 else if (!strcasecmp("b", node))
676 else if (!strcasecmp("i", node))
678 else if (!strcasecmp("u", node))
682 case XML_READER_STARTELEM:
683 if (!strcasecmp("font", node))
684 rv = HandleFontAttributes(p_xml_reader, &p_fonts);
685 else if (!strcasecmp("b", node))
687 else if (!strcasecmp("i", node))
689 else if (!strcasecmp("u", node))
691 else if (!strcasecmp("br", node)) {
692 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
693 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), CFSTR("\n"));
695 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
698 CFAttributedStringReplaceAttributedString(p_attrString,
699 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
701 CFRelease(p_attrnode);
704 case XML_READER_TEXT:
706 CFStringRef p_cfString;
709 // Turn any multiple-whitespaces into single spaces
710 char *dup = strdup(node);
713 char *s = strpbrk(dup, "\t\r\n ");
716 int i_whitespace = strspn(s, "\t\r\n ");
718 if (i_whitespace > 1)
721 strlen(s) - i_whitespace + 1);
724 s = strpbrk(s, "\t\r\n ");
728 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
729 p_cfString = CFStringCreateWithCString(NULL, dup, kCFStringEncodingUTF8);
730 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), p_cfString);
731 CFRelease(p_cfString);
732 len = CFAttributedStringGetLength(p_attrnode);
734 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
738 CFAttributedStringReplaceAttributedString(p_attrString,
739 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
741 CFRelease(p_attrnode);
749 while(VLC_SUCCESS == PopFont(&p_fonts));
754 static int RenderHtml(filter_t *p_filter, subpicture_region_t *p_region_out,
755 subpicture_region_t *p_region_in,
756 const vlc_fourcc_t *p_chroma_list)
758 int rv = VLC_SUCCESS;
759 stream_t *p_sub = NULL;
761 xml_reader_t *p_xml_reader = NULL;
762 VLC_UNUSED(p_chroma_list);
764 if (!p_region_in || !p_region_in->psz_html)
767 /* Reset the default fontsize in case screen metrics have changed */
768 p_filter->p_sys->i_font_size = GetFontSize(p_filter);
770 p_sub = stream_MemoryNew(VLC_OBJECT(p_filter),
771 (uint8_t *) p_region_in->psz_html,
772 strlen(p_region_in->psz_html),
775 p_xml = xml_Create(p_filter);
777 p_xml_reader = xml_ReaderCreate(p_xml, p_sub);
779 /* Look for Root Node */
781 if (xml_ReaderNextNode(p_xml_reader, &name)
782 == XML_READER_STARTELEM) {
783 if (!strcasecmp("karaoke", name)) {
784 /* We're going to have to render the text a number
785 * of times to show the progress marker on the text.
787 var_SetBool(p_filter, "text-rerender", true);
789 /* Only text and karaoke tags are supported */
790 msg_Dbg(p_filter, "Unsupported top-level tag "
791 "<%s> ignored.", name);
795 msg_Err(p_filter, "Malformed HTML subtitle");
799 if (rv != VLC_SUCCESS) {
800 xml_ReaderDelete(p_xml_reader);
808 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
809 rv = ProcessNodes(p_filter, p_xml_reader,
810 p_region_in->p_style, p_attrString);
812 i_len = CFAttributedStringGetLength(p_attrString);
814 p_region_out->i_x = p_region_in->i_x;
815 p_region_out->i_y = p_region_in->i_y;
817 if ((rv == VLC_SUCCESS) && (i_len > 0))
818 RenderYUVA(p_filter, p_region_out, p_attrString);
820 CFRelease(p_attrString);
822 xml_ReaderDelete(p_xml_reader);
826 stream_Delete(p_sub);
832 static CGContextRef CreateOffScreenContext(int i_width, int i_height,
833 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace)
835 offscreen_bitmap_t *p_bitmap;
836 CGContextRef p_context = NULL;
838 p_bitmap = (offscreen_bitmap_t *) malloc(sizeof(offscreen_bitmap_t));
840 p_bitmap->i_bitsPerChannel = 8;
841 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
842 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
843 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
845 p_bitmap->p_data = calloc(i_height, p_bitmap->i_bytesPerRow);
847 *pp_colorSpace = CGColorSpaceCreateDeviceRGB();
849 if (p_bitmap->p_data && *pp_colorSpace)
850 p_context = CGBitmapContextCreate(p_bitmap->p_data, i_width, i_height,
851 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
852 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
855 if (CGContextSetAllowsAntialiasing != NULL)
856 CGContextSetAllowsAntialiasing(p_context, true);
858 *pp_memory = p_bitmap;
864 static offscreen_bitmap_t *Compose(int i_text_align,
865 CFMutableAttributedStringRef p_attrString,
868 unsigned *pi_textblock_height)
870 offscreen_bitmap_t *p_offScreen = NULL;
871 CGColorSpaceRef p_colorSpace = NULL;
872 CGContextRef p_context = NULL;
874 p_context = CreateOffScreenContext(i_width, i_height, &p_offScreen, &p_colorSpace);
876 *pi_textblock_height = 0;
880 CGContextSetTextMatrix(p_context, CGAffineTransformIdentity);
882 if (i_text_align == SUBPICTURE_ALIGN_RIGHT)
884 else if (i_text_align != SUBPICTURE_ALIGN_LEFT)
889 // Create the framesetter with the attributed string.
890 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
893 CGMutablePathRef p_path = CGPathCreateMutable();
894 CGRect p_bounds = CGRectMake((float)HORIZONTAL_MARGIN,
895 (float)VERTICAL_MARGIN,
896 (float)(i_width - HORIZONTAL_MARGIN*2),
897 (float)(i_height - VERTICAL_MARGIN *2));
898 CGPathAddRect(p_path, NULL, p_bounds);
900 // Create the frame and draw it into the graphics context
901 frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
903 CGPathRelease(p_path);
905 // Set up black outlining of the text --
906 CGContextSetRGBStrokeColor(p_context, 0, 0, 0, 0.5);
907 CGContextSetTextDrawingMode(p_context, kCGTextFillStroke);
913 lines = CTFrameGetLines(frame);
914 penPosition.y = i_height;
915 for (int i=0; i<CFArrayGetCount(lines); i++) {
916 CGFloat ascent, descent, leading;
918 CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
919 CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
921 // Set the outlining for this line to be dependant on the size of the line -
922 // make it about 5% of the ascent, with a minimum at 1.0
923 float f_thickness = ascent * 0.05;
924 CGContextSetLineWidth(p_context, ((f_thickness > 1.0) ? 1.0 : f_thickness));
926 double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2));
927 penPosition.x = HORIZONTAL_MARGIN + penOffset;
928 penPosition.y -= ascent;
929 CGContextSetTextPosition(p_context, penPosition.x, penPosition.y);
930 CTLineDraw(line, p_context);
931 penPosition.y -= descent + leading;
934 *pi_textblock_height = i_height - penPosition.y;
938 CFRelease(framesetter);
940 CGContextFlush(p_context);
941 CGContextRelease(p_context);
943 if (p_colorSpace) CGColorSpaceRelease(p_colorSpace);
948 static int GetFontSize(filter_t *p_filter)
952 int i_ratio = var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" );
954 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
958 msg_Warn( p_filter, "invalid fontsize, using 12" );
964 static int RenderYUVA(filter_t *p_filter, subpicture_region_t *p_region,
965 CFMutableAttributedStringRef p_attrString)
967 offscreen_bitmap_t *p_offScreen = NULL;
968 unsigned i_textblock_height = 0;
970 unsigned i_width = p_filter->fmt_out.video.i_visible_width;
971 unsigned i_height = p_filter->fmt_out.video.i_visible_height;
972 unsigned i_text_align = p_region->i_align & 0x3;
975 msg_Err(p_filter, "Invalid argument to RenderYUVA");
979 p_offScreen = Compose(i_text_align, p_attrString,
980 i_width, i_height, &i_textblock_height);
983 msg_Err(p_filter, "No offscreen buffer");
987 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
991 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
993 // Create a new subpicture region
994 memset(&fmt, 0, sizeof(video_format_t));
995 fmt.i_chroma = VLC_CODEC_YUVA;
996 fmt.i_width = fmt.i_visible_width = i_width;
997 fmt.i_height = fmt.i_visible_height = __MIN(i_height, i_textblock_height + VERTICAL_MARGIN * 2);
998 fmt.i_x_offset = fmt.i_y_offset = 0;
1002 p_region->p_picture = picture_NewFromFormat(&fmt);
1003 if (!p_region->p_picture) {
1004 free(p_offScreen->p_data);
1006 return VLC_EGENERIC;
1008 p_region->fmt = fmt;
1010 p_dst_y = p_region->p_picture->Y_PIXELS;
1011 p_dst_u = p_region->p_picture->U_PIXELS;
1012 p_dst_v = p_region->p_picture->V_PIXELS;
1013 p_dst_a = p_region->p_picture->A_PIXELS;
1014 i_pitch = p_region->p_picture->A_PITCH;
1016 i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1017 for (unsigned y = 0; y < fmt.i_height; y++) {
1018 for (unsigned x = 0; x < fmt.i_width; x++) {
1019 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1020 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1021 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1022 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1024 i_y = (uint8_t)__MIN(abs(2104 * i_red + 4130 * i_green +
1025 802 * i_blue + 4096 + 131072) >> 13, 235);
1026 i_u = (uint8_t)__MIN(abs(-1214 * i_red + -2384 * i_green +
1027 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1028 i_v = (uint8_t)__MIN(abs(3598 * i_red + -3013 * i_green +
1029 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1031 p_dst_y[ i_offset + x ] = i_y;
1032 p_dst_u[ i_offset + x ] = i_u;
1033 p_dst_v[ i_offset + x ] = i_v;
1034 p_dst_a[ i_offset + x ] = i_alpha;
1036 i_offset += i_pitch;
1039 free(p_offScreen->p_data);