1 /*****************************************************************************
2 * erase.c : logo erase video filter
3 *****************************************************************************
4 * Copyright (C) 2007 VLC authors and VideoLAN
7 * Authors: Antoine Cellerier <dionoea -at- videolan -dot- org>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * 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>
36 #include <vlc_filter.h>
37 #include <vlc_picture.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 filter", 0 )
72 set_category( CAT_VIDEO
)
73 set_subcategory( SUBCAT_VIDEO_VFILTER
)
75 add_loadfile( CFG_PREFIX
"mask", NULL
,
76 MASK_TEXT
, MASK_LONGTEXT
, false )
77 add_integer( CFG_PREFIX
"x", 0, POSX_TEXT
, POSX_LONGTEXT
, false )
78 add_integer( CFG_PREFIX
"y", 0, 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 video_format_Init( &fmt_in
, 0 );
105 video_format_Init( &fmt_out
, VLC_CODEC_YUVA
);
106 p_image
= image_HandlerCreate( p_filter
);
107 char *psz_url
= vlc_path2uri( psz_filename
, NULL
);
108 p_filter
->p_sys
->p_mask
=
109 image_ReadUrl( p_image
, psz_url
, &fmt_in
, &fmt_out
);
111 video_format_Clean( &fmt_in
);
112 video_format_Clean( &fmt_out
);
113 if( p_filter
->p_sys
->p_mask
)
116 picture_Release( p_old_mask
);
118 else if( p_old_mask
)
120 p_filter
->p_sys
->p_mask
= p_old_mask
;
121 msg_Err( p_filter
, "Error while loading new mask. Keeping old mask." );
124 msg_Err( p_filter
, "Error while loading new mask. No mask available." );
126 image_HandlerDelete( p_image
);
129 /*****************************************************************************
131 *****************************************************************************/
132 static int Create( vlc_object_t
*p_this
)
134 filter_t
*p_filter
= (filter_t
*)p_this
;
138 switch( p_filter
->fmt_in
.video
.i_chroma
)
149 msg_Err( p_filter
, "Unsupported input chroma (%4.4s)",
150 (char*)&(p_filter
->fmt_in
.video
.i_chroma
) );
154 /* Allocate structure */
155 p_filter
->p_sys
= malloc( sizeof( filter_sys_t
) );
156 if( p_filter
->p_sys
== NULL
)
158 p_sys
= p_filter
->p_sys
;
160 p_filter
->pf_video_filter
= Filter
;
162 config_ChainParse( p_filter
, CFG_PREFIX
, ppsz_filter_options
,
166 var_CreateGetNonEmptyStringCommand( p_filter
, CFG_PREFIX
"mask" );
170 msg_Err( p_filter
, "Missing 'mask' option value." );
175 p_sys
->p_mask
= NULL
;
176 LoadMask( p_filter
, psz_filename
);
177 free( psz_filename
);
179 p_sys
->i_x
= var_CreateGetIntegerCommand( p_filter
, CFG_PREFIX
"x" );
180 p_sys
->i_y
= var_CreateGetIntegerCommand( p_filter
, CFG_PREFIX
"y" );
182 vlc_mutex_init( &p_sys
->lock
);
183 var_AddCallback( p_filter
, CFG_PREFIX
"x", EraseCallback
, p_sys
);
184 var_AddCallback( p_filter
, CFG_PREFIX
"y", EraseCallback
, p_sys
);
185 var_AddCallback( p_filter
, CFG_PREFIX
"mask", EraseCallback
, p_sys
);
190 /*****************************************************************************
192 *****************************************************************************/
193 static void Destroy( vlc_object_t
*p_this
)
195 filter_t
*p_filter
= (filter_t
*)p_this
;
196 filter_sys_t
*p_sys
= p_filter
->p_sys
;
198 picture_Release( p_sys
->p_mask
);
200 var_DelCallback( p_filter
, CFG_PREFIX
"x", EraseCallback
, p_sys
);
201 var_DelCallback( p_filter
, CFG_PREFIX
"y", EraseCallback
, p_sys
);
202 var_DelCallback( p_filter
, CFG_PREFIX
"mask", EraseCallback
, p_sys
);
203 vlc_mutex_destroy( &p_sys
->lock
);
205 free( p_filter
->p_sys
);
208 /*****************************************************************************
210 *****************************************************************************/
211 static picture_t
*Filter( filter_t
*p_filter
, picture_t
*p_pic
)
214 filter_sys_t
*p_sys
= p_filter
->p_sys
;
216 if( !p_pic
) return NULL
;
218 p_outpic
= filter_NewPicture( p_filter
);
221 picture_Release( p_pic
);
225 /* If the mask is empty: just copy the image */
226 vlc_mutex_lock( &p_sys
->lock
);
228 FilterErase( p_filter
, p_pic
, p_outpic
);
230 picture_CopyPixels( p_outpic
, p_pic
);
231 vlc_mutex_unlock( &p_sys
->lock
);
233 return CopyInfoAndRelease( p_outpic
, p_pic
);
236 /*****************************************************************************
238 *****************************************************************************/
239 static void FilterErase( filter_t
*p_filter
, picture_t
*p_inpic
,
240 picture_t
*p_outpic
)
242 filter_sys_t
*p_sys
= p_filter
->p_sys
;
244 const int i_mask_pitch
= p_sys
->p_mask
->A_PITCH
;
245 const int i_mask_visible_pitch
= p_sys
->p_mask
->p
[A_PLANE
].i_visible_pitch
;
246 const int i_mask_visible_lines
= p_sys
->p_mask
->p
[A_PLANE
].i_visible_lines
;
248 for( int i_plane
= 0; i_plane
< p_inpic
->i_planes
; i_plane
++ )
250 const int i_pitch
= p_outpic
->p
[i_plane
].i_pitch
;
251 const int i_2pitch
= i_pitch
<<1;
252 const int i_visible_pitch
= p_inpic
->p
[i_plane
].i_visible_pitch
;
253 const int i_visible_lines
= p_inpic
->p
[i_plane
].i_visible_lines
;
255 uint8_t *p_mask
= p_sys
->p_mask
->A_PIXELS
;
256 int i_x
= p_sys
->i_x
, i_y
= p_sys
->i_y
;
258 int i_height
= i_mask_visible_lines
;
259 int i_width
= i_mask_visible_pitch
;
261 const bool b_line_factor
= ( i_plane
/* U_PLANE or V_PLANE */ &&
262 !( p_inpic
->format
.i_chroma
== VLC_CODEC_I422
263 || p_inpic
->format
.i_chroma
== VLC_CODEC_J422
) );
265 if( i_plane
) /* U_PLANE or V_PLANE */
275 i_height
= __MIN( i_visible_lines
- i_y
, i_height
);
276 i_width
= __MIN( i_visible_pitch
- i_x
, i_width
);
278 /* Copy original pixel buffer */
279 plane_CopyPixels( &p_outpic
->p
[i_plane
], &p_inpic
->p
[i_plane
] );
281 /* Horizontal linear interpolation of masked areas */
282 uint8_t *p_outpix
= p_outpic
->p
[i_plane
].p_pixels
+ i_y
*i_pitch
+ i_x
;
283 for( int y
= 0; y
< i_height
;
284 y
++, p_mask
+= i_mask_pitch
, p_outpix
+= i_pitch
)
286 uint8_t prev
, next
= 0;
287 int prev_x
= -1, next_x
= -2;
290 /* Find a suitable value for the previous color to use when
291 * interpoling a masked pixel's value */
294 /* There are pixels before current position on the same line.
296 prev
= *(p_outpix
-1);
300 /* This is the first pixel on a line but there other lines
301 * above us. Use the pixel right above */
302 prev
= *(p_outpix
-i_pitch
);
306 /* We're in the upper left corner. This sucks. We can't use
307 * any previous value, so we'll use a dummy one. In most
308 * cases this dummy value will be fixed later on in the
313 for( int x
= 0; x
< i_width
; x
++ )
315 if( p_mask
[i_plane
?x
<<1:x
] > 127 )
317 /* This is a masked pixel */
318 if( next_x
<= prev_x
)
321 /* Look for the next non masked pixel on the same
322 * line (inside the mask's bounding box) */
323 for( x0
= x
; x0
< i_width
; x0
++ )
325 if( p_mask
[i_plane
?x0
<<1:x0
] <= 127 )
327 /* We found an unmasked pixel. Victory! */
333 if( next_x
<= prev_x
)
335 /* We didn't find an unmasked pixel yet. Try
338 if( x0
< i_visible_pitch
)
340 /* If we didn't find a non masked pixel on the
341 * same line inside the mask's bounding box,
342 * use the next pixel on the line (except if
343 * it doesn't exist) */
349 /* The last pixel on the line is masked,
350 * so we'll use the "prev" value. A better
351 * approach would be to use unmasked pixels
352 * at the end of adjacent lines */
357 if( !( i_x
|| y
|| i_y
) )
358 /* We were unable to find a suitable value for
359 * the previous color (which means that we are
360 * on the first line in the upper left corner)
364 /* Divide only once instead of next_x-prev_x-1 times */
365 quot
= ((next
-prev
)<<16)/(next_x
-prev_x
);
367 /* Interpolate new value, and round correctly */
368 p_outpix
[x
] = prev
+ (((x
-prev_x
)*quot
+(1<<16))>>16);
372 /* This pixel isn't masked. It's thus suitable as a
373 * previous color for the next interpolation */
380 /* Vertical bluring */
381 p_mask
= p_sys
->p_mask
->A_PIXELS
;
382 i_height
= b_line_factor
? i_mask_visible_lines
>>1
383 : i_mask_visible_lines
;
384 /* Make sure that we stop at least 2 lines before the picture's end
385 * (since our bluring algorithm uses the 2 next lines) */
386 i_height
= __MIN( i_visible_lines
- i_y
- 2, i_height
);
387 /* Make sure that we start at least 2 lines from the top (since our
388 * bluring algorithm uses the 2 previous lines) */
389 int y
= __MAX(i_y
,2);
390 p_outpix
= p_outpic
->p
[i_plane
].p_pixels
+ (i_y
+y
)*i_pitch
+ i_x
;
391 for( ; y
< i_height
; y
++, p_mask
+= i_mask_pitch
, p_outpix
+= i_pitch
)
393 for( int x
= 0; x
< i_width
; x
++ )
395 if( p_mask
[i_plane
?x
<<1:x
] > 127 )
397 /* Ugly bluring function */
399 ( (p_outpix
[x
-i_2pitch
]<<1) /* 2 */
400 + (p_outpix
[x
-i_pitch
]<<2) /* 4 */
401 + (p_outpix
[x
]<<2) /* 4 */
402 + (p_outpix
[x
+i_pitch
]<<2) /* 4 */
403 + (p_outpix
[x
+i_2pitch
]<<1) )>>4; /* 2 */
410 static int EraseCallback( vlc_object_t
*p_this
, char const *psz_var
,
411 vlc_value_t oldval
, vlc_value_t newval
, void *p_data
)
414 filter_sys_t
*p_sys
= (filter_sys_t
*)p_data
;
416 if( !strcmp( psz_var
, CFG_PREFIX
"x" ) )
418 vlc_mutex_lock( &p_sys
->lock
);
419 p_sys
->i_x
= newval
.i_int
;
420 vlc_mutex_unlock( &p_sys
->lock
);
422 else if( !strcmp( psz_var
, CFG_PREFIX
"y" ) )
424 vlc_mutex_lock( &p_sys
->lock
);
425 p_sys
->i_y
= newval
.i_int
;
426 vlc_mutex_unlock( &p_sys
->lock
);
428 else if( !strcmp( psz_var
, CFG_PREFIX
"mask" ) )
430 vlc_mutex_lock( &p_sys
->lock
);
431 LoadMask( (filter_t
*)p_this
, newval
.psz_string
);
432 vlc_mutex_unlock( &p_sys
->lock
);
436 msg_Warn( p_this
, "Unknown callback command." );