misc/*: 2nd lecture. refs #438
[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 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
58      "that will be rendered on the video. " \
59      "If set to something different than 0 this option will override the " \
60      "relative font size. " )
61 #define OPACITY_TEXT N_("Opacity")
62 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
63      "text that will be rendered on the video. 0 = transparent, " \
64      "255 = totally opaque. " )
65 #define COLOR_TEXT N_("Text default color")
66 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
67   "the video. This must be an hexadecimal (like HTML colors). The first two "\
68   "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
69   " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
70 #define FONTSIZER_TEXT N_("Relative font size")
71 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
72   "fonts that will be rendered on the video. If absolute font size is set, "\
73    "relative size will be overriden." )
74
75 static int   pi_sizes[] = { 20, 18, 16, 12, 6 };
76 static char *ppsz_sizes_text[] = { N_("Smaller"), N_("Small"), N_("Normal"),
77                                    N_("Large"), N_("Larger") };
78 static int pi_color_values[] = {
79   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
80   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
81   0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
82
83 static char *ppsz_color_descriptions[] = {
84   N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
85   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
86   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
87
88 vlc_module_begin();
89     set_shortname( _("Text renderer"));
90     set_description( _("Win32 font renderer") );
91     set_category( CAT_VIDEO );
92     set_subcategory( SUBCAT_VIDEO_SUBPIC );
93
94     add_integer( "win32text-fontsize", 0, NULL, FONTSIZE_TEXT,
95                  FONTSIZE_LONGTEXT, VLC_TRUE );
96
97     /* opacity valid on 0..255, with default 255 = fully opaque */
98     add_integer_with_range( "win32-opacity", 255, 0, 255, NULL,
99         OPACITY_TEXT, OPACITY_LONGTEXT, VLC_FALSE );
100
101     /* hook to the color values list, with default 0x00ffffff = white */
102     add_integer( "win32text-color", 0x00FFFFFF, NULL, COLOR_TEXT,
103                  COLOR_LONGTEXT, VLC_TRUE );
104         change_integer_list( pi_color_values, ppsz_color_descriptions, 0 );
105
106     add_integer( "win32text-rel-fontsize", 16, NULL, FONTSIZER_TEXT,
107                  FONTSIZER_LONGTEXT, VLC_FALSE );
108         change_integer_list( pi_sizes, ppsz_sizes_text, 0 );
109
110     set_capability( "text renderer", 50 );
111     add_shortcut( "text" );
112     set_callbacks( Create, Destroy );
113 vlc_module_end();
114
115 /*****************************************************************************
116  * filter_sys_t: win32text local data
117  *****************************************************************************/
118 struct filter_sys_t
119 {
120     uint8_t        i_font_opacity;
121     int            i_font_color;
122     int            i_font_size;
123
124     int            i_default_font_size;
125     int            i_display_height;
126
127     HDC hcdc;
128     HFONT hfont;
129     HFONT hfont_bak;
130     int i_logpy;
131 };
132
133 static uint8_t pi_gamma[16] =
134   {0x00, 0x41, 0x52, 0x63, 0x84, 0x85, 0x96, 0xa7, 0xb8, 0xc9,
135    0xca, 0xdb, 0xdc, 0xed, 0xee, 0xff};
136
137 /*****************************************************************************
138  * Create: creates the module
139  *****************************************************************************/
140 static int Create( vlc_object_t *p_this )
141 {
142     filter_t *p_filter = (filter_t *)p_this;
143     filter_sys_t *p_sys;
144     char *psz_fontfile = NULL;
145     vlc_value_t val;
146     HDC hdc;
147
148     /* Allocate structure */
149     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
150     if( !p_sys )
151     {
152         msg_Err( p_filter, "out of memory" );
153         return VLC_ENOMEM;
154     }
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     if( psz_fontfile ) free( psz_fontfile );
185     p_filter->pf_render_text = RenderText;
186     return VLC_SUCCESS;
187
188  error:
189     if( psz_fontfile ) free( psz_fontfile );
190     free( p_sys );
191     return VLC_EGENERIC;
192 }
193
194 /*****************************************************************************
195  * Destroy: destroy the module
196  *****************************************************************************/
197 static void Destroy( vlc_object_t *p_this )
198 {
199     filter_t *p_filter = (filter_t *)p_this;
200     filter_sys_t *p_sys = p_filter->p_sys;
201
202     if( p_sys->hfont_bak ) SelectObject( p_sys->hcdc, p_sys->hfont_bak );
203     if( p_sys->hfont ) DeleteObject( p_sys->hfont );
204     DeleteDC( p_sys->hcdc );
205     free( p_sys );
206 }
207
208 /*****************************************************************************
209  * Render: place string in picture
210  *****************************************************************************
211  * This function merges the previously rendered win32text glyphs into a picture
212  *****************************************************************************/
213 static int Render( filter_t *p_filter, subpicture_region_t *p_region,
214                    uint8_t *p_bitmap, int i_width, int i_height )
215 {
216     uint8_t *p_dst;
217     video_format_t fmt;
218     int i, i_pitch;
219     subpicture_region_t *p_region_tmp;
220     vlc_bool_t b_outline = VLC_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     p_region_tmp = spu_CreateRegion( p_filter, &fmt );
229     if( !p_region_tmp )
230     {
231         msg_Err( p_filter, "cannot allocate SPU region" );
232         return VLC_EGENERIC;
233     }
234
235     /* Build palette */
236     fmt.p_palette->i_entries = 16;
237     for( i = 0; i < fmt.p_palette->i_entries; i++ )
238     {
239         fmt.p_palette->palette[i][0] = pi_gamma[i];
240         fmt.p_palette->palette[i][1] = 128;
241         fmt.p_palette->palette[i][2] = 128;
242         fmt.p_palette->palette[i][3] = pi_gamma[i];
243     }
244
245     p_region->fmt = p_region_tmp->fmt;
246     p_region->picture = p_region_tmp->picture;
247     free( p_region_tmp );
248
249     p_dst = p_region->picture.Y_PIXELS;
250     i_pitch = p_region->picture.Y_PITCH;
251
252     if( b_outline )
253     {
254         memset( p_dst, 0, i_pitch * fmt.i_height );
255         p_dst += p_region->picture.Y_PITCH * 2 + 2;
256     }
257
258     for( i = 0; i < i_height; i++ )
259     {
260         memcpy( p_dst, p_bitmap, i_width );
261         p_bitmap += (i_width+3) & ~3;
262         p_dst += i_pitch;
263     }
264
265     /* Outlining (find something better than nearest neighbour filtering ?) */
266     if( b_outline )
267     {
268         uint8_t *p_top = p_dst; /* Use 1st line as a cache */
269         uint8_t left, current;
270         int x, y;
271
272         p_dst = p_region->picture.Y_PIXELS;
273
274         for( y = 1; y < (int)fmt.i_height - 1; y++ )
275         {
276             memcpy( p_top, p_dst, fmt.i_width );
277             p_dst += i_pitch;
278             left = 0;
279
280             for( x = 1; x < (int)fmt.i_width - 1; x++ )
281             {
282                 current = p_dst[x];
283                 p_dst[x] = ( 4 * (int)p_dst[x] + left + p_top[x] + p_dst[x+1] +
284                              p_dst[x + i_pitch]) / 8;
285                 left = current;
286             }
287         }
288         memset( p_top, 0, fmt.i_width );
289     }
290
291     return VLC_SUCCESS;
292 }
293
294 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
295                        subpicture_region_t *p_region_in )
296 {
297     filter_sys_t *p_sys = p_filter->p_sys;
298     int i_font_color, i_font_alpha, i_font_size;
299     uint8_t *p_bitmap;
300     TCHAR *psz_string;
301     int i, i_width, i_height;
302     HBITMAP bitmap, bitmap_bak;
303     BITMAPINFO *p_bmi;
304     RECT rect = {0};
305     SIZE size;
306
307     /* Sanity check */
308     if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
309 #ifdef UNICODE
310     psz_string = malloc( (strlen( p_region_in->psz_text )+1) * sizeof(TCHAR) );
311     if( mbstowcs( psz_string, p_region_in->psz_text,
312                   strlen( p_region_in->psz_text ) * sizeof(TCHAR) ) < 0 )
313     {
314         free( psz_string );
315         return VLC_EGENERIC;
316     }
317 #else
318     psz_string = strdup( p_region_in->psz_text );
319 #endif
320     if( !psz_string || !*psz_string ) return VLC_EGENERIC;
321
322     if( p_region_in->p_style )
323     {
324         i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
325         i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
326         i_font_size  = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 );
327     }
328     else
329     {
330         i_font_color = p_sys->i_font_color;
331         i_font_alpha = 255 - p_sys->i_font_opacity;
332         i_font_size = p_sys->i_default_font_size;
333     }
334
335     SetFont( p_filter, i_font_size );
336
337     SetTextColor( p_sys->hcdc, RGB( (i_font_color >> 16) & 0xff,
338                   (i_font_color >> 8) & 0xff, i_font_color & 0xff) );
339
340     GetTextExtentExPoint( p_sys->hcdc, psz_string, _tcslen(psz_string),
341                           0, 0, 0, &size );
342     i_width = rect.right = size.cx; i_height = rect.bottom = size.cy;
343
344     p_bmi = malloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*16);
345     memset( p_bmi, 0, sizeof(BITMAPINFOHEADER) );
346     p_bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
347     p_bmi->bmiHeader.biWidth = (i_width+3) & ~3;
348     p_bmi->bmiHeader.biHeight = - i_height;
349     p_bmi->bmiHeader.biPlanes = 1;
350     p_bmi->bmiHeader.biBitCount = 8;
351     p_bmi->bmiHeader.biCompression = BI_RGB;
352     p_bmi->bmiHeader.biClrUsed = 16;
353
354     for( i = 0; i < 16; i++ )
355     {
356         p_bmi->bmiColors[i].rgbBlue =
357             p_bmi->bmiColors[i].rgbGreen =
358                 p_bmi->bmiColors[i].rgbRed = pi_gamma[i];
359     }
360
361     bitmap = CreateDIBSection( p_sys->hcdc, p_bmi, DIB_RGB_COLORS,
362                                (void **)&p_bitmap, NULL, 0 );
363     if( !bitmap )
364     {
365         msg_Err( p_filter, "could not create bitmap" );
366         return VLC_EGENERIC;
367     }
368
369     bitmap_bak = SelectObject( p_sys->hcdc, bitmap );
370     FillRect( p_sys->hcdc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH) );
371
372     //TextOut( p_sys->hcdc, 0, 0, psz_string, strlen(psz_string) );
373     if( !DrawText( p_sys->hcdc, psz_string, -1, &rect, 0 ) )
374     {
375         msg_Err( p_filter, "could not draw text" );
376     }
377
378     p_region_out->i_x = p_region_in->i_x;
379     p_region_out->i_y = p_region_in->i_y;
380     Render( p_filter, p_region_out, p_bitmap, i_width, i_height );
381
382     SelectObject( p_sys->hcdc, bitmap_bak );
383     DeleteObject( bitmap );
384     return VLC_SUCCESS;
385 }
386
387 static int SetFont( filter_t *p_filter, int i_size )
388 {
389     filter_sys_t *p_sys = p_filter->p_sys;
390     LOGFONT logfont;
391
392     if( i_size && i_size == p_sys->i_font_size ) return VLC_SUCCESS;
393
394     if( !i_size )
395     {
396         vlc_value_t val;
397
398         if( !p_sys->i_default_font_size &&
399             p_sys->i_display_height == (int)p_filter->fmt_out.video.i_height )
400             return VLC_SUCCESS;
401
402         if( p_sys->i_default_font_size )
403         {
404             i_size = p_sys->i_default_font_size;
405         }
406         else
407         {
408             var_Get( p_filter, "win32text-rel-fontsize", &val );
409             i_size = (int)p_filter->fmt_out.video.i_height / val.i_int;
410             p_filter->p_sys->i_display_height =
411                 p_filter->fmt_out.video.i_height;
412         }
413         if( i_size <= 0 )
414         {
415             msg_Warn( p_filter, "invalid fontsize, using 12" );
416             i_size = 12;
417         }
418
419         msg_Dbg( p_filter, "using fontsize: %i", i_size );
420     }
421
422     p_sys->i_font_size = i_size;
423
424     if( p_sys->hfont_bak ) SelectObject( p_sys->hcdc, p_sys->hfont_bak );
425     if( p_sys->hfont ) DeleteObject( p_sys->hfont );
426
427     i_size = i_size * (int64_t)p_sys->i_logpy / 72;
428
429     logfont.lfHeight = i_size;
430     logfont.lfWidth = 0;
431     logfont.lfEscapement = 0;
432     logfont.lfOrientation = 0;
433     logfont.lfWeight = 0;
434     logfont.lfItalic = FALSE;
435     logfont.lfUnderline = FALSE;
436     logfont.lfStrikeOut = FALSE;
437     logfont.lfCharSet = ANSI_CHARSET;
438     logfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
439     logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
440     logfont.lfQuality = ANTIALIASED_QUALITY;
441     logfont.lfPitchAndFamily = DEFAULT_PITCH;
442     memcpy( logfont.lfFaceName, _T("Arial"), sizeof(_T("Arial")) );
443
444     p_sys->hfont = CreateFontIndirect( &logfont );
445
446     p_sys->hfont_bak = SelectObject( p_sys->hcdc, p_sys->hfont );
447
448     return VLC_SUCCESS;
449 }