1 /* GStreamer
2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 * Copyright (C) <2003> David Schleef <ds@schleef.org>
4 * Copyright (C) <2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
5 *
6 * EffecTV - Realtime Digital Video Effector
7 * Copyright (C) 2001-2002 FUKUCHI Kentarou
8 *
9 * AgingTV - film-aging effect.
10 *
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Library General Public
13 * License as published by the Free Software Foundation; either
14 * version 2 of the License, or (at your option) any later version.
15 *
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Library General Public License for more details.
20 *
21 * You should have received a copy of the GNU Library General Public
22 * License along with this library; if not, write to the
23 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
24 * Boston, MA 02110-1301, USA.
25 */
26
27 /**
28 * SECTION:element-agingtv
29 * @title: agingtv
30 *
31 * AgingTV ages a video stream in realtime, changes the colors and adds
32 * scratches and dust.
33 *
34 * ## Example launch line
35 * |[
36 * gst-launch-1.0 -v videotestsrc ! agingtv ! videoconvert ! autovideosink
37 * ]| This pipeline shows the effect of agingtv on a test stream.
38 *
39 */
40
41 #ifdef HAVE_CONFIG_H
42 #include "config.h"
43 #endif
44
45 #include <string.h>
46 #include <math.h>
47
48 #include "gstaging.h"
49 #include "gsteffectv.h"
50
51 static const gint dx[8] = { 1, 1, 0, -1, -1, -1, 0, 1 };
52 static const gint dy[8] = { 0, -1, -1, -1, 0, 1, 1, 1 };
53
54 enum
55 {
56 PROP_0 = 0,
57 PROP_SCRATCH_LINES,
58 PROP_COLOR_AGING,
59 PROP_PITS,
60 PROP_DUSTS
61 };
62
63 #define DEFAULT_SCRATCH_LINES 7
64 #define DEFAULT_COLOR_AGING TRUE
65 #define DEFAULT_PITS TRUE
66 #define DEFAULT_DUSTS TRUE
67
68 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
69 #define CAPS_STR GST_VIDEO_CAPS_MAKE ("{ BGRx, RGBx }")
70 #else
71 #define CAPS_STR GST_VIDEO_CAPS_MAKE ("{ xRGB, xBGR }")
72 #endif
73
74 static GstStaticPadTemplate gst_agingtv_src_template =
75 GST_STATIC_PAD_TEMPLATE ("src",
76 GST_PAD_SRC,
77 GST_PAD_ALWAYS,
78 GST_STATIC_CAPS (CAPS_STR)
79 );
80
81 static GstStaticPadTemplate gst_agingtv_sink_template =
82 GST_STATIC_PAD_TEMPLATE ("sink",
83 GST_PAD_SINK,
84 GST_PAD_ALWAYS,
85 GST_STATIC_CAPS (CAPS_STR)
86 );
87
88 G_DEFINE_TYPE (GstAgingTV, gst_agingtv, GST_TYPE_VIDEO_FILTER);
89 GST_ELEMENT_REGISTER_DEFINE (agingtv, "agingtv", GST_RANK_NONE,
90 GST_TYPE_AGINGTV);
91
92 static void
coloraging(guint32 * src,guint32 * dest,gint video_area,gint * c)93 coloraging (guint32 * src, guint32 * dest, gint video_area, gint * c)
94 {
95 guint32 a, b;
96 gint i;
97 gint c_tmp = *c;
98
99 c_tmp -= (gint) (fastrand ()) >> 28;
100 if (c_tmp < 0)
101 c_tmp = 0;
102 if (c_tmp > 0x18)
103 c_tmp = 0x18;
104
105 for (i = 0; i < video_area; i++) {
106 a = *src++;
107 b = (a & 0xfcfcfc) >> 2;
108 *dest++ =
109 a - b + (c_tmp | (c_tmp << 8) | (c_tmp << 16)) +
110 ((fastrand () >> 8) & 0x101010);
111 }
112 *c = c_tmp;
113 }
114
115
116 static void
scratching(scratch * scratches,gint scratch_lines,guint32 * dest,gint width,gint height)117 scratching (scratch * scratches, gint scratch_lines, guint32 * dest, gint width,
118 gint height)
119 {
120 gint i, y, y1, y2;
121 guint32 *p, a, b;
122 scratch *scratch;
123
124 for (i = 0; i < scratch_lines; i++) {
125 scratch = &scratches[i];
126
127 if (scratch->life) {
128 scratch->x = scratch->x + scratch->dx;
129
130 if (scratch->x < 0 || scratch->x > width * 256) {
131 scratch->life = 0;
132 break;
133 }
134 p = dest + (scratch->x >> 8);
135 if (scratch->init) {
136 y1 = scratch->init;
137 scratch->init = 0;
138 } else {
139 y1 = 0;
140 }
141 scratch->life--;
142 if (scratch->life) {
143 y2 = height;
144 } else {
145 y2 = fastrand () % height;
146 }
147 for (y = y1; y < y2; y++) {
148 a = *p & 0xfefeff;
149 a += 0x202020;
150 b = a & 0x1010100;
151 *p = a | (b - (b >> 8));
152 p += width;
153 }
154 } else {
155 if ((fastrand () & 0xf0000000) == 0) {
156 scratch->life = 2 + (fastrand () >> 27);
157 scratch->x = fastrand () % (width * 256);
158 scratch->dx = ((int) fastrand ()) >> 23;
159 scratch->init = (fastrand () % (height - 1)) + 1;
160 }
161 }
162 }
163 }
164
165 static void
dusts(guint32 * dest,gint width,gint height,gint * dust_interval,gint area_scale)166 dusts (guint32 * dest, gint width, gint height, gint * dust_interval,
167 gint area_scale)
168 {
169 gint i, j;
170 gint dnum;
171 gint d, len;
172 guint x, y;
173
174 if (*dust_interval == 0) {
175 if ((fastrand () & 0xf0000000) == 0) {
176 *dust_interval = fastrand () >> 29;
177 }
178 return;
179 }
180 dnum = area_scale * 4 + (fastrand () >> 27);
181
182 for (i = 0; i < dnum; i++) {
183 x = fastrand () % width;
184 y = fastrand () % height;
185 d = fastrand () >> 29;
186 len = fastrand () % area_scale + 5;
187 for (j = 0; j < len; j++) {
188 dest[y * width + x] = 0x101010;
189 y += dy[d];
190 x += dx[d];
191
192 if (y >= height || x >= width)
193 break;
194
195 d = (d + fastrand () % 3 - 1) & 7;
196 }
197 }
198 *dust_interval = *dust_interval - 1;
199 }
200
201 static void
pits(guint32 * dest,gint width,gint height,gint area_scale,gint * pits_interval)202 pits (guint32 * dest, gint width, gint height, gint area_scale,
203 gint * pits_interval)
204 {
205 gint i, j;
206 gint pnum, size, pnumscale;
207 guint x, y;
208
209 pnumscale = area_scale * 2;
210 if (*pits_interval) {
211 pnum = pnumscale + (fastrand () % pnumscale);
212
213 *pits_interval = *pits_interval - 1;
214 } else {
215 pnum = fastrand () % pnumscale;
216
217 if ((fastrand () & 0xf8000000) == 0) {
218 *pits_interval = (fastrand () >> 28) + 20;
219 }
220 }
221 for (i = 0; i < pnum; i++) {
222 x = fastrand () % (width - 1);
223 y = fastrand () % (height - 1);
224
225 size = fastrand () >> 28;
226
227 for (j = 0; j < size; j++) {
228 x = x + fastrand () % 3 - 1;
229 y = y + fastrand () % 3 - 1;
230
231 if (y >= height || x >= width)
232 break;
233
234 dest[y * width + x] = 0xc0c0c0;
235 }
236 }
237 }
238
239 static void
gst_agingtv_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)240 gst_agingtv_get_property (GObject * object, guint prop_id,
241 GValue * value, GParamSpec * pspec)
242 {
243 GstAgingTV *agingtv = GST_AGINGTV (object);
244
245 GST_OBJECT_LOCK (agingtv);
246 switch (prop_id) {
247 case PROP_SCRATCH_LINES:
248 g_value_set_uint (value, agingtv->scratch_lines);
249 break;
250 case PROP_COLOR_AGING:
251 g_value_set_boolean (value, agingtv->color_aging);
252 break;
253 case PROP_PITS:
254 g_value_set_boolean (value, agingtv->pits);
255 break;
256 case PROP_DUSTS:
257 g_value_set_boolean (value, agingtv->dusts);
258 break;
259 default:
260 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
261 }
262 GST_OBJECT_UNLOCK (agingtv);
263 }
264
265 static void
gst_agingtv_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)266 gst_agingtv_set_property (GObject * object, guint prop_id,
267 const GValue * value, GParamSpec * pspec)
268 {
269 GstAgingTV *agingtv = GST_AGINGTV (object);
270
271 switch (prop_id) {
272 case PROP_SCRATCH_LINES:
273 agingtv->scratch_lines = g_value_get_uint (value);
274 break;
275 case PROP_COLOR_AGING:
276 agingtv->color_aging = g_value_get_boolean (value);
277 break;
278 case PROP_PITS:
279 agingtv->pits = g_value_get_boolean (value);
280 break;
281 case PROP_DUSTS:
282 agingtv->dusts = g_value_get_boolean (value);
283 break;
284 default:
285 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
286 }
287 }
288
289 static gboolean
gst_agingtv_start(GstBaseTransform * trans)290 gst_agingtv_start (GstBaseTransform * trans)
291 {
292 GstAgingTV *agingtv = GST_AGINGTV (trans);
293
294 agingtv->coloraging_state = 0x18;
295 agingtv->dust_interval = 0;
296 agingtv->pits_interval = 0;
297
298 memset (agingtv->scratches, 0, sizeof (agingtv->scratches));
299
300 return TRUE;
301 }
302
303 static GstFlowReturn
gst_agingtv_transform_frame(GstVideoFilter * filter,GstVideoFrame * in_frame,GstVideoFrame * out_frame)304 gst_agingtv_transform_frame (GstVideoFilter * filter, GstVideoFrame * in_frame,
305 GstVideoFrame * out_frame)
306 {
307 GstAgingTV *agingtv = GST_AGINGTV (filter);
308 gint area_scale;
309 GstClockTime timestamp, stream_time;
310 gint width, height, stride, video_size;
311 guint32 *src, *dest;
312
313 timestamp = GST_BUFFER_TIMESTAMP (in_frame->buffer);
314 stream_time =
315 gst_segment_to_stream_time (&GST_BASE_TRANSFORM (filter)->segment,
316 GST_FORMAT_TIME, timestamp);
317
318 GST_DEBUG_OBJECT (agingtv, "sync to %" GST_TIME_FORMAT,
319 GST_TIME_ARGS (timestamp));
320
321 if (GST_CLOCK_TIME_IS_VALID (stream_time))
322 gst_object_sync_values (GST_OBJECT (agingtv), stream_time);
323
324 width = GST_VIDEO_FRAME_WIDTH (in_frame);
325 height = GST_VIDEO_FRAME_HEIGHT (in_frame);
326 stride = GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 0);
327 video_size = stride * height / 4;
328
329 src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
330 dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);
331
332 area_scale = width * height / 64 / 480;
333 if (area_scale <= 0)
334 area_scale = 1;
335
336 if (agingtv->color_aging)
337 coloraging (src, dest, video_size, &agingtv->coloraging_state);
338 else
339 memcpy (dest, src, stride * height);
340
341 scratching (agingtv->scratches, agingtv->scratch_lines, dest, width, height);
342 if (agingtv->pits)
343 pits (dest, width, height, area_scale, &agingtv->pits_interval);
344 if (area_scale > 1 && agingtv->dusts)
345 dusts (dest, width, height, &agingtv->dust_interval, area_scale);
346
347 return GST_FLOW_OK;
348 }
349
350 static void
gst_agingtv_class_init(GstAgingTVClass * klass)351 gst_agingtv_class_init (GstAgingTVClass * klass)
352 {
353 GObjectClass *gobject_class = (GObjectClass *) klass;
354 GstElementClass *gstelement_class = (GstElementClass *) klass;
355 GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass;
356 GstVideoFilterClass *vfilter_class = (GstVideoFilterClass *) klass;
357
358 gobject_class->set_property = gst_agingtv_set_property;
359 gobject_class->get_property = gst_agingtv_get_property;
360
361 g_object_class_install_property (gobject_class, PROP_SCRATCH_LINES,
362 g_param_spec_uint ("scratch-lines", "Scratch Lines",
363 "Number of scratch lines", 0, SCRATCH_MAX, DEFAULT_SCRATCH_LINES,
364 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
365
366 g_object_class_install_property (gobject_class, PROP_COLOR_AGING,
367 g_param_spec_boolean ("color-aging", "Color Aging",
368 "Color Aging", DEFAULT_COLOR_AGING,
369 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
370
371 g_object_class_install_property (gobject_class, PROP_PITS,
372 g_param_spec_boolean ("pits", "Pits",
373 "Pits", DEFAULT_PITS,
374 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
375
376 g_object_class_install_property (gobject_class, PROP_DUSTS,
377 g_param_spec_boolean ("dusts", "Dusts",
378 "Dusts", DEFAULT_DUSTS,
379 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
380
381 gst_element_class_set_static_metadata (gstelement_class, "AgingTV effect",
382 "Filter/Effect/Video",
383 "AgingTV adds age to video input using scratches and dust",
384 "Sam Lantinga <slouken@devolution.com>");
385
386 gst_element_class_add_static_pad_template (gstelement_class,
387 &gst_agingtv_sink_template);
388 gst_element_class_add_static_pad_template (gstelement_class,
389 &gst_agingtv_src_template);
390
391 trans_class->start = GST_DEBUG_FUNCPTR (gst_agingtv_start);
392
393 vfilter_class->transform_frame =
394 GST_DEBUG_FUNCPTR (gst_agingtv_transform_frame);
395 }
396
397 static void
gst_agingtv_init(GstAgingTV * agingtv)398 gst_agingtv_init (GstAgingTV * agingtv)
399 {
400 agingtv->scratch_lines = DEFAULT_SCRATCH_LINES;
401 agingtv->color_aging = DEFAULT_COLOR_AGING;
402 agingtv->pits = DEFAULT_PITS;
403 agingtv->dusts = DEFAULT_DUSTS;
404 }
405