gradient.c 28.6 KB
Newer Older
1
/*****************************************************************************
2
 * gradient.c : Gradient and edge detection video effects plugin for vlc
3
 *****************************************************************************
4
 * Copyright (C) 2000-2008 the VideoLAN team
5
 * $Id$
6 7
 *
 * Authors: Samuel Hocevar <sam@zoy.org>
8
 *          Antoine Cellerier <dionoea -at- videolan -dot- org>
9 10 11 12 13
 *
 * 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.
14
 *
15 16 17 18 19 20 21
 * 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
Antoine Cellerier's avatar
Antoine Cellerier committed
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 24 25 26 27 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

29 30 31 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33 34
#include <math.h>                                            /* sin(), cos() */

35
#include <vlc_common.h>
36
#include <vlc_plugin.h>
Clément Stenac's avatar
Clément Stenac committed
37
#include <vlc_sout.h>
38

Rémi Duraffort's avatar
Rémi Duraffort committed
39
#include <vlc_filter.h>
40
#include "filter_picture.h"
41

42
enum { GRADIENT, EDGE, HOUGH };
43 44 45 46 47 48 49

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int  Create    ( vlc_object_t * );
static void Destroy   ( vlc_object_t * );

50
static picture_t *Filter( filter_t *, picture_t * );
51 52 53
static int GradientCallback( vlc_object_t *, char const *,
                             vlc_value_t, vlc_value_t,
                             void * );
54

55 56 57
static void FilterGradient( filter_t *, picture_t *, picture_t * );
static void FilterEdge    ( filter_t *, picture_t *, picture_t * );
static void FilterHough   ( filter_t *, picture_t *, picture_t * );
58

59 60 61
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
Christophe Massiot's avatar
Christophe Massiot committed
62
#define MODE_TEXT N_("Distort mode")
63
#define MODE_LONGTEXT N_("Distort mode, one of \"gradient\", \"edge\" and \"hough\".")
64

65
#define GRADIENT_TEXT N_("Gradient image type")
66 67
#define GRADIENT_LONGTEXT N_("Gradient image type (0 or 1). 0 will " \
        "turn the image to white while 1 will keep colors." )
68 69

#define CARTOON_TEXT N_("Apply cartoon effect")
70 71
#define CARTOON_LONGTEXT N_("Apply cartoon effect. It is only used by " \
    "\"gradient\" and \"edge\".")
72

73 74
static const char *const mode_list[] = { "gradient", "edge", "hough" };
static const char *const mode_list_text[] = { N_("Gradient"), N_("Edge"), N_("Hough") };
75 76

#define FILTER_PREFIX "gradient-"
77

78 79 80 81 82 83
vlc_module_begin ()
    set_description( N_("Gradient video filter") )
    set_shortname( N_( "Gradient" ))
    set_capability( "video filter2", 0 )
    set_category( CAT_VIDEO )
    set_subcategory( SUBCAT_VIDEO_VFILTER )
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
84

85
    add_string( FILTER_PREFIX "mode", "gradient", NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
86
                MODE_TEXT, MODE_LONGTEXT, false )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
87
        change_string_list( mode_list, mode_list_text, 0 )
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
88

89
    add_integer_with_range( FILTER_PREFIX "type", 0, 0, 1, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
90
                GRADIENT_TEXT, GRADIENT_LONGTEXT, false )
91
    add_bool( FILTER_PREFIX "cartoon", 1, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
92
                CARTOON_TEXT, CARTOON_LONGTEXT, false )
93

94 95 96
    add_shortcut( "gradient" )
    set_callbacks( Create, Destroy )
vlc_module_end ()
97

98
static const char *const ppsz_filter_options[] = {
99 100 101
    "mode", "type", "cartoon", NULL
};

102
/*****************************************************************************
Jean-Paul Saman's avatar
Jean-Paul Saman committed
103
 * filter_sys_t: Distort video output method descriptor
104 105 106 107
 *****************************************************************************
 * This structure is part of the video output thread descriptor.
 * It describes the Distort specific properties of an output thread.
 *****************************************************************************/
108
struct filter_sys_t
109
{
110
    vlc_mutex_t lock;
111
    int i_mode;
112 113 114

    /* For the gradient mode */
    int i_gradient_type;
115
    bool b_cartoon;
116

117 118 119 120
    uint32_t *p_buf32;
    uint32_t *p_buf32_bis;
    uint8_t *p_buf8;

Antoine Cellerier's avatar
Antoine Cellerier committed
121
    /* For hough mode */
122
    int *p_pre_hough;
123 124 125 126 127 128 129 130 131
};

/*****************************************************************************
 * Create: allocates Distort video thread output method
 *****************************************************************************
 * This function allocates and initializes a Distort vout method.
 *****************************************************************************/
static int Create( vlc_object_t *p_this )
{
132 133
    filter_t *p_filter = (filter_t *)p_this;
    char *psz_method;
134

135 136 137 138 139 140 141 142 143 144 145
    switch( p_filter->fmt_in.video.i_chroma )
    {
        CASE_PLANAR_YUV
            break;

        default:
             msg_Err( p_filter, "Unsupported input chroma (%4s)",
                      (char*)&(p_filter->fmt_in.video.i_chroma) );
            return VLC_EGENERIC;
    }

146
    /* Allocate structure */
147 148
    p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
    if( p_filter->p_sys == NULL )
149
        return VLC_ENOMEM;
150

151 152 153 154
    p_filter->pf_video_filter = Filter;

    p_filter->p_sys->p_pre_hough = NULL;

155
    config_ChainParse( p_filter, FILTER_PREFIX, ppsz_filter_options,
156
                   p_filter->p_cfg );
157

158 159
    if( !(psz_method =
        var_CreateGetNonEmptyStringCommand( p_filter, FILTER_PREFIX "mode" )) )
160
    {
161 162 163
        msg_Err( p_filter, "configuration variable "
                 FILTER_PREFIX "mode empty" );
        p_filter->p_sys->i_mode = GRADIENT;
164
    }
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
165
    else
166
    {
167
        if( !strcmp( psz_method, "gradient" ) )
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
168
        {
169
            p_filter->p_sys->i_mode = GRADIENT;
170 171 172
        }
        else if( !strcmp( psz_method, "edge" ) )
        {
173
            p_filter->p_sys->i_mode = EDGE;
174
        }
175 176
        else if( !strcmp( psz_method, "hough" ) )
        {
177
            p_filter->p_sys->i_mode = HOUGH;
178
        }
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
179 180
        else
        {
Antoine Cellerier's avatar
Antoine Cellerier committed
181
            msg_Err( p_filter, "no valid gradient mode provided (%s)", psz_method );
182
            p_filter->p_sys->i_mode = GRADIENT;
183 184
        }
    }
185
    free( psz_method );
186

187
    p_filter->p_sys->i_gradient_type =
188
        var_CreateGetIntegerCommand( p_filter, FILTER_PREFIX "type" );
189
    p_filter->p_sys->b_cartoon =
190 191
        var_CreateGetBoolCommand( p_filter, FILTER_PREFIX "cartoon" );

192
    vlc_mutex_init( &p_filter->p_sys->lock );
193 194 195 196 197 198
    var_AddCallback( p_filter, FILTER_PREFIX "mode",
                     GradientCallback, p_filter->p_sys );
    var_AddCallback( p_filter, FILTER_PREFIX "type",
                     GradientCallback, p_filter->p_sys );
    var_AddCallback( p_filter, FILTER_PREFIX "cartoon",
                     GradientCallback, p_filter->p_sys );
199

200 201 202 203
    p_filter->p_sys->p_buf32 = NULL;
    p_filter->p_sys->p_buf32_bis = NULL;
    p_filter->p_sys->p_buf8 = NULL;

204
    return VLC_SUCCESS;
205
}
206

207 208 209 210 211 212 213
/*****************************************************************************
 * Destroy: destroy Distort video thread output method
 *****************************************************************************
 * Terminate an output method created by DistortCreateOutputMethod
 *****************************************************************************/
static void Destroy( vlc_object_t *p_this )
{
214
    filter_t *p_filter = (filter_t *)p_this;
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
    filter_sys_t *p_sys = p_filter->p_sys;

    var_DelCallback( p_filter, FILTER_PREFIX "mode",
                     GradientCallback, p_sys );
    var_DelCallback( p_filter, FILTER_PREFIX "type",
                     GradientCallback, p_sys );
    var_DelCallback( p_filter, FILTER_PREFIX "cartoon",
                     GradientCallback, p_sys );
    vlc_mutex_destroy( &p_sys->lock );

    free( p_sys->p_buf32 );
    free( p_sys->p_buf32_bis );
    free( p_sys->p_buf8 );
    free( p_sys->p_pre_hough );

    free( p_sys );
231 232 233 234 235 236 237 238 239
}

/*****************************************************************************
 * 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.
 *****************************************************************************/
240
static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
241 242 243
{
    picture_t *p_outpic;

244 245
    if( !p_pic ) return NULL;

Laurent Aimar's avatar
Laurent Aimar committed
246
    p_outpic = filter_NewPicture( p_filter );
247
    if( !p_outpic )
248
    {
249
        picture_Release( p_pic );
250
        return NULL;
251 252
    }

253
    vlc_mutex_lock( &p_filter->p_sys->lock );
254
    switch( p_filter->p_sys->i_mode )
255
    {
Antoine Cellerier's avatar
Antoine Cellerier committed
256
        case EDGE:
257
            FilterEdge( p_filter, p_pic, p_outpic );
258 259
            break;

Antoine Cellerier's avatar
Antoine Cellerier committed
260
        case GRADIENT:
261
            FilterGradient( p_filter, p_pic, p_outpic );
262
            break;
Antoine Cellerier's avatar
Antoine Cellerier committed
263 264

        case HOUGH:
265
            FilterHough( p_filter, p_pic, p_outpic );
Antoine Cellerier's avatar
Antoine Cellerier committed
266 267
            break;

268 269 270
        default:
            break;
    }
271
    vlc_mutex_unlock( &p_filter->p_sys->lock );
272

273
    return CopyInfoAndRelease( p_outpic, p_pic );
274 275
}

276
/*****************************************************************************
Antoine Cellerier's avatar
Antoine Cellerier committed
277 278 279 280 281 282 283 284 285
 * 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 |
286
 *****************************************************************************/
Antoine Cellerier's avatar
Antoine Cellerier committed
287
static void GaussianConvolution( picture_t *p_inpic, uint32_t *p_smooth )
288
{
289 290 291 292
    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_num_lines = p_inpic->p[Y_PLANE].i_visible_lines;
293

Antoine Cellerier's avatar
Antoine Cellerier committed
294
    int x,y;
295
    for( y = 2; y < i_num_lines - 2; y++ )
296
    {
297
        for( x = 2; x < i_src_visible - 2; x++ )
298
        {
299
            p_smooth[y*i_src_visible+x] = (uint32_t)(
300
              /* 2 rows up */
301 302 303 304 305
                ( 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] )
306
              /* 1 row up */
307 308 309 310 311
              + ((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]
312
              /* */
313 314 315 316 317
              +   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]
318
              /* 1 row down */
319 320 321 322 323
              +   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 )
324
              /* 2 rows down */
325 326 327 328 329 330
              + ( 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 */;
331 332
        }
    }
Antoine Cellerier's avatar
Antoine Cellerier committed
333 334 335
}

/*****************************************************************************
336
 * FilterGradient: Sobel
Antoine Cellerier's avatar
Antoine Cellerier committed
337
 *****************************************************************************/
338 339
static void FilterGradient( filter_t *p_filter, picture_t *p_inpic,
                                                picture_t *p_outpic )
Antoine Cellerier's avatar
Antoine Cellerier committed
340 341
{
    int x, y;
342 343 344 345
    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_dst_pitch = p_outpic->p[Y_PLANE].i_pitch;
    const int i_num_lines = p_inpic->p[Y_PLANE].i_visible_lines;
Antoine Cellerier's avatar
Antoine Cellerier committed
346

347
    const uint8_t *p_inpix = p_inpic->p[Y_PLANE].p_pixels;
Antoine Cellerier's avatar
Antoine Cellerier committed
348 349
    uint8_t *p_outpix = p_outpic->p[Y_PLANE].p_pixels;

350 351 352 353 354
    uint32_t *p_smooth;
    if( !p_filter->p_sys->p_buf32 )
        p_filter->p_sys->p_buf32 =
        (uint32_t *)malloc( i_num_lines * i_src_visible * sizeof(uint32_t));
    p_smooth = p_filter->p_sys->p_buf32;
Antoine Cellerier's avatar
Antoine Cellerier committed
355 356 357

    if( !p_smooth ) return;

358
    if( p_filter->p_sys->b_cartoon )
Antoine Cellerier's avatar
Antoine Cellerier committed
359
    {
360
        vlc_memcpy( p_outpic->p[U_PLANE].p_pixels,
Antoine Cellerier's avatar
Antoine Cellerier committed
361 362
            p_inpic->p[U_PLANE].p_pixels,
            p_outpic->p[U_PLANE].i_lines * p_outpic->p[U_PLANE].i_pitch );
363
        vlc_memcpy( p_outpic->p[V_PLANE].p_pixels,
Antoine Cellerier's avatar
Antoine Cellerier committed
364 365 366 367 368
            p_inpic->p[V_PLANE].p_pixels,
            p_outpic->p[V_PLANE].i_lines * p_outpic->p[V_PLANE].i_pitch );
    }
    else
    {
369
        vlc_memset( p_outpic->p[U_PLANE].p_pixels, 0x80,
Antoine Cellerier's avatar
Antoine Cellerier committed
370
            p_outpic->p[U_PLANE].i_lines * p_outpic->p[U_PLANE].i_pitch );
371
        vlc_memset( p_outpic->p[V_PLANE].p_pixels, 0x80,
Antoine Cellerier's avatar
Antoine Cellerier committed
372 373 374 375
            p_outpic->p[V_PLANE].i_lines * p_outpic->p[V_PLANE].i_pitch );
    }

    GaussianConvolution( p_inpic, p_smooth );
376 377 378 379 380 381 382

    /* Sobel gradient

     | -1 0 1 |     |  1  2  1 |
     | -2 0 2 | and |  0  0  0 |
     | -1 0 1 |     | -1 -2 -1 | */

383 384 385 386 387
#define FOR                                                     \
    for( y = 1; y < i_num_lines - 1; y++ )                      \
    {                                                           \
        for( x = 1; x < i_src_visible - 1; x++ )                \
        {                                                       \
388
            const uint32_t a =                                  \
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
            (                                                   \
              abs(                                              \
                 ( p_smooth[(y-1)*i_src_visible+x-1]            \
                   - p_smooth[(y+1)*i_src_visible+x-1] )        \
               + ( ( p_smooth[(y-1)*i_src_visible+x]            \
                    - p_smooth[(y+1)*i_src_visible+x] ) <<1 )   \
               + ( p_smooth[(y-1)*i_src_visible+x+1]            \
                   - p_smooth[(y+1)*i_src_visible+x+1] )        \
              )                                                 \
            +                                                   \
              abs(                                              \
                 ( p_smooth[(y-1)*i_src_visible+x-1]            \
                   - p_smooth[(y-1)*i_src_visible+x+1] )        \
               + ( ( p_smooth[y*i_src_visible+x-1]              \
                    - p_smooth[y*i_src_visible+x+1] ) <<1 )     \
               + ( p_smooth[(y+1)*i_src_visible+x-1]            \
                   - p_smooth[(y+1)*i_src_visible+x+1] )        \
              )                                                 \
            );
    if( p_filter->p_sys->i_gradient_type )
409
    {
410
        if( p_filter->p_sys->b_cartoon )
411
        {
412 413
            FOR
            if( a > 60 )
414
            {
415
                p_outpix[y*i_dst_pitch+x] = 0x00;
416 417 418
            }
            else
            {
419 420 421 422 423 424 425 426 427
                if( p_smooth[y*i_src_visible+x] > 0xa0 )
                    p_outpix[y*i_dst_pitch+x] =
                        0xff - ((0xff - p_inpix[y*i_src_pitch+x] )>>2);
                else if( p_smooth[y*i_src_visible+x] > 0x70 )
                    p_outpix[y*i_dst_pitch+x] =
                        0xa0 - ((0xa0 - p_inpix[y*i_src_pitch+x] )>>2);
                else if( p_smooth[y*i_src_visible+x] > 0x28 )
                    p_outpix[y*i_dst_pitch+x] =
                        0x70 - ((0x70 - p_inpix[y*i_src_pitch+x] )>>2);
428
                else
429 430
                    p_outpix[y*i_dst_pitch+x] =
                        0x28 - ((0x28 - p_inpix[y*i_src_pitch+x] )>>2);
431
            }
432 433 434 435 436
            }}
        }
        else
        {
            FOR
Antoine Cellerier's avatar
Antoine Cellerier committed
437
            p_outpix[y*i_dst_pitch+x] = clip_uint8_vlc( a );
438
            }}
439 440
        }
    }
441 442 443 444 445 446 447 448 449 450
    else
    {
        FOR
        if( a>>8 )
            p_outpix[y*i_dst_pitch+x] = 0;
        else
            p_outpix[y*i_dst_pitch+x] = 0xff-(uint8_t)a;
        }}
    }
#undef FOR
451 452 453
}

/*****************************************************************************
454
 * FilterEdge: Canny edge detection algorithm
455 456 457 458 459 460 461 462 463 464 465 466 467
 *****************************************************************************
 * http://fourier.eng.hmc.edu/e161/lectures/canny/node1.html
 * (well ... my implementation isn't really the canny algorithm ... but some
 * ideas are the same)
 *****************************************************************************/
/* angle : | */
#define THETA_Y 0
/* angle : - */
#define THETA_X 1
/* angle : / */
#define THETA_P 2
/* angle : \ */
#define THETA_M 3
468 469
static void FilterEdge( filter_t *p_filter, picture_t *p_inpic,
                                            picture_t *p_outpic )
470 471 472
{
    int x, y;

473 474 475 476
    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_dst_pitch = p_outpic->p[Y_PLANE].i_pitch;
    const int i_num_lines = p_inpic->p[Y_PLANE].i_visible_lines;
477

478
    const uint8_t *p_inpix = p_inpic->p[Y_PLANE].p_pixels;
479 480
    uint8_t *p_outpix = p_outpic->p[Y_PLANE].p_pixels;

481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
    uint32_t *p_smooth;
    uint32_t *p_grad;
    uint8_t *p_theta;

    if( !p_filter->p_sys->p_buf32 )
        p_filter->p_sys->p_buf32 =
        (uint32_t *)malloc( i_num_lines * i_src_visible * sizeof(uint32_t));
    p_smooth = p_filter->p_sys->p_buf32;

    if( !p_filter->p_sys->p_buf32_bis )
        p_filter->p_sys->p_buf32_bis =
        (uint32_t *)malloc( i_num_lines * i_src_visible * sizeof(uint32_t));
    p_grad = p_filter->p_sys->p_buf32_bis;

    if( !p_filter->p_sys->p_buf8 )
        p_filter->p_sys->p_buf8 =
        (uint8_t *)malloc( i_num_lines * i_src_visible * sizeof(uint8_t));
    p_theta = p_filter->p_sys->p_buf8;
Antoine Cellerier's avatar
Antoine Cellerier committed
499

500
    if( !p_smooth || !p_grad || !p_theta ) return;
Antoine Cellerier's avatar
Antoine Cellerier committed
501

502
    if( p_filter->p_sys->b_cartoon )
503
    {
504
        vlc_memcpy( p_outpic->p[U_PLANE].p_pixels,
Antoine Cellerier's avatar
Antoine Cellerier committed
505
            p_inpic->p[U_PLANE].p_pixels,
506
            p_outpic->p[U_PLANE].i_lines * p_outpic->p[U_PLANE].i_pitch );
507
        vlc_memcpy( p_outpic->p[V_PLANE].p_pixels,
Antoine Cellerier's avatar
Antoine Cellerier committed
508
            p_inpic->p[V_PLANE].p_pixels,
509 510 511 512
            p_outpic->p[V_PLANE].i_lines * p_outpic->p[V_PLANE].i_pitch );
    }
    else
    {
513
        vlc_memset( p_outpic->p[Y_PLANE].p_pixels, 0xff,
514
              p_outpic->p[Y_PLANE].i_lines * p_outpic->p[Y_PLANE].i_pitch );
515
        vlc_memset( p_outpic->p[U_PLANE].p_pixels, 0x80,
516
            p_outpic->p[U_PLANE].i_lines * p_outpic->p[U_PLANE].i_pitch );
517
        vlc_memset( p_outpic->p[V_PLANE].p_pixels, 0x80,
518 519 520
            p_outpic->p[V_PLANE].i_lines * p_outpic->p[V_PLANE].i_pitch );
    }

Antoine Cellerier's avatar
Antoine Cellerier committed
521
    GaussianConvolution( p_inpic, p_smooth );
522 523 524 525 526 527 528

    /* Sobel gradient

     | -1 0 1 |     |  1  2  1 |
     | -2 0 2 | and |  0  0  0 |
     | -1 0 1 |     | -1 -2 -1 | */

529
    for( y = 1; y < i_num_lines - 1; y++ )
530
    {
531
        for( x = 1; x < i_src_visible - 1; x++ )
532
        {
Antoine Cellerier's avatar
Antoine Cellerier committed
533

534
            const int gradx =
535
                 ( p_smooth[(y-1)*i_src_visible+x-1]
Antoine Cellerier's avatar
Antoine Cellerier committed
536
                   - p_smooth[(y+1)*i_src_visible+x-1] )
537 538
               + ( ( p_smooth[(y-1)*i_src_visible+x]
                    - p_smooth[(y+1)*i_src_visible+x] ) <<1 )
Antoine Cellerier's avatar
Antoine Cellerier committed
539 540
               + ( p_smooth[(y-1)*i_src_visible+x+1]
                   - p_smooth[(y+1)*i_src_visible+x+1] );
541
            const int grady =
542
                 ( p_smooth[(y-1)*i_src_visible+x-1]
Antoine Cellerier's avatar
Antoine Cellerier committed
543
                   - p_smooth[(y-1)*i_src_visible+x+1] )
544 545
               + ( ( p_smooth[y*i_src_visible+x-1]
                    - p_smooth[y*i_src_visible+x+1] ) <<1 )
Antoine Cellerier's avatar
Antoine Cellerier committed
546 547 548 549
               + ( p_smooth[(y+1)*i_src_visible+x-1]
                   - p_smooth[(y+1)*i_src_visible+x+1] );

            p_grad[y*i_src_visible+x] = (uint32_t)(abs( gradx ) + abs( grady ));
550

551 552 553 554 555
            /* tan( 22.5 ) = 0,414213562 .. * 128 = 53
             * tan( 26,565051177 ) = 0.5
             * tan( 45 + 22.5 ) = 2,414213562 .. * 128 = 309
             * tan( 63,434948823 ) 2 */
            if( (grady<<1) > gradx )
556
                p_theta[y*i_src_visible+x] = THETA_P;
557
            else if( (grady<<1) < -gradx )
558
                p_theta[y*i_src_visible+x] = THETA_M;
559
            else if( !gradx || abs(grady) > abs(gradx)<<1 )
560
                p_theta[y*i_src_visible+x] = THETA_Y;
561
            else
562
                p_theta[y*i_src_visible+x] = THETA_X;
563 564 565 566
        }
    }

    /* edge computing */
567
    for( y = 1; y < i_num_lines - 1; y++ )
568
    {
569
        for( x = 1; x < i_src_visible - 1; x++ )
570
        {
571
            if( p_grad[y*i_src_visible+x] > 40 )
572
            {
573
                switch( p_theta[y*i_src_visible+x] )
574 575
                {
                    case THETA_Y:
576 577
                        if(    p_grad[y*i_src_visible+x] > p_grad[(y-1)*i_src_visible+x]
                            && p_grad[y*i_src_visible+x] > p_grad[(y+1)*i_src_visible+x] )
578
                        {
579
                            p_outpix[y*i_dst_pitch+x] = 0;
580
                            break;
581 582
                        } else goto colorize;
                    case THETA_P:
583 584
                        if(    p_grad[y*i_src_visible+x] > p_grad[(y-1)*i_src_visible+x-1]
                            && p_grad[y*i_src_visible+x] > p_grad[(y+1)*i_src_visible+x+1] )
585
                        {
586
                            p_outpix[y*i_dst_pitch+x] = 0;
587
                            break;
588 589
                        } else goto colorize;
                    case THETA_M:
590 591
                        if(    p_grad[y*i_src_visible+x] > p_grad[(y-1)*i_src_visible+x+1]
                            && p_grad[y*i_src_visible+x] > p_grad[(y+1)*i_src_visible+x-1] )
592
                        {
593
                            p_outpix[y*i_dst_pitch+x] = 0;
594
                            break;
595 596
                        } else goto colorize;
                    case THETA_X:
597 598
                        if(    p_grad[y*i_src_visible+x] > p_grad[y*i_src_visible+x-1]
                            && p_grad[y*i_src_visible+x] > p_grad[y*i_src_visible+x+1] )
599
                        {
600
                            p_outpix[y*i_dst_pitch+x] = 0;
601
                            break;
602 603 604 605 606 607
                        } else goto colorize;
                }
            }
            else
            {
                colorize:
608
                if( p_filter->p_sys->b_cartoon )
609
                {
610 611 612 613 614 615 616 617 618
                    if( p_smooth[y*i_src_visible+x] > 0xa0 )
                        p_outpix[y*i_dst_pitch+x] = (uint8_t)
                            0xff - ((0xff - p_inpix[y*i_src_pitch+x] )>>2);
                    else if( p_smooth[y*i_src_visible+x] > 0x70 )
                        p_outpix[y*i_dst_pitch+x] =(uint8_t)
                            0xa0 - ((0xa0 - p_inpix[y*i_src_pitch+x] )>>2);
                    else if( p_smooth[y*i_src_visible+x] > 0x28 )
                        p_outpix[y*i_dst_pitch+x] =(uint8_t)
                            0x70 - ((0x70 - p_inpix[y*i_src_pitch+x] )>>2);
619
                    else
620 621
                        p_outpix[y*i_dst_pitch+x] =(uint8_t)
                            0x28 - ((0x28 - p_inpix[y*i_src_pitch+x] )>>2);
622 623 624 625 626
                }
            }
        }
    }
}
627 628

/*****************************************************************************
629
 * FilterHough
630
 *****************************************************************************/
631 632 633
#define p_pre_hough p_filter->p_sys->p_pre_hough
static void FilterHough( filter_t *p_filter, picture_t *p_inpic,
                                             picture_t *p_outpic )
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649
{
    int x, y, i;
    int i_src_visible = p_inpic->p[Y_PLANE].i_visible_pitch;
    int i_dst_pitch = p_outpic->p[Y_PLANE].i_pitch;
    int i_num_lines = p_inpic->p[Y_PLANE].i_visible_lines;

    uint8_t *p_outpix = p_outpic->p[Y_PLANE].p_pixels;

    int i_diag = sqrt( i_num_lines * i_num_lines +
                        i_src_visible * i_src_visible);
    int i_max, i_phi_max, i_rho, i_rho_max;
    int i_nb_steps = 90;
    double d_step = M_PI / i_nb_steps;
    double d_sin;
    double d_cos;
    uint32_t *p_smooth;
650

651 652
    int *p_hough = malloc( i_diag * i_nb_steps * sizeof(int) );
    if( ! p_hough ) return;
653

Antoine Cellerier's avatar
Antoine Cellerier committed
654
    p_smooth = (uint32_t *)malloc( i_num_lines*i_src_visible*sizeof(uint32_t));
655 656 657 658 659
    if( !p_smooth )
    {
        free( p_hough );
        return;
    }
660 661 662

    if( ! p_pre_hough )
    {
663
        msg_Dbg(p_filter, "Starting precalculation");
Antoine Cellerier's avatar
Antoine Cellerier committed
664
        p_pre_hough = malloc( i_num_lines*i_src_visible*i_nb_steps*sizeof(int));
665 666 667 668 669 670
        if( ! p_pre_hough )
        {
            free( p_smooth );
            free( p_hough );
            return;
        }
671 672 673 674 675 676 677 678 679 680 681
        for( i = 0 ; i < i_nb_steps ; i++)
        {
            d_sin = sin(d_step * i);
            d_cos = cos(d_step * i);
            for( y = 0 ; y < i_num_lines ; y++ )
                for( x = 0 ; x < i_src_visible ; x++ )
                {
                    p_pre_hough[(i*i_num_lines+y)*i_src_visible + x] =
                        ceil(x*d_sin + y*d_cos);
                }
        }
682
        msg_Dbg(p_filter, "Precalculation done");
683 684
    }

685
    vlc_memset( p_hough, 0, i_diag * i_nb_steps * sizeof(int) );
Antoine Cellerier's avatar
Antoine Cellerier committed
686

687
    vlc_memcpy(
Antoine Cellerier's avatar
Antoine Cellerier committed
688
        p_outpic->p[Y_PLANE].p_pixels, p_inpic->p[Y_PLANE].p_pixels,
689
        p_outpic->p[Y_PLANE].i_lines * p_outpic->p[Y_PLANE].i_pitch );
690
    vlc_memcpy(
Antoine Cellerier's avatar
Antoine Cellerier committed
691
        p_outpic->p[U_PLANE].p_pixels, p_inpic->p[U_PLANE].p_pixels,
692
        p_outpic->p[U_PLANE].i_lines * p_outpic->p[U_PLANE].i_pitch );
693
    vlc_memcpy(
Antoine Cellerier's avatar
Antoine Cellerier committed
694
        p_outpic->p[V_PLANE].p_pixels, p_inpic->p[V_PLANE].p_pixels,
695 696
        p_outpic->p[V_PLANE].i_lines * p_outpic->p[V_PLANE].i_pitch );

Antoine Cellerier's avatar
Antoine Cellerier committed
697
    GaussianConvolution( p_inpic, p_smooth );
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714

    /* Sobel gradient

     | -1 0 1 |     |  1  2  1 |
     | -2 0 2 | and |  0  0  0 |
     | -1 0 1 |     | -1 -2 -1 | */

    i_max = 0;
    i_rho_max = 0;
    i_phi_max = 0;
    for( y = 4; y < i_num_lines - 4; y++ )
    {
        for( x = 4; x < i_src_visible - 4; x++ )
        {
            uint32_t a =
            (
              abs(
Antoine Cellerier's avatar
Antoine Cellerier committed
715 716 717 718 719 720
                ( ( p_smooth[(y-1)*i_src_visible+x]
                    - p_smooth[(y+1)*i_src_visible+x] ) <<1 )
               + ( p_smooth[(y-1)*i_src_visible+x-1]
                   - p_smooth[(y+1)*i_src_visible+x-1] )
               + ( p_smooth[(y-1)*i_src_visible+x+1]
                   - p_smooth[(y+1)*i_src_visible+x+1] )
721 722 723
              )
            +
              abs(
Antoine Cellerier's avatar
Antoine Cellerier committed
724 725 726 727 728 729
                ( ( p_smooth[y*i_src_visible+x-1]
                    - p_smooth[y*i_src_visible+x+1] ) <<1 )
               + ( p_smooth[(y-1)*i_src_visible+x-1]
                   - p_smooth[(y-1)*i_src_visible+x+1] )
               + ( p_smooth[(y+1)*i_src_visible+x-1]
                   - p_smooth[(y+1)*i_src_visible+x+1] )
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759
              )
            );
            if( a>>8 )
            {
                for( i = 0 ; i < i_nb_steps ; i ++ )
                {
                    i_rho = p_pre_hough[(i*i_num_lines+y)*i_src_visible + x];
                    if( p_hough[i_rho + i_diag/2 + i * i_diag]++ > i_max )
                    {
                        i_max = p_hough[i_rho + i_diag/2 + i * i_diag];
                        i_rho_max = i_rho;
                        i_phi_max = i;
                    }
                }
            }
        }
    }

    d_sin = sin(i_phi_max*d_step);
    d_cos = cos(i_phi_max*d_step);
    if( d_cos != 0 )
    {
        for( x = 0 ; x < i_src_visible ; x++ )
        {
            y = (i_rho_max - x * d_sin) / d_cos;
            if( y >= 0 && y < i_num_lines )
                p_outpix[y*i_dst_pitch+x] = 255;
        }
    }

760 761
    free( p_hough );
    free( p_smooth );
762 763
}
#undef p_pre_hough
764 765 766 767 768 769


static int GradientCallback( vlc_object_t *p_this, char const *psz_var,
                             vlc_value_t oldval, vlc_value_t newval,
                             void *p_data )
{
Rafaël Carré's avatar
Rafaël Carré committed
770
    VLC_UNUSED(oldval);
771
    filter_sys_t *p_sys = (filter_sys_t *)p_data;
772 773

    vlc_mutex_lock( &p_sys->lock );
774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
    if( !strcmp( psz_var, FILTER_PREFIX "mode" ) )
    {
        if( !strcmp( newval.psz_string, "gradient" ) )
        {
            p_sys->i_mode = GRADIENT;
        }
        else if( !strcmp( newval.psz_string, "edge" ) )
        {
            p_sys->i_mode = EDGE;
        }
        else if( !strcmp( newval.psz_string, "hough" ) )
        {
            p_sys->i_mode = HOUGH;
        }
        else
        {
Antoine Cellerier's avatar
Antoine Cellerier committed
790
            msg_Err( p_this, "no valid gradient mode provided (%s)", newval.psz_string );
791 792 793 794 795 796 797 798 799 800 801
            p_sys->i_mode = GRADIENT;
        }
    }
    else if( !strcmp( psz_var, FILTER_PREFIX "type" ) )
    {
        p_sys->i_gradient_type = newval.i_int;
    }
    else if( !strcmp( psz_var, FILTER_PREFIX "cartoon" ) )
    {
        p_sys->b_cartoon = newval.b_bool;
    }
802 803
    vlc_mutex_unlock( &p_sys->lock );

804 805
    return VLC_SUCCESS;
}