* ./modules/*: moved plugins to the new tree. Yet untested builds include
[vlc.git] / modules / video_filter / crop.c
1 /*****************************************************************************
2  * crop.c : Crop video plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2002 VideoLAN
5  * $Id: crop.c,v 1.1 2002/08/04 17:23:43 sam Exp $
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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <errno.h>
28 #include <stdlib.h>                                      /* malloc(), free() */
29 #include <string.h>
30
31 #include <vlc/vlc.h>
32 #include <vlc/vout.h>
33
34 #include "filter_common.h"
35
36 /*****************************************************************************
37  * Local prototypes
38  *****************************************************************************/
39 static int  Create    ( vlc_object_t * );
40 static void Destroy   ( vlc_object_t * );
41
42 static int  Init      ( vout_thread_t * );
43 static void End       ( vout_thread_t * );
44 static int  Manage    ( vout_thread_t * );
45 static void Render    ( vout_thread_t *, picture_t * );
46
47 static void UpdateStats    ( vout_thread_t *, picture_t * );
48
49 /*****************************************************************************
50  * Module descriptor
51  *****************************************************************************/
52 #define GEOMETRY_TEXT N_("Crop geometry")
53 #define GEOMETRY_LONGTEXT N_("Set the geometry of the zone to crop")
54
55 #define AUTOCROP_TEXT N_("Automatic cropping")
56 #define AUTOCROP_LONGTEXT N_("Activate automatic black border cropping")
57
58 vlc_module_begin();
59     add_category_hint( N_("Miscellaneous"), NULL );
60     add_string( "crop-geometry", NULL, NULL, GEOMETRY_TEXT, GEOMETRY_LONGTEXT );
61     add_bool( "autocrop", 0, NULL, AUTOCROP_TEXT, AUTOCROP_LONGTEXT );
62     set_description( _("image crop video module") );
63     set_capability( "video filter", 0 );
64     add_shortcut( "crop" );
65     set_callbacks( Create, Destroy );
66 vlc_module_end();
67
68 /*****************************************************************************
69  * vout_sys_t: Crop video output method descriptor
70  *****************************************************************************
71  * This structure is part of the video output thread descriptor.
72  * It describes the Crop specific properties of an output thread.
73  *****************************************************************************/
74 struct vout_sys_t
75 {
76     vout_thread_t *p_vout;
77
78     unsigned int i_x, i_y;
79     unsigned int i_width, i_height, i_aspect;
80
81     vlc_bool_t b_autocrop;
82
83     /* Autocrop specific variables */
84     unsigned int i_lastchange;
85     vlc_bool_t   b_changed;
86 };
87
88 /*****************************************************************************
89  * Create: allocates Crop video thread output method
90  *****************************************************************************
91  * This function allocates and initializes a Crop vout method.
92  *****************************************************************************/
93 static int Create( vlc_object_t *p_this )
94 {
95     vout_thread_t *p_vout = (vout_thread_t *)p_this;
96
97     /* Allocate structure */
98     p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
99     if( p_vout->p_sys == NULL )
100     {
101         msg_Err( p_vout, "out of memory" );
102         return 1;
103     }
104
105     p_vout->pf_init = Init;
106     p_vout->pf_end = End;
107     p_vout->pf_manage = Manage;
108     p_vout->pf_render = Render;
109     p_vout->pf_display = NULL;
110
111     return 0;
112 }
113
114 /*****************************************************************************
115  * Init: initialize Crop video thread output method
116  *****************************************************************************/
117 static int Init( vout_thread_t *p_vout )
118 {
119     int   i_index;
120     char *psz_var;
121     picture_t *p_pic;
122     
123     I_OUTPUTPICTURES = 0;
124
125     p_vout->p_sys->i_lastchange = 0;
126     p_vout->p_sys->b_changed = 0;
127
128     /* Initialize the output structure */
129     p_vout->output.i_chroma = p_vout->render.i_chroma;
130     p_vout->output.i_width  = p_vout->render.i_width;
131     p_vout->output.i_height = p_vout->render.i_height;
132     p_vout->output.i_aspect = p_vout->render.i_aspect;
133
134     /* Shall we use autocrop ? */
135     p_vout->p_sys->b_autocrop = config_GetInt( p_vout, "autocrop" );
136
137     /* Get geometry value from the user */
138     psz_var = config_GetPsz( p_vout, "crop-geometry" );
139     if( psz_var )
140     {
141         char *psz_parser, *psz_tmp;
142
143         psz_parser = psz_tmp = psz_var;
144         while( *psz_tmp && *psz_tmp != 'x' ) psz_tmp++;
145
146         if( *psz_tmp )
147         {
148             psz_tmp[0] = '\0';
149             p_vout->p_sys->i_width = atoi( psz_parser );
150
151             psz_parser = ++psz_tmp;
152             while( *psz_tmp && *psz_tmp != '+' ) psz_tmp++;
153
154             if( *psz_tmp )
155             {
156                 psz_tmp[0] = '\0';
157                 p_vout->p_sys->i_height = atoi( psz_parser );
158
159                 psz_parser = ++psz_tmp;
160                 while( *psz_tmp && *psz_tmp != '+' ) psz_tmp++;
161
162                 if( *psz_tmp )
163                 {
164                     psz_tmp[0] = '\0';
165                     p_vout->p_sys->i_x = atoi( psz_parser );
166                     p_vout->p_sys->i_y = atoi( ++psz_tmp );
167                 }
168                 else
169                 {
170                     p_vout->p_sys->i_x = atoi( psz_parser );
171                     p_vout->p_sys->i_y =
172                      ( p_vout->output.i_height - p_vout->p_sys->i_height ) / 2;
173                 }
174             }
175             else
176             {
177                 p_vout->p_sys->i_height = atoi( psz_parser );
178                 p_vout->p_sys->i_x =
179                      ( p_vout->output.i_width - p_vout->p_sys->i_width ) / 2;
180                 p_vout->p_sys->i_y =
181                      ( p_vout->output.i_height - p_vout->p_sys->i_height ) / 2;
182             }
183         }
184         else
185         {
186             p_vout->p_sys->i_width = atoi( psz_parser );
187             p_vout->p_sys->i_height = p_vout->output.i_height;
188             p_vout->p_sys->i_x =
189                      ( p_vout->output.i_width - p_vout->p_sys->i_width ) / 2;
190             p_vout->p_sys->i_y =
191                      ( p_vout->output.i_height - p_vout->p_sys->i_height ) / 2;
192         }
193
194         /* Check for validity */
195         if( p_vout->p_sys->i_x + p_vout->p_sys->i_width
196                                                    > p_vout->output.i_width )
197         {
198             p_vout->p_sys->i_x = 0;
199             if( p_vout->p_sys->i_width > p_vout->output.i_width )
200             {
201                 p_vout->p_sys->i_width = p_vout->output.i_width;
202             }
203         }
204
205         if( p_vout->p_sys->i_y + p_vout->p_sys->i_height
206                                                    > p_vout->output.i_height )
207         {
208             p_vout->p_sys->i_y = 0;
209             if( p_vout->p_sys->i_height > p_vout->output.i_height )
210             {
211                 p_vout->p_sys->i_height = p_vout->output.i_height;
212             }
213         }
214
215         free( psz_var );
216     }
217     else
218     {
219         p_vout->p_sys->i_width  = p_vout->output.i_width;
220         p_vout->p_sys->i_height = p_vout->output.i_height;
221         p_vout->p_sys->i_x = p_vout->p_sys->i_y = 0;
222     }
223
224     /* Pheeew. Parsing done. */
225     msg_Dbg( p_vout, "cropping at %ix%i+%i+%i, %sautocropping",
226                      p_vout->p_sys->i_width, p_vout->p_sys->i_height,
227                      p_vout->p_sys->i_x, p_vout->p_sys->i_y,
228                      p_vout->p_sys->b_autocrop ? "" : "not " );
229
230     /* Set current output image properties */
231     p_vout->p_sys->i_aspect = p_vout->output.i_aspect
232                             * p_vout->output.i_height / p_vout->p_sys->i_height
233                             * p_vout->p_sys->i_width / p_vout->output.i_width;
234
235     /* Try to open the real video output */
236     p_vout->p_sys->p_vout =
237         vout_CreateThread( p_vout,
238                     p_vout->p_sys->i_width, p_vout->p_sys->i_height,
239                     p_vout->render.i_chroma, p_vout->p_sys->i_aspect );
240     if( p_vout->p_sys->p_vout == NULL )
241     {
242         msg_Err( p_vout, "failed to create vout" );
243         return 0;
244     }
245
246     ALLOCATE_DIRECTBUFFERS( VOUT_MAX_PICTURES );
247
248     return 0;
249 }
250
251 /*****************************************************************************
252  * End: terminate Crop video thread output method
253  *****************************************************************************/
254 static void End( vout_thread_t *p_vout )
255 {
256     int i_index;
257
258     /* Free the fake output buffers we allocated */
259     for( i_index = I_OUTPUTPICTURES ; i_index ; )
260     {
261         i_index--;
262         free( PP_OUTPUTPICTURE[ i_index ]->p_data_orig );
263     }
264 }
265
266 /*****************************************************************************
267  * Destroy: destroy Crop video thread output method
268  *****************************************************************************
269  * Terminate an output method created by CropCreateOutputMethod
270  *****************************************************************************/
271 static void Destroy( vlc_object_t *p_this )
272 {
273     vout_thread_t *p_vout = (vout_thread_t *)p_this;
274
275     vout_DestroyThread( p_vout->p_sys->p_vout );
276     free( p_vout->p_sys );
277 }
278
279 /*****************************************************************************
280  * Manage: handle Crop events
281  *****************************************************************************
282  * This function should be called regularly by video output thread. It manages
283  * console events. It returns a non null value on error.
284  *****************************************************************************/
285 static int Manage( vout_thread_t *p_vout )
286 {
287     if( !p_vout->p_sys->b_changed )
288     {
289         return 0;
290     }
291
292     vout_DestroyThread( p_vout->p_sys->p_vout );
293
294     p_vout->p_sys->p_vout =
295         vout_CreateThread( p_vout,
296                     p_vout->p_sys->i_width, p_vout->p_sys->i_height,
297                     p_vout->render.i_chroma, p_vout->p_sys->i_aspect );
298     if( p_vout->p_sys->p_vout == NULL )
299     {
300         msg_Err( p_vout, "failed to create vout" );
301         return 1;
302     }
303
304     p_vout->p_sys->b_changed = 0;
305     p_vout->p_sys->i_lastchange = 0;
306
307     return 0;
308 }
309
310 /*****************************************************************************
311  * Render: display previously rendered output
312  *****************************************************************************
313  * This function sends the currently rendered image to Crop image, waits
314  * until it is displayed and switches the two rendering buffers, preparing next
315  * frame.
316  *****************************************************************************/
317 static void Render( vout_thread_t *p_vout, picture_t *p_pic )
318 {
319     picture_t *p_outpic = NULL;
320     int i_plane;
321
322     if( p_vout->p_sys->b_changed )
323     {
324         return;
325     }
326
327     while( ( p_outpic =
328                  vout_CreatePicture( p_vout->p_sys->p_vout, 0, 0, 0 )
329            ) == NULL )
330     {
331         if( p_vout->b_die || p_vout->b_error )
332         {
333             vout_DestroyPicture( p_vout->p_sys->p_vout, p_outpic );
334             return;
335         }
336
337         msleep( VOUT_OUTMEM_SLEEP );
338     }
339
340     vout_DatePicture( p_vout->p_sys->p_vout, p_outpic, p_pic->date );
341     vout_LinkPicture( p_vout->p_sys->p_vout, p_outpic );
342
343     for( i_plane = 0 ; i_plane < p_pic->i_planes ; i_plane++ )
344     {
345         u8 *p_in, *p_out, *p_out_end;
346         int i_in_pitch = p_pic->p[i_plane].i_pitch;
347         const int i_out_pitch = p_outpic->p[i_plane].i_pitch;
348
349         p_in = p_pic->p[i_plane].p_pixels
350                 /* Skip the right amount of lines */
351                 + i_in_pitch * ( p_pic->p[i_plane].i_lines * p_vout->p_sys->i_y
352                                   / p_vout->output.i_height )
353                 /* Skip the right amount of columns */
354                 + i_in_pitch * p_vout->p_sys->i_x / p_vout->output.i_width;
355
356         p_out = p_outpic->p[i_plane].p_pixels;
357         p_out_end = p_out + i_out_pitch * p_outpic->p[i_plane].i_lines;
358
359         while( p_out < p_out_end )
360         {
361             p_vout->p_vlc->pf_memcpy( p_out, p_in, i_out_pitch );
362             p_in += i_in_pitch;
363             p_out += i_out_pitch;
364         }
365     }
366
367     vout_UnlinkPicture( p_vout->p_sys->p_vout, p_outpic );
368     vout_DisplayPicture( p_vout->p_sys->p_vout, p_outpic );
369
370     /* The source image may still be in the cache ... parse it! */
371     if( !p_vout->p_sys->b_autocrop )
372     {
373         return;
374     }
375
376     UpdateStats( p_vout, p_pic );
377 }
378
379 static void UpdateStats( vout_thread_t *p_vout, picture_t *p_pic )
380 {
381     u8 *p_in = p_pic->p[0].p_pixels;
382     int i_pitch = p_pic->p[0].i_pitch;
383     int i_lines = p_pic->p[0].i_lines;
384     int i_firstwhite = -1, i_lastwhite = -1, i;
385
386     /* Determine where black borders are */
387     switch( p_vout->output.i_chroma )
388     {
389     case VLC_FOURCC('I','4','2','0'):
390         /* XXX: Do not laugh ! I know this is very naive. But it's just a
391          *      proof of concept code snippet... */
392         for( i = i_lines ; i-- ; )
393         {
394             const int i_col = i * i_pitch / i_lines;
395
396             if( p_in[i_col/2] > 40
397                  && p_in[i_pitch / 2] > 40
398                  && p_in[i_pitch/2 + i_col/2] > 40 )
399             {
400                 if( i_lastwhite == -1 )
401                 {
402                     i_lastwhite = i;
403                 }
404                 i_firstwhite = i;
405             }
406             p_in += i_pitch;
407         }
408         break;
409
410     default:
411         break;
412     }
413
414     /* Decide whether it's worth changing the size */
415     if( i_lastwhite == -1 )
416     {
417         p_vout->p_sys->i_lastchange = 0;
418         return;
419     }
420
421     if( i_lastwhite - i_firstwhite < p_vout->p_sys->i_height / 2 )
422     {
423         p_vout->p_sys->i_lastchange = 0;
424         return;
425     }
426
427     if( i_lastwhite - i_firstwhite < p_vout->p_sys->i_height + 16
428          && i_lastwhite - i_firstwhite + 16 > p_vout->p_sys->i_height )
429     {
430         p_vout->p_sys->i_lastchange = 0;
431         return;
432     }
433
434     /* We need at least 25 images to make up our mind */
435     p_vout->p_sys->i_lastchange++;
436     if( p_vout->p_sys->i_lastchange < 25 )
437     {
438         return;
439     }
440
441     /* Tune a few values */
442     if( i_firstwhite & 1 )
443     {
444         i_firstwhite--;
445     }
446
447     if( !(i_lastwhite & 1) )
448     {
449         i_lastwhite++;
450     }
451
452     /* Change size */
453     p_vout->p_sys->i_y = i_firstwhite;
454     p_vout->p_sys->i_height = i_lastwhite - i_firstwhite + 1;
455
456     p_vout->p_sys->i_aspect = p_vout->output.i_aspect
457                             * p_vout->output.i_height / p_vout->p_sys->i_height
458                             * p_vout->p_sys->i_width / p_vout->output.i_width;
459
460     p_vout->p_sys->b_changed = 1;
461 }