Merge remote-tracking branch 'origin/master' into 0.11
[platform/upstream/gst-plugins-good.git] / gst / effectv / gstripple.c
1 /* GStreamer
2  * Copyright (C) <2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
3  *
4  * EffecTV - Realtime Digital Video Effector
5  * Copyright (C) 2001-2006 FUKUCHI Kentaro
6  *
7  * RippleTV - Water ripple effect.
8  * Copyright (C) 2001-2002 FUKUCHI Kentaro
9  *
10  * This combines the RippleTV and BaltanTV effects, which are
11  * very similar. BaltanTV is used if the feedback property is set
12  * to TRUE, otherwise RippleTV is used.
13  *
14  * EffecTV is free software. This library is free software;
15  * you can redistribute it and/or
16  * modify it under the terms of the GNU Library General Public
17  * License as published by the Free Software Foundation; either
18  * version 2 of the License, or (at your option) any later version.
19  *
20  * This library is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23  * Library General Public License for more details.
24  *
25  * You should have received a copy of the GNU Library General Public
26  * License along with this library; if not, write to the
27  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
28  * Boston, MA 02111-1307, USA.
29  */
30
31 /**
32  * SECTION:element-rippletv
33  *
34  * RippleTV does ripple mark effect on the video input. The ripple is caused
35  * by motion or random rain drops.
36  *
37  * <refsect2>
38  * <title>Example launch line</title>
39  * |[
40  * gst-launch -v videotestsrc ! rippletv ! videoconvert ! autovideosink
41  * ]| This pipeline shows the effect of rippletv on a test stream.
42  * </refsect2>
43  */
44
45 #ifdef HAVE_CONFIG_H
46 #include "config.h"
47 #endif
48
49 #include <math.h>
50 #include <string.h>
51
52 #include "gstripple.h"
53 #include "gsteffectv.h"
54
55 #define DEFAULT_MODE 0
56
57 enum
58 {
59   PROP_0,
60   PROP_RESET,
61   PROP_MODE
62 };
63
64 static gint sqrtable[256];
65
66 #define GST_TYPE_RIPPLETV_MODE (gst_rippletv_mode_get_type())
67 static GType
68 gst_rippletv_mode_get_type (void)
69 {
70   static GType type = 0;
71
72   static const GEnumValue enumvalue[] = {
73     {0, "Motion Detection", "motion-detection"},
74     {1, "Rain", "rain"},
75     {0, NULL, NULL},
76   };
77
78   if (!type) {
79     type = g_enum_register_static ("GstRippleTVMode", enumvalue);
80   }
81   return type;
82 }
83
84 #define gst_rippletv_parent_class parent_class
85 G_DEFINE_TYPE (GstRippleTV, gst_rippletv, GST_TYPE_VIDEO_FILTER);
86
87 static GstStaticPadTemplate gst_rippletv_src_template =
88 GST_STATIC_PAD_TEMPLATE ("src",
89     GST_PAD_SRC,
90     GST_PAD_ALWAYS,
91     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ BGRx, RGBx, xBGR, xRGB }"))
92     );
93
94 static GstStaticPadTemplate gst_rippletv_sink_template =
95 GST_STATIC_PAD_TEMPLATE ("sink",
96     GST_PAD_SINK,
97     GST_PAD_ALWAYS,
98     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ BGRx, RGBx, xBGR, xRGB }"))
99     );
100
101 static const gint point = 16;
102 static const gint impact = 2;
103 static const gint decay = 8;
104 static const gint loopnum = 2;
105
106 static void
107 setTable (void)
108 {
109   gint i;
110
111   for (i = 0; i < 128; i++) {
112     sqrtable[i] = i * i;
113   }
114   for (i = 1; i <= 128; i++) {
115     sqrtable[256 - i] = -i * i;
116   }
117 }
118
119 static void
120 image_bgset_y (guint32 * src, gint16 * background, gint video_area)
121 {
122   gint i;
123   gint R, G, B;
124   guint32 *p;
125   gint16 *q;
126
127   p = src;
128   q = background;
129   for (i = 0; i < video_area; i++) {
130     R = ((*p) & 0xff0000) >> (16 - 1);
131     G = ((*p) & 0xff00) >> (8 - 2);
132     B = (*p) & 0xff;
133     *q = (gint16) (R + G + B);
134     p++;
135     q++;
136   }
137 }
138
139 static gint
140 setBackground (GstRippleTV * filter, guint32 * src)
141 {
142   image_bgset_y (src, filter->background,
143       GST_VIDEO_INFO_WIDTH (&filter->info) *
144       GST_VIDEO_INFO_HEIGHT (&filter->info));
145   filter->bg_is_set = TRUE;
146
147   return 0;
148 }
149
150 static void
151 image_bgsubtract_update_y (guint32 * src, gint16 * background, guint8 * diff,
152     gint video_area)
153 {
154   gint i;
155   gint R, G, B;
156   guint32 *p;
157   gint16 *q;
158   guint8 *r;
159   gint v;
160
161   p = src;
162   q = background;
163   r = diff;
164   for (i = 0; i < video_area; i++) {
165     R = ((*p) & 0xff0000) >> (16 - 1);
166     G = ((*p) & 0xff00) >> (8 - 2);
167     B = (*p) & 0xff;
168     v = (R + G + B) - (gint) (*q);
169     *q = (gint16) (R + G + B);
170     *r = ((v + 70 * 7) >> 24) | ((70 * 7 - v) >> 24);
171
172     p++;
173     q++;
174     r++;
175   }
176 }
177
178 static void
179 motiondetect (GstRippleTV * filter, guint32 * src)
180 {
181   guint8 *diff = filter->diff;
182   gint width, height;
183   gint *p, *q;
184   gint x, y, h;
185
186   width = GST_VIDEO_INFO_WIDTH (&filter->info);
187   height = GST_VIDEO_INFO_HEIGHT (&filter->info);
188
189   if (!filter->bg_is_set)
190     setBackground (filter, src);
191
192   image_bgsubtract_update_y (src, filter->background, filter->diff,
193       width * height);
194   p = filter->map1 + filter->map_w + 1;
195   q = filter->map2 + filter->map_w + 1;
196   diff += width + 2;
197
198   for (y = filter->map_h - 2; y > 0; y--) {
199     for (x = filter->map_w - 2; x > 0; x--) {
200       h = (gint) * diff + (gint) * (diff + 1) + (gint) * (diff + width) +
201           (gint) * (diff + width + 1);
202       if (h > 0) {
203         *p = h << (point + impact - 8);
204         *q = *p;
205       }
206       p++;
207       q++;
208       diff += 2;
209     }
210     diff += width + 2;
211     p += 2;
212     q += 2;
213   }
214 }
215
216 static inline void
217 drop (gint power, gint * map1, gint * map2, gint map_w, gint map_h)
218 {
219   gint x, y;
220   gint *p, *q;
221
222   x = fastrand () % (map_w - 4) + 2;
223   y = fastrand () % (map_h - 4) + 2;
224   p = map1 + y * map_w + x;
225   q = map2 + y * map_w + x;
226   *p = power;
227   *q = power;
228   *(p - map_w) = *(p - 1) = *(p + 1) = *(p + map_w) = power / 2;
229   *(p - map_w - 1) = *(p - map_w + 1) = *(p + map_w - 1) = *(p + map_w + 1) =
230       power / 4;
231   *(q - map_w) = *(q - 1) = *(q + 1) = *(q + map_w) = power / 2;
232   *(q - map_w - 1) = *(q - map_w + 1) = *(q + map_w - 1) = *(p + map_w + 1) =
233       power / 4;
234 }
235
236 static void
237 raindrop (GstRippleTV * filter)
238 {
239   gint i;
240
241   if (filter->period == 0) {
242     switch (filter->rain_stat) {
243       case 0:
244         filter->period = (fastrand () >> 23) + 100;
245         filter->drop_prob = 0;
246         filter->drop_prob_increment = 0x00ffffff / filter->period;
247         filter->drop_power = (-(fastrand () >> 28) - 2) << point;
248         filter->drops_per_frame_max = 2 << (fastrand () >> 30); // 2,4,8 or 16
249         filter->rain_stat = 1;
250         break;
251       case 1:
252         filter->drop_prob = 0x00ffffff;
253         filter->drops_per_frame = 1;
254         filter->drop_prob_increment = 1;
255         filter->period = (filter->drops_per_frame_max - 1) * 16;
256         filter->rain_stat = 2;
257         break;
258       case 2:
259         filter->period = (fastrand () >> 22) + 1000;
260         filter->drop_prob_increment = 0;
261         filter->rain_stat = 3;
262         break;
263       case 3:
264         filter->period = (filter->drops_per_frame_max - 1) * 16;
265         filter->drop_prob_increment = -1;
266         filter->rain_stat = 4;
267         break;
268       case 4:
269         filter->period = (fastrand () >> 24) + 60;
270         filter->drop_prob_increment = -(filter->drop_prob / filter->period);
271         filter->rain_stat = 5;
272         break;
273       case 5:
274       default:
275         filter->period = (fastrand () >> 23) + 500;
276         filter->drop_prob = 0;
277         filter->rain_stat = 0;
278         break;
279     }
280   }
281   switch (filter->rain_stat) {
282     default:
283     case 0:
284       break;
285     case 1:
286     case 5:
287       if ((fastrand () >> 8) < filter->drop_prob) {
288         drop (filter->drop_power, filter->map1, filter->map2, filter->map_w,
289             filter->map_h);
290       }
291       filter->drop_prob += filter->drop_prob_increment;
292       break;
293     case 2:
294     case 3:
295     case 4:
296       for (i = filter->drops_per_frame / 16; i > 0; i--) {
297         drop (filter->drop_power, filter->map1, filter->map2, filter->map_w,
298             filter->map_h);
299       }
300       filter->drops_per_frame += filter->drop_prob_increment;
301       break;
302   }
303   filter->period--;
304 }
305
306 static GstFlowReturn
307 gst_rippletv_transform (GstBaseTransform * trans, GstBuffer * in,
308     GstBuffer * out)
309 {
310   GstRippleTV *filter = GST_RIPPLETV (trans);
311   guint32 *src, *dest;
312   GstVideoFrame in_frame, out_frame;
313   gint x, y, i;
314   gint dx, dy, o_dx;
315   gint h, v;
316   gint m_w, m_h, v_w, v_h;
317   gint *p, *q, *r;
318   gint8 *vp;
319   GstClockTime timestamp, stream_time;
320
321   timestamp = GST_BUFFER_TIMESTAMP (in);
322   stream_time =
323       gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME, timestamp);
324
325   GST_DEBUG_OBJECT (filter, "sync to %" GST_TIME_FORMAT,
326       GST_TIME_ARGS (timestamp));
327
328   if (GST_CLOCK_TIME_IS_VALID (stream_time))
329     gst_object_sync_values (GST_OBJECT (filter), stream_time);
330
331   if (!gst_video_frame_map (&in_frame, &filter->info, in, GST_MAP_READ))
332     goto invalid_in;
333
334   if (!gst_video_frame_map (&out_frame, &filter->info, out, GST_MAP_WRITE))
335     goto invalid_out;
336
337   src = GST_VIDEO_FRAME_PLANE_DATA (&in_frame, 0);
338   dest = GST_VIDEO_FRAME_PLANE_DATA (&out_frame, 0);
339
340   GST_OBJECT_LOCK (filter);
341   /* impact from the motion or rain drop */
342   if (filter->mode)
343     raindrop (filter);
344   else
345     motiondetect (filter, src);
346
347   m_w = filter->map_w;
348   m_h = filter->map_h;
349   v_w = GST_VIDEO_FRAME_WIDTH (&in_frame);
350   v_h = GST_VIDEO_FRAME_HEIGHT (&in_frame);
351
352   /* simulate surface wave */
353
354   /* This function is called only 30 times per second. To increase a speed
355    * of wave, iterates this loop several times. */
356   for (i = loopnum; i > 0; i--) {
357     /* wave simulation */
358     p = filter->map1 + m_w + 1;
359     q = filter->map2 + m_w + 1;
360     r = filter->map3 + m_w + 1;
361     for (y = m_h - 2; y > 0; y--) {
362       for (x = m_w - 2; x > 0; x--) {
363         h = *(p - m_w - 1) + *(p - m_w + 1) + *(p + m_w - 1) + *(p + m_w + 1)
364             + *(p - m_w) + *(p - 1) + *(p + 1) + *(p + m_w) - (*p) * 9;
365         h = h >> 3;
366         v = *p - *q;
367         v += h - (v >> decay);
368         *r = v + *p;
369         p++;
370         q++;
371         r++;
372       }
373       p += 2;
374       q += 2;
375       r += 2;
376     }
377
378     /* low pass filter */
379     p = filter->map3 + m_w + 1;
380     q = filter->map2 + m_w + 1;
381     for (y = m_h - 2; y > 0; y--) {
382       for (x = m_w - 2; x > 0; x--) {
383         h = *(p - m_w) + *(p - 1) + *(p + 1) + *(p + m_w) + (*p) * 60;
384         *q = h >> 6;
385         p++;
386         q++;
387       }
388       p += 2;
389       q += 2;
390     }
391
392     p = filter->map1;
393     filter->map1 = filter->map2;
394     filter->map2 = p;
395   }
396
397   vp = filter->vtable;
398   p = filter->map1;
399   for (y = m_h - 1; y > 0; y--) {
400     for (x = m_w - 1; x > 0; x--) {
401       /* difference of the height between two voxel. They are twiced to
402        * emphasise the wave. */
403       vp[0] = sqrtable[((p[0] - p[1]) >> (point - 1)) & 0xff];
404       vp[1] = sqrtable[((p[0] - p[m_w]) >> (point - 1)) & 0xff];
405       p++;
406       vp += 2;
407     }
408     p++;
409     vp += 2;
410   }
411
412   vp = filter->vtable;
413
414   /* draw refracted image. The vector table is stretched. */
415   for (y = 0; y < v_h; y += 2) {
416     for (x = 0; x < v_w; x += 2) {
417       h = (gint) vp[0];
418       v = (gint) vp[1];
419       dx = x + h;
420       dy = y + v;
421       dx = CLAMP (dx, 0, (v_w - 1));
422       dy = CLAMP (dy, 0, (v_h - 1));
423       dest[0] = src[dy * v_w + dx];
424
425       o_dx = dx;
426
427       dx = x + 1 + (h + (gint) vp[2]) / 2;
428       dx = CLAMP (dx, 0, (v_w - 1));
429       dest[1] = src[dy * v_w + dx];
430
431       dy = y + 1 + (v + (gint) vp[m_w * 2 + 1]) / 2;
432       dy = CLAMP (dy, 0, (v_h - 1));
433       dest[v_w] = src[dy * v_w + o_dx];
434
435       dest[v_w + 1] = src[dy * v_w + dx];
436       dest += 2;
437       vp += 2;
438     }
439     dest += v_w;
440     vp += 2;
441   }
442   GST_OBJECT_UNLOCK (filter);
443
444   gst_video_frame_unmap (&in_frame);
445   gst_video_frame_unmap (&out_frame);
446
447   return GST_FLOW_OK;
448
449   /* ERRORS */
450 invalid_in:
451   {
452     GST_DEBUG_OBJECT (filter, "invalid input frame");
453     return GST_FLOW_ERROR;
454   }
455 invalid_out:
456   {
457     GST_DEBUG_OBJECT (filter, "invalid output frame");
458     gst_video_frame_unmap (&in_frame);
459     return GST_FLOW_ERROR;
460   }
461
462 }
463
464 static gboolean
465 gst_rippletv_set_caps (GstBaseTransform * btrans, GstCaps * incaps,
466     GstCaps * outcaps)
467 {
468   GstRippleTV *filter = GST_RIPPLETV (btrans);
469   GstVideoInfo info;
470   gint width, height;
471
472   if (!gst_video_info_from_caps (&info, incaps))
473     goto invalid_caps;
474
475   filter->info = info;
476
477   width = GST_VIDEO_INFO_WIDTH (&info);
478   height = GST_VIDEO_INFO_HEIGHT (&info);
479
480   GST_OBJECT_LOCK (filter);
481   filter->map_h = height / 2 + 1;
482   filter->map_w = width / 2 + 1;
483
484   /* we over allocate the buffers, as the render code does not handle clipping
485    * very well */
486   if (filter->map)
487     g_free (filter->map);
488   filter->map = g_new0 (gint, (1 + filter->map_h) * filter->map_w * 3);
489
490   filter->map1 = filter->map;
491   filter->map2 = filter->map + filter->map_w * filter->map_h;
492   filter->map3 = filter->map + filter->map_w * filter->map_h * 2;
493
494   if (filter->vtable)
495     g_free (filter->vtable);
496   filter->vtable = g_new0 (gint8, (1 + filter->map_h) * filter->map_w * 2);
497
498   if (filter->background)
499     g_free (filter->background);
500   filter->background = g_new0 (gint16, width * (height + 1));
501
502   if (filter->diff)
503     g_free (filter->diff);
504   filter->diff = g_new0 (guint8, width * (height + 1));
505   GST_OBJECT_UNLOCK (filter);
506
507   return TRUE;
508
509   /* ERRORS */
510 invalid_caps:
511   {
512     GST_DEBUG_OBJECT (filter, "invalid caps received");
513     return FALSE;
514   }
515 }
516
517 static gboolean
518 gst_rippletv_start (GstBaseTransform * trans)
519 {
520   GstRippleTV *filter = GST_RIPPLETV (trans);
521
522   filter->bg_is_set = FALSE;
523
524   filter->period = 0;
525   filter->rain_stat = 0;
526   filter->drop_prob = 0;
527   filter->drop_prob_increment = 0;
528   filter->drops_per_frame_max = 0;
529   filter->drops_per_frame = 0;
530   filter->drop_power = 0;
531
532   return TRUE;
533 }
534
535 static void
536 gst_rippletv_finalize (GObject * object)
537 {
538   GstRippleTV *filter = GST_RIPPLETV (object);
539
540   if (filter->map)
541     g_free (filter->map);
542   filter->map = NULL;
543
544   if (filter->vtable)
545     g_free (filter->vtable);
546   filter->vtable = NULL;
547
548   if (filter->background)
549     g_free (filter->background);
550   filter->background = NULL;
551
552   if (filter->diff)
553     g_free (filter->diff);
554   filter->diff = NULL;
555
556   G_OBJECT_CLASS (parent_class)->finalize (object);
557 }
558
559 static void
560 gst_rippletv_set_property (GObject * object, guint prop_id,
561     const GValue * value, GParamSpec * pspec)
562 {
563   GstRippleTV *filter = GST_RIPPLETV (object);
564
565   GST_OBJECT_LOCK (filter);
566   switch (prop_id) {
567     case PROP_RESET:{
568       memset (filter->map, 0,
569           filter->map_h * filter->map_w * 2 * sizeof (gint));
570       break;
571     }
572     case PROP_MODE:
573       filter->mode = g_value_get_enum (value);
574       break;
575     default:
576       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
577       break;
578   }
579   GST_OBJECT_UNLOCK (filter);
580 }
581
582 static void
583 gst_rippletv_get_property (GObject * object, guint prop_id, GValue * value,
584     GParamSpec * pspec)
585 {
586   GstRippleTV *filter = GST_RIPPLETV (object);
587
588   switch (prop_id) {
589     case PROP_MODE:
590       g_value_set_enum (value, filter->mode);
591       break;
592     default:
593       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
594       break;
595   }
596 }
597
598 static void
599 gst_rippletv_class_init (GstRippleTVClass * klass)
600 {
601   GObjectClass *gobject_class = (GObjectClass *) klass;
602   GstElementClass *gstelement_class = (GstElementClass *) klass;
603   GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass;
604
605   gobject_class->set_property = gst_rippletv_set_property;
606   gobject_class->get_property = gst_rippletv_get_property;
607
608   gobject_class->finalize = gst_rippletv_finalize;
609
610   g_object_class_install_property (gobject_class, PROP_RESET,
611       g_param_spec_boolean ("reset", "Reset",
612           "Reset all current ripples", FALSE,
613           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
614
615   g_object_class_install_property (gobject_class, PROP_MODE,
616       g_param_spec_enum ("mode", "Mode",
617           "Mode", GST_TYPE_RIPPLETV_MODE, DEFAULT_MODE,
618           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
619
620   gst_element_class_set_details_simple (gstelement_class, "RippleTV effect",
621       "Filter/Effect/Video",
622       "RippleTV does ripple mark effect on the video input",
623       "FUKUCHI, Kentarou <fukuchi@users.sourceforge.net>, "
624       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
625
626   gst_element_class_add_pad_template (gstelement_class,
627       gst_static_pad_template_get (&gst_rippletv_sink_template));
628   gst_element_class_add_pad_template (gstelement_class,
629       gst_static_pad_template_get (&gst_rippletv_src_template));
630
631   trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_rippletv_set_caps);
632   trans_class->transform = GST_DEBUG_FUNCPTR (gst_rippletv_transform);
633   trans_class->start = GST_DEBUG_FUNCPTR (gst_rippletv_start);
634
635   setTable ();
636 }
637
638 static void
639 gst_rippletv_init (GstRippleTV * filter)
640 {
641   filter->mode = DEFAULT_MODE;
642
643   /* FIXME: remove this when memory corruption after resizes are fixed */
644   gst_pad_use_fixed_caps (GST_BASE_TRANSFORM_SRC_PAD (filter));
645   gst_pad_use_fixed_caps (GST_BASE_TRANSFORM_SINK_PAD (filter));
646 }