| /* GStreamer |
| * Copyright (C) <2009> Sebastian Dröge <sebastian.droege@collabora.co.uk> |
| * |
| * EffecTV - Realtime Digital Video Effector |
| * Copyright (C) 2001-2006 FUKUCHI Kentaro |
| * |
| * RippleTV - Water ripple effect. |
| * Copyright (C) 2001-2002 FUKUCHI Kentaro |
| * |
| * This combines the RippleTV and BaltanTV effects, which are |
| * very similar. BaltanTV is used if the feedback property is set |
| * to TRUE, otherwise RippleTV is used. |
| * |
| * EffecTV is free software. This library is free software; |
| * you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library 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 |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| /** |
| * SECTION:element-rippletv |
| * |
| * RippleTV does ripple mark effect on the video input. The ripple is caused |
| * by motion or random rain drops. |
| * |
| * <refsect2> |
| * <title>Example launch line</title> |
| * |[ |
| * gst-launch-1.0 -v videotestsrc ! rippletv ! videoconvert ! autovideosink |
| * ]| This pipeline shows the effect of rippletv on a test stream. |
| * </refsect2> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <math.h> |
| #include <string.h> |
| |
| #include "gstripple.h" |
| #include "gsteffectv.h" |
| |
| #define DEFAULT_MODE 0 |
| |
| enum |
| { |
| PROP_0, |
| PROP_RESET, |
| PROP_MODE |
| }; |
| |
| static gint sqrtable[256]; |
| |
| #define GST_TYPE_RIPPLETV_MODE (gst_rippletv_mode_get_type()) |
| static GType |
| gst_rippletv_mode_get_type (void) |
| { |
| static GType type = 0; |
| |
| static const GEnumValue enumvalue[] = { |
| {0, "Motion Detection", "motion-detection"}, |
| {1, "Rain", "rain"}, |
| {0, NULL, NULL}, |
| }; |
| |
| if (!type) { |
| type = g_enum_register_static ("GstRippleTVMode", enumvalue); |
| } |
| return type; |
| } |
| |
| #define gst_rippletv_parent_class parent_class |
| G_DEFINE_TYPE (GstRippleTV, gst_rippletv, GST_TYPE_VIDEO_FILTER); |
| |
| static GstStaticPadTemplate gst_rippletv_src_template = |
| GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ BGRx, RGBx, xBGR, xRGB }")) |
| ); |
| |
| static GstStaticPadTemplate gst_rippletv_sink_template = |
| GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ BGRx, RGBx, xBGR, xRGB }")) |
| ); |
| |
| static const gint point = 16; |
| static const gint impact = 2; |
| static const gint decay = 8; |
| static const gint loopnum = 2; |
| |
| static void |
| setTable (void) |
| { |
| gint i; |
| |
| for (i = 0; i < 128; i++) { |
| sqrtable[i] = i * i; |
| } |
| for (i = 1; i <= 128; i++) { |
| sqrtable[256 - i] = -i * i; |
| } |
| } |
| |
| static void |
| image_bgset_y (guint32 * src, gint16 * background, gint video_area) |
| { |
| gint i; |
| gint R, G, B; |
| guint32 *p; |
| gint16 *q; |
| |
| p = src; |
| q = background; |
| for (i = 0; i < video_area; i++) { |
| R = ((*p) & 0xff0000) >> (16 - 1); |
| G = ((*p) & 0xff00) >> (8 - 2); |
| B = (*p) & 0xff; |
| *q = (gint16) (R + G + B); |
| p++; |
| q++; |
| } |
| } |
| |
| static gint |
| setBackground (GstRippleTV * filter, guint32 * src) |
| { |
| GstVideoInfo *info; |
| |
| info = &GST_VIDEO_FILTER (filter)->in_info; |
| |
| image_bgset_y (src, filter->background, |
| GST_VIDEO_INFO_WIDTH (info) * GST_VIDEO_INFO_HEIGHT (info)); |
| filter->bg_is_set = TRUE; |
| |
| return 0; |
| } |
| |
| static void |
| image_bgsubtract_update_y (guint32 * src, gint16 * background, guint8 * diff, |
| gint video_area) |
| { |
| gint i; |
| gint R, G, B; |
| guint32 *p; |
| gint16 *q; |
| guint8 *r; |
| gint v; |
| |
| p = src; |
| q = background; |
| r = diff; |
| for (i = 0; i < video_area; i++) { |
| R = ((*p) & 0xff0000) >> (16 - 1); |
| G = ((*p) & 0xff00) >> (8 - 2); |
| B = (*p) & 0xff; |
| v = (R + G + B) - (gint) (*q); |
| *q = (gint16) (R + G + B); |
| *r = ((v + 70 * 7) >> 24) | ((70 * 7 - v) >> 24); |
| |
| p++; |
| q++; |
| r++; |
| } |
| } |
| |
| static void |
| motiondetect (GstRippleTV * filter, guint32 * src) |
| { |
| guint8 *diff = filter->diff; |
| gint width, height; |
| gint *p, *q; |
| gint x, y, h; |
| GstVideoInfo *info; |
| |
| info = &GST_VIDEO_FILTER (filter)->in_info; |
| |
| width = GST_VIDEO_INFO_WIDTH (info); |
| height = GST_VIDEO_INFO_HEIGHT (info); |
| |
| if (!filter->bg_is_set) |
| setBackground (filter, src); |
| |
| image_bgsubtract_update_y (src, filter->background, filter->diff, |
| width * height); |
| p = filter->map1 + filter->map_w + 1; |
| q = filter->map2 + filter->map_w + 1; |
| diff += width + 2; |
| |
| for (y = filter->map_h - 2; y > 0; y--) { |
| for (x = filter->map_w - 2; x > 0; x--) { |
| h = (gint) * diff + (gint) * (diff + 1) + (gint) * (diff + width) + |
| (gint) * (diff + width + 1); |
| if (h > 0) { |
| *p = h << (point + impact - 8); |
| *q = *p; |
| } |
| p++; |
| q++; |
| diff += 2; |
| } |
| diff += width + 2; |
| p += 2; |
| q += 2; |
| } |
| } |
| |
| static inline void |
| drop (gint power, gint * map1, gint * map2, gint map_w, gint map_h) |
| { |
| gint x, y; |
| gint *p, *q; |
| |
| x = fastrand () % (map_w - 4) + 2; |
| y = fastrand () % (map_h - 4) + 2; |
| p = map1 + y * map_w + x; |
| q = map2 + y * map_w + x; |
| *p = power; |
| *q = power; |
| *(p - map_w) = *(p - 1) = *(p + 1) = *(p + map_w) = power / 2; |
| *(p - map_w - 1) = *(p - map_w + 1) = *(p + map_w - 1) = *(p + map_w + 1) = |
| power / 4; |
| *(q - map_w) = *(q - 1) = *(q + 1) = *(q + map_w) = power / 2; |
| *(q - map_w - 1) = *(q - map_w + 1) = *(q + map_w - 1) = *(p + map_w + 1) = |
| power / 4; |
| } |
| |
| static void |
| raindrop (GstRippleTV * filter) |
| { |
| gint i; |
| |
| if (filter->period == 0) { |
| switch (filter->rain_stat) { |
| case 0: |
| filter->period = (fastrand () >> 23) + 100; |
| filter->drop_prob = 0; |
| filter->drop_prob_increment = 0x00ffffff / filter->period; |
| filter->drop_power = (-(fastrand () >> 28) - 2) << point; |
| filter->drops_per_frame_max = 2 << (fastrand () >> 30); // 2,4,8 or 16 |
| filter->rain_stat = 1; |
| break; |
| case 1: |
| filter->drop_prob = 0x00ffffff; |
| filter->drops_per_frame = 1; |
| filter->drop_prob_increment = 1; |
| filter->period = (filter->drops_per_frame_max - 1) * 16; |
| filter->rain_stat = 2; |
| break; |
| case 2: |
| filter->period = (fastrand () >> 22) + 1000; |
| filter->drop_prob_increment = 0; |
| filter->rain_stat = 3; |
| break; |
| case 3: |
| filter->period = (filter->drops_per_frame_max - 1) * 16; |
| filter->drop_prob_increment = -1; |
| filter->rain_stat = 4; |
| break; |
| case 4: |
| filter->period = (fastrand () >> 24) + 60; |
| filter->drop_prob_increment = -(filter->drop_prob / filter->period); |
| filter->rain_stat = 5; |
| break; |
| case 5: |
| default: |
| filter->period = (fastrand () >> 23) + 500; |
| filter->drop_prob = 0; |
| filter->rain_stat = 0; |
| break; |
| } |
| } |
| switch (filter->rain_stat) { |
| default: |
| case 0: |
| break; |
| case 1: |
| case 5: |
| if ((fastrand () >> 8) < filter->drop_prob) { |
| drop (filter->drop_power, filter->map1, filter->map2, filter->map_w, |
| filter->map_h); |
| } |
| filter->drop_prob += filter->drop_prob_increment; |
| break; |
| case 2: |
| case 3: |
| case 4: |
| for (i = filter->drops_per_frame / 16; i > 0; i--) { |
| drop (filter->drop_power, filter->map1, filter->map2, filter->map_w, |
| filter->map_h); |
| } |
| filter->drops_per_frame += filter->drop_prob_increment; |
| break; |
| } |
| filter->period--; |
| } |
| |
| static GstFlowReturn |
| gst_rippletv_transform_frame (GstVideoFilter * vfilter, |
| GstVideoFrame * in_frame, GstVideoFrame * out_frame) |
| { |
| GstRippleTV *filter = GST_RIPPLETV (vfilter); |
| guint32 *src, *dest; |
| gint x, y, i; |
| gint dx, dy, o_dx; |
| gint h, v; |
| gint m_w, m_h, v_w, v_h; |
| gint *p, *q, *r; |
| gint8 *vp; |
| GstClockTime timestamp, stream_time; |
| |
| timestamp = GST_BUFFER_TIMESTAMP (in_frame->buffer); |
| stream_time = |
| gst_segment_to_stream_time (&GST_BASE_TRANSFORM (vfilter)->segment, |
| GST_FORMAT_TIME, timestamp); |
| |
| GST_DEBUG_OBJECT (filter, "sync to %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (timestamp)); |
| |
| if (GST_CLOCK_TIME_IS_VALID (stream_time)) |
| gst_object_sync_values (GST_OBJECT (filter), stream_time); |
| |
| src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0); |
| dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0); |
| |
| GST_OBJECT_LOCK (filter); |
| /* impact from the motion or rain drop */ |
| if (filter->mode) |
| raindrop (filter); |
| else |
| motiondetect (filter, src); |
| |
| m_w = filter->map_w; |
| m_h = filter->map_h; |
| v_w = GST_VIDEO_FRAME_WIDTH (in_frame); |
| v_h = GST_VIDEO_FRAME_HEIGHT (in_frame); |
| |
| /* simulate surface wave */ |
| |
| /* This function is called only 30 times per second. To increase a speed |
| * of wave, iterates this loop several times. */ |
| for (i = loopnum; i > 0; i--) { |
| /* wave simulation */ |
| p = filter->map1 + m_w + 1; |
| q = filter->map2 + m_w + 1; |
| r = filter->map3 + m_w + 1; |
| for (y = m_h - 2; y > 0; y--) { |
| for (x = m_w - 2; x > 0; x--) { |
| h = *(p - m_w - 1) + *(p - m_w + 1) + *(p + m_w - 1) + *(p + m_w + 1) |
| + *(p - m_w) + *(p - 1) + *(p + 1) + *(p + m_w) - (*p) * 9; |
| h = h >> 3; |
| v = *p - *q; |
| v += h - (v >> decay); |
| *r = v + *p; |
| p++; |
| q++; |
| r++; |
| } |
| p += 2; |
| q += 2; |
| r += 2; |
| } |
| |
| /* low pass filter */ |
| p = filter->map3 + m_w + 1; |
| q = filter->map2 + m_w + 1; |
| for (y = m_h - 2; y > 0; y--) { |
| for (x = m_w - 2; x > 0; x--) { |
| h = *(p - m_w) + *(p - 1) + *(p + 1) + *(p + m_w) + (*p) * 60; |
| *q = h >> 6; |
| p++; |
| q++; |
| } |
| p += 2; |
| q += 2; |
| } |
| |
| p = filter->map1; |
| filter->map1 = filter->map2; |
| filter->map2 = p; |
| } |
| |
| vp = filter->vtable; |
| p = filter->map1; |
| for (y = m_h - 1; y > 0; y--) { |
| for (x = m_w - 1; x > 0; x--) { |
| /* difference of the height between two voxel. They are twiced to |
| * emphasise the wave. */ |
| vp[0] = sqrtable[((p[0] - p[1]) >> (point - 1)) & 0xff]; |
| vp[1] = sqrtable[((p[0] - p[m_w]) >> (point - 1)) & 0xff]; |
| p++; |
| vp += 2; |
| } |
| p++; |
| vp += 2; |
| } |
| |
| vp = filter->vtable; |
| |
| /* draw refracted image. The vector table is stretched. */ |
| for (y = 0; y < v_h; y += 2) { |
| for (x = 0; x < v_w; x += 2) { |
| h = (gint) vp[0]; |
| v = (gint) vp[1]; |
| dx = x + h; |
| dy = y + v; |
| dx = CLAMP (dx, 0, (v_w - 2)); |
| dy = CLAMP (dy, 0, (v_h - 2)); |
| dest[0] = src[dy * v_w + dx]; |
| |
| o_dx = dx; |
| |
| dx = x + 1 + (h + (gint) vp[2]) / 2; |
| dx = CLAMP (dx, 0, (v_w - 2)); |
| dest[1] = src[dy * v_w + dx]; |
| |
| dy = y + 1 + (v + (gint) vp[m_w * 2 + 1]) / 2; |
| dy = CLAMP (dy, 0, (v_h - 2)); |
| dest[v_w] = src[dy * v_w + o_dx]; |
| |
| dest[v_w + 1] = src[dy * v_w + dx]; |
| dest += 2; |
| vp += 2; |
| } |
| dest += v_w; |
| vp += 2; |
| } |
| GST_OBJECT_UNLOCK (filter); |
| |
| return GST_FLOW_OK; |
| } |
| |
| static gboolean |
| gst_rippletv_set_info (GstVideoFilter * vfilter, GstCaps * incaps, |
| GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info) |
| { |
| GstRippleTV *filter = GST_RIPPLETV (vfilter); |
| gint width, height; |
| |
| width = GST_VIDEO_INFO_WIDTH (in_info); |
| height = GST_VIDEO_INFO_HEIGHT (in_info); |
| |
| GST_OBJECT_LOCK (filter); |
| filter->map_h = height / 2 + 1; |
| filter->map_w = width / 2 + 1; |
| |
| /* we over allocate the buffers, as the render code does not handle clipping |
| * very well */ |
| g_free (filter->map); |
| filter->map = g_new0 (gint, (1 + filter->map_h) * filter->map_w * 3); |
| |
| filter->map1 = filter->map; |
| filter->map2 = filter->map + filter->map_w * filter->map_h; |
| filter->map3 = filter->map + filter->map_w * filter->map_h * 2; |
| |
| g_free (filter->vtable); |
| filter->vtable = g_new0 (gint8, (1 + filter->map_h) * filter->map_w * 2); |
| |
| g_free (filter->background); |
| filter->background = g_new0 (gint16, width * (height + 1)); |
| |
| g_free (filter->diff); |
| filter->diff = g_new0 (guint8, width * (height + 1)); |
| GST_OBJECT_UNLOCK (filter); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_rippletv_start (GstBaseTransform * trans) |
| { |
| GstRippleTV *filter = GST_RIPPLETV (trans); |
| |
| filter->bg_is_set = FALSE; |
| |
| filter->period = 0; |
| filter->rain_stat = 0; |
| filter->drop_prob = 0; |
| filter->drop_prob_increment = 0; |
| filter->drops_per_frame_max = 0; |
| filter->drops_per_frame = 0; |
| filter->drop_power = 0; |
| |
| return TRUE; |
| } |
| |
| static void |
| gst_rippletv_finalize (GObject * object) |
| { |
| GstRippleTV *filter = GST_RIPPLETV (object); |
| |
| g_free (filter->map); |
| filter->map = NULL; |
| |
| g_free (filter->vtable); |
| filter->vtable = NULL; |
| |
| g_free (filter->background); |
| filter->background = NULL; |
| |
| g_free (filter->diff); |
| filter->diff = NULL; |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_rippletv_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstRippleTV *filter = GST_RIPPLETV (object); |
| |
| GST_OBJECT_LOCK (filter); |
| switch (prop_id) { |
| case PROP_RESET:{ |
| memset (filter->map, 0, |
| filter->map_h * filter->map_w * 2 * sizeof (gint)); |
| break; |
| } |
| case PROP_MODE: |
| filter->mode = g_value_get_enum (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| GST_OBJECT_UNLOCK (filter); |
| } |
| |
| static void |
| gst_rippletv_get_property (GObject * object, guint prop_id, GValue * value, |
| GParamSpec * pspec) |
| { |
| GstRippleTV *filter = GST_RIPPLETV (object); |
| |
| switch (prop_id) { |
| case PROP_MODE: |
| g_value_set_enum (value, filter->mode); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_rippletv_class_init (GstRippleTVClass * klass) |
| { |
| GObjectClass *gobject_class = (GObjectClass *) klass; |
| GstElementClass *gstelement_class = (GstElementClass *) klass; |
| GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass; |
| GstVideoFilterClass *vfilter_class = (GstVideoFilterClass *) klass; |
| |
| gobject_class->set_property = gst_rippletv_set_property; |
| gobject_class->get_property = gst_rippletv_get_property; |
| |
| gobject_class->finalize = gst_rippletv_finalize; |
| |
| g_object_class_install_property (gobject_class, PROP_RESET, |
| g_param_spec_boolean ("reset", "Reset", |
| "Reset all current ripples", FALSE, |
| G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE)); |
| |
| g_object_class_install_property (gobject_class, PROP_MODE, |
| g_param_spec_enum ("mode", "Mode", |
| "Mode", GST_TYPE_RIPPLETV_MODE, DEFAULT_MODE, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE)); |
| |
| gst_element_class_set_static_metadata (gstelement_class, "RippleTV effect", |
| "Filter/Effect/Video", |
| "RippleTV does ripple mark effect on the video input", |
| "FUKUCHI, Kentarou <fukuchi@users.sourceforge.net>, " |
| "Sebastian Dröge <sebastian.droege@collabora.co.uk>"); |
| |
| gst_element_class_add_static_pad_template (gstelement_class, |
| &gst_rippletv_sink_template); |
| gst_element_class_add_static_pad_template (gstelement_class, |
| &gst_rippletv_src_template); |
| |
| trans_class->start = GST_DEBUG_FUNCPTR (gst_rippletv_start); |
| |
| vfilter_class->set_info = GST_DEBUG_FUNCPTR (gst_rippletv_set_info); |
| vfilter_class->transform_frame = |
| GST_DEBUG_FUNCPTR (gst_rippletv_transform_frame); |
| |
| setTable (); |
| } |
| |
| static void |
| gst_rippletv_init (GstRippleTV * filter) |
| { |
| filter->mode = DEFAULT_MODE; |
| |
| /* FIXME: remove this when memory corruption after resizes are fixed */ |
| gst_pad_use_fixed_caps (GST_BASE_TRANSFORM_SRC_PAD (filter)); |
| gst_pad_use_fixed_caps (GST_BASE_TRANSFORM_SINK_PAD (filter)); |
| } |