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., 51 Franklin St, Fifth Floor,
28 * Boston, MA 02110-1301, USA.
29 */
30
31 /**
32 * SECTION:element-rippletv
33 * @title: rippletv
34 *
35 * RippleTV does ripple mark effect on the video input. The ripple is caused
36 * by motion or random rain drops.
37 *
38 * ## Example launch line
39 * |[
40 * gst-launch-1.0 -v videotestsrc ! rippletv ! videoconvert ! autovideosink
41 * ]| This pipeline shows the effect of rippletv on a test stream.
42 *
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
gst_rippletv_mode_get_type(void)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 GST_ELEMENT_REGISTER_DEFINE (rippletv, "rippletv", GST_RANK_NONE,
87 GST_TYPE_RIPPLETV);
88
89 static GstStaticPadTemplate gst_rippletv_src_template =
90 GST_STATIC_PAD_TEMPLATE ("src",
91 GST_PAD_SRC,
92 GST_PAD_ALWAYS,
93 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ BGRx, RGBx, xBGR, xRGB }"))
94 );
95
96 static GstStaticPadTemplate gst_rippletv_sink_template =
97 GST_STATIC_PAD_TEMPLATE ("sink",
98 GST_PAD_SINK,
99 GST_PAD_ALWAYS,
100 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ BGRx, RGBx, xBGR, xRGB }"))
101 );
102
103 static const gint point = 16;
104 static const gint impact = 2;
105 static const gint decay = 8;
106 static const gint loopnum = 2;
107
108 static void
setTable(void)109 setTable (void)
110 {
111 gint i;
112
113 for (i = 0; i < 128; i++) {
114 sqrtable[i] = i * i;
115 }
116 for (i = 1; i <= 128; i++) {
117 sqrtable[256 - i] = -i * i;
118 }
119 }
120
121 static void
image_bgset_y(guint32 * src,gint16 * background,gint video_area)122 image_bgset_y (guint32 * src, gint16 * background, gint video_area)
123 {
124 gint i;
125 gint R, G, B;
126 guint32 *p;
127 gint16 *q;
128
129 p = src;
130 q = background;
131 for (i = 0; i < video_area; i++) {
132 R = ((*p) & 0xff0000) >> (16 - 1);
133 G = ((*p) & 0xff00) >> (8 - 2);
134 B = (*p) & 0xff;
135 *q = (gint16) (R + G + B);
136 p++;
137 q++;
138 }
139 }
140
141 static gint
setBackground(GstRippleTV * filter,guint32 * src)142 setBackground (GstRippleTV * filter, guint32 * src)
143 {
144 GstVideoInfo *info;
145
146 info = &GST_VIDEO_FILTER (filter)->in_info;
147
148 image_bgset_y (src, filter->background,
149 GST_VIDEO_INFO_WIDTH (info) * GST_VIDEO_INFO_HEIGHT (info));
150 filter->bg_is_set = TRUE;
151
152 return 0;
153 }
154
155 static void
image_bgsubtract_update_y(guint32 * src,gint16 * background,guint8 * diff,gint video_area)156 image_bgsubtract_update_y (guint32 * src, gint16 * background, guint8 * diff,
157 gint video_area)
158 {
159 gint i;
160 gint R, G, B;
161 guint32 *p;
162 gint16 *q;
163 guint8 *r;
164 gint v;
165
166 p = src;
167 q = background;
168 r = diff;
169 for (i = 0; i < video_area; i++) {
170 R = ((*p) & 0xff0000) >> (16 - 1);
171 G = ((*p) & 0xff00) >> (8 - 2);
172 B = (*p) & 0xff;
173 v = (R + G + B) - (gint) (*q);
174 *q = (gint16) (R + G + B);
175 *r = ((v + 70 * 7) >> 24) | ((70 * 7 - v) >> 24);
176
177 p++;
178 q++;
179 r++;
180 }
181 }
182
183 static void
motiondetect(GstRippleTV * filter,guint32 * src)184 motiondetect (GstRippleTV * filter, guint32 * src)
185 {
186 guint8 *diff = filter->diff;
187 gint width, height;
188 gint *p, *q;
189 gint x, y, h;
190 GstVideoInfo *info;
191
192 info = &GST_VIDEO_FILTER (filter)->in_info;
193
194 width = GST_VIDEO_INFO_WIDTH (info);
195 height = GST_VIDEO_INFO_HEIGHT (info);
196
197 if (!filter->bg_is_set)
198 setBackground (filter, src);
199
200 image_bgsubtract_update_y (src, filter->background, filter->diff,
201 width * height);
202 p = filter->map1 + filter->map_w + 1;
203 q = filter->map2 + filter->map_w + 1;
204 diff += width + 2;
205
206 for (y = filter->map_h - 2; y > 0; y--) {
207 for (x = filter->map_w - 2; x > 0; x--) {
208 h = (gint) * diff + (gint) * (diff + 1) + (gint) * (diff + width) +
209 (gint) * (diff + width + 1);
210 if (h > 0) {
211 *p = h << (point + impact - 8);
212 *q = *p;
213 }
214 p++;
215 q++;
216 diff += 2;
217 }
218 diff += width + 2;
219 p += 2;
220 q += 2;
221 }
222 }
223
224 static inline void
drop(gint power,gint * map1,gint * map2,gint map_w,gint map_h)225 drop (gint power, gint * map1, gint * map2, gint map_w, gint map_h)
226 {
227 gint x, y;
228 gint *p, *q;
229
230 x = fastrand () % (map_w - 4) + 2;
231 y = fastrand () % (map_h - 4) + 2;
232 p = map1 + y * map_w + x;
233 q = map2 + y * map_w + x;
234 *p = power;
235 *q = power;
236 *(p - map_w) = *(p - 1) = *(p + 1) = *(p + map_w) = power / 2;
237 *(p - map_w - 1) = *(p - map_w + 1) = *(p + map_w - 1) = *(p + map_w + 1) =
238 power / 4;
239 *(q - map_w) = *(q - 1) = *(q + 1) = *(q + map_w) = power / 2;
240 *(q - map_w - 1) = *(q - map_w + 1) = *(q + map_w - 1) = *(p + map_w + 1) =
241 power / 4;
242 }
243
244 static void
raindrop(GstRippleTV * filter)245 raindrop (GstRippleTV * filter)
246 {
247 gint i;
248
249 if (filter->period == 0) {
250 switch (filter->rain_stat) {
251 case 0:
252 filter->period = (fastrand () >> 23) + 100;
253 filter->drop_prob = 0;
254 filter->drop_prob_increment = 0x00ffffff / filter->period;
255 filter->drop_power = (-(fastrand () >> 28) - 2) << point;
256 filter->drops_per_frame_max = 2 << (fastrand () >> 30); // 2,4,8 or 16
257 filter->rain_stat = 1;
258 break;
259 case 1:
260 filter->drop_prob = 0x00ffffff;
261 filter->drops_per_frame = 1;
262 filter->drop_prob_increment = 1;
263 filter->period = (filter->drops_per_frame_max - 1) * 16;
264 filter->rain_stat = 2;
265 break;
266 case 2:
267 filter->period = (fastrand () >> 22) + 1000;
268 filter->drop_prob_increment = 0;
269 filter->rain_stat = 3;
270 break;
271 case 3:
272 filter->period = (filter->drops_per_frame_max - 1) * 16;
273 filter->drop_prob_increment = -1;
274 filter->rain_stat = 4;
275 break;
276 case 4:
277 filter->period = (fastrand () >> 24) + 60;
278 filter->drop_prob_increment = -(filter->drop_prob / filter->period);
279 filter->rain_stat = 5;
280 break;
281 case 5:
282 default:
283 filter->period = (fastrand () >> 23) + 500;
284 filter->drop_prob = 0;
285 filter->rain_stat = 0;
286 break;
287 }
288 }
289 switch (filter->rain_stat) {
290 default:
291 case 0:
292 break;
293 case 1:
294 case 5:
295 if ((fastrand () >> 8) < filter->drop_prob) {
296 drop (filter->drop_power, filter->map1, filter->map2, filter->map_w,
297 filter->map_h);
298 }
299 filter->drop_prob += filter->drop_prob_increment;
300 break;
301 case 2:
302 case 3:
303 case 4:
304 for (i = filter->drops_per_frame / 16; i > 0; i--) {
305 drop (filter->drop_power, filter->map1, filter->map2, filter->map_w,
306 filter->map_h);
307 }
308 filter->drops_per_frame += filter->drop_prob_increment;
309 break;
310 }
311 filter->period--;
312 }
313
314 static GstFlowReturn
gst_rippletv_transform_frame(GstVideoFilter * vfilter,GstVideoFrame * in_frame,GstVideoFrame * out_frame)315 gst_rippletv_transform_frame (GstVideoFilter * vfilter,
316 GstVideoFrame * in_frame, GstVideoFrame * out_frame)
317 {
318 GstRippleTV *filter = GST_RIPPLETV (vfilter);
319 guint32 *src, *dest;
320 gint x, y, i;
321 gint dx, dy, o_dx;
322 gint h, v;
323 gint m_w, m_h, v_w, v_h;
324 gint *p, *q, *r;
325 gint8 *vp;
326 GstClockTime timestamp, stream_time;
327
328 timestamp = GST_BUFFER_TIMESTAMP (in_frame->buffer);
329 stream_time =
330 gst_segment_to_stream_time (&GST_BASE_TRANSFORM (vfilter)->segment,
331 GST_FORMAT_TIME, timestamp);
332
333 GST_DEBUG_OBJECT (filter, "sync to %" GST_TIME_FORMAT,
334 GST_TIME_ARGS (timestamp));
335
336 if (GST_CLOCK_TIME_IS_VALID (stream_time))
337 gst_object_sync_values (GST_OBJECT (filter), stream_time);
338
339 src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
340 dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);
341
342 GST_OBJECT_LOCK (filter);
343 /* impact from the motion or rain drop */
344 if (filter->mode)
345 raindrop (filter);
346 else
347 motiondetect (filter, src);
348
349 m_w = filter->map_w;
350 m_h = filter->map_h;
351 v_w = GST_VIDEO_FRAME_WIDTH (in_frame);
352 v_h = GST_VIDEO_FRAME_HEIGHT (in_frame);
353
354 /* simulate surface wave */
355
356 /* This function is called only 30 times per second. To increase a speed
357 * of wave, iterates this loop several times. */
358 for (i = loopnum; i > 0; i--) {
359 /* wave simulation */
360 p = filter->map1 + m_w + 1;
361 q = filter->map2 + m_w + 1;
362 r = filter->map3 + m_w + 1;
363 for (y = m_h - 2; y > 0; y--) {
364 for (x = m_w - 2; x > 0; x--) {
365 h = *(p - m_w - 1) + *(p - m_w + 1) + *(p + m_w - 1) + *(p + m_w + 1)
366 + *(p - m_w) + *(p - 1) + *(p + 1) + *(p + m_w) - (*p) * 9;
367 h = h >> 3;
368 v = *p - *q;
369 v += h - (v >> decay);
370 *r = v + *p;
371 p++;
372 q++;
373 r++;
374 }
375 p += 2;
376 q += 2;
377 r += 2;
378 }
379
380 /* low pass filter */
381 p = filter->map3 + m_w + 1;
382 q = filter->map2 + m_w + 1;
383 for (y = m_h - 2; y > 0; y--) {
384 for (x = m_w - 2; x > 0; x--) {
385 h = *(p - m_w) + *(p - 1) + *(p + 1) + *(p + m_w) + (*p) * 60;
386 *q = h >> 6;
387 p++;
388 q++;
389 }
390 p += 2;
391 q += 2;
392 }
393
394 p = filter->map1;
395 filter->map1 = filter->map2;
396 filter->map2 = p;
397 }
398
399 vp = filter->vtable;
400 p = filter->map1;
401 for (y = m_h - 1; y > 0; y--) {
402 for (x = m_w - 1; x > 0; x--) {
403 /* difference of the height between two voxel. They are twiced to
404 * emphasise the wave. */
405 vp[0] = sqrtable[((p[0] - p[1]) >> (point - 1)) & 0xff];
406 vp[1] = sqrtable[((p[0] - p[m_w]) >> (point - 1)) & 0xff];
407 p++;
408 vp += 2;
409 }
410 p++;
411 vp += 2;
412 }
413
414 vp = filter->vtable;
415
416 /* draw refracted image. The vector table is stretched. */
417 for (y = 0; y < v_h; y += 2) {
418 for (x = 0; x < v_w; x += 2) {
419 h = (gint) vp[0];
420 v = (gint) vp[1];
421 dx = x + h;
422 dy = y + v;
423 dx = CLAMP (dx, 0, (v_w - 2));
424 dy = CLAMP (dy, 0, (v_h - 2));
425 dest[0] = src[dy * v_w + dx];
426
427 o_dx = dx;
428
429 dx = x + 1 + (h + (gint) vp[2]) / 2;
430 dx = CLAMP (dx, 0, (v_w - 2));
431 dest[1] = src[dy * v_w + dx];
432
433 dy = y + 1 + (v + (gint) vp[m_w * 2 + 1]) / 2;
434 dy = CLAMP (dy, 0, (v_h - 2));
435 dest[v_w] = src[dy * v_w + o_dx];
436
437 dest[v_w + 1] = src[dy * v_w + dx];
438 dest += 2;
439 vp += 2;
440 }
441 dest += v_w;
442 vp += 2;
443 }
444 GST_OBJECT_UNLOCK (filter);
445
446 return GST_FLOW_OK;
447 }
448
449 static gboolean
gst_rippletv_set_info(GstVideoFilter * vfilter,GstCaps * incaps,GstVideoInfo * in_info,GstCaps * outcaps,GstVideoInfo * out_info)450 gst_rippletv_set_info (GstVideoFilter * vfilter, GstCaps * incaps,
451 GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info)
452 {
453 GstRippleTV *filter = GST_RIPPLETV (vfilter);
454 gint width, height;
455
456 width = GST_VIDEO_INFO_WIDTH (in_info);
457 height = GST_VIDEO_INFO_HEIGHT (in_info);
458
459 GST_OBJECT_LOCK (filter);
460 filter->map_h = height / 2 + 1;
461 filter->map_w = width / 2 + 1;
462
463 /* we over allocate the buffers, as the render code does not handle clipping
464 * very well */
465 g_free (filter->map);
466 filter->map = g_new0 (gint, (1 + filter->map_h) * filter->map_w * 3);
467
468 filter->map1 = filter->map;
469 filter->map2 = filter->map + filter->map_w * filter->map_h;
470 filter->map3 = filter->map + filter->map_w * filter->map_h * 2;
471
472 g_free (filter->vtable);
473 filter->vtable = g_new0 (gint8, (1 + filter->map_h) * filter->map_w * 2);
474
475 g_free (filter->background);
476 filter->background = g_new0 (gint16, width * (height + 1));
477
478 g_free (filter->diff);
479 filter->diff = g_new0 (guint8, width * (height + 1));
480 GST_OBJECT_UNLOCK (filter);
481
482 return TRUE;
483 }
484
485 static gboolean
gst_rippletv_start(GstBaseTransform * trans)486 gst_rippletv_start (GstBaseTransform * trans)
487 {
488 GstRippleTV *filter = GST_RIPPLETV (trans);
489
490 filter->bg_is_set = FALSE;
491
492 filter->period = 0;
493 filter->rain_stat = 0;
494 filter->drop_prob = 0;
495 filter->drop_prob_increment = 0;
496 filter->drops_per_frame_max = 0;
497 filter->drops_per_frame = 0;
498 filter->drop_power = 0;
499
500 return TRUE;
501 }
502
503 static void
gst_rippletv_finalize(GObject * object)504 gst_rippletv_finalize (GObject * object)
505 {
506 GstRippleTV *filter = GST_RIPPLETV (object);
507
508 g_free (filter->map);
509 filter->map = NULL;
510
511 g_free (filter->vtable);
512 filter->vtable = NULL;
513
514 g_free (filter->background);
515 filter->background = NULL;
516
517 g_free (filter->diff);
518 filter->diff = NULL;
519
520 G_OBJECT_CLASS (parent_class)->finalize (object);
521 }
522
523 static void
gst_rippletv_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)524 gst_rippletv_set_property (GObject * object, guint prop_id,
525 const GValue * value, GParamSpec * pspec)
526 {
527 GstRippleTV *filter = GST_RIPPLETV (object);
528
529 GST_OBJECT_LOCK (filter);
530 switch (prop_id) {
531 case PROP_RESET:{
532 memset (filter->map, 0,
533 filter->map_h * filter->map_w * 2 * sizeof (gint));
534 break;
535 }
536 case PROP_MODE:
537 filter->mode = g_value_get_enum (value);
538 break;
539 default:
540 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
541 break;
542 }
543 GST_OBJECT_UNLOCK (filter);
544 }
545
546 static void
gst_rippletv_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)547 gst_rippletv_get_property (GObject * object, guint prop_id, GValue * value,
548 GParamSpec * pspec)
549 {
550 GstRippleTV *filter = GST_RIPPLETV (object);
551
552 switch (prop_id) {
553 case PROP_MODE:
554 g_value_set_enum (value, filter->mode);
555 break;
556 default:
557 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
558 break;
559 }
560 }
561
562 static void
gst_rippletv_class_init(GstRippleTVClass * klass)563 gst_rippletv_class_init (GstRippleTVClass * klass)
564 {
565 GObjectClass *gobject_class = (GObjectClass *) klass;
566 GstElementClass *gstelement_class = (GstElementClass *) klass;
567 GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass;
568 GstVideoFilterClass *vfilter_class = (GstVideoFilterClass *) klass;
569
570 gobject_class->set_property = gst_rippletv_set_property;
571 gobject_class->get_property = gst_rippletv_get_property;
572
573 gobject_class->finalize = gst_rippletv_finalize;
574
575 g_object_class_install_property (gobject_class, PROP_RESET,
576 g_param_spec_boolean ("reset", "Reset",
577 "Reset all current ripples", FALSE,
578 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
579
580 g_object_class_install_property (gobject_class, PROP_MODE,
581 g_param_spec_enum ("mode", "Mode",
582 "Mode", GST_TYPE_RIPPLETV_MODE, DEFAULT_MODE,
583 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
584
585 gst_element_class_set_static_metadata (gstelement_class, "RippleTV effect",
586 "Filter/Effect/Video",
587 "RippleTV does ripple mark effect on the video input",
588 "FUKUCHI, Kentarou <fukuchi@users.sourceforge.net>, "
589 "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
590
591 gst_element_class_add_static_pad_template (gstelement_class,
592 &gst_rippletv_sink_template);
593 gst_element_class_add_static_pad_template (gstelement_class,
594 &gst_rippletv_src_template);
595
596 trans_class->start = GST_DEBUG_FUNCPTR (gst_rippletv_start);
597
598 vfilter_class->set_info = GST_DEBUG_FUNCPTR (gst_rippletv_set_info);
599 vfilter_class->transform_frame =
600 GST_DEBUG_FUNCPTR (gst_rippletv_transform_frame);
601
602 setTable ();
603
604 gst_type_mark_as_plugin_api (GST_TYPE_RIPPLETV_MODE, 0);
605 }
606
607 static void
gst_rippletv_init(GstRippleTV * filter)608 gst_rippletv_init (GstRippleTV * filter)
609 {
610 filter->mode = DEFAULT_MODE;
611
612 /* FIXME: remove this when memory corruption after resizes are fixed */
613 gst_pad_use_fixed_caps (GST_BASE_TRANSFORM_SRC_PAD (filter));
614 gst_pad_use_fixed_caps (GST_BASE_TRANSFORM_SINK_PAD (filter));
615 }
616