NEWS: update from 3.0.x branch
[vlc.git] / modules / video_filter / vhs.c
blobe44aff65edb114de0995dd152b469ca883dbeb69
1 /*****************************************************************************
2 * vhs.c : VHS effect video filter
3 *****************************************************************************
4 * Copyright (C) 2013 Vianney Boyer
6 * Authors: Vianney Boyer <vlcvboyer -at- gmail -dot- com>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
23 /*****************************************************************************
24 * Preamble
25 *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_filter.h>
34 #include <vlc_picture.h>
35 #include <vlc_rand.h>
36 #include <vlc_tick.h>
38 #include "filter_picture.h"
40 static inline int64_t MOD(int64_t a, int64_t b) {
41 return ( ( a % b ) + b ) % b; }
43 #define MAX_BLUE_RED_LINES 100
45 typedef struct {
46 unsigned i_offset;
47 uint16_t i_intensity;
48 bool b_blue_red;
49 vlc_tick_t i_stop_trigger;
50 } blue_red_line_t;
52 typedef struct
55 /* general data */
56 bool b_init;
57 size_t i_planes;
58 int32_t i_height[VOUT_MAX_PLANES]; /* note: each plane may have different dimensions */
59 int32_t i_width[VOUT_MAX_PLANES];
60 int32_t i_visible_pitch[VOUT_MAX_PLANES];
61 vlc_tick_t i_start_time;
62 vlc_tick_t i_last_time;
63 vlc_tick_t i_cur_time;
65 /* sliding & offset effect */
66 int i_phase_speed;
67 int i_phase_ofs;
68 int i_offset_ofs;
69 int i_sliding_ofs;
70 int i_sliding_speed;
71 vlc_tick_t i_offset_trigger;
72 vlc_tick_t i_sliding_trigger;
73 vlc_tick_t i_sliding_stop_trig;
74 bool i_sliding_type_duplicate;
76 /* blue red lines effect */
77 vlc_tick_t i_BR_line_trigger;
78 blue_red_line_t *p_BR_lines[MAX_BLUE_RED_LINES];
80 } filter_sys_t;
82 /*****************************************************************************
83 * Prototypes
84 *****************************************************************************/
86 static picture_t *Filter( filter_t *, picture_t * );
88 static int vhs_allocate_data( filter_t *, picture_t * );
89 static void vhs_free_allocated_data( filter_t * );
91 static int vhs_blue_red_line_effect( filter_t *, picture_t * );
92 static void vhs_blue_red_dots_effect( filter_t *, picture_t * );
93 static int vhs_sliding_effect( filter_t *, picture_t * );
95 static int vhs_sliding_effect_apply( filter_t *, picture_t * );
97 /*****************************************************************************
98 * Module descriptor
99 *****************************************************************************/
101 static int Open ( filter_t * );
102 static void Close( filter_t * );
104 vlc_module_begin()
105 set_description( N_("VHS movie effect video filter") )
106 set_shortname( N_("VHS movie" ) )
107 set_category( CAT_VIDEO )
108 set_subcategory( SUBCAT_VIDEO_VFILTER )
110 set_callback_video_filter( Open )
111 vlc_module_end()
114 * Open the filter
116 static int Open( filter_t *p_filter )
118 filter_sys_t *p_sys;
120 /* Assert video in match with video out */
121 if( !es_format_IsSimilar( &p_filter->fmt_in, &p_filter->fmt_out ) ) {
122 msg_Err( p_filter, "Input and output format does not match" );
123 return VLC_EGENERIC;
126 /* Reject 0 bpp and unsupported chroma */
127 const vlc_fourcc_t fourcc = p_filter->fmt_in.video.i_chroma;
128 const vlc_chroma_description_t *p_chroma =
129 vlc_fourcc_GetChromaDescription( p_filter->fmt_in.video.i_chroma );
130 if( !p_chroma || p_chroma->pixel_size == 0
131 || p_chroma->plane_count < 3 || p_chroma->pixel_size > 1
132 || !vlc_fourcc_IsYUV( fourcc ) )
134 msg_Err( p_filter, "Unsupported chroma (%4.4s)", (char*)&fourcc );
135 return VLC_EGENERIC;
138 /* Allocate structure */
139 p_filter->p_sys = p_sys = calloc(1, sizeof(*p_sys) );
140 if( unlikely( !p_sys ) )
141 return VLC_ENOMEM;
143 /* init data */
144 static const struct vlc_filter_operations filter_ops =
146 .filter_video = Filter, .close = Close,
148 p_filter->ops = &filter_ops;
149 p_sys->i_start_time = p_sys->i_cur_time = p_sys->i_last_time = vlc_tick_now();
151 return VLC_SUCCESS;
155 * Close the filter
157 static void Close( filter_t *p_filter ) {
158 filter_sys_t *p_sys = p_filter->p_sys;
160 /* Free allocated memory */
161 vhs_free_allocated_data( p_filter );
162 free( p_sys );
166 * Filter a picture
168 static picture_t *Filter( filter_t *p_filter, picture_t *p_pic_in ) {
169 if( unlikely( !p_pic_in || !p_filter) )
170 return NULL;
172 filter_sys_t *p_sys = p_filter->p_sys;
174 picture_t *p_pic_out = filter_NewPicture( p_filter );
175 if( unlikely( !p_pic_out ) ) {
176 picture_Release( p_pic_in );
177 return NULL;
181 * manage time
183 p_sys->i_last_time = p_sys->i_cur_time;
184 p_sys->i_cur_time = vlc_tick_now();
187 * allocate data
189 if ( unlikely( !p_sys->b_init ) )
190 if ( unlikely( vhs_allocate_data( p_filter, p_pic_in ) != VLC_SUCCESS ) ) {
191 picture_Release( p_pic_in );
192 return NULL;
194 p_sys->b_init = true;
197 * preset output pic: raw copy src to dst
199 picture_CopyPixels(p_pic_out, p_pic_in);
202 * apply effects on picture
204 if ( unlikely( vhs_blue_red_line_effect( p_filter, p_pic_out ) != VLC_SUCCESS ) )
205 return CopyInfoAndRelease( p_pic_out, p_pic_in );
207 if ( unlikely( vhs_sliding_effect(p_filter, p_pic_out ) != VLC_SUCCESS ) )
208 return CopyInfoAndRelease( p_pic_out, p_pic_in );
210 vhs_blue_red_dots_effect( p_filter, p_pic_out );
212 return CopyInfoAndRelease( p_pic_out, p_pic_in );
216 * Allocate data
218 static int vhs_allocate_data( filter_t *p_filter, picture_t *p_pic_in ) {
219 filter_sys_t *p_sys = p_filter->p_sys;
221 vhs_free_allocated_data( p_filter );
224 * take into account different characteristics for each plane
226 p_sys->i_planes = p_pic_in->i_planes;
228 for ( size_t i_p = 0; i_p < p_sys->i_planes; i_p++) {
229 p_sys->i_visible_pitch [i_p] = (int) p_pic_in->p[i_p].i_visible_pitch;
230 p_sys->i_height[i_p] = (int) p_pic_in->p[i_p].i_visible_lines;
231 p_sys->i_width [i_p] = (int) p_pic_in->p[i_p].i_visible_pitch / p_pic_in->p[i_p].i_pixel_pitch;
233 return VLC_SUCCESS;
237 * Free allocated data
239 static void vhs_free_allocated_data( filter_t *p_filter ) {
240 filter_sys_t *p_sys = p_filter->p_sys;
242 for ( uint32_t i_b = 0; i_b < MAX_BLUE_RED_LINES; i_b++ )
243 FREENULL( p_sys->p_BR_lines[i_b] );
245 p_sys->i_planes = 0;
248 static vlc_tick_t RandomEnd(filter_sys_t *p_sys, vlc_tick_t modulo)
250 return p_sys->i_cur_time + (uint64_t)vlc_mrand48() % modulo + modulo / 2;
254 * Horizontal blue or red lines random management and effect
256 static int vhs_blue_red_line_effect( filter_t *p_filter, picture_t *p_pic_out ) {
257 filter_sys_t *p_sys = p_filter->p_sys;
259 #define BR_LINES_GENERATOR_PERIOD VLC_TICK_FROM_SEC(50)
260 #define BR_LINES_DURATION VLC_TICK_FROM_MS(20)
262 /* generate new blue or red lines */
263 if ( p_sys->i_BR_line_trigger <= p_sys->i_cur_time ) {
264 for ( size_t i_b = 0; i_b < MAX_BLUE_RED_LINES; i_b++ )
265 if (p_sys->p_BR_lines[i_b] == NULL) {
266 /* allocate data */
267 p_sys->p_BR_lines[i_b] = calloc( 1, sizeof(blue_red_line_t) );
268 if ( unlikely( !p_sys->p_BR_lines[i_b] ) )
269 return VLC_ENOMEM;
271 /* set random parameters */
272 p_sys->p_BR_lines[i_b]->i_offset = (unsigned)vlc_mrand48()
273 % __MAX( 1, p_sys->i_height[Y_PLANE] - 10 )
274 + 5;
276 p_sys->p_BR_lines[i_b]->b_blue_red = (unsigned)vlc_mrand48() & 0x01;
278 p_sys->p_BR_lines[i_b]->i_stop_trigger = RandomEnd( p_sys, BR_LINES_DURATION );
280 break;
282 p_sys->i_BR_line_trigger = RandomEnd( p_sys, BR_LINES_GENERATOR_PERIOD );
286 /* manage and apply current blue/red lines */
287 for ( size_t i_b = 0; i_b < MAX_BLUE_RED_LINES; i_b++ )
288 if ( p_sys->p_BR_lines[i_b] ) {
289 /* remove outdated ones */
290 if ( p_sys->p_BR_lines[i_b]->i_stop_trigger <= p_sys->i_cur_time ) {
291 FREENULL( p_sys->p_BR_lines[i_b] );
292 continue;
295 /* otherwise apply */
296 for ( size_t i_p=0; i_p < p_sys->i_planes; i_p++ ) {
297 uint32_t i_pix_ofs = p_sys->p_BR_lines[i_b]->i_offset
298 * p_pic_out->p[i_p].i_visible_lines
299 / p_sys->i_height[Y_PLANE]
300 * p_pic_out->p[i_p].i_pitch;
302 switch ( i_p ) {
303 case Y_PLANE:
304 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs], 127,
305 p_pic_out->p[i_p].i_visible_pitch);
306 break;
307 case U_PLANE:
308 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs],
309 (p_sys->p_BR_lines[i_b]->b_blue_red?255:0),
310 p_pic_out->p[i_p].i_visible_pitch);
311 break;
312 case V_PLANE:
313 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs],
314 (p_sys->p_BR_lines[i_b]->b_blue_red?0:255),
315 p_pic_out->p[i_p].i_visible_pitch);
316 break;
321 return VLC_SUCCESS;
325 * insert randomly blue and red dots on the picture
327 static void vhs_blue_red_dots_effect( filter_t *p_filter, picture_t *p_pic_out ) {
328 #define BR_DOTS_RATIO 10000
330 filter_sys_t *p_sys = p_filter->p_sys;
332 for ( int32_t i_dots = 0;
333 i_dots < p_sys->i_width[Y_PLANE] * p_sys->i_height[Y_PLANE] / BR_DOTS_RATIO;
334 i_dots++) {
336 uint32_t i_length = (unsigned)vlc_mrand48()
337 % __MAX( 1, p_sys->i_width[Y_PLANE] / 30 ) + 1;
339 uint16_t i_x = (unsigned)vlc_mrand48()
340 % __MAX( 1, p_sys->i_width[Y_PLANE] - i_length );
341 uint16_t i_y = (unsigned)vlc_mrand48() % p_sys->i_height[Y_PLANE];
342 bool b_color = ( ( (unsigned)vlc_mrand48() % 2 ) == 0);
344 for ( size_t i_p = 0; i_p < p_sys->i_planes; i_p++ ) {
345 uint32_t i_pix_ofs = i_y
346 * p_pic_out->p[i_p].i_visible_lines
347 / p_sys->i_height[Y_PLANE]
348 * p_pic_out->p[i_p].i_pitch
349 + i_x
350 * p_pic_out->p[i_p].i_pixel_pitch;
352 uint32_t i_length_in_plane = i_length
353 * p_pic_out->p[i_p].i_visible_pitch
354 / p_pic_out->p[Y_PLANE].i_visible_pitch;
356 switch ( i_p ) {
357 case Y_PLANE:
358 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs], 127,
359 i_length_in_plane );
360 break;
361 case U_PLANE:
362 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs],
363 (b_color?255:0),
364 i_length_in_plane );
365 break;
366 case V_PLANE:
367 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs],
368 (b_color?0:255),
369 i_length_in_plane );
370 break;
377 * sliding effects
379 static int vhs_sliding_effect( filter_t *p_filter, picture_t *p_pic_out ) {
380 filter_sys_t *p_sys = p_filter->p_sys;
383 * one shot offset section
386 #define OFFSET_AVERAGE_PERIOD VLC_TICK_FROM_SEC(10)
388 /* start trigger to be (re)initialized */
389 if ( p_sys->i_offset_trigger == 0
390 || p_sys->i_sliding_speed != 0 ) { /* do not mix sliding and offset */
392 /* random trigger for offset effect */
393 p_sys->i_offset_trigger = RandomEnd( p_sys, OFFSET_AVERAGE_PERIOD );
394 p_sys->i_offset_ofs = 0;
395 } else if (p_sys->i_offset_trigger <= p_sys->i_cur_time) {
396 /* trigger for offset effect occurs */
397 p_sys->i_offset_trigger = 0;
398 p_sys->i_offset_ofs = (uint32_t)vlc_mrand48()
399 % p_sys->i_height[Y_PLANE];
401 else
402 p_sys->i_offset_ofs = 0;
406 * phase section
409 #define MAX_PHASE_OFS (p_sys->i_height[Y_PLANE]*100/15)
411 p_sys->i_phase_speed += MOD( (int32_t)vlc_mrand48(), 3) - 1;
412 p_sys->i_phase_ofs += p_sys->i_phase_speed;
413 p_sys->i_phase_ofs = VLC_CLIP( p_sys->i_phase_ofs, -MAX_PHASE_OFS, +MAX_PHASE_OFS);
414 if ( abs( p_sys->i_phase_ofs ) >= MAX_PHASE_OFS )
415 p_sys->i_phase_speed = 0;
419 * sliding section
422 #define SLIDING_AVERAGE_PERIOD VLC_TICK_FROM_SEC(20)
423 #define SLIDING_AVERAGE_DURATION VLC_TICK_FROM_SEC(3)
425 /* start trigger to be (re)initialized */
426 if ( ( p_sys->i_sliding_stop_trig == 0 ) &&
427 ( p_sys->i_sliding_trigger == 0 ) &&
428 ( p_sys->i_sliding_speed == 0 ) ) {
430 /* random trigger which enable sliding effect */
431 p_sys->i_sliding_trigger = RandomEnd( p_sys, SLIDING_AVERAGE_PERIOD );
434 /* start trigger just occurs */
435 else if ( ( p_sys->i_sliding_stop_trig == 0 ) &&
436 ( p_sys->i_sliding_trigger <= p_sys->i_cur_time ) &&
437 ( p_sys->i_sliding_speed == 0 ) ) {
439 /* init sliding parameters */
440 p_sys->i_sliding_trigger = 0;
441 p_sys->i_sliding_stop_trig = RandomEnd( p_sys, SLIDING_AVERAGE_DURATION );
442 p_sys->i_sliding_ofs = 0;
443 /* note: sliding speed unit = image per 100 s */
444 p_sys->i_sliding_speed = MOD( (int32_t)vlc_mrand48(), 1001 ) - 500;
445 p_sys->i_sliding_type_duplicate = (unsigned)vlc_mrand48() & 0x01;
448 /* stop trigger disabling sliding effect occurs */
449 else if ( ( p_sys->i_sliding_stop_trig <= p_sys->i_cur_time )
450 && ( p_sys->i_sliding_trigger == 0 ) ) {
452 /* first increase speed to ensure we will stop sliding on plain pict */
453 if ( abs( p_sys->i_sliding_speed ) < 5 )
454 p_sys->i_sliding_speed += 1;
456 int threshold = p_sys->i_sliding_speed
457 * p_sys->i_height[Y_PLANE]
458 * SEC_FROM_VLC_TICK( p_sys->i_cur_time - p_sys->i_last_time );
460 /* check if offset is close to 0 and then ready to stop */
461 if ( abs( p_sys->i_sliding_ofs ) < abs( threshold )
462 || abs( p_sys->i_sliding_ofs ) < p_sys->i_height[Y_PLANE] * 100 / 20 ) {
464 /* reset sliding parameters */
465 p_sys->i_sliding_ofs = p_sys->i_sliding_speed = 0;
466 p_sys->i_sliding_trigger = p_sys->i_sliding_stop_trig = 0;
467 p_sys->i_sliding_type_duplicate = false;
471 /* update offset */
472 p_sys->i_sliding_ofs = MOD( p_sys->i_sliding_ofs
473 + p_sys->i_sliding_speed * p_sys->i_height[Y_PLANE]
474 * SEC_FROM_VLC_TICK( p_sys->i_cur_time - p_sys->i_last_time),
475 p_sys->i_height[Y_PLANE] * 100 );
477 return vhs_sliding_effect_apply( p_filter, p_pic_out );
481 * apply both sliding and offset effect
483 static int vhs_sliding_effect_apply( filter_t *p_filter, picture_t *p_pic_out )
485 filter_sys_t *p_sys = p_filter->p_sys;
487 for ( size_t i_p = 0; i_p < p_sys->i_planes; i_p++ ) {
488 /* first allocate temporary buffer for swap operation */
489 uint8_t *p_temp_buf;
490 if ( !p_sys->i_sliding_type_duplicate ) {
491 p_temp_buf= calloc( p_pic_out->p[i_p].i_lines
492 * p_pic_out->p[i_p].i_pitch, sizeof(uint8_t) );
493 if ( unlikely( !p_temp_buf ) )
494 return VLC_ENOMEM;
495 memcpy( p_temp_buf, p_pic_out->p[i_p].p_pixels,
496 p_pic_out->p[i_p].i_lines * p_pic_out->p[i_p].i_pitch );
498 else
499 p_temp_buf = p_pic_out->p[i_p].p_pixels;
501 /* copy lines to output_pic */
502 size_t lines = p_pic_out->p[i_p].i_visible_lines;
503 for ( size_t i_y = 0; i_y < lines; i_y++ )
505 int i_ofs = p_sys->i_offset_ofs + p_sys->i_sliding_ofs;
507 if ( ( p_sys->i_sliding_speed == 0 ) || !p_sys->i_sliding_type_duplicate )
508 i_ofs += p_sys->i_phase_ofs;
510 i_ofs = MOD( i_ofs / 100, p_sys->i_height[Y_PLANE] );
511 i_ofs *= p_pic_out->p[i_p].i_visible_lines;
512 i_ofs /= p_sys->i_height[Y_PLANE];
514 memcpy( &p_pic_out->p[i_p].p_pixels[ i_y * p_pic_out->p[i_p].i_pitch ],
515 &p_temp_buf[ ( ( i_y + i_ofs ) % p_pic_out->p[i_p].i_visible_lines ) * p_pic_out->p[i_p].i_pitch ],
516 p_pic_out->p[i_p].i_visible_pitch );
518 if ( !p_sys->i_sliding_type_duplicate )
519 free(p_temp_buf);
522 return VLC_SUCCESS;