]> git.sesse.net Git - vlc/blob - modules/video_filter/puzzle.c
6c6433bbeea541dfe0b7e4292a96195491acd78e
[vlc] / modules / video_filter / puzzle.c
1 /*****************************************************************************
2  * puzzle.c : Puzzle game
3  *****************************************************************************
4  * Copyright (C) 2005-2009 VLC authors and VideoLAN
5  * Copyright (C) 2013      Vianney Boyer
6  * $Id$
7  *
8  * Authors: Antoine Cellerier <dionoea -at- videolan -dot- org>
9  *          Vianney Boyer <vlcvboyer -at- gmail -dot- com>
10  *
11  * This program is free software; you can redistribute it and/or modify it
12  * under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation; either version 2.1 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33 #include <math.h>
34
35 #include <vlc_common.h>
36 #include <vlc_plugin.h>
37 #include <vlc_filter.h>
38 #include <vlc_rand.h>
39
40 #include "filter_picture.h"
41
42 #include "puzzle.h"
43 #include "puzzle_bezier.h"
44 #include "puzzle_lib.h"
45 #include "puzzle_pce.h"
46 #include "puzzle_mgt.h"
47
48 /*****************************************************************************
49  * Module descriptor
50  *****************************************************************************/
51 #define ROWS_TEXT N_("Number of puzzle rows")
52 #define ROWS_LONGTEXT N_("Number of puzzle rows")
53 #define COLS_TEXT N_("Number of puzzle columns")
54 #define COLS_LONGTEXT N_("Number of puzzle columns")
55 #define MODE_TEXT N_("Game mode")
56 #define MODE_LONGTEXT N_("Select game mode variation from jigsaw puzzle to sliding puzzle.")
57 #define BORDER_TEXT N_("Border")
58 #define BORDER_LONGTEXT N_("Unshuffled Border width.")
59 #define PREVIEW_TEXT N_("Small preview")
60 #define PREVIEW_LONGTEXT N_("Show small preview.")
61 #define PREVIEWSIZE_TEXT N_("Small preview size")
62 #define PREVIEWSIZE_LONGTEXT N_("Show small preview size (percent of source).")
63 #define SHAPE_SIZE_TEXT N_("Piece edge shape size")
64 #define SHAPE_SIZE_LONGTEXT N_("Size of the curve along the piece's edge")
65 #define AUTO_SHUFFLE_TEXT N_("Auto shuffle")
66 #define AUTO_SHUFFLE_LONGTEXT N_("Auto shuffle delay during game")
67 #define AUTO_SOLVE_TEXT N_("Auto solve")
68 #define AUTO_SOLVE_LONGTEXT N_("Auto solve delay during game")
69 #define ROTATION_TEXT N_("Rotation")
70 #define ROTATION_LONGTEXT N_("Rotation parameter: none;180;90-270;mirror")
71
72 const int pi_mode_values[] = { (int) 0, (int) 1, (int) 2, (int) 3 };
73 const char *const ppsz_mode_descriptions[] = { N_("jigsaw puzzle"), N_("sliding puzzle"), N_("swap puzzle"), N_("exchange puzzle") };
74 const int pi_rotation_values[] = { (int) 0, (int) 1, (int) 2, (int) 3 };
75 const char *const ppsz_rotation_descriptions[] = { N_("0"), N_("0/180"), N_("0/90/180/270"), N_("0/90/180/270/mirror") };
76
77 #define CFG_PREFIX "puzzle-"
78
79 int  Open ( vlc_object_t * );
80 void Close( vlc_object_t * );
81
82 vlc_module_begin()
83     set_description( N_("Puzzle interactive game video filter") )
84     set_shortname( N_( "Puzzle" ))
85     set_capability( "video filter2", 0 )
86     set_category( CAT_VIDEO )
87     set_subcategory( SUBCAT_VIDEO_VFILTER )
88
89     add_integer_with_range( CFG_PREFIX "rows", 4, 2, 16,
90                             ROWS_TEXT, ROWS_LONGTEXT, false )
91     add_integer_with_range( CFG_PREFIX "cols", 4, 2, 16,
92                             COLS_TEXT, COLS_LONGTEXT, false )
93     add_integer_with_range( CFG_PREFIX "border", 3, 0, 40,
94               BORDER_TEXT, BORDER_LONGTEXT, false )
95     add_bool( CFG_PREFIX "preview", false,
96               PREVIEW_TEXT, PREVIEW_LONGTEXT, false )
97     add_integer_with_range( CFG_PREFIX "preview-size", 15, 0, 100,
98               PREVIEWSIZE_TEXT, PREVIEWSIZE_LONGTEXT, false )
99     add_integer_with_range( CFG_PREFIX "shape-size", 90, 0, 100,
100               SHAPE_SIZE_TEXT, SHAPE_SIZE_LONGTEXT, false )
101     add_integer_with_range( CFG_PREFIX "auto-shuffle", 0, 0, 30000,
102               AUTO_SHUFFLE_TEXT, AUTO_SHUFFLE_LONGTEXT, false )
103     add_integer_with_range( CFG_PREFIX "auto-solve", 0, 0, 30000,
104               AUTO_SOLVE_TEXT, AUTO_SOLVE_LONGTEXT, false )
105     add_integer( CFG_PREFIX "rotation", 0,
106               ROTATION_TEXT, ROTATION_LONGTEXT, false )
107         change_integer_list(pi_rotation_values, ppsz_rotation_descriptions )
108     add_integer( CFG_PREFIX "mode", 0,
109               MODE_TEXT, MODE_LONGTEXT, false )
110         change_integer_list(pi_mode_values, ppsz_mode_descriptions )
111
112     set_callbacks( Open, Close )
113 vlc_module_end()
114
115 /*****************************************************************************
116  * Local prototypes
117  *****************************************************************************/
118
119 const char *const ppsz_filter_options[] = {
120     "rows", "cols","border", "preview", "preview-size", "mode", "shape-size", "auto-shuffle", "auto-solve",  "rotation", NULL
121 };
122
123 /**
124  * Open the filter
125  */
126 int Open( vlc_object_t *p_this )
127 {
128     filter_t *p_filter = (filter_t *)p_this;
129     filter_sys_t *p_sys;
130
131     /* Assert video in match with video out */
132     if( !es_format_IsSimilar( &p_filter->fmt_in, &p_filter->fmt_out ) ) {
133         msg_Err( p_filter, "Input and output format does not match" );
134         return VLC_EGENERIC;
135     }
136
137     const vlc_chroma_description_t *p_chroma =
138         vlc_fourcc_GetChromaDescription( p_filter->fmt_in.video.i_chroma );
139     if( p_chroma == NULL || p_chroma->plane_count == 0 )
140         return VLC_EGENERIC;
141
142     /* Allocate structure */
143     p_filter->p_sys = p_sys = calloc(1, sizeof( *p_sys ) );
144     if( !p_sys )
145         return VLC_ENOMEM;
146
147     /* init some values */
148     p_sys->b_shuffle_rqst    = true;
149     p_sys->b_change_param    = true;
150     p_sys->i_mouse_drag_pce  = NO_PCE;
151     p_sys->i_pointed_pce     = NO_PCE;
152     p_sys->i_magnet_accuracy = 3;
153
154     /* Generate values of random bezier shapes */
155     p_sys->ps_bezier_pts_H = calloc( SHAPES_QTY, sizeof( point_t *) );
156     if( !p_sys->ps_bezier_pts_H )
157     {
158         free(p_filter->p_sys);
159         p_filter->p_sys = NULL;
160         return VLC_ENOMEM;
161     }
162     for (int32_t i_shape = 0; i_shape<SHAPES_QTY; i_shape++)
163         p_sys->ps_bezier_pts_H[i_shape] = puzzle_rand_bezier(7);
164
165
166     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
167                        p_filter->p_cfg );
168
169     vlc_mutex_init( &p_sys->lock );
170     vlc_mutex_init( &p_sys->pce_lock );
171
172     p_sys->s_new_param.i_rows =
173         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "rows" );
174     p_sys->s_new_param.i_cols =
175         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "cols" );
176     p_sys->s_new_param.i_border =
177         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "border" );
178     p_sys->s_new_param.b_preview =
179         var_CreateGetBoolCommand( p_filter, CFG_PREFIX "preview" );
180     p_sys->s_new_param.i_preview_size =
181         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "preview-size" );
182     p_sys->s_new_param.i_shape_size =
183         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "shape-size" );
184     p_sys->s_new_param.i_auto_shuffle_speed =
185         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "auto-shuffle" );
186     p_sys->s_new_param.i_auto_solve_speed =
187         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "auto-solve" );
188     p_sys->s_new_param.i_rotate =
189         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "rotation" );
190     p_sys->s_new_param.i_mode =
191         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "mode" );
192
193     var_AddCallback( p_filter, CFG_PREFIX "rows",         puzzle_Callback, p_sys );
194     var_AddCallback( p_filter, CFG_PREFIX "cols",         puzzle_Callback, p_sys );
195     var_AddCallback( p_filter, CFG_PREFIX "border",       puzzle_Callback, p_sys );
196     var_AddCallback( p_filter, CFG_PREFIX "preview",      puzzle_Callback, p_sys );
197     var_AddCallback( p_filter, CFG_PREFIX "preview-size", puzzle_Callback, p_sys );
198     var_AddCallback( p_filter, CFG_PREFIX "shape-size",   puzzle_Callback, p_sys );
199     var_AddCallback( p_filter, CFG_PREFIX "auto-shuffle", puzzle_Callback, p_sys );
200     var_AddCallback( p_filter, CFG_PREFIX "auto-solve",   puzzle_Callback, p_sys );
201     var_AddCallback( p_filter, CFG_PREFIX "rotation",     puzzle_Callback, p_sys );
202     var_AddCallback( p_filter, CFG_PREFIX "mode",     puzzle_Callback, p_sys );
203
204     p_filter->pf_video_filter = Filter;
205     p_filter->pf_video_mouse = puzzle_mouse;
206
207     return VLC_SUCCESS;
208 }
209
210 /**
211  * Close the filter
212  */
213 void Close( vlc_object_t *p_this ) {
214     filter_t *p_filter = (filter_t *)p_this;
215     filter_sys_t *p_sys = p_filter->p_sys;
216
217     var_DelCallback( p_filter, CFG_PREFIX "rows",          puzzle_Callback, p_sys );
218     var_DelCallback( p_filter, CFG_PREFIX "cols",          puzzle_Callback, p_sys );
219     var_DelCallback( p_filter, CFG_PREFIX "border",        puzzle_Callback, p_sys );
220     var_DelCallback( p_filter, CFG_PREFIX "preview",       puzzle_Callback, p_sys );
221     var_DelCallback( p_filter, CFG_PREFIX "preview-size",  puzzle_Callback, p_sys );
222     var_DelCallback( p_filter, CFG_PREFIX "shape-size",    puzzle_Callback, p_sys );
223     var_DelCallback( p_filter, CFG_PREFIX "auto-shuffle",  puzzle_Callback, p_sys );
224     var_DelCallback( p_filter, CFG_PREFIX "auto-solve",    puzzle_Callback, p_sys );
225     var_DelCallback( p_filter, CFG_PREFIX "rotation",      puzzle_Callback, p_sys );
226     var_DelCallback( p_filter, CFG_PREFIX "mode",          puzzle_Callback, p_sys );
227
228     vlc_mutex_destroy( &p_sys->lock );
229     vlc_mutex_destroy( &p_sys->pce_lock );
230
231     /* Free allocated memory */
232     puzzle_free_ps_puzzle_array ( p_filter );
233     puzzle_free_ps_pieces_shapes ( p_filter);
234     puzzle_free_ps_pieces ( p_filter );
235     free(p_sys->ps_desk_planes);
236     free(p_sys->ps_pict_planes);
237     free( p_sys->pi_order );
238
239     for (int32_t i_shape = 0; i_shape<SHAPES_QTY; i_shape++)
240         free(p_sys->ps_bezier_pts_H[i_shape]);
241     free(p_sys->ps_bezier_pts_H);
242
243     free( p_sys );
244 }
245
246 /**
247  * Filter a picture
248  */
249 picture_t *Filter( filter_t *p_filter, picture_t *p_pic_in ) {
250     if( !p_pic_in || !p_filter) return NULL;
251
252     const video_format_t  *p_fmt_in = &p_filter->fmt_in.video;
253     filter_sys_t *p_sys = p_filter->p_sys;
254
255     picture_t *p_pic_out = filter_NewPicture( p_filter );
256     if( !p_pic_out ) {
257         picture_Release( p_pic_in );
258         return NULL;
259     }
260
261     int i_ret = 0;
262     p_sys->b_bake_request = false;
263
264     if ((p_sys->pi_order == NULL) || (p_sys->ps_desk_planes == NULL) || (p_sys->ps_pict_planes == NULL)  || (p_sys->ps_puzzle_array == NULL) || (p_sys->ps_pieces == NULL))
265         p_sys->b_init = false;
266
267     if ((p_sys->ps_pieces_shapes == NULL) && p_sys->s_current_param.b_advanced && (p_sys->s_current_param.i_shape_size != 0))
268         p_sys->b_init = false;
269
270     /* assert initialized & allocated data match with current frame characteristics */
271     if ( p_sys->s_allocated.i_planes != p_pic_out->i_planes)
272         p_sys->b_init = false;
273     p_sys->s_current_param.i_planes = p_pic_out->i_planes;
274     if (p_sys->ps_pict_planes != NULL) {
275         for (uint8_t i_plane = 0; i_plane < p_sys->s_allocated.i_planes; i_plane++) {
276             if ( (p_sys->ps_pict_planes[i_plane].i_lines != p_pic_in->p[i_plane].i_visible_lines)
277                     || (p_sys->ps_pict_planes[i_plane].i_width != p_pic_in->p[i_plane].i_visible_pitch / p_pic_in->p[i_plane].i_pixel_pitch)
278                     || (p_sys->ps_desk_planes[i_plane].i_lines != p_pic_out->p[i_plane].i_visible_lines)
279                     || (p_sys->ps_desk_planes[i_plane].i_width != p_pic_out->p[i_plane].i_visible_pitch / p_pic_out->p[i_plane].i_pixel_pitch) )
280                 p_sys->b_init = false;
281         }
282     }
283
284     p_sys->s_current_param.i_pict_width  = (int) p_pic_in->p[0].i_visible_pitch / p_pic_in->p[0].i_pixel_pitch;
285     p_sys->s_current_param.i_pict_height = (int) p_pic_in->p[0].i_visible_lines;
286     p_sys->s_current_param.i_desk_width  = (int) p_pic_out->p[0].i_visible_pitch / p_pic_out->p[0].i_pixel_pitch;
287     p_sys->s_current_param.i_desk_height = (int) p_pic_out->p[0].i_visible_lines;
288
289     /* assert no mismatch between sizes */
290     if (    p_sys->s_current_param.i_pict_width  != p_sys->s_current_param.i_desk_width
291          || p_sys->s_current_param.i_pict_height != p_sys->s_current_param.i_desk_height
292          || p_sys->s_current_param.i_pict_width  != (int) p_fmt_in->i_visible_width
293          || p_sys->s_current_param.i_pict_height != (int) p_fmt_in->i_visible_height ) {
294         picture_Release(p_pic_in);
295         picture_Release(p_pic_out);
296         return NULL;
297     }
298
299     vlc_mutex_lock( &p_sys->lock );
300
301     /* check if we have to compute initial data */
302     if ( p_sys->b_change_param || p_sys->b_bake_request || !p_sys->b_init ) {
303         if ( p_sys->s_allocated.i_rows != p_sys->s_new_param.i_rows
304                 || p_sys->s_allocated.i_cols != p_sys->s_new_param.i_cols
305                 || p_sys->s_allocated.i_rotate != p_sys->s_new_param.i_rotate
306                 || p_sys->s_allocated.i_mode != p_sys->s_new_param.i_mode
307                 || p_sys->b_bake_request  || !p_sys->b_init )
308         {
309             p_sys->b_bake_request = true;
310             p_sys->b_init = false;
311             p_sys->b_shuffle_rqst = true;
312             p_sys->b_shape_init = false;
313         }
314
315         if ( p_sys->s_current_param.i_border != p_sys->s_new_param.i_border
316                 || p_sys->s_current_param.i_shape_size != p_sys->s_new_param.i_shape_size )
317         {
318             p_sys->b_bake_request = true;
319             p_sys->b_shape_init = false;
320         }
321
322         /* depending on the game selected, set associated internal flags */
323         switch ( p_sys->s_new_param.i_mode )
324         {
325           case 0:  /* jigsaw puzzle */
326             p_sys->s_new_param.b_advanced    = true;
327             p_sys->s_new_param.b_blackslot   = false;
328             p_sys->s_new_param.b_near        = false;
329             break;
330           case 1:  /* sliding puzzle */
331             p_sys->s_new_param.b_advanced    = false;
332             p_sys->s_new_param.b_blackslot   = true;
333             p_sys->s_new_param.b_near        = true;
334             break;
335           case 2:  /* swap puzzle */
336             p_sys->s_new_param.b_advanced    = false;
337             p_sys->s_new_param.b_blackslot   = false;
338             p_sys->s_new_param.b_near        = true;
339             break;
340           case 3:  /* exchange puzzle */
341             p_sys->s_new_param.b_advanced    = false;
342             p_sys->s_new_param.b_blackslot   = false;
343             p_sys->s_new_param.b_near        = false;
344             break;
345         }
346         p_sys->s_current_param.i_mode = p_sys->s_new_param.i_mode;
347
348         if ( p_sys->s_current_param.b_blackslot != p_sys->s_new_param.b_blackslot
349                 && p_sys->i_selected == NO_PCE
350                 && p_sys->s_current_param.b_blackslot )
351             p_sys->i_selected = 0;
352
353         if ( p_sys->s_current_param.i_auto_shuffle_speed != p_sys->s_new_param.i_auto_shuffle_speed )
354             p_sys->i_auto_shuffle_countdown_val = init_countdown(p_sys->s_new_param.i_auto_shuffle_speed);
355
356         if ( p_sys->s_current_param.i_auto_solve_speed != p_sys->s_new_param.i_auto_solve_speed )
357             p_sys->i_auto_solve_countdown_val = init_countdown(p_sys->s_current_param.i_auto_solve_speed);
358
359         p_sys->s_current_param.i_rows       = p_sys->s_new_param.i_rows;
360         p_sys->s_current_param.i_cols       = p_sys->s_new_param.i_cols;
361         p_sys->s_current_param.i_pieces_nbr = p_sys->s_current_param.i_rows * p_sys->s_current_param.i_cols;
362         p_sys->s_current_param.b_advanced   = p_sys->s_new_param.b_advanced;
363         if (!p_sys->s_new_param.b_advanced) {
364             p_sys->s_current_param.b_blackslot   = p_sys->s_new_param.b_blackslot;
365             p_sys->s_current_param.b_near        = p_sys->s_new_param.b_near || p_sys->s_new_param.b_blackslot;
366             p_sys->s_current_param.i_border      = 0;
367             p_sys->s_current_param.b_preview     = false;
368             p_sys->s_current_param.i_preview_size= 0;
369             p_sys->s_current_param.i_shape_size  = 0;
370             p_sys->s_current_param.i_auto_shuffle_speed  = 0;
371             p_sys->s_current_param.i_auto_solve_speed    = 0;
372             p_sys->s_current_param.i_rotate      = 0;
373         }
374         else
375         {
376             p_sys->s_current_param.b_blackslot = false;
377             p_sys->s_current_param.b_near      = false;
378             p_sys->s_current_param.i_border    = p_sys->s_new_param.i_border;
379             p_sys->s_current_param.b_preview   = p_sys->s_new_param.b_preview;
380             p_sys->s_current_param.i_preview_size        = p_sys->s_new_param.i_preview_size;
381             p_sys->s_current_param.i_shape_size          = p_sys->s_new_param.i_shape_size;
382             p_sys->s_current_param.i_auto_shuffle_speed  = p_sys->s_new_param.i_auto_shuffle_speed;
383             p_sys->s_current_param.i_auto_solve_speed    = p_sys->s_new_param.i_auto_solve_speed;
384             p_sys->s_current_param.i_rotate     = p_sys->s_new_param.i_rotate;
385         }
386         p_sys->b_change_param = false;
387     }
388
389     vlc_mutex_unlock( &p_sys->lock );
390
391     /* generate initial puzzle data when needed */
392     if ( p_sys->b_bake_request ) {
393         if (!p_sys->b_shuffle_rqst) {
394             /* here we have to keep the same position
395              * we have to save locations before generating new data
396              */
397             save_game_t *ps_save_game = puzzle_save(p_filter);
398             if (!ps_save_game)
399                 return CopyInfoAndRelease( p_pic_out, p_pic_in );
400             i_ret = puzzle_bake( p_filter, p_pic_out, p_pic_in );
401             if ( i_ret != VLC_SUCCESS )
402             {
403                 free(ps_save_game->ps_pieces);
404                 free(ps_save_game);
405                 return CopyInfoAndRelease( p_pic_out, p_pic_in );
406             }
407             puzzle_load( p_filter, ps_save_game);
408             free(ps_save_game->ps_pieces);
409             free(ps_save_game);
410         }
411         else {
412             i_ret = puzzle_bake( p_filter, p_pic_out, p_pic_in );
413             if ( i_ret != VLC_SUCCESS )
414                 return CopyInfoAndRelease( p_pic_out, p_pic_in );
415         }
416     }
417
418     /* shuffle the desk and generate pieces data  */
419     if ( p_sys->b_shuffle_rqst && p_sys->b_init ) {
420         i_ret = puzzle_bake_piece ( p_filter );
421         if (i_ret != VLC_SUCCESS)
422             return CopyInfoAndRelease( p_pic_out, p_pic_in );
423     }
424
425     /* preset output pic */
426     if ( !p_sys->b_bake_request && !p_sys->b_shuffle_rqst && p_sys->b_init && !p_sys->b_finished )
427         puzzle_preset_desk_background(p_pic_out, 0, 127, 127);
428     else {
429         /* copy src to dst during init & bake process */
430         for( uint8_t i_plane = 0; i_plane < p_pic_out->i_planes; i_plane++ )
431             memcpy( p_pic_out->p[i_plane].p_pixels, p_pic_in->p[i_plane].p_pixels,
432                 p_pic_in->p[i_plane].i_pitch * (int32_t) p_pic_in->p[i_plane].i_visible_lines );
433     }
434
435     vlc_mutex_lock( &p_sys->pce_lock );
436
437     /* manage the game, adjust locations, groups and regenerate some corrupted data if any */
438     for (uint32_t i = 0; i < __MAX( 4, p_sys->s_allocated.i_pieces_nbr / 4 )
439                              && ( !p_sys->b_bake_request && !p_sys->b_mouse_drag
440                              && p_sys->b_init && p_sys->s_current_param.b_advanced ); i++)
441     {
442         puzzle_solve_pces_accuracy( p_filter );
443     }
444
445     for (uint32_t i = 0; i < __MAX( 4, p_sys->s_allocated.i_pieces_nbr / 4 )
446                              && ( !p_sys->b_bake_request && !p_sys->b_mouse_drag
447                              && p_sys->b_init && p_sys->s_current_param.b_advanced ); i++)
448     {
449         puzzle_solve_pces_group( p_filter );
450     }
451
452     if ( !p_sys->b_bake_request && !p_sys->b_mouse_drag && p_sys->b_init
453             && p_sys->s_current_param.b_advanced )
454         puzzle_count_pce_group( p_filter);
455     if ( !p_sys->b_bake_request && !p_sys->b_mouse_drag && p_sys->b_init
456             && p_sys->s_current_param.b_advanced ) {
457         i_ret = puzzle_sort_layers( p_filter);
458         if (i_ret != VLC_SUCCESS)
459         {
460             vlc_mutex_unlock( &p_sys->pce_lock );
461             return CopyInfoAndRelease( p_pic_out, p_pic_in );
462         }
463     }
464
465     for (uint32_t i = 0; i < __MAX( 4, p_sys->s_allocated.i_pieces_nbr / 24 )
466                             && ( !p_sys->b_bake_request && !p_sys->b_mouse_drag
467                             && p_sys->b_init && p_sys->s_current_param.b_advanced ); i++)
468     {
469         p_sys->i_calc_corn_loop++;
470         p_sys->i_calc_corn_loop %= p_sys->s_allocated.i_pieces_nbr;
471         puzzle_calculate_corners( p_filter, p_sys->i_calc_corn_loop );
472     }
473
474     /* computer moves some piece depending on auto_solve and auto_shuffle param */
475     if ( !p_sys->b_bake_request && !p_sys->b_mouse_drag && p_sys->b_init
476              && p_sys->ps_puzzle_array != NULL && p_sys->s_current_param.b_advanced )
477     {
478         puzzle_auto_shuffle( p_filter );
479         puzzle_auto_solve( p_filter );
480     }
481
482     vlc_mutex_unlock( &p_sys->pce_lock );
483
484     /* draw output pic */
485     if ( !p_sys->b_bake_request && p_sys->b_init  && p_sys->ps_puzzle_array != NULL ) {
486
487         puzzle_draw_borders(p_filter, p_pic_in, p_pic_out);
488
489         p_sys->i_pointed_pce = NO_PCE;
490         puzzle_draw_pieces(p_filter, p_pic_in, p_pic_out);
491
492         /* when puzzle_draw_pieces() has not updated p_sys->i_pointed_pce,
493          * use puzzle_find_piece to define the piece pointed by the mouse
494          */
495         if (p_sys->i_pointed_pce == NO_PCE)
496             p_sys->i_mouse_drag_pce = puzzle_find_piece( p_filter, p_sys->i_mouse_x, p_sys->i_mouse_y, -1);
497         else
498             p_sys->i_mouse_drag_pce = p_sys->i_pointed_pce;
499
500         if (p_sys->s_current_param.b_preview )
501             puzzle_draw_preview(p_filter, p_pic_in, p_pic_out);
502
503         /* highlight the selected piece when not playing jigsaw mode */
504         if ( p_sys->i_selected != NO_PCE && !p_sys->s_current_param.b_blackslot
505                 && !p_sys->s_current_param.b_advanced )
506         {
507             int32_t c = (p_sys->i_selected % p_sys->s_allocated.i_cols);
508             int32_t r = (p_sys->i_selected / p_sys->s_allocated.i_cols);
509
510             puzzle_draw_rectangle(p_pic_out,
511                 p_sys->ps_puzzle_array[r][c][0].i_x,
512                 p_sys->ps_puzzle_array[r][c][0].i_y,
513                 p_sys->ps_puzzle_array[r][c][0].i_width,
514                 p_sys->ps_puzzle_array[r][c][0].i_lines,
515                 255, 127, 127);
516         }
517
518         /* draw the blackslot when playing sliding puzzle mode */
519         if ( p_sys->i_selected != NO_PCE && p_sys->s_current_param.b_blackslot
520                 && !p_sys->s_current_param.b_advanced )
521         {
522             int32_t c = (p_sys->i_selected % p_sys->s_allocated.i_cols);
523             int32_t r = (p_sys->i_selected / p_sys->s_allocated.i_cols);
524
525             puzzle_fill_rectangle(p_pic_out,
526                 p_sys->ps_puzzle_array[r][c][0].i_x,
527                 p_sys->ps_puzzle_array[r][c][0].i_y,
528                 p_sys->ps_puzzle_array[r][c][0].i_width,
529                 p_sys->ps_puzzle_array[r][c][0].i_lines,
530                 0, 127, 127);
531         }
532
533         /* Draw the 'puzzle_shuffle' button if the puzzle is finished */
534         if ( p_sys->b_finished )
535             puzzle_draw_sign(p_pic_out, 0, 0, SHUFFLE_WIDTH, SHUFFLE_LINES, ppsz_shuffle_button, false);
536
537         /* draw an arrow at mouse pointer to indicate current action (rotation...) */
538         if ((p_sys->i_mouse_drag_pce != NO_PCE) && !p_sys->b_mouse_drag
539                 && !p_sys->b_finished && p_sys->s_current_param.b_advanced )
540         {
541             vlc_mutex_lock( &p_sys->pce_lock );
542
543             int32_t i_delta_x;
544
545             if (p_sys->s_current_param.i_rotate != 3)
546                 i_delta_x = 0;
547             else if ( (p_sys->ps_pieces[p_sys->i_mouse_drag_pce].i_actual_angle & 1) == 0)
548                 i_delta_x = p_sys->ps_desk_planes[0].i_pce_max_width / 6;
549             else
550                 i_delta_x = p_sys->ps_desk_planes[0].i_pce_max_lines / 6;
551
552             if (p_sys->s_current_param.i_rotate == 0)
553                 p_sys->i_mouse_action = 0;
554             else if (p_sys->s_current_param.i_rotate == 1)
555                 p_sys->i_mouse_action = 2;
556             else if ( p_sys->i_mouse_x >= ( p_sys->ps_pieces[p_sys->i_mouse_drag_pce].i_center_x + i_delta_x) )
557                 p_sys->i_mouse_action = -1;  /* rotate counterclockwise */
558             else if ( p_sys->i_mouse_x <= ( p_sys->ps_pieces[p_sys->i_mouse_drag_pce].i_center_x - i_delta_x) )
559                 p_sys->i_mouse_action = +1;
560             else
561                 p_sys->i_mouse_action = 4;   /* center click: only mirror */
562
563             if ( p_sys->i_mouse_action == +1 )
564                 puzzle_draw_sign(p_pic_out, p_sys->i_mouse_x - ARROW_WIDTH,
565                                  p_sys->i_mouse_y, ARROW_WIDTH, ARROW_LINES, ppsz_rot_arrow_sign, false);
566             else if ( p_sys->i_mouse_action == -1 )
567                 puzzle_draw_sign(p_pic_out, p_sys->i_mouse_x - ARROW_WIDTH,
568                                  p_sys->i_mouse_y, ARROW_WIDTH, ARROW_LINES, ppsz_rot_arrow_sign, true);
569             else if ( p_sys->i_mouse_action == 4 )
570                 puzzle_draw_sign(p_pic_out, p_sys->i_mouse_x - ARROW_WIDTH,
571                                  p_sys->i_mouse_y, ARROW_WIDTH, ARROW_LINES, ppsz_mir_arrow_sign, false);
572
573             vlc_mutex_unlock( &p_sys->pce_lock );
574         }
575     }
576
577     return CopyInfoAndRelease( p_pic_out, p_pic_in );
578 }
579
580 /*****************************************************************************
581  * Misc stuff...
582  *****************************************************************************/
583 int puzzle_Callback( vlc_object_t *p_this, char const *psz_var,
584                            vlc_value_t oldval, vlc_value_t newval,
585                            void *p_data )
586 {
587     VLC_UNUSED(p_this); VLC_UNUSED(oldval);
588     filter_sys_t *p_sys = (filter_sys_t *)p_data;
589
590     vlc_mutex_lock( &p_sys->lock );
591     if( !strcmp( psz_var, CFG_PREFIX "rows" ) ) {
592         p_sys->s_new_param.i_rows = __MAX( 1, newval.i_int );
593     }
594     else if( !strcmp( psz_var, CFG_PREFIX "cols" ) ) {
595         p_sys->s_new_param.i_cols = __MAX( 1, newval.i_int );
596     }
597     else if( !strcmp( psz_var, CFG_PREFIX "border" ) ) {
598         p_sys->s_new_param.i_border = __MAX( 0, newval.i_int );
599     }
600     else if( !strcmp( psz_var, CFG_PREFIX "preview" ) ) {
601         p_sys->s_new_param.b_preview = newval.b_bool;
602     }
603     else if( !strcmp( psz_var, CFG_PREFIX "preview-size" ) ) {
604         p_sys->s_new_param.i_preview_size = newval.i_int;
605     }
606     else if( !strcmp( psz_var, CFG_PREFIX "shape-size" ) ) {
607         p_sys->s_new_param.i_shape_size = newval.i_int;
608     }
609     else if( !strcmp( psz_var, CFG_PREFIX "auto-shuffle" ) ) {
610         p_sys->s_new_param.i_auto_shuffle_speed = newval.i_int;
611     }
612     else if( !strcmp( psz_var, CFG_PREFIX "auto-solve" ) ) {
613         p_sys->s_new_param.i_auto_solve_speed = newval.i_int;
614     }
615     else if( !strcmp( psz_var, CFG_PREFIX "rotation" ) ) {
616         p_sys->s_new_param.i_rotate = newval.i_int;
617     }
618     else if( !strcmp( psz_var, CFG_PREFIX "mode" ) ) {
619         p_sys->s_new_param.i_mode = newval.i_int;
620     }
621
622     p_sys->b_change_param = true;
623     vlc_mutex_unlock( &p_sys->lock );
624
625     return VLC_SUCCESS;
626 }
627
628 /* mouse callback */
629 int puzzle_mouse( filter_t *p_filter, vlc_mouse_t *p_mouse,
630                   const vlc_mouse_t *p_old, const vlc_mouse_t *p_new )
631 {
632     filter_sys_t *p_sys = p_filter->p_sys;
633     const video_format_t  *p_fmt_in = &p_filter->fmt_in.video;
634
635     /* Only take events inside the puzzle area */
636     if( p_new->i_x < 0 || p_new->i_x >= (int)p_fmt_in->i_width ||
637         p_new->i_y < 0 || p_new->i_y >= (int)p_fmt_in->i_height )
638         return VLC_EGENERIC;
639
640     if (! p_sys->b_init || p_sys->b_change_param) {
641         *p_mouse = *p_new;
642         return VLC_SUCCESS;
643     }
644
645     p_sys->i_mouse_x = p_new->i_x;
646     p_sys->i_mouse_y = p_new->i_y;
647
648     /* If the puzzle is finished, shuffle it if needed */
649     if( p_sys->b_finished ) {
650         p_sys->b_mouse_drag = false;
651         p_sys->b_mouse_mvt = false;
652         if( vlc_mouse_HasPressed( p_old, p_new, MOUSE_BUTTON_LEFT ) &&
653             p_new->i_x < SHUFFLE_WIDTH && p_new->i_y < SHUFFLE_LINES )
654         {
655             p_sys->b_shuffle_rqst = true;
656             return VLC_EGENERIC;
657         }
658         else
659         {
660             /* otherwise we can forward the mouse */
661             *p_mouse = *p_new;
662             return VLC_SUCCESS;
663         }
664     }
665
666     if ( !p_sys->s_current_param.b_advanced ) {
667         /* "square" game mode (sliding puzzle, swap...) */
668         const bool b_clicked = vlc_mouse_HasPressed( p_old, p_new, MOUSE_BUTTON_LEFT );
669
670         if( b_clicked )
671         {
672             /* */
673             const int32_t i_border_width = p_fmt_in->i_width * p_sys->s_current_param.i_border / 100 / 2;
674             const int32_t i_border_height = p_fmt_in->i_height * p_sys->s_current_param.i_border / 100 / 2;
675             const int32_t i_pos_x = (p_new->i_x - i_border_width) * p_sys->s_allocated.i_cols / (p_fmt_in->i_width - 2*i_border_width);
676             const int32_t i_pos_y = (p_new->i_y - i_border_height) * p_sys->s_allocated.i_rows / (p_fmt_in->i_height - 2*i_border_height);
677
678             const int32_t i_pos = i_pos_y * p_sys->s_allocated.i_cols + i_pos_x;
679             p_sys->i_mouse_drag_pce = i_pos;
680
681             /* do not take into account if border clicked */
682             if ((p_new->i_x <= i_border_width) || (p_new->i_y <=  i_border_height) || (p_new->i_x >= (int) p_fmt_in->i_width -  i_border_width) || (p_new->i_y >= (int) p_fmt_in->i_height -  i_border_height ) )
683             {
684                 *p_mouse = *p_new;
685                 return VLC_SUCCESS;
686             }
687             else if( p_sys->i_selected == NO_PCE )
688                 p_sys->i_selected = i_pos;
689             else if( p_sys->i_selected == i_pos && !p_sys->s_current_param.b_blackslot )
690                 p_sys->i_selected = -1;
691             else if( ( p_sys->i_selected == i_pos + 1 && p_sys->i_selected%p_sys->s_allocated.i_cols != 0 )
692                   || ( p_sys->i_selected == i_pos - 1 && i_pos % p_sys->s_allocated.i_cols != 0 )
693                   || p_sys->i_selected == i_pos +  p_sys->s_allocated.i_cols
694                   || p_sys->i_selected == i_pos -  p_sys->s_allocated.i_cols
695                   || !p_sys->s_current_param.b_near )
696
697             {
698                 /* Swap two pieces */
699                 int32_t a = p_sys->pi_order[ p_sys->i_selected ];
700                 p_sys->pi_order[ p_sys->i_selected ] = p_sys->pi_order[ i_pos ];
701                 p_sys->pi_order[ i_pos ] = a;
702
703                 /* regen piece location from updated pi_order */
704                 if ( p_sys->ps_pieces != NULL && p_sys->pi_order != NULL )
705                 {
706                     int32_t i = 0;
707                     for (int32_t row = 0; row < p_sys->s_allocated.i_rows; row++) {
708                         for (int32_t col = 0; col < p_sys->s_allocated.i_cols; col++) {
709                             int32_t orow = p_sys->pi_order[i] / (p_sys->s_allocated.i_cols);
710                             int32_t ocol = p_sys->pi_order[i] % (p_sys->s_allocated.i_cols);
711
712                             p_sys->ps_pieces[i].i_original_row = orow;
713                             p_sys->ps_pieces[i].i_original_col = ocol;
714                             p_sys->ps_pieces[i].i_top_shape    = 0;
715                             p_sys->ps_pieces[i].i_btm_shape    = 0;
716                             p_sys->ps_pieces[i].i_right_shape  = 0;
717                             p_sys->ps_pieces[i].i_left_shape   = 0;
718                             p_sys->ps_pieces[i].i_actual_angle = 0;
719                             p_sys->ps_pieces[i].i_actual_mirror = +1;
720                             p_sys->ps_pieces[i].b_overlap      = false;
721                             p_sys->ps_pieces[i].b_finished     = false;
722                             p_sys->ps_pieces[i].i_group_ID     = i;
723
724                             for (uint8_t i_plane = 0; i_plane < p_sys->s_allocated.i_planes; i_plane++) {
725                                 p_sys->ps_pieces[i].ps_piece_in_plane[i_plane].i_width     = p_sys->ps_puzzle_array[row][col][i_plane].i_width;
726                                 p_sys->ps_pieces[i].ps_piece_in_plane[i_plane].i_lines     = p_sys->ps_puzzle_array[row][col][i_plane].i_lines;
727                                 p_sys->ps_pieces[i].ps_piece_in_plane[i_plane].i_original_x = p_sys->ps_puzzle_array[orow][ocol][i_plane].i_x;
728                                 p_sys->ps_pieces[i].ps_piece_in_plane[i_plane].i_original_y = p_sys->ps_puzzle_array[orow][ocol][i_plane].i_y;
729                                 p_sys->ps_pieces[i].ps_piece_in_plane[i_plane].i_actual_x   = p_sys->ps_puzzle_array[row][col][i_plane].i_x;
730                                 p_sys->ps_pieces[i].ps_piece_in_plane[i_plane].i_actual_y   = p_sys->ps_puzzle_array[row][col][i_plane].i_y;
731                             }
732                             i++;
733                         }
734                     }
735                 }
736
737                 p_sys->i_selected = p_sys->s_current_param.b_blackslot ? i_pos : NO_PCE;
738                 p_sys->b_finished = puzzle_is_finished( p_sys, p_sys->pi_order );
739             }
740         }
741     }
742     else /* jigsaw puzzle mode */
743     {
744         if ((p_sys->ps_desk_planes == NULL)  || (p_sys->ps_pict_planes == NULL)  || (p_sys->ps_puzzle_array == NULL) || (p_sys->ps_pieces == NULL)) {
745             *p_mouse = *p_new;
746             return VLC_SUCCESS;
747         }
748
749         if( vlc_mouse_HasPressed( p_old, p_new, MOUSE_BUTTON_LEFT ) )
750         {
751
752             vlc_mutex_lock( &p_sys->pce_lock );
753
754             if (p_sys->i_mouse_drag_pce != NO_PCE) {
755                 int i_ret = puzzle_piece_foreground( p_filter, p_sys->i_mouse_drag_pce);
756                 if (i_ret != VLC_SUCCESS)
757                 {
758                     vlc_mutex_unlock( &p_sys->pce_lock );
759                     return i_ret;
760                 }
761                 p_sys->i_mouse_drag_pce = 0;
762
763                 uint32_t i_group_ID = p_sys->ps_pieces[0].i_group_ID;
764                 for (uint32_t i = 0; i < p_sys->s_allocated.i_pieces_nbr; i++) {
765                     if ( i_group_ID == p_sys->ps_pieces[i].i_group_ID ) {
766                         p_sys->ps_pieces[i].b_finished = false;
767                     }
768                     else {
769                         break;
770                     }
771                 }
772
773                 p_sys->b_mouse_drag = true;
774                 p_sys->b_mouse_mvt = false;
775             }
776             else {
777             /* player click an empty area then search a piece which is overlapping another one and place it here */
778                 p_sys->b_mouse_drag = false;
779                 for (uint32_t i = 0; i < p_sys->s_allocated.i_pieces_nbr; i++)
780                     if ( p_sys->ps_pieces[i].b_overlap ) {
781                         puzzle_move_group( p_filter, i, p_new->i_x - p_sys->ps_pieces[i].i_center_x,  p_new->i_y - p_sys->ps_pieces[i].i_center_y );
782                         p_sys->ps_pieces[i].b_overlap = false;
783                         break;
784                     }
785                 p_sys->b_mouse_drag = false;
786             }
787
788             vlc_mutex_unlock( &p_sys->pce_lock );
789
790         }
791         else if( vlc_mouse_HasReleased( p_old, p_new, MOUSE_BUTTON_LEFT ) )
792         {
793             if ( !p_sys->b_mouse_mvt && p_sys->b_mouse_drag ) {
794                 /* piece clicked without any mouse mvt => rotate it or mirror */
795                 if ( p_sys->s_current_param.i_rotate != 0) {
796                     vlc_mutex_lock( &p_sys->pce_lock );
797
798                     uint32_t i_group_ID = p_sys->ps_pieces[0].i_group_ID;
799
800                     for (uint32_t i = 0; i < p_sys->s_allocated.i_pieces_nbr; i++)
801                         if ( i_group_ID == p_sys->ps_pieces[i].i_group_ID )
802                             puzzle_rotate_pce( p_filter, i, p_sys->i_mouse_action, p_sys->ps_pieces[0].i_center_x, p_sys->ps_pieces[0].i_center_y, p_sys->i_mouse_action != 4 ? true : false );
803
804                     vlc_mutex_unlock( &p_sys->pce_lock );
805                 }
806             }
807             p_sys->b_mouse_drag = false;
808             p_sys->b_mouse_mvt = false;
809         }
810         else /* no action on left button */
811         {
812             /* check if the mouse is in the preview area */
813             switch ( p_sys->i_preview_pos )
814             {
815               case 0:
816                 if ( p_new->i_x < (int)p_fmt_in->i_width / 2 && p_new->i_y < (int)p_fmt_in->i_height / 2 )
817                     p_sys->i_preview_pos++;
818                 break;
819               case 1:
820                 if ( p_new->i_x > (int)p_fmt_in->i_width / 2 && p_new->i_y < (int)p_fmt_in->i_height / 2 )
821                     p_sys->i_preview_pos++;
822                 break;
823               case 2:
824                 if ( p_new->i_x > (int)p_fmt_in->i_width / 2 && p_new->i_y > (int)p_fmt_in->i_height / 2 )
825                     p_sys->i_preview_pos++;
826                 break;
827               case 3:
828                 if ( p_new->i_x < (int)p_fmt_in->i_width / 2 && p_new->i_y > (int)p_fmt_in->i_height / 2 )
829                     p_sys->i_preview_pos++;
830                 break;
831             }
832             p_sys->i_preview_pos %= 4;
833
834             if ( !vlc_mouse_IsLeftPressed( p_new ) )
835                 p_sys->b_mouse_drag = false;
836
837             int i_dx, i_dy;
838             vlc_mouse_GetMotion( &i_dx, &i_dy, p_old, p_new );
839             if ( i_dx != 0 || i_dy != 0 )
840                 p_sys->b_mouse_mvt = true;
841
842             if (p_sys->b_mouse_drag) {
843                 if ( ( p_new->i_x <= 0 ) || ( p_new->i_y <=  0 ) || ( p_new->i_x >= (int) p_fmt_in->i_width )
844                         || ( p_new->i_y >= (int) p_fmt_in->i_height ) )
845                 {
846                     /* if the mouse is outside the window, stop moving the piece/group */
847                     p_sys->b_mouse_drag = false;
848                     p_sys->b_mouse_mvt = true;
849                 }
850                 else if ( i_dx != 0 || i_dy != 0 )
851                 {
852                     vlc_mutex_lock( &p_sys->pce_lock );
853
854                     puzzle_move_group( p_filter, p_sys->i_mouse_drag_pce, i_dx, i_dy);
855
856                     vlc_mutex_unlock( &p_sys->pce_lock );
857                 }
858             }
859         }
860     }
861     return VLC_EGENERIC;
862 }