1 /*****************************************************************************
2 * crop.c : Crop video plugin for vlc
3 *****************************************************************************
4 * Copyright (C) 2002, 2003 the VideoLAN team
7 * Authors: Samuel Hocevar <sam@zoy.org>
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.
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.
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 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
27 #include <stdlib.h> /* malloc(), free() */
32 #include <vlc_interaction.h>
34 #include "filter_common.h"
36 /*****************************************************************************
38 *****************************************************************************/
39 static int Create ( vlc_object_t * );
40 static void Destroy ( vlc_object_t * );
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 * );
47 static void UpdateStats ( vout_thread_t *, picture_t * );
49 static int SendEvents( vlc_object_t *, char const *,
50 vlc_value_t, vlc_value_t, void * );
52 /*****************************************************************************
54 *****************************************************************************/
55 #define GEOMETRY_TEXT N_("Crop geometry (pixels)")
56 #define GEOMETRY_LONGTEXT N_("Set the geometry of the zone to crop. This is set as <width> x <height> + <left offset> + <top offset>.")
58 #define AUTOCROP_TEXT N_("Automatic cropping")
59 #define AUTOCROP_LONGTEXT N_("Automatic black border cropping.")
62 set_description( _("Crop video filter") );
63 set_shortname( N_("Crop" ));
64 set_category( CAT_VIDEO );
65 set_subcategory( SUBCAT_VIDEO_VFILTER );
66 set_capability( "video filter", 0 );
68 add_string( "crop-geometry", NULL, NULL, GEOMETRY_TEXT, GEOMETRY_LONGTEXT, VLC_FALSE );
69 add_bool( "autocrop", 0, NULL, AUTOCROP_TEXT, AUTOCROP_LONGTEXT, VLC_FALSE );
71 add_shortcut( "crop" );
72 set_callbacks( Create, Destroy );
75 /*****************************************************************************
76 * vout_sys_t: Crop video output method descriptor
77 *****************************************************************************
78 * This structure is part of the video output thread descriptor.
79 * It describes the Crop specific properties of an output thread.
80 *****************************************************************************/
83 vout_thread_t *p_vout;
85 unsigned int i_x, i_y;
86 unsigned int i_width, i_height, i_aspect;
88 vlc_bool_t b_autocrop;
90 /* Autocrop specific variables */
91 unsigned int i_lastchange;
95 /*****************************************************************************
96 * Control: control facility for the vout (forwards to child vout)
97 *****************************************************************************/
98 static int Control( vout_thread_t *p_vout, int i_query, va_list args )
100 return vout_vaControl( p_vout->p_sys->p_vout, i_query, args );
103 /*****************************************************************************
104 * Create: allocates Crop video thread output method
105 *****************************************************************************
106 * This function allocates and initializes a Crop vout method.
107 *****************************************************************************/
108 static int Create( vlc_object_t *p_this )
110 vout_thread_t *p_vout = (vout_thread_t *)p_this;
112 /* Allocate structure */
113 p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
114 if( p_vout->p_sys == NULL )
116 msg_Err( p_vout, "out of memory" );
120 p_vout->pf_init = Init;
121 p_vout->pf_end = End;
122 p_vout->pf_manage = Manage;
123 p_vout->pf_render = Render;
124 p_vout->pf_display = NULL;
125 p_vout->pf_control = Control;
130 /*****************************************************************************
131 * Init: initialize Crop video thread output method
132 *****************************************************************************/
133 static int Init( vout_thread_t *p_vout )
138 video_format_t fmt = {0};
140 I_OUTPUTPICTURES = 0;
142 p_vout->p_sys->i_lastchange = 0;
143 p_vout->p_sys->b_changed = VLC_FALSE;
145 /* Initialize the output structure */
146 p_vout->output.i_chroma = p_vout->render.i_chroma;
147 p_vout->output.i_width = p_vout->render.i_width;
148 p_vout->output.i_height = p_vout->render.i_height;
149 p_vout->output.i_aspect = p_vout->render.i_aspect;
150 p_vout->fmt_out = p_vout->fmt_in;
152 /* Shall we use autocrop ? */
153 p_vout->p_sys->b_autocrop = config_GetInt( p_vout, "autocrop" );
155 /* Get geometry value from the user */
156 psz_var = config_GetPsz( p_vout, "crop-geometry" );
159 char *psz_parser, *psz_tmp;
161 psz_parser = psz_tmp = psz_var;
162 while( *psz_tmp && *psz_tmp != 'x' ) psz_tmp++;
167 p_vout->p_sys->i_width = atoi( psz_parser );
169 psz_parser = ++psz_tmp;
170 while( *psz_tmp && *psz_tmp != '+' ) psz_tmp++;
175 p_vout->p_sys->i_height = atoi( psz_parser );
177 psz_parser = ++psz_tmp;
178 while( *psz_tmp && *psz_tmp != '+' ) psz_tmp++;
183 p_vout->p_sys->i_x = atoi( psz_parser );
184 p_vout->p_sys->i_y = atoi( ++psz_tmp );
188 p_vout->p_sys->i_x = atoi( psz_parser );
190 ( p_vout->output.i_height - p_vout->p_sys->i_height ) / 2;
195 p_vout->p_sys->i_height = atoi( psz_parser );
197 ( p_vout->output.i_width - p_vout->p_sys->i_width ) / 2;
199 ( p_vout->output.i_height - p_vout->p_sys->i_height ) / 2;
204 p_vout->p_sys->i_width = atoi( psz_parser );
205 p_vout->p_sys->i_height = p_vout->output.i_height;
207 ( p_vout->output.i_width - p_vout->p_sys->i_width ) / 2;
209 ( p_vout->output.i_height - p_vout->p_sys->i_height ) / 2;
212 /* Check for validity */
213 if( p_vout->p_sys->i_x + p_vout->p_sys->i_width
214 > p_vout->output.i_width )
216 p_vout->p_sys->i_x = 0;
217 if( p_vout->p_sys->i_width > p_vout->output.i_width )
219 p_vout->p_sys->i_width = p_vout->output.i_width;
223 if( p_vout->p_sys->i_y + p_vout->p_sys->i_height
224 > p_vout->output.i_height )
226 p_vout->p_sys->i_y = 0;
227 if( p_vout->p_sys->i_height > p_vout->output.i_height )
229 p_vout->p_sys->i_height = p_vout->output.i_height;
237 p_vout->p_sys->i_width = p_vout->fmt_out.i_visible_width;
238 p_vout->p_sys->i_height = p_vout->fmt_out.i_visible_height;
239 p_vout->p_sys->i_x = p_vout->fmt_out.i_x_offset;
240 p_vout->p_sys->i_y = p_vout->fmt_out.i_y_offset;
243 /* Pheeew. Parsing done. */
244 msg_Dbg( p_vout, "cropping at %ix%i+%i+%i, %sautocropping",
245 p_vout->p_sys->i_width, p_vout->p_sys->i_height,
246 p_vout->p_sys->i_x, p_vout->p_sys->i_y,
247 p_vout->p_sys->b_autocrop ? "" : "not " );
249 /* Set current output image properties */
250 p_vout->p_sys->i_aspect = p_vout->fmt_out.i_aspect
251 * p_vout->fmt_out.i_visible_height / p_vout->p_sys->i_height
252 * p_vout->p_sys->i_width / p_vout->fmt_out.i_visible_width;
254 fmt.i_width = fmt.i_visible_width = p_vout->p_sys->i_width;
255 fmt.i_height = fmt.i_visible_height = p_vout->p_sys->i_height;
256 fmt.i_x_offset = fmt.i_y_offset = 0;
257 fmt.i_chroma = p_vout->render.i_chroma;
258 fmt.i_aspect = p_vout->p_sys->i_aspect;
259 fmt.i_sar_num = p_vout->p_sys->i_aspect * fmt.i_height / fmt.i_width;
260 fmt.i_sar_den = VOUT_ASPECT_FACTOR;
262 /* Try to open the real video output */
263 p_vout->p_sys->p_vout = vout_Create( p_vout, &fmt );
264 if( p_vout->p_sys->p_vout == NULL )
266 msg_Err( p_vout, "failed to create vout" );
267 intf_UserFatal( p_vout, VLC_FALSE, _("Cropping failed"),
268 _("VLC could not open the video output module.") );
272 ALLOCATE_DIRECTBUFFERS( VOUT_MAX_PICTURES );
274 ADD_CALLBACKS( p_vout->p_sys->p_vout, SendEvents );
276 ADD_PARENT_CALLBACKS( SendEventsToChild );
281 /*****************************************************************************
282 * End: terminate Crop video thread output method
283 *****************************************************************************/
284 static void End( vout_thread_t *p_vout )
288 /* Free the fake output buffers we allocated */
289 for( i_index = I_OUTPUTPICTURES ; i_index ; )
292 free( PP_OUTPUTPICTURE[ i_index ]->p_data_orig );
296 /*****************************************************************************
297 * Destroy: destroy Crop video thread output method
298 *****************************************************************************
299 * Terminate an output method created by CropCreateOutputMethod
300 *****************************************************************************/
301 static void Destroy( vlc_object_t *p_this )
303 vout_thread_t *p_vout = (vout_thread_t *)p_this;
305 if( p_vout->p_sys->p_vout )
307 DEL_CALLBACKS( p_vout->p_sys->p_vout, SendEvents );
308 vlc_object_detach( p_vout->p_sys->p_vout );
309 vout_Destroy( p_vout->p_sys->p_vout );
312 DEL_PARENT_CALLBACKS( SendEventsToChild );
314 free( p_vout->p_sys );
317 /*****************************************************************************
318 * Manage: handle Crop events
319 *****************************************************************************
320 * This function should be called regularly by video output thread. It manages
321 * console events. It returns a non null value on error.
322 *****************************************************************************/
323 static int Manage( vout_thread_t *p_vout )
325 video_format_t fmt = {0};
327 if( !p_vout->p_sys->b_changed )
332 vout_Destroy( p_vout->p_sys->p_vout );
334 fmt.i_width = fmt.i_visible_width = p_vout->p_sys->i_width;
335 fmt.i_height = fmt.i_visible_height = p_vout->p_sys->i_height;
336 fmt.i_x_offset = fmt.i_y_offset = 0;
337 fmt.i_chroma = p_vout->render.i_chroma;
338 fmt.i_aspect = p_vout->p_sys->i_aspect;
339 fmt.i_sar_num = p_vout->p_sys->i_aspect * fmt.i_height / fmt.i_width;
340 fmt.i_sar_den = VOUT_ASPECT_FACTOR;
342 p_vout->p_sys->p_vout = vout_Create( p_vout, &fmt );
343 if( p_vout->p_sys->p_vout == NULL )
345 msg_Err( p_vout, "failed to create vout" );
346 intf_UserFatal( p_vout, VLC_FALSE, _("Cropping failed"),
347 _("VLC could not open the video output module.") );
351 p_vout->p_sys->b_changed = VLC_FALSE;
352 p_vout->p_sys->i_lastchange = 0;
357 /*****************************************************************************
358 * Render: display previously rendered output
359 *****************************************************************************
360 * This function sends the currently rendered image to Crop image, waits
361 * until it is displayed and switches the two rendering buffers, preparing next
363 *****************************************************************************/
364 static void Render( vout_thread_t *p_vout, picture_t *p_pic )
366 picture_t *p_outpic = NULL;
369 if( p_vout->p_sys->b_changed )
375 vout_CreatePicture( p_vout->p_sys->p_vout, 0, 0, 0 )
378 if( p_vout->b_die || p_vout->b_error )
380 vout_DestroyPicture( p_vout->p_sys->p_vout, p_outpic );
384 msleep( VOUT_OUTMEM_SLEEP );
387 vout_DatePicture( p_vout->p_sys->p_vout, p_outpic, p_pic->date );
388 vout_LinkPicture( p_vout->p_sys->p_vout, p_outpic );
390 for( i_plane = 0 ; i_plane < p_pic->i_planes ; i_plane++ )
392 uint8_t *p_in, *p_out, *p_out_end;
393 int i_in_pitch = p_pic->p[i_plane].i_pitch;
394 const int i_out_pitch = p_outpic->p[i_plane].i_pitch;
395 const int i_copy_pitch = p_outpic->p[i_plane].i_visible_pitch;
397 p_in = p_pic->p[i_plane].p_pixels
398 /* Skip the right amount of lines */
399 + i_in_pitch * ( p_pic->p[i_plane].i_visible_lines *
400 p_vout->p_sys->i_y / p_vout->output.i_height )
401 /* Skip the right amount of columns */
402 + i_in_pitch * p_vout->p_sys->i_x / p_vout->output.i_width;
404 p_out = p_outpic->p[i_plane].p_pixels;
405 p_out_end = p_out + i_out_pitch * p_outpic->p[i_plane].i_visible_lines;
407 while( p_out < p_out_end )
409 p_vout->p_libvlc->pf_memcpy( p_out, p_in, i_copy_pitch );
411 p_out += i_out_pitch;
415 vout_UnlinkPicture( p_vout->p_sys->p_vout, p_outpic );
416 vout_DisplayPicture( p_vout->p_sys->p_vout, p_outpic );
418 /* The source image may still be in the cache ... parse it! */
419 if( p_vout->p_sys->b_autocrop )
421 UpdateStats( p_vout, p_pic );
425 static void UpdateStats( vout_thread_t *p_vout, picture_t *p_pic )
427 uint8_t *p_in = p_pic->p[0].p_pixels;
428 int i_pitch = p_pic->p[0].i_pitch;
429 int i_visible_pitch = p_pic->p[0].i_visible_pitch;
430 int i_lines = p_pic->p[0].i_visible_lines;
431 int i_firstwhite = -1, i_lastwhite = -1, i;
433 /* Determine where black borders are */
434 switch( p_vout->output.i_chroma )
436 case VLC_FOURCC('I','4','2','0'):
437 /* XXX: Do not laugh ! I know this is very naive. But it's just a
438 * proof of concept code snippet... */
439 for( i = i_lines ; i-- ; )
441 const int i_col = i * i_pitch / i_lines;
443 if( p_in[i_col/2] > 40
444 && p_in[i_visible_pitch/2] > 40
445 && p_in[i_visible_pitch/2 + i_col/2] > 40 )
447 if( i_lastwhite == -1 )
461 /* Decide whether it's worth changing the size */
462 if( i_lastwhite == -1 )
464 p_vout->p_sys->i_lastchange = 0;
468 if( (unsigned int)(i_lastwhite - i_firstwhite)
469 < p_vout->p_sys->i_height / 2 )
471 p_vout->p_sys->i_lastchange = 0;
475 if( (unsigned int)(i_lastwhite - i_firstwhite)
476 < p_vout->p_sys->i_height + 16
477 && (unsigned int)(i_lastwhite - i_firstwhite + 16)
478 > p_vout->p_sys->i_height )
480 p_vout->p_sys->i_lastchange = 0;
484 /* We need at least 25 images to make up our mind */
485 p_vout->p_sys->i_lastchange++;
486 if( p_vout->p_sys->i_lastchange < 25 )
491 /* Tune a few values */
492 if( i_firstwhite & 1 )
497 if( !(i_lastwhite & 1) )
503 p_vout->p_sys->i_y = i_firstwhite;
504 p_vout->p_sys->i_height = i_lastwhite - i_firstwhite + 1;
506 p_vout->p_sys->i_aspect = p_vout->output.i_aspect
507 * p_vout->output.i_height / p_vout->p_sys->i_height
508 * p_vout->p_sys->i_width / p_vout->output.i_width;
510 p_vout->p_sys->b_changed = VLC_TRUE;
513 /*****************************************************************************
514 * SendEvents: forward mouse and keyboard events to the parent p_vout
515 *****************************************************************************/
516 static int SendEvents( vlc_object_t *p_this, char const *psz_var,
517 vlc_value_t oldval, vlc_value_t newval, void *_p_vout )
519 vout_thread_t *p_vout = (vout_thread_t *)_p_vout;
520 vlc_value_t sentval = newval;
522 /* Translate the mouse coordinates */
523 if( !strcmp( psz_var, "mouse-x" ) )
525 sentval.i_int += p_vout->p_sys->i_x;
527 else if( !strcmp( psz_var, "mouse-y" ) )
529 sentval.i_int += p_vout->p_sys->i_y;
532 var_Set( p_vout, psz_var, sentval );
537 /*****************************************************************************
538 * SendEventsToChild: forward events to the child/children vout
539 *****************************************************************************/
540 static int SendEventsToChild( vlc_object_t *p_this, char const *psz_var,
541 vlc_value_t oldval, vlc_value_t newval, void *p_data )
543 vout_thread_t *p_vout = (vout_thread_t *)p_this;
544 var_Set( p_vout->p_sys->p_vout, psz_var, newval );