Memory leak - fixes #2255
[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_FOURCC('Y','U','V','P');
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->i_entries = 16;
231     for( i = 0; i < fmt.p_palette->i_entries; i++ )
232     {
233         fmt.p_palette->palette[i][0] = pi_gamma[i];
234         fmt.p_palette->palette[i][1] = 128;
235         fmt.p_palette->palette[i][2] = 128;
236         fmt.p_palette->palette[i][3] = pi_gamma[i];
237     }
238
239     p_region->p_picture = picture_New( fmt.i_chroma, fmt.i_width, fmt.i_height, fmt.i_aspect );
240     if( !p_region->p_picture )
241         return VLC_EGENERIC;
242     p_region->fmt = fmt;
243
244     p_dst = p_region->p_picture->Y_PIXELS;
245     i_pitch = p_region->p_picture->Y_PITCH;
246
247     if( b_outline )
248     {
249         memset( p_dst, 0, i_pitch * fmt.i_height );
250         p_dst += p_region->p_picture->Y_PITCH * 2 + 2;
251     }
252
253     for( i = 0; i < i_height; i++ )
254     {
255         memcpy( p_dst, p_bitmap, i_width );
256         p_bitmap += (i_width+3) & ~3;
257         p_dst += i_pitch;
258     }
259
260     /* Outlining (find something better than nearest neighbour filtering ?) */
261     if( b_outline )
262     {
263         uint8_t *p_top = p_dst; /* Use 1st line as a cache */
264         uint8_t left, current;
265         int x, y;
266
267         p_dst = p_region->p_picture->Y_PIXELS;
268
269         for( y = 1; y < (int)fmt.i_height - 1; y++ )
270         {
271             memcpy( p_top, p_dst, fmt.i_width );
272             p_dst += i_pitch;
273             left = 0;
274
275             for( x = 1; x < (int)fmt.i_width - 1; x++ )
276             {
277                 current = p_dst[x];
278                 p_dst[x] = ( 4 * (int)p_dst[x] + left + p_top[x] + p_dst[x+1] +
279                              p_dst[x + i_pitch]) / 8;
280                 left = current;
281             }
282         }
283         memset( p_top, 0, fmt.i_width );
284     }
285
286     return VLC_SUCCESS;
287 }
288
289 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
290                        subpicture_region_t *p_region_in )
291 {
292     filter_sys_t *p_sys = p_filter->p_sys;
293     int i_font_color, i_font_alpha, i_font_size;
294     uint8_t *p_bitmap;
295     TCHAR *psz_string;
296     int i, i_width, i_height;
297     HBITMAP bitmap, bitmap_bak;
298     BITMAPINFO *p_bmi;
299     RECT rect = {0};
300     SIZE size;
301
302     /* Sanity check */
303     if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
304     psz_string = malloc( (strlen( p_region_in->psz_text )+1) * sizeof(TCHAR) );
305     if( !psz_string )
306         return VLC_ENOMEM;
307 #ifdef UNICODE
308     if( mbstowcs( psz_string, p_region_in->psz_text,
309                   strlen( p_region_in->psz_text ) * sizeof(TCHAR) ) < 0 )
310     {
311         free( psz_string );
312         return VLC_EGENERIC;
313     }
314 #else
315     strcpy( psz_string, p_region_in->psz_text );
316 #endif
317     if( !*psz_string )
318     {
319         free( psz_strin );
320         return VLC_EGENERIC;
321     }
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 }