win32text: fix obvious null pointer dereference
[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
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_vout.h>
35 #include <vlc_osd.h>
36 #include <vlc_block.h>
37 #include <vlc_filter.h>
38
39 #include <math.h>
40
41 /*****************************************************************************
42  * Local prototypes
43  *****************************************************************************/
44 static int  Create ( vlc_object_t * );
45 static void Destroy( vlc_object_t * );
46
47 /* The RenderText call maps to pf_render_string, defined in vlc_filter.h */
48 static int RenderText( filter_t *, subpicture_region_t *,
49                        subpicture_region_t * );
50
51 static int Render( filter_t *, subpicture_region_t *, uint8_t *, int, int);
52 static int SetFont( filter_t *, int );
53
54 /*****************************************************************************
55  * Module descriptor
56  *****************************************************************************/
57 #define FONT_TEXT N_("Font")
58 #define FONT_LONGTEXT N_("Filename for the font you want to use")
59 #define FONTSIZE_TEXT N_("Font size in pixels")
60 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
61      "that will be rendered on the video. " \
62      "If set to something different than 0 this option will override the " \
63      "relative font size." )
64 #define OPACITY_TEXT N_("Opacity")
65 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
66      "text that will be rendered on the video. 0 = transparent, " \
67      "255 = totally opaque. " )
68 #define COLOR_TEXT N_("Text default color")
69 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
70   "the video. This must be an hexadecimal (like HTML colors). The first two "\
71   "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
72   " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
73 #define FONTSIZER_TEXT N_("Relative font size")
74 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
75   "fonts that will be rendered on the video. If absolute font size is set, "\
76    "relative size will be overriden." )
77
78 static int const pi_sizes[] = { 20, 18, 16, 12, 6 };
79 static char *const ppsz_sizes_text[] = {
80     N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
81 static const int pi_color_values[] = {
82   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
83   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
84   0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
85
86 static const char *const ppsz_color_descriptions[] = {
87   N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
88   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
89   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
90
91 vlc_module_begin ()
92     set_shortname( N_("Text renderer"))
93     set_description( N_("Win32 font renderer") )
94     set_category( CAT_VIDEO )
95     set_subcategory( SUBCAT_VIDEO_SUBPIC )
96
97     add_integer( "win32text-fontsize", 0, NULL, FONTSIZE_TEXT,
98                  FONTSIZE_LONGTEXT, true )
99
100     /* opacity valid on 0..255, with default 255 = fully opaque */
101     add_integer_with_range( "win32-opacity", 255, 0, 255, NULL,
102         OPACITY_TEXT, OPACITY_LONGTEXT, false )
103
104     /* hook to the color values list, with default 0x00ffffff = white */
105     add_integer( "win32text-color", 0x00FFFFFF, NULL, COLOR_TEXT,
106                  COLOR_LONGTEXT, true )
107         change_integer_list( pi_color_values, ppsz_color_descriptions, NULL );
108
109     add_integer( "win32text-rel-fontsize", 16, NULL, FONTSIZER_TEXT,
110                  FONTSIZER_LONGTEXT, false )
111         change_integer_list( pi_sizes, ppsz_sizes_text, NULL );
112
113     set_capability( "text renderer", 50 )
114     add_shortcut( "text" )
115     set_callbacks( Create, Destroy )
116 vlc_module_end ()
117
118 /*****************************************************************************
119  * filter_sys_t: win32text local data
120  *****************************************************************************/
121 struct filter_sys_t
122 {
123     uint8_t        i_font_opacity;
124     int            i_font_color;
125     int            i_font_size;
126
127     int            i_default_font_size;
128     int            i_display_height;
129
130     HDC hcdc;
131     HFONT hfont;
132     HFONT hfont_bak;
133     int i_logpy;
134 };
135
136 static const uint8_t pi_gamma[16] =
137   {0x00, 0x41, 0x52, 0x63, 0x84, 0x85, 0x96, 0xa7, 0xb8, 0xc9,
138    0xca, 0xdb, 0xdc, 0xed, 0xee, 0xff};
139
140 /*****************************************************************************
141  * Create: creates the module
142  *****************************************************************************/
143 static int Create( vlc_object_t *p_this )
144 {
145     filter_t *p_filter = (filter_t *)p_this;
146     filter_sys_t *p_sys;
147     char *psz_fontfile = NULL;
148     vlc_value_t val;
149     HDC hdc;
150
151     /* Allocate structure */
152     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
153     if( !p_sys )
154         return VLC_ENOMEM;
155     p_sys->i_font_size = 0;
156     p_sys->i_display_height = 0;
157
158     var_Create( p_filter, "win32text-font",
159                 VLC_VAR_STRING | VLC_VAR_DOINHERIT );
160     var_Create( p_filter, "win32text-fontsize",
161                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
162     var_Create( p_filter, "win32text-rel-fontsize",
163                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
164     var_Create( p_filter, "win32text-opacity",
165                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
166     var_Get( p_filter, "win32text-opacity", &val );
167     p_sys->i_font_opacity = __MAX( __MIN( val.i_int, 255 ), 0 );
168     var_Create( p_filter, "win32text-color",
169                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
170     var_Get( p_filter, "win32text-color", &val );
171     p_sys->i_font_color = __MAX( __MIN( val.i_int, 0xFFFFFF ), 0 );
172
173     p_sys->hfont = p_sys->hfont_bak = 0;
174     hdc = GetDC( NULL );
175     p_sys->hcdc = CreateCompatibleDC( hdc );
176     p_sys->i_logpy = GetDeviceCaps( hdc, LOGPIXELSY );
177     ReleaseDC( NULL, hdc );
178     SetBkMode( p_sys->hcdc, TRANSPARENT );
179
180     var_Get( p_filter, "win32text-fontsize", &val );
181     p_sys->i_default_font_size = val.i_int;
182     if( SetFont( p_filter, 0 ) != VLC_SUCCESS ) goto error;
183
184     free( psz_fontfile );
185     p_filter->pf_render_text = RenderText;
186     p_filter->pf_render_html = NULL;
187     return VLC_SUCCESS;
188
189  error:
190     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     bool b_outline = true;
221
222     /* Create a new subpicture region */
223     memset( &fmt, 0, sizeof(video_format_t) );
224     fmt.i_chroma = VLC_CODEC_YUVP;
225     fmt.i_width = fmt.i_visible_width = i_width + (b_outline ? 4 : 0);
226     fmt.i_height = fmt.i_visible_height = i_height + (b_outline ? 4 : 0);
227     fmt.i_x_offset = fmt.i_y_offset = 0;
228
229     /* Build palette */
230     fmt.p_palette = calloc( 1, sizeof(*fmt.p_palette) );
231     if( !fmt.p_palette )
232         return VLC_EGENERIC;
233     fmt.p_palette->i_entries = 16;
234     for( i = 0; i < fmt.p_palette->i_entries; i++ )
235     {
236         fmt.p_palette->palette[i][0] = pi_gamma[i];
237         fmt.p_palette->palette[i][1] = 128;
238         fmt.p_palette->palette[i][2] = 128;
239         fmt.p_palette->palette[i][3] = pi_gamma[i];
240     }
241
242     p_region->p_picture = picture_New( fmt.i_chroma, fmt.i_width, fmt.i_height, fmt.i_aspect );
243     if( !p_region->p_picture )
244     {
245         free( fmt.p_palette );
246         return VLC_EGENERIC;
247     }
248     p_region->fmt = fmt;
249
250     p_dst = p_region->p_picture->Y_PIXELS;
251     i_pitch = p_region->p_picture->Y_PITCH;
252
253     if( b_outline )
254     {
255         memset( p_dst, 0, i_pitch * fmt.i_height );
256         p_dst += p_region->p_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->p_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     psz_string = malloc( (strlen( p_region_in->psz_text )+1) * sizeof(TCHAR) );
311     if( !psz_string )
312         return VLC_ENOMEM;
313 #ifdef UNICODE
314     if( mbstowcs( psz_string, p_region_in->psz_text,
315                   strlen( p_region_in->psz_text ) * sizeof(TCHAR) ) < 0 )
316     {
317         free( psz_string );
318         return VLC_EGENERIC;
319     }
320 #else
321     strcpy( psz_string, p_region_in->psz_text );
322 #endif
323     if( !*psz_string )
324     {
325         free( psz_string );
326         return VLC_EGENERIC;
327     }
328
329     if( p_region_in->p_style )
330     {
331         i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
332         i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
333         i_font_size  = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 );
334     }
335     else
336     {
337         i_font_color = p_sys->i_font_color;
338         i_font_alpha = 255 - p_sys->i_font_opacity;
339         i_font_size = p_sys->i_default_font_size;
340     }
341
342     SetFont( p_filter, i_font_size );
343
344     SetTextColor( p_sys->hcdc, RGB( (i_font_color >> 16) & 0xff,
345                   (i_font_color >> 8) & 0xff, i_font_color & 0xff) );
346
347     GetTextExtentExPoint( p_sys->hcdc, psz_string, _tcslen(psz_string),
348                           0, 0, 0, &size );
349     i_width = rect.right = size.cx; i_height = rect.bottom = size.cy;
350
351     p_bmi = malloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*16);
352     memset( p_bmi, 0, sizeof(BITMAPINFOHEADER) );
353     p_bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
354     p_bmi->bmiHeader.biWidth = (i_width+3) & ~3;
355     p_bmi->bmiHeader.biHeight = - i_height;
356     p_bmi->bmiHeader.biPlanes = 1;
357     p_bmi->bmiHeader.biBitCount = 8;
358     p_bmi->bmiHeader.biCompression = BI_RGB;
359     p_bmi->bmiHeader.biClrUsed = 16;
360
361     for( i = 0; i < 16; i++ )
362     {
363         p_bmi->bmiColors[i].rgbBlue =
364             p_bmi->bmiColors[i].rgbGreen =
365                 p_bmi->bmiColors[i].rgbRed = pi_gamma[i];
366     }
367
368     bitmap = CreateDIBSection( p_sys->hcdc, p_bmi, DIB_RGB_COLORS,
369                                (void **)&p_bitmap, NULL, 0 );
370     if( !bitmap )
371     {
372         msg_Err( p_filter, "could not create bitmap" );
373         return VLC_EGENERIC;
374     }
375
376     bitmap_bak = SelectObject( p_sys->hcdc, bitmap );
377     FillRect( p_sys->hcdc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH) );
378
379     //TextOut( p_sys->hcdc, 0, 0, psz_string, strlen(psz_string) );
380     if( !DrawText( p_sys->hcdc, psz_string, -1, &rect, 0 ) )
381     {
382         msg_Err( p_filter, "could not draw text" );
383     }
384
385     p_region_out->i_x = p_region_in->i_x;
386     p_region_out->i_y = p_region_in->i_y;
387     Render( p_filter, p_region_out, p_bitmap, i_width, i_height );
388
389     SelectObject( p_sys->hcdc, bitmap_bak );
390     DeleteObject( bitmap );
391     return VLC_SUCCESS;
392 }
393
394 static int SetFont( filter_t *p_filter, int i_size )
395 {
396     filter_sys_t *p_sys = p_filter->p_sys;
397     LOGFONT logfont;
398
399     if( i_size && i_size == p_sys->i_font_size ) return VLC_SUCCESS;
400
401     if( !i_size )
402     {
403         vlc_value_t val;
404
405         if( !p_sys->i_default_font_size &&
406             p_sys->i_display_height == (int)p_filter->fmt_out.video.i_height )
407             return VLC_SUCCESS;
408
409         if( p_sys->i_default_font_size )
410         {
411             i_size = p_sys->i_default_font_size;
412         }
413         else
414         {
415             var_Get( p_filter, "win32text-rel-fontsize", &val );
416             i_size = (int)p_filter->fmt_out.video.i_height / val.i_int;
417             p_filter->p_sys->i_display_height =
418                 p_filter->fmt_out.video.i_height;
419         }
420         if( i_size <= 0 )
421         {
422             msg_Warn( p_filter, "invalid fontsize, using 12" );
423             i_size = 12;
424         }
425
426         msg_Dbg( p_filter, "using fontsize: %i", i_size );
427     }
428
429     p_sys->i_font_size = i_size;
430
431     if( p_sys->hfont_bak ) SelectObject( p_sys->hcdc, p_sys->hfont_bak );
432     if( p_sys->hfont ) DeleteObject( p_sys->hfont );
433
434     i_size = i_size * (int64_t)p_sys->i_logpy / 72;
435
436     logfont.lfHeight = i_size;
437     logfont.lfWidth = 0;
438     logfont.lfEscapement = 0;
439     logfont.lfOrientation = 0;
440     logfont.lfWeight = 0;
441     logfont.lfItalic = FALSE;
442     logfont.lfUnderline = FALSE;
443     logfont.lfStrikeOut = FALSE;
444     logfont.lfCharSet = ANSI_CHARSET;
445     logfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
446     logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
447     logfont.lfQuality = ANTIALIASED_QUALITY;
448     logfont.lfPitchAndFamily = DEFAULT_PITCH;
449     memcpy( logfont.lfFaceName, _T("Arial"), sizeof(_T("Arial")) );
450
451     p_sys->hfont = CreateFontIndirect( &logfont );
452
453     p_sys->hfont_bak = SelectObject( p_sys->hcdc, p_sys->hfont );
454
455     return VLC_SUCCESS;
456 }