Fix typo.
[vlc.git] / modules / video_filter / wall.c
1 /*****************************************************************************
2  * wall.c : Wall video plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2000-2009 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Samuel Hocevar <sam@zoy.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 #include <assert.h>
32
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_video_splitter.h>
36
37 /* FIXME it is needed for VOUT_ALIGN_* only */
38 #include <vlc_vout.h>
39
40 /*****************************************************************************
41  * Module descriptor
42  *****************************************************************************/
43 #define COLS_TEXT N_("Number of columns")
44 #define COLS_LONGTEXT N_("Number of horizontal windows in " \
45     "which to split the video.")
46
47 #define ROWS_TEXT N_("Number of rows")
48 #define ROWS_LONGTEXT N_("Number of vertical windows in " \
49     "which to split the video.")
50
51 #define ACTIVE_TEXT N_("Active windows")
52 #define ACTIVE_LONGTEXT N_("Comma-separated list of active windows, " \
53     "defaults to all")
54
55 #define ASPECT_TEXT N_("Element aspect ratio")
56 #define ASPECT_LONGTEXT N_("Aspect ratio of the individual displays " \
57    "building the wall.")
58
59 #define CFG_PREFIX "wall-"
60
61 static int  Open ( vlc_object_t * );
62 static void Close( vlc_object_t * );
63
64 vlc_module_begin()
65     set_description( N_("Wall video filter") )
66     set_shortname( N_("Image wall" ))
67     set_capability( "video splitter", 0 )
68     set_category( CAT_VIDEO )
69     set_subcategory( SUBCAT_VIDEO_VFILTER )
70
71     add_integer( CFG_PREFIX "cols", 3, NULL, COLS_TEXT, COLS_LONGTEXT, false )
72     add_integer( CFG_PREFIX "rows", 3, NULL, ROWS_TEXT, ROWS_LONGTEXT, false )
73     add_string( CFG_PREFIX "active", NULL, NULL, ACTIVE_TEXT, ACTIVE_LONGTEXT,
74                  true )
75     add_string( CFG_PREFIX "element-aspect", "4:3", NULL, ASPECT_TEXT, ASPECT_LONGTEXT, false )
76
77     add_shortcut( "wall" )
78     set_callbacks( Open, Close )
79 vlc_module_end()
80
81 /*****************************************************************************
82  * Local prototypes
83  *****************************************************************************/
84 static const char *const ppsz_filter_options[] = {
85     "cols", "rows", "active", "element-aspect", NULL
86 };
87
88 /* */
89 typedef struct
90 {
91     bool b_active;
92     int  i_output;
93     int  i_width;
94     int  i_height;
95     int  i_align;
96     int  i_left;
97     int  i_top;
98 } wall_output_t;
99
100 #define ROW_MAX (15)
101 #define COL_MAX (15)
102 struct video_splitter_sys_t
103 {
104     int           i_col;
105     int           i_row;
106     int           i_output;
107     wall_output_t pp_output[COL_MAX][ROW_MAX]; /* [x][y] */
108 };
109
110 static int Filter( video_splitter_t *, picture_t *pp_dst[], picture_t * );
111 static int Mouse( video_splitter_t *, vlc_mouse_t *,
112                   int i_index,
113                   const vlc_mouse_t *p_old, const vlc_mouse_t *p_new );
114
115 /**
116  * This function allocates and initializes a Wall splitter module.
117  */
118 static int Open( vlc_object_t *p_this )
119 {
120     video_splitter_t *p_splitter = (video_splitter_t*)p_this;
121     video_splitter_sys_t *p_sys;
122
123     p_splitter->p_sys = p_sys = malloc( sizeof(*p_sys) );
124     if( !p_sys )
125         return VLC_ENOMEM;
126
127     config_ChainParse( p_splitter, CFG_PREFIX, ppsz_filter_options,
128                        p_splitter->p_cfg );
129
130     /* */
131     p_sys->i_col = var_CreateGetInteger( p_splitter, CFG_PREFIX "cols" );
132     p_sys->i_col = __MAX( 1, __MIN( COL_MAX, p_sys->i_col ) );
133
134     p_sys->i_row = var_CreateGetInteger( p_splitter, CFG_PREFIX "rows" );
135     p_sys->i_row = __MAX( 1, __MIN( ROW_MAX, p_sys->i_row ) );
136
137     msg_Dbg( p_splitter, "opening a %i x %i wall",
138              p_sys->i_col, p_sys->i_row );
139
140     /* */
141     char *psz_state = var_CreateGetNonEmptyString( p_splitter, CFG_PREFIX "active" );
142
143     /* */
144     bool pb_active[COL_MAX*ROW_MAX];
145     for( int i = 0; i < COL_MAX*ROW_MAX; i++ )
146         pb_active[i] = psz_state == NULL;
147
148     /* Parse active list if provided */
149     char *psz_tmp = psz_state;
150     while( psz_tmp && *psz_tmp )
151     {
152         char *psz_next = strchr( psz_tmp, ',' );
153         if( psz_next )
154             *psz_next++ = '\0';
155
156         const int i_index = atoi( psz_tmp );
157         if( i_index >= 0 && i_index < COL_MAX*ROW_MAX )
158             pb_active[i_index] = true;
159
160         psz_tmp = psz_next;
161     }
162     free( psz_state );
163
164     /* Parse aspect ratio if provided */
165     int i_aspect = 0;
166     char *psz_aspect = var_CreateGetNonEmptyString( p_splitter,
167                                                     CFG_PREFIX "element-aspect" );
168     if( psz_aspect )
169     {
170         int i_ar_num, i_ar_den;
171         if( sscanf( psz_aspect, "%d:%d", &i_ar_num, &i_ar_den ) == 2 &&
172             i_ar_num > 0 && i_ar_den > 0 )
173         {
174             i_aspect = i_ar_num * VOUT_ASPECT_FACTOR / i_ar_den;
175         }
176         else
177         {
178             msg_Warn( p_splitter, "invalid aspect ratio specification" );
179         }
180         free( psz_aspect );
181     }
182     if( i_aspect <= 0 )
183         i_aspect = 4 * VOUT_ASPECT_FACTOR / 3;
184
185     /* Compute placements/size of the windows */
186     const unsigned w1 = ( p_splitter->fmt.i_width / p_sys->i_col ) & ~1;
187     const unsigned h1 = ( w1 * VOUT_ASPECT_FACTOR / i_aspect ) & ~1;
188
189     const unsigned h2 = ( p_splitter->fmt.i_height / p_sys->i_row ) & ~1;
190     const unsigned w2 = ( h2 * i_aspect / VOUT_ASPECT_FACTOR ) & ~1;
191
192     unsigned i_target_width;
193     unsigned i_target_height;
194     unsigned i_hstart, i_hend;
195     unsigned i_vstart, i_vend;
196     bool b_vstart_rounded;
197     bool b_hstart_rounded;
198
199     if( h1 * p_sys->i_row < p_splitter->fmt.i_height )
200     {
201         i_target_width = w2;
202         i_target_height = h2;
203
204         i_vstart = 0;
205         b_vstart_rounded = false;
206         i_vend = p_splitter->fmt.i_height;
207
208         unsigned i_tmp = i_target_width * p_sys->i_col;
209         while( i_tmp < p_splitter->fmt.i_width )
210             i_tmp += p_sys->i_col;
211
212         i_hstart = (( i_tmp - p_splitter->fmt.i_width ) / 2)&~1;
213         b_hstart_rounded  = ( ( i_tmp - p_splitter->fmt.i_width ) % 2 ) ||
214             ( ( ( i_tmp - p_splitter->fmt.i_width ) / 2 ) & 1 );
215         i_hend = i_hstart + p_splitter->fmt.i_width;
216     }
217     else
218     {
219         i_target_height = h1;
220         i_target_width = w1;
221
222         i_hstart = 0;
223         b_hstart_rounded = false;
224         i_hend = p_splitter->fmt.i_width;
225
226         unsigned i_tmp = i_target_height * p_sys->i_row;
227         while( i_tmp < p_splitter->fmt.i_height )
228             i_tmp += p_sys->i_row;
229
230         i_vstart = ( ( i_tmp - p_splitter->fmt.i_height ) / 2 ) & ~1;
231         b_vstart_rounded  = ( ( i_tmp - p_splitter->fmt.i_height ) % 2 ) ||
232             ( ( ( i_tmp - p_splitter->fmt.i_height ) / 2 ) & 1 );
233         i_vend = i_vstart + p_splitter->fmt.i_height;
234     }
235     msg_Dbg( p_splitter, "target resolution %dx%d", i_target_width, i_target_height );
236     msg_Dbg( p_splitter, "target window (%d,%d)-(%d,%d)", i_hstart,i_vstart,i_hend,i_vend );
237
238     int i_active = 0;
239     for( int y = 0, i_top = 0; y < p_sys->i_row; y++ )
240     {
241         /* */
242         int i_height = 0;
243         int i_halign = 0;
244         if( y * i_target_height >= i_vstart &&
245             ( y + 1 ) * i_target_height <= i_vend )
246         {
247             i_height = i_target_height;
248         }
249         else if( ( y + 1 ) * i_target_height < i_vstart ||
250                  ( y * i_target_height ) > i_vend )
251         {
252             i_height = 0;
253         }
254         else
255         {
256             i_height = ( i_target_height -
257                          i_vstart%i_target_height );
258             if(  y >= ( p_sys->i_row / 2 ) )
259             {
260                 i_halign = VOUT_ALIGN_TOP;
261                 i_height -= b_vstart_rounded ? 2: 0;
262             }
263             else
264             {
265                 i_halign = VOUT_ALIGN_BOTTOM;
266             }
267         }
268
269         /* */
270         for( int x = 0, i_left = 0; x < p_sys->i_col; x++ )
271         {
272             wall_output_t *p_output = &p_sys->pp_output[x][y];
273
274             /* */
275             int i_width;
276             int i_valign = 0;
277             if( x*i_target_width >= i_hstart &&
278                 (x+1)*i_target_width <= i_hend )
279             {
280                 i_width = i_target_width;
281             }
282             else if( ( x + 1 ) * i_target_width < i_hstart ||
283                      ( x * i_target_width ) > i_hend )
284             {
285                 i_width = 0;
286             }
287             else
288             {
289                 i_width = ( i_target_width - i_hstart % i_target_width );
290                 if( x >= ( p_sys->i_col / 2 ) )
291                 {
292                     i_valign = VOUT_ALIGN_LEFT;
293                     i_width -= b_hstart_rounded ? 2: 0;
294                 }
295                 else
296                 {
297                     i_valign = VOUT_ALIGN_RIGHT;
298                 }
299             }
300
301             /* */
302             p_output->b_active = pb_active[y * p_sys->i_col + x] &&
303                                  i_height > 0 && i_width > 0;
304             p_output->i_output = -1;
305             p_output->i_align = i_valign | i_halign;
306             p_output->i_width = i_width;
307             p_output->i_height = i_height;
308             p_output->i_left = i_left;
309             p_output->i_top = i_top;
310
311             msg_Dbg( p_splitter, "window %dx%d at %d:%d size %dx%d", 
312                      x, y, i_left, i_top, i_width, i_height );
313
314             if( p_output->b_active )
315                 i_active++;
316
317             i_left += i_width;
318         }
319         i_top += i_height;
320     }
321     if( i_active <= 0 )
322     {
323         msg_Err( p_splitter, "No active video output" );
324         free( p_sys );
325         return VLC_EGENERIC;
326     }
327
328     /* Setup output configuration */
329     p_splitter->i_output = i_active;
330     p_splitter->p_output = calloc( p_splitter->i_output,
331                                    sizeof(*p_splitter->p_output) );
332     if( !p_splitter->p_output )
333     {
334         free( p_sys );
335         return VLC_ENOMEM;
336     }
337     for( int y = 0, i_output = 0; y < p_sys->i_row; y++ )
338     {
339         for( int x = 0; x < p_sys->i_col; x++ )
340         {
341             wall_output_t *p_output = &p_sys->pp_output[x][y];
342             if( !p_output->b_active )
343                 continue;
344
345             p_output->i_output = i_output++;
346
347             video_splitter_output_t *p_cfg = &p_splitter->p_output[p_output->i_output];
348
349             video_format_Copy( &p_cfg->fmt, &p_splitter->fmt );
350             p_cfg->fmt.i_visible_width  =
351             p_cfg->fmt.i_width          = p_output->i_width;
352             p_cfg->fmt.i_visible_height =
353             p_cfg->fmt.i_height         = p_output->i_height;
354             p_cfg->fmt.i_aspect         = (int64_t)i_aspect * i_target_height * p_output->i_width / i_target_width / p_output->i_height;
355
356             p_cfg->window.i_x     = p_output->i_left; /* FIXME relative to video-x/y (TODO in wrapper.c) ? */
357             p_cfg->window.i_y     = p_output->i_top;
358             p_cfg->window.i_align = p_output->i_align;
359             p_cfg->psz_module = NULL;
360         }
361     }
362
363     /* */
364     p_splitter->pf_filter = Filter;
365     p_splitter->pf_mouse = Mouse;
366
367     return VLC_SUCCESS;
368 }
369
370 /**
371  * Terminate a splitter module.
372  */
373 static void Close( vlc_object_t *p_this )
374 {
375     video_splitter_t *p_splitter = (video_splitter_t*)p_this;
376     video_splitter_sys_t *p_sys = p_splitter->p_sys;
377
378     free( p_splitter->p_output );
379     free( p_sys );
380 }
381
382 static int Filter( video_splitter_t *p_splitter, picture_t *pp_dst[], picture_t *p_src )
383 {
384     video_splitter_sys_t *p_sys = p_splitter->p_sys;
385
386     if( video_splitter_NewPicture( p_splitter, pp_dst ) )
387     {
388         picture_Release( p_src );
389         return VLC_EGENERIC;
390     }
391
392     for( int y = 0; y < p_sys->i_row; y++ )
393     {
394         for( int x = 0; x < p_sys->i_col; x++ )
395         {
396             wall_output_t *p_output = &p_sys->pp_output[x][y];
397             if( !p_output->b_active )
398                 continue;
399
400             video_splitter_output_t *p_cfg = &p_splitter->p_output[p_output->i_output];
401             picture_t *p_dst = pp_dst[p_output->i_output];
402
403             /* */
404             picture_t tmp = *p_src;
405             for( int i = 0; i < tmp.i_planes; i++ )
406             {
407                 plane_t *p0 = &tmp.p[0];
408                 plane_t *p = &tmp.p[i];
409                 const int i_y = p_output->i_top  * p->i_visible_pitch / p0->i_visible_pitch;
410                 const int i_x = p_output->i_left * p->i_visible_lines / p0->i_visible_lines;
411
412                 p->p_pixels += i_y * p->i_pitch + ( i_x - (i_x % p->i_pixel_pitch));
413             }
414             picture_Copy( p_dst, &tmp );
415         }
416     }
417
418     picture_Release( p_src );
419     return VLC_SUCCESS;
420 }
421 static int Mouse( video_splitter_t *p_splitter, vlc_mouse_t *p_mouse,
422                   int i_index,
423                   const vlc_mouse_t *p_old, const vlc_mouse_t *p_new )
424 {
425     video_splitter_sys_t *p_sys = p_splitter->p_sys;
426
427     for( int y = 0; y < p_sys->i_row; y++ )
428     {
429         for( int x = 0; x < p_sys->i_col; x++ )
430         {
431             wall_output_t *p_output = &p_sys->pp_output[x][y];
432             if( p_output->b_active && p_output->i_output == i_index )
433             {
434                 *p_mouse = *p_new;
435                 p_mouse->i_x += p_output->i_left;
436                 p_mouse->i_y += p_output->i_top;
437                 return VLC_SUCCESS;
438             }
439         }
440     }
441     assert(0);
442     return VLC_EGENERIC;
443 }
444