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 "Arial Black"
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;
313 int i_font_alpha, i_font_size;
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_alpha = VLC_CLIP(p_region_in->p_style->i_font_alpha, 0, 255);
333 i_font_size = VLC_CLIP(p_region_in->p_style->i_font_size, 0, 255);
334 if (p_region_in->p_style->i_style_flags) {
335 if (p_region_in->p_style->i_style_flags & STYLE_BOLD)
337 if (p_region_in->p_style->i_style_flags & STYLE_ITALIC)
339 if (p_region_in->p_style->i_style_flags & STYLE_UNDERLINE)
343 i_font_color = p_sys->i_font_color;
344 i_font_alpha = 255 - p_sys->i_font_opacity;
345 i_font_size = p_sys->i_font_size;
349 i_font_alpha = 255 - p_sys->i_font_opacity;
351 if (i_font_size <= 0) {
352 msg_Warn(p_filter, "invalid fontsize, using 12");
353 if (VLC_SUCCESS == var_Get(p_filter, "scale", &val))
354 i_font_size = 12 * val.i_int / 1000;
359 p_region_out->i_x = p_region_in->i_x;
360 p_region_out->i_y = p_region_in->i_y;
362 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
365 CFStringRef p_cfString;
368 EliminateCRLF(psz_string);
369 p_cfString = CFStringCreateWithCString(NULL, psz_string, kCFStringEncodingUTF8);
370 CFAttributedStringReplaceString(p_attrString, CFRangeMake(0, 0), p_cfString);
371 CFRelease(p_cfString);
372 len = CFAttributedStringGetLength(p_attrString);
374 setFontAttibutes(p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline,
375 CFRangeMake(0, len), p_attrString);
377 RenderYUVA(p_filter, p_region_out, p_attrString);
378 CFRelease(p_attrString);
385 static int PushFont(font_stack_t **p_font, const char *psz_name, int i_size,
393 p_new = malloc(sizeof(font_stack_t));
397 p_new->p_next = NULL;
400 p_new->psz_name = strdup(psz_name);
402 p_new->psz_name = NULL;
404 p_new->i_size = i_size;
405 p_new->i_color = i_color;
410 font_stack_t *p_last;
412 for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
415 p_last->p_next = p_new;
420 static int PopFont(font_stack_t **p_font)
422 font_stack_t *p_last, *p_next_to_last;
424 if (!p_font || !*p_font)
427 p_next_to_last = NULL;
428 for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
429 p_next_to_last = p_last;
432 p_next_to_last->p_next = NULL;
436 free(p_last->psz_name);
442 static int PeekFont(font_stack_t **p_font, char **psz_name, int *i_size,
445 font_stack_t *p_last;
447 if (!p_font || !*p_font)
452 p_last=p_last->p_next)
455 *psz_name = p_last->psz_name;
456 *i_size = p_last->i_size;
457 *i_color = p_last->i_color;
462 static int HandleFontAttributes(xml_reader_t *p_xml_reader,
463 font_stack_t **p_fonts)
466 char *psz_fontname = NULL;
467 uint32_t i_font_color = 0xffffff;
468 int i_font_alpha = 0;
469 int i_font_size = 24;
470 const char *attr, *value;
472 /* Default all attributes to the top font in the stack -- in case not
473 * all attributes are specified in the sub-font */
474 if (VLC_SUCCESS == PeekFont(p_fonts,
478 psz_fontname = strdup(psz_fontname);
479 i_font_size = i_font_size;
481 i_font_alpha = (i_font_color >> 24) & 0xff;
482 i_font_color &= 0x00ffffff;
484 while ((attr = xml_ReaderNextAttr(p_xml_reader, &value))) {
485 if (!strcasecmp("face", attr)) {
487 psz_fontname = strdup(value);
488 } else if (!strcasecmp("size", attr)) {
489 if ((*value == '+') || (*value == '-')) {
490 int i_value = atoi(value);
492 if ((i_value >= -5) && (i_value <= 5))
493 i_font_size += (i_value * i_font_size) / 10;
494 else if (i_value < -5)
495 i_font_size = - i_value;
496 else if (i_value > 5)
497 i_font_size = i_value;
500 i_font_size = atoi(value);
501 } else if (!strcasecmp("color", attr)) {
502 if (value[0] == '#') {
503 i_font_color = strtol(value + 1, NULL, 16);
504 i_font_color &= 0x00ffffff;
506 /* color detection fallback */
507 unsigned int count = sizeof(ppsz_color_names);
508 for (unsigned x = 0; x < count; x++) {
509 if (!strcmp(value, ppsz_color_names[x])) {
510 i_font_color = pi_color_values[x];
515 } else if (!strcasecmp("alpha", attr) && (value[0] == '#')) {
516 i_font_alpha = strtol(value + 1, NULL, 16);
517 i_font_alpha &= 0xff;
520 rv = PushFont(p_fonts,
523 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24));
530 static void setFontAttibutes(char *psz_fontname, int i_font_size, uint32_t i_font_color,
531 bool b_bold, bool b_italic, bool b_underline,
532 CFRange p_range, CFMutableAttributedStringRef p_attrString)
534 CFStringRef p_cfString;
537 // Handle font name and size
538 p_cfString = CFStringCreateWithCString(NULL,
540 kCFStringEncodingUTF8);
541 p_font = CTFontCreateWithName(p_cfString,
544 CFRelease(p_cfString);
545 CFAttributedStringSetAttribute(p_attrString,
547 kCTFontAttributeName,
554 _uline = kCTUnderlineStyleSingle;
556 _uline = kCTUnderlineStyleNone;
558 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
559 CFAttributedStringSetAttribute(p_attrString,
561 kCTUnderlineStyleAttributeName,
563 CFRelease(underline);
572 CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
573 CFAttributedStringSetAttribute(p_attrString,
586 CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
587 CFAttributedStringSetAttribute(p_attrString,
593 // Handle foreground colour
594 CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
595 CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
596 (float)((i_font_color & 0x0000ff00) >> 8) / 255.0,
597 (float)((i_font_color & 0x000000ff)) / 255.0,
598 (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
599 CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
600 CGColorSpaceRelease(rgbColorSpace);
602 CFAttributedStringSetAttribute(p_attrString,
604 kCTForegroundColorAttributeName,
610 static void GetAttrStrFromFontStack(font_stack_t **p_fonts,
611 bool b_bold, bool b_italic, bool b_uline,
612 CFRange p_range, CFMutableAttributedStringRef p_attrString)
614 char *psz_fontname = NULL;
616 uint32_t i_font_color = 0;
618 if (VLC_SUCCESS == PeekFont(p_fonts, &psz_fontname, &i_font_size,
620 setFontAttibutes(psz_fontname,
623 b_bold, b_italic, b_uline,
629 static int ProcessNodes(filter_t *p_filter,
630 xml_reader_t *p_xml_reader,
631 text_style_t *p_font_style,
632 CFMutableAttributedStringRef p_attrString)
634 int rv = VLC_SUCCESS;
635 filter_sys_t *p_sys = p_filter->p_sys;
636 font_stack_t *p_fonts = NULL;
641 bool b_italic = false;
643 bool b_uline = false;
646 rv = PushFont(&p_fonts,
647 p_font_style->psz_fontname,
648 p_font_style->i_font_size,
649 (p_font_style->i_font_color & 0xffffff) |
650 ((p_font_style->i_font_alpha & 0xff) << 24));
652 if (p_font_style->i_style_flags & STYLE_BOLD)
654 if (p_font_style->i_style_flags & STYLE_ITALIC)
656 if (p_font_style->i_style_flags & STYLE_UNDERLINE)
659 rv = PushFont(&p_fonts,
660 p_sys->psz_font_name,
662 p_sys->i_font_color);
664 if (rv != VLC_SUCCESS)
667 while ((type = xml_ReaderNextNode(p_xml_reader, &node)) > 0) {
669 case XML_READER_ENDELEM:
670 if (!strcasecmp("font", node))
672 else if (!strcasecmp("b", node))
674 else if (!strcasecmp("i", node))
676 else if (!strcasecmp("u", node))
680 case XML_READER_STARTELEM:
681 if (!strcasecmp("font", node))
682 rv = HandleFontAttributes(p_xml_reader, &p_fonts);
683 else if (!strcasecmp("b", node))
685 else if (!strcasecmp("i", node))
687 else if (!strcasecmp("u", node))
689 else if (!strcasecmp("br", node)) {
690 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
691 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), CFSTR("\n"));
693 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
696 CFAttributedStringReplaceAttributedString(p_attrString,
697 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
699 CFRelease(p_attrnode);
702 case XML_READER_TEXT:
704 CFStringRef p_cfString;
707 // Turn any multiple-whitespaces into single spaces
708 char *dup = strdup(node);
711 char *s = strpbrk(dup, "\t\r\n ");
714 int i_whitespace = strspn(s, "\t\r\n ");
716 if (i_whitespace > 1)
719 strlen(s) - i_whitespace + 1);
722 s = strpbrk(s, "\t\r\n ");
726 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
727 p_cfString = CFStringCreateWithCString(NULL, dup, kCFStringEncodingUTF8);
728 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), p_cfString);
729 CFRelease(p_cfString);
730 len = CFAttributedStringGetLength(p_attrnode);
732 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
736 CFAttributedStringReplaceAttributedString(p_attrString,
737 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
739 CFRelease(p_attrnode);
747 while(VLC_SUCCESS == PopFont(&p_fonts));
752 static int RenderHtml(filter_t *p_filter, subpicture_region_t *p_region_out,
753 subpicture_region_t *p_region_in,
754 const vlc_fourcc_t *p_chroma_list)
756 int rv = VLC_SUCCESS;
757 stream_t *p_sub = NULL;
759 xml_reader_t *p_xml_reader = NULL;
760 VLC_UNUSED(p_chroma_list);
762 if (!p_region_in || !p_region_in->psz_html)
765 /* Reset the default fontsize in case screen metrics have changed */
766 p_filter->p_sys->i_font_size = GetFontSize(p_filter);
768 p_sub = stream_MemoryNew(VLC_OBJECT(p_filter),
769 (uint8_t *) p_region_in->psz_html,
770 strlen(p_region_in->psz_html),
773 p_xml = xml_Create(p_filter);
775 bool b_karaoke = false;
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 } else if (!strcasecmp("text", name))
792 /* Only text and karaoke tags are supported */
793 msg_Dbg(p_filter, "Unsupported top-level tag "
794 "<%s> ignored.", name);
798 msg_Err(p_filter, "Malformed HTML subtitle");
802 if (rv != VLC_SUCCESS) {
803 xml_ReaderDelete(p_xml_reader);
811 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
812 rv = ProcessNodes(p_filter, p_xml_reader,
813 p_region_in->p_style, p_attrString);
815 i_len = CFAttributedStringGetLength(p_attrString);
817 p_region_out->i_x = p_region_in->i_x;
818 p_region_out->i_y = p_region_in->i_y;
820 if ((rv == VLC_SUCCESS) && (i_len > 0))
821 RenderYUVA(p_filter, p_region_out, p_attrString);
823 CFRelease(p_attrString);
825 xml_ReaderDelete(p_xml_reader);
829 stream_Delete(p_sub);
835 static CGContextRef CreateOffScreenContext(int i_width, int i_height,
836 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace)
838 offscreen_bitmap_t *p_bitmap;
839 CGContextRef p_context = NULL;
841 p_bitmap = (offscreen_bitmap_t *) malloc(sizeof(offscreen_bitmap_t));
843 p_bitmap->i_bitsPerChannel = 8;
844 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
845 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
846 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
848 p_bitmap->p_data = calloc(i_height, p_bitmap->i_bytesPerRow);
850 *pp_colorSpace = CGColorSpaceCreateDeviceRGB();
852 if (p_bitmap->p_data && *pp_colorSpace)
853 p_context = CGBitmapContextCreate(p_bitmap->p_data, i_width, i_height,
854 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
855 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
858 if (CGContextSetAllowsAntialiasing != NULL)
859 CGContextSetAllowsAntialiasing(p_context, true);
861 *pp_memory = p_bitmap;
867 static offscreen_bitmap_t *Compose(int i_text_align,
868 CFMutableAttributedStringRef p_attrString,
871 unsigned *pi_textblock_height)
873 offscreen_bitmap_t *p_offScreen = NULL;
874 CGColorSpaceRef p_colorSpace = NULL;
875 CGContextRef p_context = NULL;
877 p_context = CreateOffScreenContext(i_width, i_height, &p_offScreen, &p_colorSpace);
879 *pi_textblock_height = 0;
883 CGContextSetTextMatrix(p_context, CGAffineTransformIdentity);
885 if (i_text_align == SUBPICTURE_ALIGN_RIGHT)
887 else if (i_text_align != SUBPICTURE_ALIGN_LEFT)
892 // Create the framesetter with the attributed string.
893 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
896 CGMutablePathRef p_path = CGPathCreateMutable();
897 CGRect p_bounds = CGRectMake((float)HORIZONTAL_MARGIN,
898 (float)VERTICAL_MARGIN,
899 (float)(i_width - HORIZONTAL_MARGIN*2),
900 (float)(i_height - VERTICAL_MARGIN *2));
901 CGPathAddRect(p_path, NULL, p_bounds);
903 // Create the frame and draw it into the graphics context
904 frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
906 CGPathRelease(p_path);
908 // Set up black outlining of the text --
909 CGContextSetRGBStrokeColor(p_context, 0, 0, 0, 0.5);
910 CGContextSetTextDrawingMode(p_context, kCGTextFillStroke);
916 lines = CTFrameGetLines(frame);
917 penPosition.y = i_height;
918 for (int i=0; i<CFArrayGetCount(lines); i++) {
919 CGFloat ascent, descent, leading;
921 CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
922 CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
924 // Set the outlining for this line to be dependant on the size of the line -
925 // make it about 5% of the ascent, with a minimum at 1.0
926 float f_thickness = ascent * 0.05;
927 CGContextSetLineWidth(p_context, ((f_thickness > 1.0) ? 1.0 : f_thickness));
929 double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2));
930 penPosition.x = HORIZONTAL_MARGIN + penOffset;
931 penPosition.y -= ascent;
932 CGContextSetTextPosition(p_context, penPosition.x, penPosition.y);
933 CTLineDraw(line, p_context);
934 penPosition.y -= descent + leading;
937 *pi_textblock_height = i_height - penPosition.y;
941 CFRelease(framesetter);
943 CGContextFlush(p_context);
944 CGContextRelease(p_context);
946 if (p_colorSpace) CGColorSpaceRelease(p_colorSpace);
951 static int GetFontSize(filter_t *p_filter)
953 return p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE;
956 static int RenderYUVA(filter_t *p_filter, subpicture_region_t *p_region,
957 CFMutableAttributedStringRef p_attrString)
959 offscreen_bitmap_t *p_offScreen = NULL;
960 unsigned i_textblock_height = 0;
962 unsigned i_width = p_filter->fmt_out.video.i_visible_width;
963 unsigned i_height = p_filter->fmt_out.video.i_visible_height;
964 unsigned i_text_align = p_region->i_align & 0x3;
967 msg_Err(p_filter, "Invalid argument to RenderYUVA");
971 p_offScreen = Compose(i_text_align, p_attrString,
972 i_width, i_height, &i_textblock_height);
975 msg_Err(p_filter, "No offscreen buffer");
979 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
983 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
985 // Create a new subpicture region
986 memset(&fmt, 0, sizeof(video_format_t));
987 fmt.i_chroma = VLC_CODEC_YUVA;
988 fmt.i_width = fmt.i_visible_width = i_width;
989 fmt.i_height = fmt.i_visible_height = __MIN(i_height, i_textblock_height + VERTICAL_MARGIN * 2);
990 fmt.i_x_offset = fmt.i_y_offset = 0;
994 p_region->p_picture = picture_NewFromFormat(&fmt);
995 if (!p_region->p_picture)
999 p_dst_y = p_region->p_picture->Y_PIXELS;
1000 p_dst_u = p_region->p_picture->U_PIXELS;
1001 p_dst_v = p_region->p_picture->V_PIXELS;
1002 p_dst_a = p_region->p_picture->A_PIXELS;
1003 i_pitch = p_region->p_picture->A_PITCH;
1005 i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1006 for (unsigned y = 0; y < fmt.i_height; y++) {
1007 for (unsigned x = 0; x < fmt.i_width; x++) {
1008 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1009 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1010 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1011 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1013 i_y = (uint8_t)__MIN(abs(2104 * i_red + 4130 * i_green +
1014 802 * i_blue + 4096 + 131072) >> 13, 235);
1015 i_u = (uint8_t)__MIN(abs(-1214 * i_red + -2384 * i_green +
1016 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1017 i_v = (uint8_t)__MIN(abs(3598 * i_red + -3013 * i_green +
1018 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1020 p_dst_y[ i_offset + x ] = i_y;
1021 p_dst_u[ i_offset + x ] = i_u;
1022 p_dst_v[ i_offset + x ] = i_v;
1023 p_dst_a[ i_offset + x ] = i_alpha;
1025 i_offset += i_pitch;
1028 free(p_offScreen->p_data);