9c6d2204eee0399536e1777ab75d535e70cf019c
[vlc.git] / modules / misc / win32text.c
1 /*****************************************************************************
2  * win32text.c : Text drawing routines using the TextOut win32 API
3  *****************************************************************************
4  * Copyright (C) 2002 - 2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Gildas Bazin <gbazin@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>                                      /* malloc(), free() */
28 #include <string.h>
29
30 #include <vlc/vlc.h>
31 #include <vlc/vout.h>
32 #include "vlc_osd.h"
33 #include "vlc_block.h"
34 #include "vlc_filter.h"
35
36 #include <math.h>
37
38 /*****************************************************************************
39  * Local prototypes
40  *****************************************************************************/
41 static int  Create ( vlc_object_t * );
42 static void Destroy( vlc_object_t * );
43
44 /* The RenderText call maps to pf_render_string, defined in vlc_filter.h */
45 static int RenderText( filter_t *, subpicture_region_t *,
46                        subpicture_region_t * );
47
48 static int Render( filter_t *, subpicture_region_t *, uint8_t *, int, int);
49 static int SetFont( filter_t *, int );
50
51 /*****************************************************************************
52  * Module descriptor
53  *****************************************************************************/
54 #define FONT_TEXT N_("Font")
55 #define FONT_LONGTEXT N_("Filename for the font you want to use")
56 #define FONTSIZE_TEXT N_("Font size in pixels")
57 /// \bug [String] extra space
58 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
59      "that will be rendered on the video. " \
60      "If set to something different than 0 this option will override the " \
61      "relative font size. " )
62 #define OPACITY_TEXT N_("Opacity")
63 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
64      "text that will be rendered on the video. 0 = transparent, " \
65      "255 = totally opaque. " )
66 #define COLOR_TEXT N_("Text default color")
67 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
68   "the video. This must be an hexadecimal (like HTML colors). The first two "\
69   "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
70   " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
71 #define FONTSIZER_TEXT N_("Relative font size")
72 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
73   "fonts that will be rendered on the video. If absolute font size is set, "\
74    "relative size will be overriden." )
75
76 static int   pi_sizes[] = { 20, 18, 16, 12, 6 };
77 static char *ppsz_sizes_text[] = { N_("Smaller"), N_("Small"), N_("Normal"),
78                                    N_("Large"), N_("Larger") };
79 static int pi_color_values[] = {
80   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
81   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
82   0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
83
84 static char *ppsz_color_descriptions[] = {
85   N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
86   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
87   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
88
89 vlc_module_begin();
90     set_shortname( _("Text renderer"));
91     set_description( _("Win32 font renderer") );
92     set_category( CAT_VIDEO );
93     set_subcategory( SUBCAT_VIDEO_SUBPIC );
94
95     add_integer( "win32text-fontsize", 0, NULL, FONTSIZE_TEXT,
96                  FONTSIZE_LONGTEXT, VLC_TRUE );
97
98     /* opacity valid on 0..255, with default 255 = fully opaque */
99     add_integer_with_range( "win32-opacity", 255, 0, 255, NULL,
100         OPACITY_TEXT, OPACITY_LONGTEXT, VLC_FALSE );
101
102     /* hook to the color values list, with default 0x00ffffff = white */
103     add_integer( "win32text-color", 0x00FFFFFF, NULL, COLOR_TEXT,
104                  COLOR_LONGTEXT, VLC_TRUE );
105         change_integer_list( pi_color_values, ppsz_color_descriptions, 0 );
106
107     add_integer( "win32text-rel-fontsize", 16, NULL, FONTSIZER_TEXT,
108                  FONTSIZER_LONGTEXT, VLC_FALSE );
109         change_integer_list( pi_sizes, ppsz_sizes_text, 0 );
110
111     set_capability( "text renderer", 50 );
112     add_shortcut( "text" );
113     set_callbacks( Create, Destroy );
114 vlc_module_end();
115
116 /*****************************************************************************
117  * filter_sys_t: win32text local data
118  *****************************************************************************/
119 struct filter_sys_t
120 {
121     uint8_t        i_font_opacity;
122     int            i_font_color;
123     int            i_font_size;
124
125     int            i_default_font_size;
126     int            i_display_height;
127
128     HDC hcdc;
129     HFONT hfont;
130     HFONT hfont_bak;
131     int i_logpy;
132 };
133
134 static uint8_t pi_gamma[16] =
135   {0x00, 0x41, 0x52, 0x63, 0x84, 0x85, 0x96, 0xa7, 0xb8, 0xc9,
136    0xca, 0xdb, 0xdc, 0xed, 0xee, 0xff};
137
138 /*****************************************************************************
139  * Create: creates the module
140  *****************************************************************************/
141 static int Create( vlc_object_t *p_this )
142 {
143     filter_t *p_filter = (filter_t *)p_this;
144     filter_sys_t *p_sys;
145     char *psz_fontfile = NULL;
146     vlc_value_t val;
147     HDC hdc;
148
149     /* Allocate structure */
150     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
151     if( !p_sys )
152     {
153         msg_Err( p_filter, "out of memory" );
154         return VLC_ENOMEM;
155     }
156     p_sys->i_font_size = 0;
157     p_sys->i_display_height = 0;
158
159     var_Create( p_filter, "win32text-font",
160                 VLC_VAR_STRING | VLC_VAR_DOINHERIT );
161     var_Create( p_filter, "win32text-fontsize",
162                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
163     var_Create( p_filter, "win32text-rel-fontsize",
164                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
165     var_Create( p_filter, "win32text-opacity",
166                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
167     var_Get( p_filter, "win32text-opacity", &val );
168     p_sys->i_font_opacity = __MAX( __MIN( val.i_int, 255 ), 0 );
169     var_Create( p_filter, "win32text-color",
170                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
171     var_Get( p_filter, "win32text-color", &val );
172     p_sys->i_font_color = __MAX( __MIN( val.i_int, 0xFFFFFF ), 0 );
173
174     p_sys->hfont = p_sys->hfont_bak = 0;
175     hdc = GetDC( NULL );
176     p_sys->hcdc = CreateCompatibleDC( hdc );
177     p_sys->i_logpy = GetDeviceCaps( hdc, LOGPIXELSY );
178     ReleaseDC( NULL, hdc );
179     SetBkMode( p_sys->hcdc, TRANSPARENT );
180
181     var_Get( p_filter, "win32text-fontsize", &val );
182     p_sys->i_default_font_size = val.i_int;
183     if( SetFont( p_filter, 0 ) != VLC_SUCCESS ) goto error;
184
185     if( psz_fontfile ) free( psz_fontfile );
186     p_filter->pf_render_text = RenderText;
187     return VLC_SUCCESS;
188
189  error:
190     if( psz_fontfile ) free( psz_fontfile );
191     free( p_sys );
192     return VLC_EGENERIC;
193 }
194
195 /*****************************************************************************
196  * Destroy: destroy the module
197  *****************************************************************************/
198 static void Destroy( vlc_object_t *p_this )
199 {
200     filter_t *p_filter = (filter_t *)p_this;
201     filter_sys_t *p_sys = p_filter->p_sys;
202
203     if( p_sys->hfont_bak ) SelectObject( p_sys->hcdc, p_sys->hfont_bak );
204     if( p_sys->hfont ) DeleteObject( p_sys->hfont );
205     DeleteDC( p_sys->hcdc );
206     free( p_sys );
207 }
208
209 /*****************************************************************************
210  * Render: place string in picture
211  *****************************************************************************
212  * This function merges the previously rendered win32text glyphs into a picture
213  *****************************************************************************/
214 static int Render( filter_t *p_filter, subpicture_region_t *p_region,
215                    uint8_t *p_bitmap, int i_width, int i_height )
216 {
217     uint8_t *p_dst;
218     video_format_t fmt;
219     int i, i_pitch;
220     subpicture_region_t *p_region_tmp;
221     vlc_bool_t b_outline = VLC_TRUE;
222
223     /* Create a new subpicture region */
224     memset( &fmt, 0, sizeof(video_format_t) );
225     fmt.i_chroma = VLC_FOURCC('Y','U','V','P');
226     fmt.i_width = fmt.i_visible_width = i_width + (b_outline ? 4 : 0);
227     fmt.i_height = fmt.i_visible_height = i_height + (b_outline ? 4 : 0);
228     fmt.i_x_offset = fmt.i_y_offset = 0;
229     p_region_tmp = spu_CreateRegion( p_filter, &fmt );
230     if( !p_region_tmp )
231     {
232         msg_Err( p_filter, "cannot allocate SPU region" );
233         return VLC_EGENERIC;
234     }
235
236     /* Build palette */
237     fmt.p_palette->i_entries = 16;
238     for( i = 0; i < fmt.p_palette->i_entries; i++ )
239     {
240         fmt.p_palette->palette[i][0] = pi_gamma[i];
241         fmt.p_palette->palette[i][1] = 128;
242         fmt.p_palette->palette[i][2] = 128;
243         fmt.p_palette->palette[i][3] = pi_gamma[i];
244     }
245
246     p_region->fmt = p_region_tmp->fmt;
247     p_region->picture = p_region_tmp->picture;
248     free( p_region_tmp );
249
250     p_dst = p_region->picture.Y_PIXELS;
251     i_pitch = p_region->picture.Y_PITCH;
252
253     if( b_outline )
254     {
255         memset( p_dst, 0, i_pitch * fmt.i_height );
256         p_dst += p_region->picture.Y_PITCH * 2 + 2;
257     }
258
259     for( i = 0; i < i_height; i++ )
260     {
261         memcpy( p_dst, p_bitmap, i_width );
262         p_bitmap += (i_width+3) & ~3;
263         p_dst += i_pitch;
264     }
265
266     /* Outlining (find something better than nearest neighbour filtering ?) */
267     if( b_outline )
268     {
269         uint8_t *p_top = p_dst; /* Use 1st line as a cache */
270         uint8_t left, current;
271         int x, y;
272
273         p_dst = p_region->picture.Y_PIXELS;
274
275         for( y = 1; y < (int)fmt.i_height - 1; y++ )
276         {
277             memcpy( p_top, p_dst, fmt.i_width );
278             p_dst += i_pitch;
279             left = 0;
280
281             for( x = 1; x < (int)fmt.i_width - 1; x++ )
282             {
283                 current = p_dst[x];
284                 p_dst[x] = ( 4 * (int)p_dst[x] + left + p_top[x] + p_dst[x+1] +
285                              p_dst[x + i_pitch]) / 8;
286                 left = current;
287             }
288         }
289         memset( p_top, 0, fmt.i_width );
290     }
291
292     return VLC_SUCCESS;
293 }
294
295 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
296                        subpicture_region_t *p_region_in )
297 {
298     filter_sys_t *p_sys = p_filter->p_sys;
299     int i_font_color, i_font_alpha, i_font_size;
300     uint8_t *p_bitmap;
301     TCHAR *psz_string;
302     int i, i_width, i_height;
303     HBITMAP bitmap, bitmap_bak;
304     BITMAPINFO *p_bmi;
305     RECT rect = {0};
306     SIZE size;
307
308     /* Sanity check */
309     if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
310 #ifdef UNICODE
311     psz_string = malloc( (strlen( p_region_in->psz_text )+1) * sizeof(TCHAR) );
312     if( mbstowcs( psz_string, p_region_in->psz_text,
313                   strlen( p_region_in->psz_text ) * sizeof(TCHAR) ) < 0 )
314     {
315         free( psz_string );
316         return VLC_EGENERIC;
317     }
318 #else
319     psz_string = strdup( p_region_in->psz_text );
320 #endif
321     if( !psz_string || !*psz_string ) return VLC_EGENERIC;
322
323     if( p_region_in->p_style )
324     {
325         i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
326         i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
327         i_font_size  = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 );
328     }
329     else
330     {
331         i_font_color = p_sys->i_font_color;
332         i_font_alpha = 255 - p_sys->i_font_opacity;
333         i_font_size = p_sys->i_default_font_size;
334     }
335
336     SetFont( p_filter, i_font_size );
337
338     SetTextColor( p_sys->hcdc, RGB( (i_font_color >> 16) & 0xff,
339                   (i_font_color >> 8) & 0xff, i_font_color & 0xff) );
340
341     GetTextExtentExPoint( p_sys->hcdc, psz_string, _tcslen(psz_string),
342                           0, 0, 0, &size );
343     i_width = rect.right = size.cx; i_height = rect.bottom = size.cy;
344
345     p_bmi = malloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*16);
346     memset( p_bmi, 0, sizeof(BITMAPINFOHEADER) );
347     p_bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
348     p_bmi->bmiHeader.biWidth = (i_width+3) & ~3;
349     p_bmi->bmiHeader.biHeight = - i_height;
350     p_bmi->bmiHeader.biPlanes = 1;
351     p_bmi->bmiHeader.biBitCount = 8;
352     p_bmi->bmiHeader.biCompression = BI_RGB;
353     p_bmi->bmiHeader.biClrUsed = 16;
354
355     for( i = 0; i < 16; i++ )
356     {
357         p_bmi->bmiColors[i].rgbBlue =
358             p_bmi->bmiColors[i].rgbGreen =
359                 p_bmi->bmiColors[i].rgbRed = pi_gamma[i];
360     }
361
362     bitmap = CreateDIBSection( p_sys->hcdc, p_bmi, DIB_RGB_COLORS,
363                                (void **)&p_bitmap, NULL, 0 );
364     if( !bitmap )
365     {
366         msg_Err( p_filter, "could not create bitmap" );
367         return VLC_EGENERIC;
368     }
369
370     bitmap_bak = SelectObject( p_sys->hcdc, bitmap );
371     FillRect( p_sys->hcdc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH) );
372
373     //TextOut( p_sys->hcdc, 0, 0, psz_string, strlen(psz_string) );
374     if( !DrawText( p_sys->hcdc, psz_string, -1, &rect, 0 ) )
375     {
376         msg_Err( p_filter, "could not draw text" );
377     }
378
379     p_region_out->i_x = p_region_in->i_x;
380     p_region_out->i_y = p_region_in->i_y;
381     Render( p_filter, p_region_out, p_bitmap, i_width, i_height );
382
383     SelectObject( p_sys->hcdc, bitmap_bak );
384     DeleteObject( bitmap );
385     return VLC_SUCCESS;
386 }
387
388 static int SetFont( filter_t *p_filter, int i_size )
389 {
390     filter_sys_t *p_sys = p_filter->p_sys;
391     LOGFONT logfont;
392
393     if( i_size && i_size == p_sys->i_font_size ) return VLC_SUCCESS;
394
395     if( !i_size )
396     {
397         vlc_value_t val;
398
399         if( !p_sys->i_default_font_size &&
400             p_sys->i_display_height == (int)p_filter->fmt_out.video.i_height )
401             return VLC_SUCCESS;
402
403         if( p_sys->i_default_font_size )
404         {
405             i_size = p_sys->i_default_font_size;
406         }
407         else
408         {
409             var_Get( p_filter, "win32text-rel-fontsize", &val );
410             i_size = (int)p_filter->fmt_out.video.i_height / val.i_int;
411             p_filter->p_sys->i_display_height =
412                 p_filter->fmt_out.video.i_height;
413         }
414         if( i_size <= 0 )
415         {
416             msg_Warn( p_filter, "invalid fontsize, using 12" );
417             i_size = 12;
418         }
419
420         msg_Dbg( p_filter, "using fontsize: %i", i_size );
421     }
422
423     p_sys->i_font_size = i_size;
424
425     if( p_sys->hfont_bak ) SelectObject( p_sys->hcdc, p_sys->hfont_bak );
426     if( p_sys->hfont ) DeleteObject( p_sys->hfont );
427
428     i_size = i_size * (int64_t)p_sys->i_logpy / 72;
429
430     logfont.lfHeight = i_size;
431     logfont.lfWidth = 0;
432     logfont.lfEscapement = 0;
433     logfont.lfOrientation = 0;
434     logfont.lfWeight = 0;
435     logfont.lfItalic = FALSE;
436     logfont.lfUnderline = FALSE;
437     logfont.lfStrikeOut = FALSE;
438     logfont.lfCharSet = ANSI_CHARSET;
439     logfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
440     logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
441     logfont.lfQuality = ANTIALIASED_QUALITY;
442     logfont.lfPitchAndFamily = DEFAULT_PITCH;
443     memcpy( logfont.lfFaceName, _T("Arial"), sizeof(_T("Arial")) );
444
445     p_sys->hfont = CreateFontIndirect( &logfont );
446
447     p_sys->hfont_bak = SelectObject( p_sys->hcdc, p_sys->hfont );
448
449     return VLC_SUCCESS;
450 }