freetype.c 75.1 KB
Newer Older
1
2
3
/*****************************************************************************
 * freetype.c : Put text on the video, using freetype2
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2002 - 2012 VLC authors and VideoLAN
5
 * $Id$
6
 *
7
 * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8
 *          Gildas Bazin <gbazin@videolan.org>
9
 *          Bernie Purcell <bitmap@videolan.org>
10
 *          Jean-Baptiste Kempf <jb@videolan.org>
11
 *          Felix Paul Kühne <fkuehne@videolan.org>
12
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
13
14
15
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
16
17
18
19
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
20
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
22
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
23
 * You should have received a copy of the GNU Lesser General Public License
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
24
 * along with this program; if not, write to the Free Software Foundation, Inc.,
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
25
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26
27
28
29
30
31
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

32
33
34
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
35
#include <math.h>
36

37
#include <vlc_common.h>
38
#include <vlc_plugin.h>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
39
40
41
42
#include <vlc_stream.h>                        /* stream_MemoryNew */
#include <vlc_input.h>                         /* vlc_input_attachment_* */
#include <vlc_xml.h>                           /* xml_reader */
#include <vlc_dialog.h>                        /* FcCache dialog */
43
44
#include <vlc_filter.h>                                      /* filter_sys_t */
#include <vlc_text_style.h>                                   /* text_style_t*/
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
45
46

/* Freetype */
47
#include <ft2build.h>
48
49
#include FT_FREETYPE_H
#include FT_GLYPH_H
50
#include FT_STROKER_H
51
#include FT_SYNTHESIS_H
52

53
54
#define FT_FLOOR(X)     ((X & -64) >> 6)
#define FT_CEIL(X)      (((X + 63) & -64) >> 6)
55
56
57
#ifndef FT_MulFix
 #define FT_MulFix(v, s) (((v)*(s))>>16)
#endif
58

59
60
/* apple stuff */
#ifdef __APPLE__
61
62
#include <TargetConditionals.h>
#if !TARGET_OS_IPHONE
63
#include <Carbon/Carbon.h>
64
#endif
65
#include <sys/param.h>                         /* for MAXPATHLEN */
66
67
68
69
#undef HAVE_FONTCONFIG
#define HAVE_STYLES
#endif

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
70
/* RTL */
71
#if defined(HAVE_FRIBIDI)
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
72
# include <fribidi/fribidi.h>
73
74
#endif

75
/* Win32 GDI */
76
#ifdef _WIN32
77
78
79
80
# define HAVE_STYLES
# undef HAVE_FONTCONFIG
#endif

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
81
/* FontConfig */
82
#ifdef HAVE_FONTCONFIG
83
84
85
# define HAVE_STYLES
#endif

86
87
#include <assert.h>

88
#include "text_renderer.h"
89
#include "platform_fonts.h"
90

91
/*****************************************************************************
92
 * Module descriptor
93
 *****************************************************************************/
94
95
96
static int  Create ( vlc_object_t * );
static void Destroy( vlc_object_t * );

97
#define FONT_TEXT N_("Font")
98
#define MONOSPACE_FONT_TEXT N_("Monospace Font")
99

100
#define FAMILY_LONGTEXT N_("Font family for the font you want to use")
Christophe Mutricy's avatar
Typos    
Christophe Mutricy committed
101
#define FONT_LONGTEXT N_("Font file for the font you want to use")
102

103
#define FONTSIZE_TEXT N_("Font size in pixels")
104
105
#define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
    "that will be rendered on the video. " \
Sam Hocevar's avatar
Sam Hocevar committed
106
    "If set to something different than 0 this option will override the " \
107
    "relative font size." )
108
#define OPACITY_TEXT N_("Text opacity")
109
110
111
112
113
114
115
116
117
118
119
#define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
    "text that will be rendered on the video. 0 = transparent, " \
    "255 = totally opaque. " )
#define COLOR_TEXT N_("Text default color")
#define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
    "the video. This must be an hexadecimal (like HTML colors). The first two "\
    "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
    " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
#define FONTSIZER_TEXT N_("Relative font size")
#define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
    "fonts that will be rendered on the video. If absolute font size is set, "\
Christophe Mutricy's avatar
Typos    
Christophe Mutricy committed
120
    "relative size will be overridden." )
121
#define BOLD_TEXT N_("Force bold")
122

123
124
125
#define BG_OPACITY_TEXT N_("Background opacity")
#define BG_COLOR_TEXT N_("Background color")

126
127
128
129
#define OUTLINE_OPACITY_TEXT N_("Outline opacity")
#define OUTLINE_COLOR_TEXT N_("Outline color")
#define OUTLINE_THICKNESS_TEXT N_("Outline thickness")

130
131
132
133
134
135
#define SHADOW_OPACITY_TEXT N_("Shadow opacity")
#define SHADOW_COLOR_TEXT N_("Shadow color")
#define SHADOW_ANGLE_TEXT N_("Shadow angle")
#define SHADOW_DISTANCE_TEXT N_("Shadow distance")


136
137
138
static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
static const char *const ppsz_sizes_text[] = {
    N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
139
140
141
#define YUVP_TEXT N_("Use YUVP renderer")
#define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
  "This option is only needed if you want to encode into DVB subtitles" )
142

143
static const int pi_color_values[] = {
144
  0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
145
146
  0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
  0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
147

148
static const char *const ppsz_color_descriptions[] = {
149
150
151
  N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
  N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
  N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
152

153
154
155
156
157
158
159
static const int pi_outline_thickness[] = {
    0, 2, 4, 6,
};
static const char *const ppsz_outline_thickness[] = {
    N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
};

160
161
162
163
164
vlc_module_begin ()
    set_shortname( N_("Text renderer"))
    set_description( N_("Freetype2 font renderer") )
    set_category( CAT_VIDEO )
    set_subcategory( SUBCAT_VIDEO_SUBPIC )
gbazin's avatar
   
gbazin committed
165

166
167
#ifdef HAVE_STYLES
    add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
168
    add_font( "freetype-monofont", DEFAULT_MONOSPACE_FAMILY, MONOSPACE_FONT_TEXT, FAMILY_LONGTEXT, false )
169
#else
170
    add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
171
    add_loadfile( "freetype-monofont", DEFAULT_MONOSPACE_FONT_FILE, MONOSPACE_FONT_TEXT, FONT_LONGTEXT, false )
172
#endif
173

174
    add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
175
                 FONTSIZE_LONGTEXT, true )
VLC_help's avatar
VLC_help committed
176
        change_integer_range( 0, 4096)
177
        change_safe()
178

179
180
181
182
183
    add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
                 FONTSIZER_LONGTEXT, false )
        change_integer_list( pi_sizes, ppsz_sizes_text )
        change_safe()

184
    /* opacity valid on 0..255, with default 255 = fully opaque */
185
    add_integer_with_range( "freetype-opacity", 255, 0, 255,
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
186
        OPACITY_TEXT, OPACITY_LONGTEXT, false )
187
        change_safe()
188

189
    /* hook to the color values list, with default 0x00ffffff = white */
190
    add_rgb( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
191
                 COLOR_LONGTEXT, false )
192
        change_integer_list( pi_color_values, ppsz_color_descriptions )
193
        change_safe()
194

195
    add_bool( "freetype-bold", false, BOLD_TEXT, NULL, false )
196
197
        change_safe()

198
    add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
199
                            BG_OPACITY_TEXT, NULL, false )
200
        change_safe()
201
    add_rgb( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
202
             NULL, false )
203
204
205
        change_integer_list( pi_color_values, ppsz_color_descriptions )
        change_safe()

206
    add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
207
                            OUTLINE_OPACITY_TEXT, NULL, false )
208
        change_safe()
209
    add_rgb( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
210
             NULL, false )
211
212
213
        change_integer_list( pi_color_values, ppsz_color_descriptions )
        change_safe()
    add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
214
             NULL, false )
215
216
217
        change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
        change_safe()

218
    add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
219
                            SHADOW_OPACITY_TEXT, NULL, false )
220
        change_safe()
221
    add_rgb( "freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT,
222
             NULL, false )
223
224
225
        change_integer_list( pi_color_values, ppsz_color_descriptions )
        change_safe()
    add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
226
                          SHADOW_ANGLE_TEXT, NULL, false )
227
228
        change_safe()
    add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
229
                          SHADOW_DISTANCE_TEXT, NULL, false )
230
231
        change_safe()

232
    add_obsolete_integer( "freetype-effect" );
gbazin's avatar
   
gbazin committed
233

234
    add_bool( "freetype-yuvp", false, YUVP_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
235
              YUVP_LONGTEXT, true )
236
237
238
239
    set_capability( "text renderer", 100 )
    add_shortcut( "text" )
    set_callbacks( Create, Destroy )
vlc_module_end ()
240

241
242
243
244
245

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/

246
247
248
typedef struct
{
    FT_BitmapGlyph p_glyph;
249
    FT_BitmapGlyph p_outline;
250
    FT_BitmapGlyph p_shadow;
251
252
    uint32_t       i_color;             /* ARGB color */
    int            i_line_offset;       /* underline/strikethrough offset */
253
    int            i_line_thickness;    /* underline/strikethrough thickness */
254
255
} line_character_t;

256
typedef struct line_desc_t line_desc_t;
sigmunau's avatar
sigmunau committed
257
258
struct line_desc_t
{
259
    line_desc_t      *p_next;
260
261

    int              i_width;
262
    int              i_height;
263
    int              i_base_line;
264
265
    int              i_character_count;
    line_character_t *p_character;
266
267
268
};

/*****************************************************************************
269
 * filter_sys_t: freetype local data
270
271
 *****************************************************************************
 * This structure is part of the video output thread descriptor.
272
 * It describes the freetype specific properties of an output thread.
273
 *****************************************************************************/
274
struct filter_sys_t
275
276
277
{
    FT_Library     p_library;   /* handle to library     */
    FT_Face        p_face;      /* handle to face object */
278
    FT_Stroker     p_stroker;   /* handle to path stroker object */
279

280
    xml_reader_t  *p_xml;       /* vlc xml parser */
281

282
    text_style_t   style;       /* Current Style */
283

284
    /* More styles... */
285
286
    float          f_shadow_vector_x;
    float          f_shadow_vector_y;
287
    int            i_default_font_size;
288
289
290
291
292

    /* Attachments */
    input_attachment_t **pp_font_attachments;
    int                  i_font_attachments;

293
294
295
296
    char * (*pf_select) (filter_t *, const char* family,
                               bool bold, bool italic, int size,
                               int *index);

297
298
};

299
/* */
Laurent Aimar's avatar
Laurent Aimar committed
300
301
static void YUVFromRGB( uint32_t i_argb,
                    uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
302
{
Laurent Aimar's avatar
Laurent Aimar committed
303
304
305
    int i_red   = ( i_argb & 0x00ff0000 ) >> 16;
    int i_green = ( i_argb & 0x0000ff00 ) >>  8;
    int i_blue  = ( i_argb & 0x000000ff );
306

Laurent Aimar's avatar
Laurent Aimar committed
307
308
309
310
311
312
    *pi_y = (uint8_t)__MIN(abs( 2104 * i_red  + 4130 * i_green +
                      802 * i_blue + 4096 + 131072 ) >> 13, 235);
    *pi_u = (uint8_t)__MIN(abs( -1214 * i_red  + -2384 * i_green +
                     3598 * i_blue + 4096 + 1048576) >> 13, 240);
    *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
                      -585 * i_blue + 4096 + 1048576) >> 13, 240);
313
}
314
315
316
317
318
319
320
static void RGBFromRGB( uint32_t i_argb,
                        uint8_t *pi_r, uint8_t *pi_g, uint8_t *pi_b )
{
    *pi_r = ( i_argb & 0x00ff0000 ) >> 16;
    *pi_g = ( i_argb & 0x0000ff00 ) >>  8;
    *pi_b = ( i_argb & 0x000000ff );
}
321

322
323
324
325
326
327
328
329
330
331
/*****************************************************************************
 * Make any TTF/OTF fonts present in the attachments of the media file
 * and store them for later use by the FreeType Engine
 *****************************************************************************/
static int LoadFontsFromAttachments( filter_t *p_filter )
{
    filter_sys_t         *p_sys = p_filter->p_sys;
    input_attachment_t  **pp_attachments;
    int                   i_attachments_cnt;

332
    if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
333
        return VLC_EGENERIC;
bitmap's avatar
bitmap committed
334

335
    p_sys->i_font_attachments = 0;
336
    p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
337
338
    if( !p_sys->pp_font_attachments )
        return VLC_ENOMEM;
339

340
    for( int k = 0; k < i_attachments_cnt; k++ )
341
342
343
    {
        input_attachment_t *p_attach = pp_attachments[k];

344
345
346
        if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
              !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) &&    // OTF
            p_attach->i_data > 0 && p_attach->p_data )
347
        {
348
            p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
349
350
351
352
353
354
        }
        else
        {
            vlc_input_attachment_Delete( p_attach );
        }
    }
bitmap's avatar
bitmap committed
355
    free( pp_attachments );
356

357
    return VLC_SUCCESS;
358
359
}

Laurent Aimar's avatar
Laurent Aimar committed
360
static int GetFontSize( filter_t *p_filter )
361
{
Laurent Aimar's avatar
Laurent Aimar committed
362
363
    filter_sys_t *p_sys = p_filter->p_sys;
    int           i_size = 0;
364

Laurent Aimar's avatar
Laurent Aimar committed
365
366
    if( p_sys->i_default_font_size )
    {
367
        i_size = p_sys->i_default_font_size;
Laurent Aimar's avatar
Laurent Aimar committed
368
369
370
    }
    else
    {
371
        int i_ratio = var_InheritInteger( p_filter, "freetype-rel-fontsize" );
372
        if( i_ratio > 0 )
Laurent Aimar's avatar
Laurent Aimar committed
373
        {
374
            i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
Laurent Aimar's avatar
Laurent Aimar committed
375
376
377
378
379
        }
    }
    if( i_size <= 0 )
    {
        msg_Warn( p_filter, "invalid fontsize, using 12" );
380
        i_size = 12;
Laurent Aimar's avatar
Laurent Aimar committed
381
382
383
    }
    return i_size;
}
384

Laurent Aimar's avatar
Laurent Aimar committed
385
386
387
388
389
390
391
392
393
394
395
static int SetFontSize( filter_t *p_filter, int i_size )
{
    filter_sys_t *p_sys = p_filter->p_sys;

    if( !i_size )
    {
        i_size = GetFontSize( p_filter );

        msg_Dbg( p_filter, "using fontsize: %i", i_size );
    }

396
    p_sys->style.i_font_size = i_size;
Laurent Aimar's avatar
Laurent Aimar committed
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412

    if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
    {
        msg_Err( p_filter, "couldn't set font size to %d", i_size );
        return VLC_EGENERIC;
    }

    return VLC_SUCCESS;
}

/*****************************************************************************
 * RenderYUVP: place string in picture
 *****************************************************************************
 * This function merges the previously rendered freetype glyphs into a picture
 *****************************************************************************/
static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
413
414
                       line_desc_t *p_line,
                       FT_BBox *p_bbox )
Laurent Aimar's avatar
Laurent Aimar committed
415
416
417
418
419
420
421
422
423
424
425
426
{
    VLC_UNUSED(p_filter);
    static const uint8_t pi_gamma[16] =
        {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
          0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

    uint8_t *p_dst;
    video_format_t fmt;
    int i, x, y, i_pitch;
    uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */

    /* Create a new subpicture region */
427
428
429
430
431
    video_format_Init( &fmt, VLC_CODEC_YUVP );
    fmt.i_width          =
    fmt.i_visible_width  = p_bbox->xMax - p_bbox->xMin + 4;
    fmt.i_height         =
    fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
432

433
    assert( !p_region->p_picture );
434
    p_region->p_picture = picture_NewFromFormat( &fmt );
435
436
    if( !p_region->p_picture )
        return VLC_EGENERIC;
437
    fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
438
    p_region->fmt = fmt;
439

440
441
    /* Calculate text color components
     * Only use the first color */
442
    int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
443
    YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
444
445

    /* Build palette */
446
    fmt.p_palette->i_entries = 16;
447
448
449
450
451
452
453
    for( i = 0; i < 8; i++ )
    {
        fmt.p_palette->palette[i][0] = 0;
        fmt.p_palette->palette[i][1] = 0x80;
        fmt.p_palette->palette[i][2] = 0x80;
        fmt.p_palette->palette[i][3] = pi_gamma[i];
        fmt.p_palette->palette[i][3] =
454
            (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
455
456
    }
    for( i = 8; i < fmt.p_palette->i_entries; i++ )
457
    {
458
        fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
459
460
        fmt.p_palette->palette[i][1] = i_u;
        fmt.p_palette->palette[i][2] = i_v;
461
        fmt.p_palette->palette[i][3] = pi_gamma[i];
462
        fmt.p_palette->palette[i][3] =
463
            (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
464
465
    }

466
467
    p_dst = p_region->p_picture->Y_PIXELS;
    i_pitch = p_region->p_picture->Y_PITCH;
468

469
    /* Initialize the region pixels */
470
    memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
471

472
    for( ; p_line != NULL; p_line = p_line->p_next )
473
    {
474
        int i_align_left = 0;
475
        if( p_line->i_width < (int)fmt.i_visible_width )
476
        {
477
            if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
478
                i_align_left = ( fmt.i_visible_width - p_line->i_width );
479
            else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
480
                i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
481
        }
482
        int i_align_top = 0;
483

484
        for( i = 0; i < p_line->i_character_count; i++ )
sigmunau's avatar
sigmunau committed
485
        {
486
487
            const line_character_t *ch = &p_line->p_character[i];
            FT_BitmapGlyph p_glyph = ch->p_glyph;
488

489
490
            int i_glyph_y = i_align_top  - p_glyph->top  + p_bbox->yMax + p_line->i_base_line;
            int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
491

492
            for( y = 0; y < p_glyph->bitmap.rows; y++ )
sigmunau's avatar
sigmunau committed
493
            {
494
                for( x = 0; x < p_glyph->bitmap.width; x++ )
sigmunau's avatar
sigmunau committed
495
                {
496
497
498
                    if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
                        p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
                            (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
sigmunau's avatar
sigmunau committed
499
500
                }
            }
501
502
        }
    }
503

504
505
506
    /* Outlining (find something better than nearest neighbour filtering ?) */
    if( 1 )
    {
507
        uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
508
509
        uint8_t *p_top = p_dst; /* Use 1st line as a cache */
        uint8_t left, current;
510

511
512
        for( y = 1; y < (int)fmt.i_height - 1; y++ )
        {
513
            if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
514
            p_dst += p_region->p_picture->Y_PITCH;
515
516
517
            left = 0;

            for( x = 1; x < (int)fmt.i_width - 1; x++ )
sigmunau's avatar
sigmunau committed
518
            {
519
                current = p_dst[x];
520
                p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
521
                             p_dst[x -1 + p_region->p_picture->Y_PITCH ] + p_dst[x + p_region->p_picture->Y_PITCH] + p_dst[x + 1 + p_region->p_picture->Y_PITCH]) / 16;
522
                left = current;
sigmunau's avatar
sigmunau committed
523
524
            }
        }
525
        memset( p_top, 0, fmt.i_width );
sigmunau's avatar
sigmunau committed
526
    }
527
528

    return VLC_SUCCESS;
sigmunau's avatar
sigmunau committed
529
}
530

531
532
533
534
535
/*****************************************************************************
 * RenderYUVA: place string in picture
 *****************************************************************************
 * This function merges the previously rendered freetype glyphs into a picture
 *****************************************************************************/
536
537
538
539
540
541
542
543
544
545
546
547
548
static void FillYUVAPicture( picture_t *p_picture,
                             int i_a, int i_y, int i_u, int i_v )
{
    memset( p_picture->p[0].p_pixels, i_y,
            p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
    memset( p_picture->p[1].p_pixels, i_u,
            p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
    memset( p_picture->p[2].p_pixels, i_v,
            p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
    memset( p_picture->p[3].p_pixels, i_a,
            p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
}

549
550
551
552
static inline void BlendYUVAPixel( picture_t *p_picture,
                                   int i_picture_x, int i_picture_y,
                                   int i_a, int i_y, int i_u, int i_v,
                                   int i_alpha )
553
{
554
    int i_an = i_a * i_alpha / 255;
555

556
557
558
559
    uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
    uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
    uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
    uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
560

561
562
    int i_ao = *p_a;
    if( i_ao == 0 )
563
    {
564
565
566
567
568
569
570
571
572
        *p_y = i_y;
        *p_u = i_u;
        *p_v = i_v;
        *p_a = i_an;
    }
    else
    {
        *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
        if( *p_a != 0 )
573
        {
574
575
576
            *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
            *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
            *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
577
        }
578
579
    }
}
580

581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
static void FillRGBAPicture( picture_t *p_picture,
                             int i_a, int i_r, int i_g, int i_b )
{
    for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
    {
        for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
        {
            uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
            p_rgba[0] = i_r;
            p_rgba[1] = i_g;
            p_rgba[2] = i_b;
            p_rgba[3] = i_a;
        }
    }
}

static inline void BlendRGBAPixel( picture_t *p_picture,
598
                                   int i_picture_x, int i_picture_y,
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
                                   int i_a, int i_r, int i_g, int i_b,
                                   int i_alpha )
{
    int i_an = i_a * i_alpha / 255;

    uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];

    int i_ao = p_rgba[3];
    if( i_ao == 0 )
    {
        p_rgba[0] = i_r;
        p_rgba[1] = i_g;
        p_rgba[2] = i_b;
        p_rgba[3] = i_an;
    }
    else
    {
        p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
        if( p_rgba[3] != 0 )
        {
            p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
            p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
            p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
        }
    }
}

626
627
static void FillARGBPicture(picture_t *pic, int a, int r, int g, int b)
{
628
629
630
631
632
633
634
635
636
637
638
639
    if (a == 0)
        r = g = b = 0;
    if (a == r && a == b && a == g)
    {   /* fast path */
        memset(pic->p->p_pixels, a, pic->p->i_visible_lines * pic->p->i_pitch);
        return;
    }

    uint_fast32_t pixel = VLC_FOURCC(a, r, g, b);
    uint8_t *line = pic->p->p_pixels;

    for (unsigned lines = pic->p->i_visible_lines; lines > 0; lines--)
640
    {
641
642
643
644
        uint32_t *pixels = (uint32_t *)line;
        for (unsigned cols = pic->p->i_visible_pitch; cols > 0; cols -= 4)
            *(pixels++) = pixel;
        line += pic->p->i_pitch;
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
    }
}

static inline void BlendARGBPixel(picture_t *pic, int pic_x, int pic_y,
                                  int a, int r, int g, int b, int alpha)
{
    uint8_t *rgba = &pic->p->p_pixels[pic_y * pic->p->i_pitch + 4 * pic_x];
    int an = a * alpha / 255;
    int ao = rgba[3];

    if (ao == 0)
    {
        rgba[0] = an;
        rgba[1] = r;
        rgba[2] = g;
        rgba[3] = b;
    }
    else
    {
        rgba[0] = 255 - (255 - rgba[0]) * (255 - an) / 255;
        if (rgba[0] != 0)
        {
            rgba[1] = (rgba[1] * ao * (255 - an) / 255 + r * an ) / rgba[0];
            rgba[2] = (rgba[2] * ao * (255 - an) / 255 + g * an ) / rgba[0];
            rgba[3] = (rgba[3] * ao * (255 - an) / 255 + b * an ) / rgba[0];
        }
    }
}

674
675
676
677
678
679
static inline void BlendAXYZGlyph( picture_t *p_picture,
                                   int i_picture_x, int i_picture_y,
                                   int i_a, int i_x, int i_y, int i_z,
                                   FT_BitmapGlyph p_glyph,
                                   void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )

680
681
682
683
{
    for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
    {
        for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
684
685
686
            BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
                        i_a, i_x, i_y, i_z,
                        p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
687
688
689
    }
}

690
static inline void BlendAXYZLine( picture_t *p_picture,
691
                                  int i_picture_x, int i_picture_y,
692
                                  int i_a, int i_x, int i_y, int i_z,
693
                                  const line_character_t *p_current,
694
695
                                  const line_character_t *p_next,
                                  void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
696
{
697
    int i_line_width = p_current->p_glyph->bitmap.width;
698
699
    if( p_next )
        i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
700

701
    for( int dx = 0; dx < i_line_width; dx++ )
702
    {
703
        for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
704
705
706
707
            BlendPixel( p_picture,
                        i_picture_x + dx,
                        i_picture_y + p_current->i_line_offset + dy,
                        i_a, i_x, i_y, i_z, 0xff );
708
709
710
    }
}

711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
static inline void RenderBackground( subpicture_region_t *p_region,
                                     line_desc_t *p_line_head,
                                     FT_BBox *p_bbox,
                                     int i_margin,
                                     picture_t *p_picture,
                                     int i_text_width,
                                     void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
                                     void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
{
    for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
    {
        int i_align_left = i_margin;
        int i_align_top = i_margin;
        int line_start = 0;
        int line_end = 0;
        unsigned line_top = 0;
        int line_bottom = 0;
        int max_height = 0;

        if( p_line->i_width < i_text_width )
        {
            /* Left offset to take into account alignment */
            if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
                i_align_left += ( i_text_width - p_line->i_width );
            else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
                i_align_left = i_margin; /* Keep it the way it is */
            else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
                i_align_left += ( i_text_width - p_line->i_width ) / 2;
        }

        /* Find the tallest character in the line */
        for( int i = 0; i < p_line->i_character_count; i++ ) {
            const line_character_t *ch = &p_line->p_character[i];
            FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
            if (p_glyph->top > max_height)
                max_height = p_glyph->top;
        }

        /* Compute the background for the line (identify leading/trailing space) */
        for( int i = 0; i < p_line->i_character_count; i++ ) {
            const line_character_t *ch = &p_line->p_character[i];
            FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
            if (p_glyph && p_glyph->bitmap.rows > 0) {
                // Found a non-whitespace character
                line_start = i_align_left + p_glyph->left - p_bbox->xMin;
                break;
            }
        }

        /* Fudge factor to make sure caption background edges are left aligned
           despite variable font width */
        if (line_start < 12)
            line_start = 0;

        /* Find right boundary for bounding box for background */
        for( int i = p_line->i_character_count; i > 0; i-- ) {
            const line_character_t *ch = &p_line->p_character[i - 1];
            FT_BitmapGlyph p_glyph = ch->p_shadow ? ch->p_shadow : ch->p_glyph;
            if (p_glyph && p_glyph->bitmap.rows > 0) {
                // Found a non-whitespace character
                line_end = i_align_left + p_glyph->left - p_bbox->xMin + p_glyph->bitmap.width;
                break;
            }
        }

        /* Setup color for the background */
        uint8_t i_x, i_y, i_z;
        ExtractComponents( 0x000000, &i_x, &i_y, &i_z );

        /* Compute the upper boundary for the background */
        if ((i_align_top + p_line->i_base_line - max_height) < 0)
            line_top = i_align_top + p_line->i_base_line;
        else
            line_top = i_align_top + p_line->i_base_line - max_height;

        /* Compute lower boundary for the background */
        line_bottom =  __MIN(line_top + p_line->i_height, p_region->fmt.i_visible_height);

        /* Render the actual background */
        for( int dy = line_top; dy < line_bottom; dy++ )
        {
            for( int dx = line_start; dx < line_end; dx++ )
                BlendPixel( p_picture, dx, dy, 0xff, i_x, i_y, i_z, 0xff );
        }
    }
}

798
799
800
801
802
803
804
805
806
static inline int RenderAXYZ( filter_t *p_filter,
                              subpicture_region_t *p_region,
                              line_desc_t *p_line_head,
                              FT_BBox *p_bbox,
                              int i_margin,
                              vlc_fourcc_t i_chroma,
                              void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
                              void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
                              void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
807
{
808
    filter_sys_t *p_sys = p_filter->p_sys;
809
810

    /* Create a new subpicture region */
811
812
    const int i_text_width  = p_bbox->xMax - p_bbox->xMin;
    const int i_text_height = p_bbox->yMax - p_bbox->yMin;
813
    video_format_t fmt;
814
    video_format_Init( &fmt, i_chroma );
815
    fmt.i_width          =
816
    fmt.i_visible_width  = i_text_width  + 2 * i_margin;
817
    fmt.i_height         =
818
    fmt.i_visible_height = i_text_height + 2 * i_margin;
819

820
    picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
821
822
823
    if( !p_region->p_picture )
        return VLC_EGENERIC;
    p_region->fmt = fmt;
824

825
    /* Initialize the picture background */
826
827
    uint8_t i_a = var_InheritInteger( p_filter, "freetype-background-opacity" );
    i_a = VLC_CLIP( i_a, 0, 255 );
828
    uint8_t i_x, i_y, i_z;
829

830
    if (p_region->b_renderbg) {
831
        /* Render the background just under the text */
832
        FillPicture( p_picture, 0x00, 0x00, 0x00, 0x00 );
833
834
        RenderBackground(p_region, p_line_head, p_bbox, i_margin, p_picture, i_text_width,
                         ExtractComponents, BlendPixel);
835
836
    } else {
        /* Render background under entire subpicture block */
837
838
839
        int i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
        i_background_color = VLC_CLIP( i_background_color, 0, 0xFFFFFF );
        ExtractComponents( i_background_color, &i_x, &i_y, &i_z );
840
        FillPicture( p_picture, i_a, i_x, i_y, i_z );
841
842
    }

843
844
    /* Render shadow then outline and then normal glyphs */
    for( int g = 0; g < 3; g++ )
845
    {
846
847
        /* Render all lines */
        for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
848
        {
849
850
851
852
853
854
            int i_align_left = i_margin;
            if( p_line->i_width < i_text_width )
            {
                /* Left offset to take into account alignment */
                if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
                    i_align_left += ( i_text_width - p_line->i_width );
855
                else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
856
                    i_align_left = i_margin; /* Keep it the way it is */
857
858
859
860
                else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
                    i_align_left += ( i_text_width - p_line->i_width ) / 2;
            }
            int i_align_top = i_margin;
861

862
863
864
865
            /* Render all glyphs and underline/strikethrough */
            for( int i = 0; i < p_line->i_character_count; i++ )
            {
                const line_character_t *ch = &p_line->p_character[i];
866
                FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
867
868
869
                if( !p_glyph )
                    continue;

870
                i_a = (ch->i_color >> 24) & 0xff;
871
872
873
                uint32_t i_color;
                switch (g) {
                case 0:
874
875
                    i_a     = i_a * p_sys->style.i_shadow_alpha / 255;
                    i_color = p_sys->style.i_shadow_color;
876
877
                    break;
                case 1:
878
879
                    i_a     = i_a * p_sys->style.i_outline_alpha / 255;
                    i_color = p_sys->style.i_outline_color;
880
881
882
883
                    break;
                default:
                    i_color = ch->i_color;
                    break;
884
                }
885
                ExtractComponents( i_color, &i_x, &i_y, &i_z );
886
887
888
889

                int i_glyph_y = i_align_top  - p_glyph->top  + p_bbox->yMax + p_line->i_base_line;
                int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;

890
                BlendAXYZGlyph( p_picture,
891
                                i_glyph_x, i_glyph_y,
892
                                i_a, i_x, i_y, i_z,
893
894
                                p_glyph,
                                BlendPixel );
895
896

                /* underline/strikethrough are only rendered for the normal glyph */
897
                if( g == 2 && ch->i_line_thickness > 0 )
898
                    BlendAXYZLine( p_picture,
899
                                   i_glyph_x, i_glyph_y + p_glyph->top,
900
                                   i_a, i_x, i_y, i_z,
901
                                   &ch[0],
902
903
                                   i + 1 < p_line->i_character_count ? &ch[1] : NULL,
                                   BlendPixel );
904
            }
905
906
        }
    }
907

908
909
910
    return VLC_SUCCESS;
}

911

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
912

913
914
static void FreeLine( line_desc_t *p_line )
{
915
    for( int i = 0; i < p_line->i_character_count; i++ )
916
917
918
919
920
    {
        line_character_t *ch = &p_line->p_character[i];
        FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
        if( ch->p_outline )
            FT_Done_Glyph( (FT_Glyph)ch->p_outline );
921
922
        if( ch->p_shadow )
            FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
923
    }
924
925

    free( p_line->p_character );
926
927
928
929
930
    free( p_line );
}

static void FreeLines( line_desc_t *p_lines )
{
931
    for( line_desc_t *p_line = p_lines; p_line != NULL; )
932
    {
933
        line_desc_t *p_next = p_line->p_next;
934
        FreeLine( p_line );
935
        p_line = p_next;
936
937
938
939
940
    }
}

static line_desc_t *NewLine( int i_count )
{
941
942
943
944
    line_desc_t *p_line = malloc( sizeof(*p_line) );

    if( !p_line )
        return NULL;
945
946

    p_line->p_next = NULL;
947
    p_line->i_width = 0;
948
    p_line->i_base_line = 0;