Commit 11de24df authored by Adrien Maglo's avatar Adrien Maglo Committed by dionoea

New video filter : augmented reality. Add a moving ball to the video and...

New video filter : augmented reality. Add a moving ball to the video and handle bounces on the edges thanks to a Sobel gradient computation.
Signed-off-by: dionoea's avatarAntoine Cellerier <dionoea@videolan.org>
parent 3c1df96c
......@@ -1200,6 +1200,7 @@ VLC_ADD_PLUGIN([wave])
VLC_ADD_PLUGIN([ripple])
VLC_ADD_PLUGIN([psychedelic])
VLC_ADD_PLUGIN([gradient])
VLC_ADD_PLUGIN([ball])
VLC_ADD_PLUGIN([motionblur])
VLC_ADD_PLUGIN([rv32])
VLC_ADD_PLUGIN([rotate])
......
......@@ -24,6 +24,7 @@ SOURCES_wave = wave.c
SOURCES_ripple = ripple.c
SOURCES_psychedelic = psychedelic.c
SOURCES_gradient = gradient.c
SOURCES_ball = ball.c
SOURCES_panoramix = panoramix.c
SOURCES_opencv_wrapper = opencv_wrapper.c
SOURCES_opencv_example = opencv_example.cpp filter_event_info.h
......
/*****************************************************************************
* ball.c : Augmented reality ball video filter module
*****************************************************************************
* Copyright (C) 2000-2009 the VideoLAN team
*
* Author: Adrien Maglo <magsoft@videolan.org>
*
* The Canny edge detection algorithm comes from gradient.c which was
* writen by:
* Samuel Hocevar <sam@zoy.org>
* Antoine Cellerier <dionoea -at- videolan -dot- org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <math.h> /* sin(), cos(), asin() */
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_sout.h>
#include <vlc_vout.h>
#include "vlc_filter.h"
#include "filter_picture.h"
#include "vlc_image.h"
enum { RED, GREEN, BLUE, WHITE };
typedef struct
{
uint8_t comp1;
uint8_t comp2;
uint8_t comp3;
}COLOR;
static COLOR colorList[4];
#define COLORS_RGB \
colorList[RED].comp1 = 255; colorList[RED].comp2 = 0; \
colorList[RED].comp3 = 0; \
colorList[GREEN].comp1 = 0; colorList[GREEN].comp2 = 255; \
colorList[GREEN].comp3 = 0; \
colorList[BLUE].comp1 = 0; colorList[BLUE].comp2 = 0; \
colorList[BLUE].comp3 = 255; \
colorList[WHITE].comp1 = 255; colorList[WHITE].comp2 = 255; \
colorList[WHITE].comp3 = 255;
#define COLORS_YUV \
colorList[RED].comp1 = 82; colorList[RED].comp2 = 240; \
colorList[RED].comp3 = 90; \
colorList[GREEN].comp1 = 145; colorList[GREEN].comp2 = 34; \
colorList[GREEN].comp3 = 54 ; \
colorList[BLUE].comp1 = 41; colorList[BLUE].comp2 = 146; \
colorList[BLUE].comp3 = 240; \
colorList[WHITE].comp1 = 255; colorList[WHITE].comp2 = 128; \
colorList[WHITE].comp3 = 128;
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static int Create ( vlc_object_t * );
static void Destroy ( vlc_object_t * );
static picture_t *Filter( filter_t *, picture_t * );
static void drawBall( filter_sys_t *p_sys, picture_t *p_outpic );
static void drawPixelRGB24( filter_sys_t *p_sys, picture_t *p_outpic,
uint8_t R, uint8_t G, uint8_t B,
int x, int y, bool b_skip );
static void drawPixelI420( filter_sys_t *p_sys, picture_t *p_outpic,
uint8_t Y, uint8_t U, uint8_t V,
int x, int y, bool b_skip );
static void drawPixelPacked( filter_sys_t *p_sys, picture_t *p_outpic,
uint8_t Y, uint8_t U, uint8_t V,
int x, int y, bool b_skip );
static void FilterBall( filter_t *, picture_t *, picture_t * );
static int ballCallback( vlc_object_t *, char const *,
vlc_value_t, vlc_value_t,
void * );
static int getBallColor( vlc_object_t *p_this, char const *psz_newval );
/*****************************************************************************
* Module descriptor
*****************************************************************************/
#define BALL_COLOR_TEXT N_("Ball color")
#define BALL_COLOR_LONGTEXT N_("Ball color, one of \"red\", \"blue\" and \"green\".")
#define EDGE_VISIBLE_TEXT N_("Edge visible")
#define EDGE_VISIBLE_LONGTEXT N_("Set edge visibility.")
#define BALL_SPEED_TEXT N_("Ball speed")
#define BALL_SPEED_LONGTEXT N_("Set ball speed, the displacement value \
in number of pixels by frame.")
#define BALL_SIZE_TEXT N_("Ball size")
#define BALL_SIZE_LONGTEXT N_("Set ball size giving its radius in number \
of pixels")
#define GRAD_THRESH_TEXT N_("Gradient threshold")
#define GRAD_THRESH_LONGTEXT N_("Set gradient threshold for edge computation.")
#define FILTER_PREFIX "ball-"
static const char *const mode_list[] = { "red", "green", "blue", "white" };
static const char *const mode_list_text[] = { N_("Red"), N_("Green"),
N_("Blue"), N_("White") };
vlc_module_begin ()
set_description( N_("Ball video filter") )
set_shortname( N_( "Ball" ))
set_capability( "video filter2", 0 )
set_category( CAT_VIDEO )
set_subcategory( SUBCAT_VIDEO_VFILTER )
add_string( FILTER_PREFIX "ball-color", "ball-color", NULL,
BALL_COLOR_TEXT, BALL_COLOR_LONGTEXT, false )
change_string_list( mode_list, mode_list_text, 0 )
add_integer_with_range( FILTER_PREFIX "ball-speed", 4, 1, 15, NULL,
BALL_SPEED_TEXT, BALL_SPEED_LONGTEXT, false )
add_integer_with_range( FILTER_PREFIX "ball-size", 10, 5, 30, NULL,
BALL_SIZE_TEXT, BALL_SIZE_LONGTEXT, false )
add_integer_with_range( FILTER_PREFIX "gradient-threshold", 40, 1, 200, NULL,
GRAD_THRESH_TEXT, GRAD_THRESH_LONGTEXT, false )
add_bool( FILTER_PREFIX "edge-visible", 1, NULL,
EDGE_VISIBLE_TEXT, EDGE_VISIBLE_LONGTEXT, true )
add_shortcut( "ball" )
set_callbacks( Create, Destroy )
vlc_module_end ()
static const char *const ppsz_filter_options[] = {
"ball-color", "ball-speed", "ball-size",
"gradient-threshold", "edge-visible", NULL
};
/*****************************************************************************
* filter_sys_t: Distort video output method descriptor
*****************************************************************************
* This structure is part of the video output thread descriptor.
* It describes the Distort specific properties of an output thread.
*****************************************************************************/
struct filter_sys_t
{
vlc_mutex_t lock;
int ballColor;
image_handler_t *p_image;
/* Ball position */
int i_ball_x;
int i_ball_y;
int i_ballSpeed;
int i_ballSize;
bool b_edgeVisible;
/* Offsets for YUV packed chroma */
int i_y_offset;
int i_u_offset;
int i_v_offset;
/* Gradient values */
uint32_t *p_smooth;
int32_t *p_grad_x;
int32_t *p_grad_y;
/* Gradient threshold */
int i_gradThresh;
/* Motion vectors */
float f_lastVect_x;
float f_lastVect_y;
float f_newVect_x;
float f_newVect_y;
float f_contVect_x;
float f_contVect_y;
/* Pointer on drawing function */
void ( *drawingPixelFunction )( filter_sys_t *, picture_t *,
uint8_t, uint8_t, uint8_t,
int, int, bool );
};
/*****************************************************************************
* Create: allocates Distort video thread output method
*****************************************************************************
* This function allocates and initializes a Distort vout method.
*****************************************************************************/
static int Create( vlc_object_t *p_this )
{
filter_t *p_filter = (filter_t *)p_this;
char *psz_method;
/* Allocate structure */
p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
if( p_filter->p_sys == NULL )
return VLC_ENOMEM;
switch( p_filter->fmt_in.video.i_chroma )
{
case VLC_CODEC_I420:
case VLC_CODEC_J420:
p_filter->p_sys->drawingPixelFunction = drawPixelI420;
COLORS_YUV
break;
CASE_PACKED_YUV_422
p_filter->p_sys->drawingPixelFunction = drawPixelPacked;
COLORS_YUV
GetPackedYuvOffsets( p_filter->fmt_in.video.i_chroma,
&p_filter->p_sys->i_y_offset,
&p_filter->p_sys->i_u_offset,
&p_filter->p_sys->i_v_offset );
break;
case VLC_CODEC_RGB24:
p_filter->p_sys->drawingPixelFunction = drawPixelRGB24;
COLORS_RGB
break;
default:
msg_Err( p_filter, "Unsupported input chroma (%4s)",
(char*)&(p_filter->fmt_in.video.i_chroma) );
return VLC_EGENERIC;
}
p_filter->p_sys->p_image = image_HandlerCreate( p_filter );
if( p_filter->p_sys->p_image == NULL )
return VLC_EGENERIC;
p_filter->pf_video_filter = Filter;
config_ChainParse( p_filter, FILTER_PREFIX, ppsz_filter_options,
p_filter->p_cfg );
if( !(psz_method =
var_CreateGetNonEmptyStringCommand( p_filter,
FILTER_PREFIX "ball-color" ) ) )
{
msg_Err( p_filter, "configuration variable "
FILTER_PREFIX "ball-color empty" );
p_filter->p_sys->ballColor = RED;
}
else
p_filter->p_sys->ballColor = getBallColor( p_this, psz_method );
free( psz_method );
p_filter->p_sys->i_ballSize =
var_CreateGetIntegerCommand( p_filter, FILTER_PREFIX "ball-size" );
p_filter->p_sys->i_ballSpeed =
var_CreateGetIntegerCommand( p_filter, FILTER_PREFIX "ball-speed" );
p_filter->p_sys->b_edgeVisible =
var_CreateGetBoolCommand( p_filter, FILTER_PREFIX "edge-visible" );
p_filter->p_sys->i_gradThresh =
var_CreateGetIntegerCommand( p_filter, FILTER_PREFIX "gradient-threshold" );
vlc_mutex_init( &p_filter->p_sys->lock );
var_AddCallback( p_filter, FILTER_PREFIX "ball-color",
ballCallback, p_filter->p_sys );
var_AddCallback( p_filter, FILTER_PREFIX "ball-size",
ballCallback, p_filter->p_sys );
var_AddCallback( p_filter, FILTER_PREFIX "ball-speed",
ballCallback, p_filter->p_sys );
var_AddCallback( p_filter, FILTER_PREFIX "edge-visible",
ballCallback, p_filter->p_sys );
p_filter->p_sys->p_smooth = NULL;
p_filter->p_sys->p_grad_x = NULL;
p_filter->p_sys->p_grad_y = NULL;
p_filter->p_sys->i_ball_x = 100;
p_filter->p_sys->i_ball_y = 100;
p_filter->p_sys->f_lastVect_x = 0;
p_filter->p_sys->f_lastVect_y = -1;
return VLC_SUCCESS;
}
/*****************************************************************************
* Destroy: destroy Distort video thread output method
*****************************************************************************
* Terminate an output method created by DistortCreateOutputMethod
*****************************************************************************/
static void Destroy( vlc_object_t *p_this )
{
filter_t *p_filter = (filter_t *)p_this;
filter_sys_t *p_sys = p_filter->p_sys;
vlc_mutex_destroy( &p_sys->lock );
image_HandlerDelete( p_sys->p_image );
free( p_sys->p_smooth );
free( p_sys->p_grad_x );
free( p_sys->p_grad_y );
free( p_sys );
}
/*****************************************************************************
* Render: displays previously rendered output
*****************************************************************************
* This function send the currently rendered image to Distort image, waits
* until it is displayed and switch the two rendering buffers, preparing next
* frame.
*****************************************************************************/
static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
{
picture_t *p_outpic;
if( !p_pic ) return NULL;
p_outpic = filter_NewPicture( p_filter );
if( !p_outpic )
{
picture_Release( p_pic );
return NULL;
}
vlc_mutex_lock( &p_filter->p_sys->lock );
FilterBall( p_filter, p_pic, p_outpic );
vlc_mutex_unlock( &p_filter->p_sys->lock );
return CopyInfoAndRelease( p_outpic, p_pic );
}
/*****************************************************************************
* Drawing functions
*****************************************************************************/
static void drawBall( filter_sys_t *p_sys, picture_t *p_outpic )
{
int x = p_sys->i_ball_x;
int y = p_sys->i_ball_y;
int size = p_sys->i_ballSize;
const int i_width = p_outpic->p[0].i_visible_pitch;
const int i_height = p_outpic->p[0].i_visible_lines;
for( int j = y - size; j <= y + size; j++ )
{
bool b_skip = ( x - size ) % 2;
for( int i = x - size; i <= x + size; i++ )
{
/* Draw the pixel if it is inside the disk
and check we don't write out the frame. */
if( ( i - x ) * ( i - x ) + ( j - y ) * ( j - y ) <= size * size
&& i >= 0 && i < i_width
&& j >= 0 && j < i_height )
{
( *p_sys->drawingPixelFunction )( p_sys, p_outpic,
colorList[ p_sys->ballColor ].comp1,
colorList[ p_sys->ballColor ].comp2,
colorList[ p_sys->ballColor ].comp3,
i, j, b_skip );
}
b_skip = !b_skip;
}
}
}
static void drawPixelRGB24( filter_sys_t *p_sys, picture_t *p_outpic,
uint8_t R, uint8_t G, uint8_t B,
int x, int y, bool b_skip )
{
VLC_UNUSED( p_sys );
VLC_UNUSED( b_skip );
uint8_t *p_pixel = p_outpic->p[0].p_pixels
+ p_outpic->p[0].i_pitch
* x + 3 * y;
*p_pixel = B;
*++p_pixel = G;
*++p_pixel = R;
}
static void drawPixelI420( filter_sys_t *p_sys, picture_t *p_outpic,
uint8_t Y, uint8_t U, uint8_t V,
int x, int y, bool b_skip )
{
VLC_UNUSED( p_sys );
*( p_outpic->p[0].p_pixels + p_outpic->p[0].i_pitch * y + x ) = Y;
if( !b_skip )
{
*( p_outpic->p[2].p_pixels + p_outpic->p[2].i_pitch
* ( y / 2 ) + x / 2 ) = U;
*( p_outpic->p[1].p_pixels + p_outpic->p[1].i_pitch
* ( y / 2 ) + x / 2 ) = V;
}
}
static void drawPixelPacked( filter_sys_t *p_sys, picture_t *p_outpic,
uint8_t Y, uint8_t U, uint8_t V,
int x, int y, bool b_skip )
{
uint8_t *p_pixel = p_outpic->p[0].p_pixels
+ p_outpic->p[0].i_pitch * y + x * 2;
*( p_pixel + p_sys->i_y_offset ) = Y;
if( !b_skip )
{
*( p_pixel + p_sys->i_u_offset ) = U;
*( p_pixel + p_sys->i_v_offset ) = V;
}
}
/*****************************************************************************
* Nomalize vector
*****************************************************************************
* Modify its value to set its norm to 1 and keep its direction.
*****************************************************************************/
static void NormalizeVector( float *vect_x, float *vect_y )
{
float norm = sqrt( *vect_x * *vect_x + *vect_y * *vect_y );
if( *vect_x != 0 || *vect_y != 0 )
{
*vect_x /= norm;
*vect_y /= norm;
}
}
/*****************************************************************************
* Gaussian Convolution
*****************************************************************************
* Gaussian convolution ( sigma == 1.4 )
*
* | 2 4 5 4 2 | | 2 4 4 4 2 |
* | 4 9 12 9 4 | | 4 8 12 8 4 |
* | 5 12 15 12 5 | ~ | 4 12 16 12 4 |
* | 4 9 12 9 4 | | 4 8 12 8 4 |
* | 2 4 5 4 2 | | 2 4 4 4 2 |
*****************************************************************************/
static void GaussianConvolution( picture_t *p_inpic, uint32_t *p_smooth )
{
const uint8_t *p_inpix = p_inpic->p[Y_PLANE].p_pixels;
const int i_src_pitch = p_inpic->p[Y_PLANE].i_pitch;
const int i_src_visible = p_inpic->p[Y_PLANE].i_visible_pitch;
const int i_numLines = p_inpic->p[Y_PLANE].i_visible_lines;
int x,y;
for( y = 2; y < i_numLines - 2; y++ )
{
for( x = 2; x < i_src_visible - 2; x++ )
{
p_smooth[y*i_src_visible+x] = (uint32_t)(
/* 2 rows up */
( p_inpix[(y-2)*i_src_pitch+x-2] )
+ ((p_inpix[(y-2)*i_src_pitch+x-1]
+ p_inpix[(y-2)*i_src_pitch+x]
+ p_inpix[(y-2)*i_src_pitch+x+1])<<1 )
+ ( p_inpix[(y-2)*i_src_pitch+x+2] )
/* 1 row up */
+ ((p_inpix[(y-1)*i_src_pitch+x-2]
+ ( p_inpix[(y-1)*i_src_pitch+x-1]<<1 )
+ ( p_inpix[(y-1)*i_src_pitch+x]*3 )
+ ( p_inpix[(y-1)*i_src_pitch+x+1]<<1 )
+ p_inpix[(y-1)*i_src_pitch+x+2]
/* */
+ p_inpix[y*i_src_pitch+x-2]
+ ( p_inpix[y*i_src_pitch+x-1]*3 )
+ ( p_inpix[y*i_src_pitch+x]<<2 )
+ ( p_inpix[y*i_src_pitch+x+1]*3 )
+ p_inpix[y*i_src_pitch+x+2]
/* 1 row down */
+ p_inpix[(y+1)*i_src_pitch+x-2]
+ ( p_inpix[(y+1)*i_src_pitch+x-1]<<1 )
+ ( p_inpix[(y+1)*i_src_pitch+x]*3 )
+ ( p_inpix[(y+1)*i_src_pitch+x+1]<<1 )
+ p_inpix[(y+1)*i_src_pitch+x+2] )<<1 )
/* 2 rows down */
+ ( p_inpix[(y+2)*i_src_pitch+x-2] )
+ ((p_inpix[(y+2)*i_src_pitch+x-1]
+ p_inpix[(y+2)*i_src_pitch+x]
+ p_inpix[(y+2)*i_src_pitch+x+1])<<1 )
+ ( p_inpix[(y+2)*i_src_pitch+x+2] )
) >> 6 /* 115 */;
}
}
}
/*****************************************************************************
* FilterBall: Augmented reality ball video filter
*****************************************************************************
* The edge detection part comes from gradient.c video filter module.
* The Canny edge detection algorithm is used :
* http://fourier.eng.hmc.edu/e161/lectures/canny/node1.html
* (well ... the implementation isn't really the canny algorithm ... but some
* ideas are the same)
*****************************************************************************/
static void FilterBall( filter_t *p_filter, picture_t *p_inpic,
picture_t *p_outpic )
{
int x, y;
filter_sys_t *p_sys = p_filter->p_sys;
uint32_t *p_smooth;
int32_t *p_grad_x;
int32_t *p_grad_y;
picture_t *p_converted;
video_format_t fmt_comp;
switch( p_filter->fmt_in.video.i_chroma )
{
case VLC_CODEC_RGB24:
CASE_PACKED_YUV_422
fmt_comp.i_width = p_filter->fmt_in.video.i_width;
fmt_comp.i_height = p_filter->fmt_in.video.i_height;
fmt_comp.i_chroma = VLC_FOURCC('G','R','E','Y');
fmt_comp.i_visible_width = fmt_comp.i_width;
fmt_comp.i_visible_height = fmt_comp.i_height;
p_converted = image_Convert( p_filter->p_sys->p_image, p_inpic,
&(p_filter->fmt_in.video),
&fmt_comp );
if( !p_converted )
return;
break;
default:
p_converted = p_inpic;
break;
}
const int i_numCols = p_converted->p[0].i_visible_pitch;
const int i_numLines = p_converted->p[0].i_visible_lines;
if( !p_filter->p_sys->p_smooth )
p_filter->p_sys->p_smooth =
(uint32_t *)malloc( i_numLines * i_numCols
* sizeof(uint32_t));
p_smooth = p_filter->p_sys->p_smooth;
if( !p_filter->p_sys->p_grad_x )
p_filter->p_sys->p_grad_x =
(int32_t *)malloc( i_numLines * i_numCols
* sizeof(int32_t));
p_grad_x = p_filter->p_sys->p_grad_x;
if( !p_filter->p_sys->p_grad_y )
p_filter->p_sys->p_grad_y =
(int32_t *)malloc( i_numLines * i_numCols
* sizeof(int32_t));
p_grad_y = p_filter->p_sys->p_grad_y;
if( !p_smooth || !p_grad_x || !p_grad_y ) return;
vlc_memcpy( p_outpic->p[0].p_pixels, p_inpic->p[0].p_pixels,
p_outpic->p[0].i_lines * p_outpic->p[0].i_pitch );
vlc_memcpy( p_outpic->p[1].p_pixels, p_inpic->p[1].p_pixels,
p_outpic->p[1].i_lines * p_outpic->p[1].i_pitch );
vlc_memcpy( p_outpic->p[2].p_pixels, p_inpic->p[2].p_pixels,
p_outpic->p[2].i_lines * p_outpic->p[2].i_pitch );