1 /*****************************************************************************
2 * erase.c : logo erase video filter
3 *****************************************************************************
4 * Copyright (C) 2007 the VideoLAN team
7 * Authors: Antoine Cellerier <dionoea -at- videolan -dot- 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 *****************************************************************************/
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
35 #include <vlc_image.h>
37 #include <vlc_filter.h>
39 #include "filter_picture.h"
41 /*****************************************************************************
43 *****************************************************************************/
44 static int Create ( vlc_object_t
* );
45 static void Destroy ( vlc_object_t
* );
47 static picture_t
*Filter( filter_t
*, picture_t
* );
48 static void FilterErase( filter_t
*, picture_t
*, picture_t
* );
49 static int EraseCallback( vlc_object_t
*, char const *,
50 vlc_value_t
, vlc_value_t
, void * );
52 /*****************************************************************************
54 *****************************************************************************/
55 #define MASK_TEXT N_("Image mask")
56 #define MASK_LONGTEXT N_("Image mask. Pixels with an alpha value greater than 50% will be erased.")
58 #define POSX_TEXT N_("X coordinate")
59 #define POSX_LONGTEXT N_("X coordinate of the mask.")
60 #define POSY_TEXT N_("Y coordinate")
61 #define POSY_LONGTEXT N_("Y coordinate of the mask.")
63 #define ERASE_HELP N_("Remove zones of the video using a picture as mask")
65 #define CFG_PREFIX "erase-"
68 set_description( N_("Erase video filter") )
69 set_shortname( N_( "Erase" ))
70 set_capability( "video filter2", 0 )
72 set_category( CAT_VIDEO
)
73 set_subcategory( SUBCAT_VIDEO_VFILTER
)
75 add_file( CFG_PREFIX
"mask", NULL
, NULL
,
76 MASK_TEXT
, MASK_LONGTEXT
, false )
77 add_integer( CFG_PREFIX
"x", 0, NULL
, POSX_TEXT
, POSX_LONGTEXT
, false )
78 add_integer( CFG_PREFIX
"y", 0, NULL
, POSY_TEXT
, POSY_LONGTEXT
, false )
80 add_shortcut( "erase" )
81 set_callbacks( Create
, Destroy
)
84 static const char *const ppsz_filter_options
[] = {
85 "mask", "x", "y", NULL
88 /*****************************************************************************
90 *****************************************************************************/
99 static void LoadMask( filter_t
*p_filter
, const char *psz_filename
)
101 image_handler_t
*p_image
;
102 video_format_t fmt_in
, fmt_out
;
103 picture_t
*p_old_mask
= p_filter
->p_sys
->p_mask
;
104 memset( &fmt_in
, 0, sizeof( video_format_t
) );
105 memset( &fmt_out
, 0, sizeof( video_format_t
) );
106 fmt_out
.i_chroma
= VLC_CODEC_YUVA
;
107 p_image
= image_HandlerCreate( p_filter
);
108 char *psz_url
= make_URI( psz_filename
, NULL
);
109 p_filter
->p_sys
->p_mask
=
110 image_ReadUrl( p_image
, psz_url
, &fmt_in
, &fmt_out
);
112 if( p_filter
->p_sys
->p_mask
)
115 picture_Release( p_old_mask
);
117 else if( p_old_mask
)
119 p_filter
->p_sys
->p_mask
= p_old_mask
;
120 msg_Err( p_filter
, "Error while loading new mask. Keeping old mask." );
123 msg_Err( p_filter
, "Error while loading new mask. No mask available." );
125 image_HandlerDelete( p_image
);
128 /*****************************************************************************
130 *****************************************************************************/
131 static int Create( vlc_object_t
*p_this
)
133 filter_t
*p_filter
= (filter_t
*)p_this
;
137 switch( p_filter
->fmt_in
.video
.i_chroma
)
148 msg_Err( p_filter
, "Unsupported input chroma (%4.4s)",
149 (char*)&(p_filter
->fmt_in
.video
.i_chroma
) );
153 /* Allocate structure */
154 p_filter
->p_sys
= malloc( sizeof( filter_sys_t
) );
155 if( p_filter
->p_sys
== NULL
)
157 p_sys
= p_filter
->p_sys
;
159 p_filter
->pf_video_filter
= Filter
;
161 config_ChainParse( p_filter
, CFG_PREFIX
, ppsz_filter_options
,
165 var_CreateGetNonEmptyStringCommand( p_filter
, CFG_PREFIX
"mask" );
169 msg_Err( p_filter
, "Missing 'mask' option value." );
174 p_sys
->p_mask
= NULL
;
175 LoadMask( p_filter
, psz_filename
);
176 free( psz_filename
);
178 p_sys
->i_x
= var_CreateGetIntegerCommand( p_filter
, CFG_PREFIX
"x" );
179 p_sys
->i_y
= var_CreateGetIntegerCommand( p_filter
, CFG_PREFIX
"y" );
181 vlc_mutex_init( &p_sys
->lock
);
182 var_AddCallback( p_filter
, CFG_PREFIX
"x", EraseCallback
, p_sys
);
183 var_AddCallback( p_filter
, CFG_PREFIX
"y", EraseCallback
, p_sys
);
184 var_AddCallback( p_filter
, CFG_PREFIX
"mask", EraseCallback
, p_sys
);
189 /*****************************************************************************
191 *****************************************************************************/
192 static void Destroy( vlc_object_t
*p_this
)
194 filter_t
*p_filter
= (filter_t
*)p_this
;
195 filter_sys_t
*p_sys
= p_filter
->p_sys
;
197 picture_Release( p_sys
->p_mask
);
199 var_DelCallback( p_filter
, CFG_PREFIX
"x", EraseCallback
, p_sys
);
200 var_DelCallback( p_filter
, CFG_PREFIX
"y", EraseCallback
, p_sys
);
201 var_DelCallback( p_filter
, CFG_PREFIX
"mask", EraseCallback
, p_sys
);
202 vlc_mutex_destroy( &p_sys
->lock
);
204 free( p_filter
->p_sys
);
207 /*****************************************************************************
209 *****************************************************************************/
210 static picture_t
*Filter( filter_t
*p_filter
, picture_t
*p_pic
)
213 filter_sys_t
*p_sys
= p_filter
->p_sys
;
215 if( !p_pic
) return NULL
;
217 p_outpic
= filter_NewPicture( p_filter
);
220 picture_Release( p_pic
);
224 /* If the mask is empty: just copy the image */
225 vlc_mutex_lock( &p_sys
->lock
);
227 FilterErase( p_filter
, p_pic
, p_outpic
);
229 picture_CopyPixels( p_outpic
, p_pic
);
230 vlc_mutex_unlock( &p_sys
->lock
);
232 return CopyInfoAndRelease( p_outpic
, p_pic
);
235 /*****************************************************************************
237 *****************************************************************************/
238 static void FilterErase( filter_t
*p_filter
, picture_t
*p_inpic
,
239 picture_t
*p_outpic
)
241 filter_sys_t
*p_sys
= p_filter
->p_sys
;
243 const int i_mask_pitch
= p_sys
->p_mask
->A_PITCH
;
244 const int i_mask_visible_pitch
= p_sys
->p_mask
->p
[A_PLANE
].i_visible_pitch
;
245 const int i_mask_visible_lines
= p_sys
->p_mask
->p
[A_PLANE
].i_visible_lines
;
247 for( int i_plane
= 0; i_plane
< p_inpic
->i_planes
; i_plane
++ )
249 const int i_pitch
= p_inpic
->p
[i_plane
].i_pitch
;
250 const int i_2pitch
= i_pitch
<<1;
251 const int i_visible_pitch
= p_inpic
->p
[i_plane
].i_visible_pitch
;
252 const int i_lines
= p_inpic
->p
[i_plane
].i_lines
;
253 const int i_visible_lines
= p_inpic
->p
[i_plane
].i_visible_lines
;
255 uint8_t *p_inpix
= p_inpic
->p
[i_plane
].p_pixels
;
256 uint8_t *p_outpix
= p_outpic
->p
[i_plane
].p_pixels
;
257 uint8_t *p_mask
= p_sys
->p_mask
->A_PIXELS
;
258 int i_x
= p_sys
->i_x
, i_y
= p_sys
->i_y
;
261 int i_height
= i_mask_visible_lines
;
262 int i_width
= i_mask_visible_pitch
;
264 const bool b_line_factor
= ( i_plane
/* U_PLANE or V_PLANE */ &&
265 !( p_inpic
->format
.i_chroma
== VLC_CODEC_I422
266 || p_inpic
->format
.i_chroma
== VLC_CODEC_J422
) );
268 if( i_plane
) /* U_PLANE or V_PLANE */
278 i_height
= __MIN( i_visible_lines
- i_y
, i_height
);
279 i_width
= __MIN( i_visible_pitch
- i_x
, i_width
);
281 /* Copy original pixel buffer */
282 vlc_memcpy( p_outpix
, p_inpix
, i_pitch
* i_lines
);
284 /* Horizontal linear interpolation of masked areas */
285 p_outpix
= p_outpic
->p
[i_plane
].p_pixels
+ i_y
*i_pitch
+ i_x
;
286 for( y
= 0; y
< i_height
;
287 y
++, p_mask
+= i_mask_pitch
, p_outpix
+= i_pitch
)
289 uint8_t prev
, next
= 0;
290 int prev_x
= -1, next_x
= -2;
293 /* Find a suitable value for the previous color to use when
294 * interpoling a masked pixel's value */
297 /* There are pixels before current position on the same line.
299 prev
= *(p_outpix
-1);
303 /* This is the first pixel on a line but there other lines
304 * above us. Use the pixel right above */
305 prev
= *(p_outpix
-i_pitch
);
309 /* We're in the upper left corner. This sucks. We can't use
310 * any previous value, so we'll use a dummy one. In most
311 * cases this dummy value will be fixed later on in the
316 for( x
= 0; x
< i_width
; x
++ )
318 if( p_mask
[i_plane
?x
<<1:x
] > 127 )
320 /* This is a masked pixel */
321 if( next_x
<= prev_x
)
324 /* Look for the next non masked pixel on the same
325 * line (inside the mask's bounding box) */
326 for( x0
= x
; x0
< i_width
; x0
++ )
328 if( p_mask
[i_plane
?x0
<<1:x0
] <= 127 )
330 /* We found an unmasked pixel. Victory! */
336 if( next_x
<= prev_x
)
338 /* We didn't find an unmasked pixel yet. Try
341 if( x0
< i_visible_pitch
)
343 /* If we didn't find a non masked pixel on the
344 * same line inside the mask's bounding box,
345 * use the next pixel on the line (except if
346 * it doesn't exist) */
352 /* The last pixel on the line is masked,
353 * so we'll use the "prev" value. A better
354 * approach would be to use unmasked pixels
355 * at the end of adjacent lines */
360 if( !( i_x
|| y
|| i_y
) )
361 /* We were unable to find a suitable value for
362 * the previous color (which means that we are
363 * on the first line in the upper left corner)
367 /* Divide only once instead of next_x-prev_x-1 times */
368 quot
= ((next
-prev
)<<16)/(next_x
-prev_x
);
370 /* Interpolate new value, and round correctly */
371 p_outpix
[x
] = prev
+ (((x
-prev_x
)*quot
+(1<<16))>>16);
375 /* This pixel isn't masked. It's thus suitable as a
376 * previous color for the next interpolation */
383 /* Vertical bluring */
384 p_mask
= p_sys
->p_mask
->A_PIXELS
;
385 i_height
= b_line_factor
? i_mask_visible_lines
>>1
386 : i_mask_visible_lines
;
387 /* Make sure that we stop at least 2 lines before the picture's end
388 * (since our bluring algorithm uses the 2 next lines) */
389 i_height
= __MIN( i_visible_lines
- i_y
- 2, i_height
);
390 /* Make sure that we start at least 2 lines from the top (since our
391 * bluring algorithm uses the 2 previous lines) */
393 p_outpix
= p_outpic
->p
[i_plane
].p_pixels
+ (i_y
+y
)*i_pitch
+ i_x
;
394 for( ; y
< i_height
; y
++, p_mask
+= i_mask_pitch
, p_outpix
+= i_pitch
)
396 for( x
= 0; x
< i_width
; x
++ )
398 if( p_mask
[i_plane
?x
<<1:x
] > 127 )
400 /* Ugly bluring function */
402 ( (p_outpix
[x
-i_2pitch
]<<1) /* 2 */
403 + (p_outpix
[x
-i_pitch
]<<2) /* 4 */
404 + (p_outpix
[x
]<<2) /* 4 */
405 + (p_outpix
[x
+i_pitch
]<<2) /* 4 */
406 + (p_outpix
[x
+i_2pitch
]<<1) )>>4; /* 2 */
413 static int EraseCallback( vlc_object_t
*p_this
, char const *psz_var
,
414 vlc_value_t oldval
, vlc_value_t newval
, void *p_data
)
417 filter_sys_t
*p_sys
= (filter_sys_t
*)p_data
;
419 if( !strcmp( psz_var
, CFG_PREFIX
"x" ) )
421 vlc_mutex_lock( &p_sys
->lock
);
422 p_sys
->i_x
= newval
.i_int
;
423 vlc_mutex_unlock( &p_sys
->lock
);
425 else if( !strcmp( psz_var
, CFG_PREFIX
"y" ) )
427 vlc_mutex_lock( &p_sys
->lock
);
428 p_sys
->i_y
= newval
.i_int
;
429 vlc_mutex_unlock( &p_sys
->lock
);
431 else if( !strcmp( psz_var
, CFG_PREFIX
"mask" ) )
433 vlc_mutex_lock( &p_sys
->lock
);
434 LoadMask( (filter_t
*)p_this
, newval
.psz_string
);
435 vlc_mutex_unlock( &p_sys
->lock
);
439 msg_Warn( p_this
, "Unknown callback command." );