a1f58183359f34e64b134a97d918171d9318bcb1
[vlc.git] / modules / video_filter / freeze.c
1 /*****************************************************************************
2  * freeze.c : Freezing video filter
3  *****************************************************************************
4  * Copyright (C) 2013      Vianney Boyer
5  *
6  * Authors: Vianney Boyer <vlcvboyer -at- gmail -dot- com>
7  *
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.
12  *
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.
17  *
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  *****************************************************************************/
22
23 /*****************************************************************************
24  * Preamble
25  *****************************************************************************/
26
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_filter.h>
34 #include <vlc_mouse.h>
35 #include <vlc_picture.h>
36 #include "filter_picture.h"
37
38 #ifndef MOD
39 #   define MOD(a, b) ((((a)%(b)) + (b))%(b))
40 #endif
41
42 typedef struct
43 {
44     bool b_init;
45
46     int32_t i_planes;
47     int32_t *i_height;
48     int32_t *i_width;
49     int32_t *i_visible_pitch;
50     int8_t  ***pi_freezed_picture;   /* records freezed pixels */
51     int16_t **pi_freezing_countdown; /* freezed pixel delay    */
52     bool    **pb_update_cache;       /* update chache request  */
53
54 } filter_sys_t;
55
56 /*****************************************************************************
57  * Prototypes
58  *****************************************************************************/
59
60 static picture_t *Filter( filter_t *, picture_t * );
61
62 static int  freeze_mouse( filter_t *, vlc_mouse_t *,
63                           const vlc_mouse_t * );
64 static int  freeze_allocate_data( filter_t *, picture_t * );
65 static void freeze_free_allocated_data( filter_t * );
66
67
68 /*****************************************************************************
69  * Module descriptor
70  *****************************************************************************/
71
72 #define CFG_PREFIX "freeze-"
73
74 static int  Open ( filter_t * );
75 static void Close( filter_t * );
76
77 vlc_module_begin()
78     set_description( N_("Freezing interactive video filter") )
79     set_shortname(   N_("Freeze" ) )
80     set_category(    CAT_VIDEO )
81     set_subcategory( SUBCAT_VIDEO_VFILTER )
82
83     set_callback_video_filter( Open )
84 vlc_module_end()
85
86 /*****************************************************************************
87  * Local prototypes
88  *****************************************************************************/
89
90 static const struct vlc_filter_operations filter_ops =
91 {
92     .filter_video = Filter,
93     .video_mouse = freeze_mouse,
94     .close = Close,
95 };
96
97 /**
98  * Open the filter
99  */
100 static int Open( filter_t *p_filter )
101 {
102     filter_sys_t *p_sys;
103
104     /* Assert video in match with video out */
105     if( !es_format_IsSimilar( &p_filter->fmt_in, &p_filter->fmt_out ) ) {
106         msg_Err( p_filter, "Input and output format does not match" );
107         return VLC_EGENERIC;
108     }
109
110     /* Reject 0 bpp and unsupported chroma */
111     const vlc_fourcc_t fourcc = p_filter->fmt_in.video.i_chroma;
112     const vlc_chroma_description_t *p_chroma
113           = vlc_fourcc_GetChromaDescription( p_filter->fmt_in.video.i_chroma );
114     if( !p_chroma || p_chroma->pixel_size == 0
115         || p_chroma->plane_count < 3 || p_chroma->pixel_size > 1
116         || !vlc_fourcc_IsYUV( fourcc ) )
117     {
118         msg_Err( p_filter, "Unsupported chroma (%4.4s)", (char*)&fourcc );
119         return VLC_EGENERIC;
120     }
121
122     /* Allocate structure */
123     p_filter->p_sys = p_sys = calloc(1, sizeof( *p_sys ) );
124     if( unlikely(!p_sys) )
125         return VLC_ENOMEM;
126
127     /* init data */
128
129     p_filter->ops = &filter_ops;
130
131     return VLC_SUCCESS;
132 }
133
134 /**
135  * Close the filter
136  */
137 static void Close( filter_t *p_filter ) {
138     filter_sys_t *p_sys = p_filter->p_sys;
139
140     /* Free allocated memory */
141     freeze_free_allocated_data( p_filter );
142     free( p_sys );
143 }
144
145 /**
146  * Filter a picture
147  */
148 static picture_t *Filter( filter_t *p_filter, picture_t *p_pic_in ) {
149     if( !p_pic_in || !p_filter) return NULL;
150
151     filter_sys_t *p_sys = p_filter->p_sys;
152
153     picture_t *p_pic_out = filter_NewPicture( p_filter );
154     if( unlikely(!p_pic_out) ) {
155         picture_Release( p_pic_in );
156         return NULL;
157     }
158
159    /*
160     * allocate data
161     */
162     if ( unlikely(!p_sys->b_init) )
163         if (freeze_allocate_data( p_filter, p_pic_in ) != VLC_SUCCESS)
164         {
165             picture_Release( p_pic_in );
166             return NULL;
167         }
168     p_sys->b_init = true;
169
170    /*
171     * preset output pic: raw copy src to dst
172     */
173     picture_CopyPixels(p_pic_out, p_pic_in);
174
175    /*
176     * cache original pict pixels selected with mouse pointer
177     */
178     for ( int32_t i_p = 0; i_p < p_sys->i_planes; i_p++ )
179         for ( int32_t i_r = 0; i_r < p_sys->i_height[i_p]; i_r++ )
180             for ( int32_t i_c = 0; i_c < p_sys->i_width[i_p]; i_c++ )
181             {
182                 uint32_t i_Yr = i_r * p_sys->i_height[Y_PLANE]
183                               / p_sys->i_height[i_p];
184                 uint32_t i_Yc = i_c * p_sys->i_width[Y_PLANE]
185                               / p_sys->i_width[i_p];
186
187                 if ( p_sys->pb_update_cache[i_Yr][i_Yc] )
188                     p_sys->pi_freezed_picture[i_p][i_r][i_c]
189                         = p_pic_in->p[i_p].p_pixels[i_r*p_pic_out->p[i_p].i_pitch
190                         + i_c*p_pic_out->p[i_p].i_pixel_pitch];
191             }
192
193    /*
194     * countdown freezed pixel delay & reset pb_update_cache flag
195     */
196     for ( int32_t i_Yr = 0; i_Yr < p_sys->i_height[Y_PLANE]; i_Yr++)
197         for ( int32_t i_Yc = 0; i_Yc < p_sys->i_width[Y_PLANE]; i_Yc++)
198         {
199             if ( p_sys->pi_freezing_countdown[i_Yr][i_Yc] > 0 )
200                  p_sys->pi_freezing_countdown[i_Yr][i_Yc]--;
201             p_sys->pb_update_cache[i_Yr][i_Yc] = false;
202         }
203
204    /*
205     * apply filter: draw freezed pixels over current picture
206     */
207     for ( int32_t i_p = 0; i_p < p_sys->i_planes; i_p++ )
208         for ( int32_t i_r = 0; i_r < p_sys->i_height[i_p]; i_r++ )
209             for ( int32_t i_c = 0; i_c < p_sys->i_width[i_p]; i_c++ )
210             {
211                 uint32_t i_Yr = i_r * p_sys->i_height[Y_PLANE]
212                               / p_sys->i_height[i_p];
213                 uint32_t i_Yc = i_c * p_sys->i_width[Y_PLANE]
214                               / p_sys->i_width[i_p];
215
216                 if ( p_sys->pi_freezing_countdown[i_Yr][i_Yc] > 0 )
217                     p_pic_out->p[i_p].p_pixels[i_r * p_pic_out->p[i_p].i_pitch
218                         + i_c * p_pic_out->p[i_p].i_pixel_pitch]
219                         = p_sys->pi_freezed_picture[i_p][i_r][i_c];
220             }
221
222     return CopyInfoAndRelease( p_pic_out, p_pic_in );
223 }
224
225 /*
226  * mouse callback
227  **/
228 static int freeze_mouse( filter_t *p_filter, vlc_mouse_t *p_new,
229                          const vlc_mouse_t *p_old )
230 {
231     filter_sys_t *p_sys = p_filter->p_sys;
232     const video_format_t  *p_fmt_in = &p_filter->fmt_in.video;
233
234     /* Only take events inside the video area */
235     if( p_new->i_x < 0 || p_new->i_x >= (int)p_fmt_in->i_width ||
236         p_new->i_y < 0 || p_new->i_y >= (int)p_fmt_in->i_height )
237         return VLC_EGENERIC;
238
239     if ( unlikely(!p_sys->b_init) )
240     {
241         return VLC_SUCCESS;
242     }
243
244     int32_t i_base_timeout = 0;
245     if( vlc_mouse_HasPressed( p_old, p_new, MOUSE_BUTTON_LEFT ) )
246         i_base_timeout = 100;
247     else if( vlc_mouse_IsLeftPressed( p_new ) )
248         i_base_timeout = 50;
249
250     if( i_base_timeout > 0 )
251     {
252         /*
253          * find pixels selected by user to apply freezing filter
254          */
255         int32_t i_min_sq_radius = (p_sys->i_width[Y_PLANE] / 15)
256                                 * (p_sys->i_width[Y_PLANE] / 15);
257         for ( int32_t i_r = 0; i_r < p_sys->i_height[Y_PLANE]; i_r++)
258             for ( int32_t i_c = 0; i_c < p_sys->i_width[Y_PLANE]; i_c++)
259             {
260                 int32_t i_sq_dist =   ( p_new->i_x - i_c )
261                                     * ( p_new->i_x - i_c )
262                                     + ( p_new->i_y - i_r )
263                                     * ( p_new->i_y - i_r );
264                 i_sq_dist = __MAX(0, i_sq_dist - i_min_sq_radius);
265
266                 uint16_t i_timeout = __MAX(i_base_timeout - i_sq_dist, 0);
267
268                 /* ask to update chache for pixel to be freezed just now */
269                 if ( p_sys->pi_freezing_countdown[i_r][i_c] == 0 && i_timeout > 0)
270                      p_sys->pb_update_cache[i_r][i_c] = true;
271
272                 /* set freezing delay */
273                 if ( p_sys->pi_freezing_countdown[i_r][i_c] < i_timeout )
274                      p_sys->pi_freezing_countdown[i_r][i_c] = i_timeout;
275             }
276     }
277
278     return VLC_EGENERIC;
279 }
280
281
282 /*
283  * Allocate data
284  */
285 static int freeze_allocate_data( filter_t *p_filter, picture_t *p_pic_in )
286 {
287     filter_sys_t *p_sys = p_filter->p_sys;
288
289     freeze_free_allocated_data( p_filter );
290
291    /*
292     * take into account different characteristics for each plane
293     */
294     p_sys->i_planes        = p_pic_in->i_planes;
295     p_sys->i_height        = calloc( p_sys->i_planes, sizeof(int32_t) );
296     p_sys->i_width         = calloc( p_sys->i_planes, sizeof(int32_t) );
297     p_sys->i_visible_pitch = calloc( p_sys->i_planes, sizeof(int32_t) );
298
299     if ( unlikely( !p_sys->i_height || !p_sys->i_width || !p_sys->i_visible_pitch ) )
300     {
301         freeze_free_allocated_data( p_filter );
302         return VLC_ENOMEM;
303     }
304
305     /* init data */
306     for ( int32_t i_p = 0; i_p < p_sys->i_planes; i_p++ )
307     {
308         p_sys->i_visible_pitch [i_p] = (int) p_pic_in->p[i_p].i_visible_pitch;
309         p_sys->i_height[i_p]         = (int) p_pic_in->p[i_p].i_visible_lines;
310         p_sys->i_width[i_p]          = (int) p_pic_in->p[i_p].i_visible_pitch
311                                      / p_pic_in->p[i_p].i_pixel_pitch;
312     }
313
314     /* buffer used to countdown freezing delay */
315     p_sys->pi_freezing_countdown
316         = calloc( p_sys->i_height[Y_PLANE], sizeof(int16_t*) );
317     if ( unlikely( !p_sys->pi_freezing_countdown ) )
318     {
319         freeze_free_allocated_data( p_filter );
320         return VLC_ENOMEM;
321     }
322
323     for ( int32_t i_r = 0; i_r < p_sys->i_height[Y_PLANE]; i_r++ )
324     {
325         p_sys->pi_freezing_countdown[i_r]
326             = calloc( p_sys->i_width[Y_PLANE], sizeof(int16_t) );
327         if ( unlikely( !p_sys->pi_freezing_countdown[i_r] ) )
328         {
329             freeze_free_allocated_data( p_filter );
330             return VLC_ENOMEM;
331         }
332     }
333
334     /* buffer used to cache freezed pixels colors */
335     p_sys->pi_freezed_picture = calloc( p_sys->i_planes, sizeof(int8_t**) );
336     if( unlikely( !p_sys->pi_freezed_picture ) )
337     {
338         freeze_free_allocated_data( p_filter );
339         return VLC_ENOMEM;
340     }
341
342     for ( int32_t i_p = 0; i_p < p_sys->i_planes; i_p++)
343     {
344         p_sys->pi_freezed_picture[i_p]
345             = calloc( p_sys->i_height[i_p], sizeof(int8_t*) );
346         if ( unlikely(!p_sys->pi_freezed_picture[i_p]) )
347         {
348             freeze_free_allocated_data( p_filter );
349             return VLC_ENOMEM;
350         }
351         for ( int32_t i_r = 0; i_r < p_sys->i_height[i_p]; i_r++ )
352         {
353             p_sys->pi_freezed_picture[i_p][i_r]
354                 = calloc( p_sys->i_width[i_p], sizeof(int8_t) );
355             if ( unlikely( !p_sys->pi_freezed_picture[i_p][i_r] ) )
356             {
357                 freeze_free_allocated_data( p_filter );
358                 return VLC_ENOMEM;
359             }
360         }
361     }
362
363     /* flag used to manage freezed pixels cache update */
364     p_sys->pb_update_cache
365         = calloc( p_sys->i_height[Y_PLANE], sizeof(bool*) );
366     if( unlikely( !p_sys->pb_update_cache ) )
367     {
368         freeze_free_allocated_data( p_filter );
369         return VLC_ENOMEM;
370     }
371
372     for ( int32_t i_r = 0; i_r < p_sys->i_height[Y_PLANE]; i_r++ )
373     {
374         p_sys->pb_update_cache[i_r]
375             = calloc( p_sys->i_width[Y_PLANE], sizeof(bool) );
376         if ( unlikely( !p_sys->pb_update_cache[i_r] ) )
377         {
378             freeze_free_allocated_data( p_filter );
379             return VLC_ENOMEM;
380         }
381     }
382
383     return VLC_SUCCESS;
384 }
385
386 /**
387  * Free allocated data
388  */
389 static void freeze_free_allocated_data( filter_t *p_filter ) {
390     filter_sys_t *p_sys = p_filter->p_sys;
391
392     if (p_sys->pi_freezing_countdown)
393         for ( int32_t i_r = 0; i_r < p_sys->i_height[Y_PLANE]; i_r++ )
394             free( p_sys->pi_freezing_countdown[i_r] );
395     FREENULL( p_sys->pi_freezing_countdown );
396
397     if ( p_sys->pb_update_cache )
398         for ( int32_t i_r = 0; i_r < p_sys->i_height[Y_PLANE]; i_r++ )
399             free( p_sys->pb_update_cache[i_r] );
400     FREENULL( p_sys->pb_update_cache );
401
402     if ( p_sys->pi_freezed_picture )
403         for ( int32_t i_p=0; i_p < p_sys->i_planes; i_p++ ) {
404             for ( int32_t i_r=0; i_r < p_sys->i_height[i_p]; i_r++ )
405                 free( p_sys->pi_freezed_picture[i_p][i_r] );
406             free( p_sys->pi_freezed_picture[i_p] );
407             }
408     FREENULL( p_sys->pi_freezed_picture );
409
410     p_sys->i_planes = 0;
411     FREENULL( p_sys->i_height );
412     FREENULL( p_sys->i_width );
413     FREENULL( p_sys->i_visible_pitch );
414 }