]> git.sesse.net Git - vlc/blob - modules/video_filter/canvas.c
a083b182892401b62b5ccdee18a2b3a9265892cf
[vlc] / modules / video_filter / canvas.c
1 /*****************************************************************************
2  * canvas.c : automatically resize and padd a video to fit in canvas
3  *****************************************************************************
4  * Copyright (C) 2008 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Antoine Cellerier <dionoea at videolan dot 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 <limits.h>
33
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_filter.h>
37
38 /*****************************************************************************
39  * Local and extern prototypes.
40  *****************************************************************************/
41 static int  Activate( vlc_object_t * );
42 static void Destroy( vlc_object_t * );
43 static picture_t *Filter( filter_t *, picture_t * );
44 static int alloc_init( filter_t *, void * );
45
46 /* This module effectively implements a form of picture-in-picture.
47  *  - The outer picture is called the canvas.
48  *  - The innter picture is callsed the subpicture.
49  *
50  * NB, all of the following operatons take into account aspect ratio
51  *
52  * A canvas is of canvas_{width,height}.
53  * In Pad mode:
54  *  - The subpicture is upconverted with a inverse scalefactor of:
55  *     (The size of subpicture's largest dimension)
56  *     --------------------------------------------
57  *     (The size of canvas's equivalent dimension)
58  *
59  *   Ie, The subpicture's largest dimension is made equal to the
60  *   equivalent canvas dimension.
61  *
62  *  - The upconverted subpicture's smallest dimension is then padded
63  *    to make the upconverted subpicture have the same dimensions of
64  *    the canvas.
65  *
66  * In Crop mode:
67  *  - The subpicture is upconverted with an inverse scalefactor of:
68  *     (The size of subpicture's smallest dimension)
69  *     --------------------------------------------
70  *     (The size of canvas's equivalent dimension)
71  *
72  *   Ie, The subpicture's smallest dimension is made equal to the
73  *   equivalent canvas dimension. (The subpicture will then be
74  *   larger than the canvas)
75  *
76  *  - The upconverted subpicture's largest dimension is then cropped
77  *    to make the upconverted subpicture have the same dimensions of
78  *    the canvas.
79  */
80
81 /* NB, use of `padd' in this module is a 16-17th Century spelling of `pad' */
82
83 #define WIDTH_TEXT N_( "Output width" )
84 #define WIDTH_LONGTEXT N_( \
85     "Output (canvas) image width" )
86 #define HEIGHT_TEXT N_( "Output height" )
87 #define HEIGHT_LONGTEXT N_( \
88     "Output (canvas) image height" )
89 #define ASPECT_TEXT N_( "Output picture aspect ratio" )
90 #define ASPECT_LONGTEXT N_( \
91     "Set the canvas' picture aspect ratio. " \
92     "If omitted, the canvas is assumed to have the same SAR as the input." )
93 #define PADD_TEXT N_( "Pad video" )
94 #define PADD_LONGTEXT N_( \
95     "If enabled, video will be padded to fit in canvas after scaling. " \
96     "Otherwise, video will be cropped to fix in canvas after scaling." )
97
98 #define CFG_PREFIX "canvas-"
99
100 /*****************************************************************************
101  * Module descriptor
102  *****************************************************************************/
103 vlc_module_begin ()
104     set_description( N_("Automatically resize and pad a video") )
105     set_capability( "video filter2", 0 )
106     set_callbacks( Activate, Destroy )
107
108     set_category( CAT_VIDEO )
109     set_subcategory( SUBCAT_VIDEO_VFILTER )
110
111     add_integer_with_range( CFG_PREFIX "width", 0, 0, INT_MAX, NULL,
112                             WIDTH_TEXT, WIDTH_LONGTEXT, false )
113     add_integer_with_range( CFG_PREFIX "height", 0, 0, INT_MAX, NULL,
114                             HEIGHT_TEXT, HEIGHT_LONGTEXT, false )
115
116     add_string( CFG_PREFIX "aspect", NULL, NULL,
117                 ASPECT_TEXT, ASPECT_LONGTEXT, false )
118
119     add_bool( CFG_PREFIX "padd", true, NULL,
120               PADD_TEXT, PADD_LONGTEXT, false )
121 vlc_module_end ()
122
123 static const char *const ppsz_filter_options[] = {
124     "width", "height", "aspect", "padd", NULL
125 };
126
127 struct filter_sys_t
128 {
129     filter_chain_t *p_chain;
130 };
131
132 /*****************************************************************************
133  *
134  *****************************************************************************/
135 static int Activate( vlc_object_t *p_this )
136 {
137     filter_t *p_filter = (filter_t *)p_this;
138     unsigned i_canvas_width; /* width of output canvas */
139     unsigned i_canvas_height; /* height of output canvas */
140     unsigned i_canvas_aspect; /* canvas PictureAspectRatio */
141     es_format_t fmt; /* target format after up/down conversion */
142     char psz_croppadd[100];
143     int i_padd,i_offset;
144     char *psz_aspect, *psz_parser;
145     bool b_padd;
146
147     if( !p_filter->b_allow_fmt_out_change )
148     {
149         msg_Err( p_filter, "Picture format change isn't allowed" );
150         return VLC_EGENERIC;
151     }
152
153     if( p_filter->fmt_in.video.i_chroma != p_filter->fmt_out.video.i_chroma )
154     {
155         msg_Err( p_filter, "Input and output chromas don't match" );
156         return VLC_EGENERIC;
157     }
158
159     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
160                        p_filter->p_cfg );
161
162     i_canvas_width = var_CreateGetInteger( p_filter, CFG_PREFIX "width" );
163     i_canvas_height = var_CreateGetInteger( p_filter, CFG_PREFIX "height" );
164
165     if( i_canvas_width == 0 || i_canvas_height == 0 )
166     {
167         msg_Err( p_filter, "Width and height options must be set" );
168         return VLC_EGENERIC;
169     }
170
171     if( i_canvas_width & 1 || i_canvas_height & 1 )
172     {
173         /* If this restriction were ever relaxed, it is very important to
174          * get the field polatiry correct */
175         msg_Err( p_filter, "Width and height options must be even integers" );
176         return VLC_EGENERIC;
177     }
178
179     psz_aspect = var_CreateGetNonEmptyString( p_filter, CFG_PREFIX "aspect" );
180     if( psz_aspect )
181     {
182         psz_parser = strchr( psz_aspect, ':' );
183         int numerator = atoi( psz_aspect );
184         int denominator = psz_parser ? atoi( psz_parser+1 ) : 0;
185         denominator = denominator == 0 ? 1 : denominator;
186         i_canvas_aspect = numerator * VOUT_ASPECT_FACTOR / denominator;
187         free( psz_aspect );
188
189         if( numerator <= 0 || denominator < 0 )
190         {
191             msg_Err( p_filter, "Aspect ratio must be strictly positive" );
192             return VLC_EGENERIC;
193         }
194     }
195     else
196     {
197         /* if there is no user supplied aspect ratio, assume the canvas
198          * has the same sample aspect ratio as the subpicture */
199         /* aspect = subpic_sar * canvas_width / canvas_height
200          *  where subpic_sar = subpic_ph * subpic_par / subpic_pw */
201         i_canvas_aspect = (uint64_t) p_filter->fmt_in.video.i_height
202                         * p_filter->fmt_in.video.i_aspect
203                         * i_canvas_width
204                         / (i_canvas_height * p_filter->fmt_in.video.i_width);
205     }
206
207     b_padd = var_CreateGetBool( p_filter, CFG_PREFIX "padd" );
208
209     filter_sys_t *p_sys = (filter_sys_t *)malloc( sizeof( filter_sys_t ) );
210     if( !p_sys )
211         return VLC_ENOMEM;
212     p_filter->p_sys = p_sys;
213
214     p_sys->p_chain = filter_chain_New( p_filter, "video filter2", true,
215                                        alloc_init, NULL, p_filter );
216     if( !p_sys->p_chain )
217     {
218         msg_Err( p_filter, "Could not allocate filter chain" );
219         free( p_sys );
220         return VLC_EGENERIC;
221     }
222
223     es_format_Copy( &fmt, &p_filter->fmt_in );
224
225     /* one dimension will end up with one of the following: */
226     fmt.video.i_width = i_canvas_width;
227     fmt.video.i_height = i_canvas_height;
228
229     if( b_padd )
230     {
231         /* Padd */
232         if( i_canvas_aspect > p_filter->fmt_in.video.i_aspect )
233         {
234             /* The canvas has a wider aspect than the subpicture:
235              *  ie, pillarbox the [scaled] subpicture */
236             /* The following is derived form:
237              * width = upconverted_subpic_height * subpic_par / canvas_sar
238              *  where canvas_sar = canvas_width / (canvas_height * canvas_par)
239              * then simplify */
240             fmt.video.i_width = i_canvas_width
241                               * p_filter->fmt_in.video.i_aspect
242                               / i_canvas_aspect;
243             if( fmt.video.i_width & 1 ) fmt.video.i_width -= 1;
244
245             i_padd = (i_canvas_width - fmt.video.i_width) / 2;
246             i_offset = (i_padd & 1);
247             snprintf( psz_croppadd, 100, "croppadd{paddleft=%d,paddright=%d}",
248                       i_padd - i_offset, i_padd + i_offset );
249         }
250         else
251         {
252             /* The canvas has a taller aspect than the subpicture:
253              *  ie, letterbox the [scaled] subpicture */
254             fmt.video.i_height = i_canvas_height
255                                * i_canvas_aspect
256                                / p_filter->fmt_in.video.i_aspect;
257             if( fmt.video.i_height & 1 ) fmt.video.i_height -= 1;
258
259             i_padd = (i_canvas_height - fmt.video.i_height ) / 2;
260             i_offset = (i_padd & 1);
261             snprintf( psz_croppadd, 100, "croppadd{paddtop=%d,paddbottom=%d}",
262                       i_padd - i_offset, i_padd + i_offset );
263         }
264     }
265     else
266     {
267         /* Crop */
268         if( i_canvas_aspect < p_filter->fmt_in.video.i_aspect )
269         {
270             /* The canvas has a narrower aspect than the subpicture:
271              *  ie, crop the [scaled] subpicture horizontally */
272             fmt.video.i_width = i_canvas_width
273                               * p_filter->fmt_in.video.i_aspect
274                               / i_canvas_aspect;
275             if( fmt.video.i_width & 1 ) fmt.video.i_width -= 1;
276
277             i_padd = (fmt.video.i_width - i_canvas_width) / 2;
278             i_offset = (i_padd & 1);
279             snprintf( psz_croppadd, 100, "croppadd{cropleft=%d,cropright=%d}",
280                       i_padd - i_offset, i_padd + i_offset );
281         }
282         else
283         {
284             /* The canvas has a shorter aspect than the subpicture:
285              *  ie, crop the [scaled] subpicture vertically */
286             fmt.video.i_height = i_canvas_height
287                                * i_canvas_aspect
288                                / p_filter->fmt_in.video.i_aspect;
289             if( fmt.video.i_height & 1 ) fmt.video.i_height -= 1;
290
291             i_padd = (fmt.video.i_height - i_canvas_height) / 2;
292             i_offset = (i_padd & 1);
293             snprintf( psz_croppadd, 100, "croppadd{croptop=%d,cropbottom=%d}",
294                       i_padd - i_offset, i_padd + i_offset );
295         }
296     }
297
298     /* xxx, should the clean area include the letter-boxing?
299      *  probably not, as some codecs can make use of that information
300      *  and it should be a scaled version of the input clean area
301      *   -- davidf */
302     fmt.video.i_visible_width = fmt.video.i_width;
303     fmt.video.i_visible_height = fmt.video.i_height;
304
305     filter_chain_Reset( p_sys->p_chain, &p_filter->fmt_in, &fmt );
306     /* Append scaling module */
307     filter_chain_AppendFilter( p_sys->p_chain, NULL, NULL, NULL, NULL );
308     /* Append padding module */
309     filter_chain_AppendFromString( p_sys->p_chain, psz_croppadd );
310
311     fmt = *filter_chain_GetFmtOut( p_sys->p_chain );
312     es_format_Copy( &p_filter->fmt_out, &fmt );
313
314     p_filter->fmt_out.video.i_aspect = i_canvas_aspect;
315
316     if( p_filter->fmt_out.video.i_width != i_canvas_width
317      || p_filter->fmt_out.video.i_height != i_canvas_height )
318     {
319         msg_Warn( p_filter, "Looks like something went wrong. "
320                   "Output size is %dx%d while we asked for %dx%d",
321                   p_filter->fmt_out.video.i_width,
322                   p_filter->fmt_out.video.i_height,
323                   i_canvas_width, i_canvas_height );
324     }
325
326     p_filter->pf_video_filter = Filter;
327
328     return VLC_SUCCESS;
329 }
330
331 /*****************************************************************************
332  *
333  *****************************************************************************/
334 static void Destroy( vlc_object_t *p_this )
335 {
336     filter_t *p_filter = (filter_t *)p_this;
337     filter_chain_Delete( p_filter->p_sys->p_chain );
338     free( p_filter->p_sys );
339 }
340
341 /*****************************************************************************
342  *
343  *****************************************************************************/
344 static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
345 {
346     return filter_chain_VideoFilter( p_filter->p_sys->p_chain, p_pic );
347 }
348
349 /*****************************************************************************
350  *
351  *****************************************************************************/
352 static picture_t *video_new( filter_t *p_filter )
353 {
354     return filter_NewPicture( (filter_t*)p_filter->p_owner );
355 }
356
357 static void video_del( filter_t *p_filter, picture_t *p_pic )
358 {
359     return filter_DeletePicture( (filter_t*)p_filter->p_owner, p_pic );
360 }
361
362 static int alloc_init( filter_t *p_filter, void *p_data )
363 {
364     p_filter->p_owner = p_data;
365     p_filter->pf_vout_buffer_new = video_new;
366     p_filter->pf_vout_buffer_del = video_del;
367     return VLC_SUCCESS;
368 }