[MOVED FROM BAD 08/29] shapewipe: Fix some issues that were exposed by the new unit...
[platform/upstream/gstreamer.git] / gst / shapewipe / gstshapewipe.c
1 /* GStreamer
2  * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /**
21  * SECTION:element-shapewipe
22  *
23  * The shapewipe element provides custom transitions on video streams
24  * based on a grayscale bitmap. The state of the transition can be
25  * controlled by the position property and an optional blended border
26  * can be added by the border property.
27  *
28  * Transition bitmaps can be downloaded from the
29  * <ulink url="http://cinelerra.org/transitions.php">Cinelerra transition</ulink>
30  * page.
31  *
32  * <refsect2>
33  * <title>Example launch line</title>
34  * |[
35  * gst-launch -v videotestsrc ! video/x-raw-yuv,width=640,height=480 ! shapewipe position=0.5 name=shape ! videomixer name=mixer ! ffmpegcolorspace ! autovideosink     filesrc location=mask.png ! typefind ! decodebin2 ! ffmpegcolorspace ! videoscale ! queue ! shape.mask_sink    videotestsrc pattern=snow ! video/x-raw-yuv,width=640,height=480 ! queue ! mixer.
36  * ]| This pipeline adds the transition from mask.png with position 0.5 to an SMPTE test screen and snow.
37  * </refsect2>
38  */
39
40
41 #ifdef HAVE_CONFIG_H
42 #  include "config.h"
43 #endif
44
45 #include <string.h>
46
47 #include <gst/gst.h>
48 #include <gst/controller/gstcontroller.h>
49
50 #include "gstshapewipe.h"
51
52 static void gst_shape_wipe_finalize (GObject * object);
53 static void gst_shape_wipe_get_property (GObject * object, guint prop_id,
54     GValue * value, GParamSpec * pspec);
55 static void gst_shape_wipe_set_property (GObject * object, guint prop_id,
56     const GValue * value, GParamSpec * pspec);
57
58 static void gst_shape_wipe_reset (GstShapeWipe * self);
59
60 static GstStateChangeReturn gst_shape_wipe_change_state (GstElement * element,
61     GstStateChange transition);
62
63 static GstFlowReturn gst_shape_wipe_video_sink_chain (GstPad * pad,
64     GstBuffer * buffer);
65 static gboolean gst_shape_wipe_video_sink_event (GstPad * pad,
66     GstEvent * event);
67 static gboolean gst_shape_wipe_video_sink_setcaps (GstPad * pad,
68     GstCaps * caps);
69 static GstCaps *gst_shape_wipe_video_sink_getcaps (GstPad * pad);
70 static GstFlowReturn gst_shape_wipe_mask_sink_chain (GstPad * pad,
71     GstBuffer * buffer);
72 static gboolean gst_shape_wipe_mask_sink_event (GstPad * pad, GstEvent * event);
73 static gboolean gst_shape_wipe_mask_sink_setcaps (GstPad * pad, GstCaps * caps);
74 static GstCaps *gst_shape_wipe_mask_sink_getcaps (GstPad * pad);
75 static gboolean gst_shape_wipe_src_event (GstPad * pad, GstEvent * event);
76 static GstCaps *gst_shape_wipe_src_getcaps (GstPad * pad);
77
78 enum
79 {
80   PROP_0,
81   PROP_POSITION,
82   PROP_BORDER
83 };
84
85 static GstStaticPadTemplate video_sink_pad_template =
86 GST_STATIC_PAD_TEMPLATE ("video_sink",
87     GST_PAD_SINK,
88     GST_PAD_ALWAYS,
89     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV")));
90
91 static GstStaticPadTemplate mask_sink_pad_template =
92     GST_STATIC_PAD_TEMPLATE ("mask_sink",
93     GST_PAD_SINK,
94     GST_PAD_ALWAYS,
95     GST_STATIC_CAPS ("video/x-raw-gray, "
96         "bpp = 8, "
97         "depth = 8, "
98         "width = " GST_VIDEO_SIZE_RANGE ", "
99         "height = " GST_VIDEO_SIZE_RANGE ", " "framerate = 0/1 ; "
100         "video/x-raw-gray, " "bpp = 16, " "depth = 16, "
101         "endianness = BYTE_ORDER, " "width = " GST_VIDEO_SIZE_RANGE ", "
102         "height = " GST_VIDEO_SIZE_RANGE ", " "framerate = 0/1"));
103
104 static GstStaticPadTemplate src_pad_template =
105 GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
106     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV")));
107
108 GST_DEBUG_CATEGORY_STATIC (gst_shape_wipe_debug);
109 #define GST_CAT_DEFAULT gst_shape_wipe_debug
110
111 GST_BOILERPLATE (GstShapeWipe, gst_shape_wipe, GstElement, GST_TYPE_ELEMENT);
112
113 static void
114 gst_shape_wipe_base_init (gpointer g_class)
115 {
116   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
117
118   gst_element_class_set_details_simple (gstelement_class,
119       "Shape Wipe transition filter",
120       "Filter/Editor/Video",
121       "Adds a shape wipe transition to a video stream",
122       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
123
124   gst_element_class_add_pad_template (gstelement_class,
125       gst_static_pad_template_get (&video_sink_pad_template));
126   gst_element_class_add_pad_template (gstelement_class,
127       gst_static_pad_template_get (&mask_sink_pad_template));
128   gst_element_class_add_pad_template (gstelement_class,
129       gst_static_pad_template_get (&src_pad_template));
130 }
131
132 static void
133 gst_shape_wipe_class_init (GstShapeWipeClass * klass)
134 {
135   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
136   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
137
138   gobject_class->finalize = gst_shape_wipe_finalize;
139   gobject_class->set_property = gst_shape_wipe_set_property;
140   gobject_class->get_property = gst_shape_wipe_get_property;
141
142   g_object_class_install_property (gobject_class, PROP_POSITION,
143       g_param_spec_float ("position", "Position", "Position of the mask",
144           0.0, 1.0, 0.0,
145           G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
146   g_object_class_install_property (gobject_class, PROP_BORDER,
147       g_param_spec_float ("border", "Border", "Border of the mask",
148           0.0, 1.0, 0.0,
149           G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
150
151   gstelement_class->change_state =
152       GST_DEBUG_FUNCPTR (gst_shape_wipe_change_state);
153 }
154
155 static void
156 gst_shape_wipe_init (GstShapeWipe * self, GstShapeWipeClass * g_class)
157 {
158   self->video_sinkpad =
159       gst_pad_new_from_static_template (&video_sink_pad_template, "video_sink");
160   gst_pad_set_chain_function (self->video_sinkpad,
161       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_chain));
162   gst_pad_set_event_function (self->video_sinkpad,
163       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_event));
164   gst_pad_set_setcaps_function (self->video_sinkpad,
165       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_setcaps));
166   gst_pad_set_getcaps_function (self->video_sinkpad,
167       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_getcaps));
168   gst_element_add_pad (GST_ELEMENT (self), self->video_sinkpad);
169
170   self->mask_sinkpad =
171       gst_pad_new_from_static_template (&mask_sink_pad_template, "mask_sink");
172   gst_pad_set_chain_function (self->mask_sinkpad,
173       GST_DEBUG_FUNCPTR (gst_shape_wipe_mask_sink_chain));
174   gst_pad_set_event_function (self->mask_sinkpad,
175       GST_DEBUG_FUNCPTR (gst_shape_wipe_mask_sink_event));
176   gst_pad_set_setcaps_function (self->mask_sinkpad,
177       GST_DEBUG_FUNCPTR (gst_shape_wipe_mask_sink_setcaps));
178   gst_pad_set_getcaps_function (self->mask_sinkpad,
179       GST_DEBUG_FUNCPTR (gst_shape_wipe_mask_sink_getcaps));
180   gst_element_add_pad (GST_ELEMENT (self), self->mask_sinkpad);
181
182   self->srcpad = gst_pad_new_from_static_template (&src_pad_template, "src");
183   gst_pad_set_event_function (self->srcpad,
184       GST_DEBUG_FUNCPTR (gst_shape_wipe_src_event));
185   gst_pad_set_getcaps_function (self->srcpad,
186       GST_DEBUG_FUNCPTR (gst_shape_wipe_src_getcaps));
187   gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
188
189   self->mask_mutex = g_mutex_new ();
190   self->mask_cond = g_cond_new ();
191
192   gst_shape_wipe_reset (self);
193 }
194
195 static void
196 gst_shape_wipe_get_property (GObject * object, guint prop_id,
197     GValue * value, GParamSpec * pspec)
198 {
199   GstShapeWipe *self = GST_SHAPE_WIPE (object);
200
201   switch (prop_id) {
202     case PROP_POSITION:
203       g_value_set_float (value, self->mask_position);
204       break;
205     case PROP_BORDER:
206       g_value_set_float (value, self->mask_border);
207       break;
208     default:
209       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
210       break;
211   }
212 }
213
214 static void
215 gst_shape_wipe_set_property (GObject * object, guint prop_id,
216     const GValue * value, GParamSpec * pspec)
217 {
218   GstShapeWipe *self = GST_SHAPE_WIPE (object);
219
220   switch (prop_id) {
221     case PROP_POSITION:
222       self->mask_position = g_value_get_float (value);
223       break;
224     case PROP_BORDER:
225       self->mask_border = g_value_get_float (value);
226       break;
227     default:
228       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
229       break;
230   }
231 }
232
233 static void
234 gst_shape_wipe_finalize (GObject * object)
235 {
236   GstShapeWipe *self = GST_SHAPE_WIPE (object);
237
238   gst_shape_wipe_reset (self);
239
240   if (self->mask_cond)
241     g_cond_free (self->mask_cond);
242   self->mask_cond = NULL;
243
244   if (self->mask_mutex)
245     g_mutex_free (self->mask_mutex);
246   self->mask_mutex = NULL;
247
248   G_OBJECT_CLASS (parent_class)->finalize (object);
249 }
250
251 static void
252 gst_shape_wipe_reset (GstShapeWipe * self)
253 {
254   if (self->mask)
255     gst_buffer_unref (self->mask);
256   self->mask = NULL;
257
258   g_cond_signal (self->mask_cond);
259
260   self->width = self->height = 0;
261   self->mask_position = 0.0;
262   self->mask_border = 0.0;
263   self->mask_bpp = 0;
264
265   gst_segment_init (&self->segment, GST_FORMAT_TIME);
266 }
267
268 static gboolean
269 gst_shape_wipe_video_sink_setcaps (GstPad * pad, GstCaps * caps)
270 {
271   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
272   gboolean ret = TRUE;
273   GstStructure *s;
274   gint width, height;
275
276   GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps);
277
278   s = gst_caps_get_structure (caps, 0);
279
280   if (!gst_structure_get_int (s, "width", &width) ||
281       !gst_structure_get_int (s, "height", &height)) {
282     ret = FALSE;
283     goto done;
284   }
285
286   if (self->width != width || self->height != height) {
287     g_mutex_lock (self->mask_mutex);
288     self->width = width;
289     self->height = height;
290
291     if (self->mask)
292       gst_buffer_unref (self->mask);
293     self->mask = NULL;
294     g_mutex_unlock (self->mask_mutex);
295   }
296
297   ret = gst_pad_set_caps (self->srcpad, caps);
298
299 done:
300   gst_object_unref (self);
301
302   return ret;
303 }
304
305 static GstCaps *
306 gst_shape_wipe_video_sink_getcaps (GstPad * pad)
307 {
308   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
309   GstCaps *ret, *tmp;
310
311   if (GST_PAD_CAPS (pad))
312     return gst_caps_copy (GST_PAD_CAPS (pad));
313
314   tmp = gst_pad_peer_get_caps (self->srcpad);
315   if (tmp) {
316     ret = gst_caps_intersect (tmp, gst_pad_get_pad_template_caps (pad));
317     gst_caps_unref (tmp);
318   } else {
319     ret = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
320   }
321
322   tmp = gst_pad_peer_get_caps (pad);
323   if (tmp) {
324     GstCaps *intersection;
325
326     intersection = gst_caps_intersect (tmp, ret);
327     gst_caps_unref (tmp);
328     gst_caps_unref (ret);
329     ret = intersection;
330   }
331
332   if (self->height && self->width) {
333     guint i, n;
334
335     n = gst_caps_get_size (ret);
336     for (i = 0; i < n; i++) {
337       GstStructure *s = gst_caps_get_structure (ret, i);
338
339       gst_structure_set (s, "width", G_TYPE_INT, self->width, "height",
340           G_TYPE_INT, self->height, NULL);
341     }
342   }
343
344   tmp = gst_pad_peer_get_caps (self->mask_sinkpad);
345   if (tmp) {
346     GstCaps *intersection, *tmp2;
347     guint i, n;
348
349     tmp = gst_caps_make_writable (tmp);
350
351     tmp2 = gst_caps_copy (gst_pad_get_pad_template_caps (self->mask_sinkpad));
352
353     intersection = gst_caps_intersect (tmp, tmp2);
354     gst_caps_unref (tmp);
355     gst_caps_unref (tmp2);
356     tmp = intersection;
357
358     n = gst_caps_get_size (tmp);
359
360     for (i = 0; i < n; i++) {
361       GstStructure *s = gst_caps_get_structure (tmp, i);
362
363       gst_structure_remove_fields (s, "bpp", "depth", "endianness", "framerate",
364           NULL);
365       gst_structure_set_name (s, "video/x-raw-yuv");
366     }
367
368     intersection = gst_caps_intersect (tmp, ret);
369     gst_caps_unref (tmp);
370     gst_caps_unref (ret);
371     ret = intersection;
372   }
373
374   gst_object_unref (self);
375
376   GST_DEBUG_OBJECT (pad, "Returning caps: %" GST_PTR_FORMAT, ret);
377
378   return ret;
379 }
380
381 static gboolean
382 gst_shape_wipe_mask_sink_setcaps (GstPad * pad, GstCaps * caps)
383 {
384   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
385   gboolean ret = TRUE;
386   GstStructure *s;
387   gint width, height, bpp;
388
389   GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps);
390
391   s = gst_caps_get_structure (caps, 0);
392
393   if (!gst_structure_get_int (s, "width", &width) ||
394       !gst_structure_get_int (s, "height", &height) ||
395       !gst_structure_get_int (s, "bpp", &bpp)) {
396     ret = FALSE;
397     goto done;
398   }
399
400   if ((self->width != width || self->height != height) &&
401       self->width > 0 && self->height > 0) {
402     GST_ERROR_OBJECT (pad, "Mask caps must have the same width/height "
403         "as the video caps");
404     ret = FALSE;
405     goto done;
406   } else {
407     self->width = width;
408     self->height = height;
409   }
410
411   self->mask_bpp = bpp;
412
413 done:
414   gst_object_unref (self);
415
416   return ret;
417 }
418
419 static GstCaps *
420 gst_shape_wipe_mask_sink_getcaps (GstPad * pad)
421 {
422   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
423   GstCaps *ret, *tmp;
424   guint i, n;
425
426   if (GST_PAD_CAPS (pad))
427     return gst_caps_copy (GST_PAD_CAPS (pad));
428
429   tmp = gst_pad_peer_get_caps (self->video_sinkpad);
430   if (tmp) {
431     ret =
432         gst_caps_intersect (tmp,
433         gst_pad_get_pad_template_caps (self->video_sinkpad));
434     gst_caps_unref (tmp);
435   } else {
436     ret = gst_caps_copy (gst_pad_get_pad_template_caps (self->video_sinkpad));
437   }
438
439   tmp = gst_pad_peer_get_caps (self->srcpad);
440   if (tmp) {
441     GstCaps *intersection;
442
443     intersection = gst_caps_intersect (ret, tmp);
444     gst_caps_unref (ret);
445     gst_caps_unref (tmp);
446     ret = intersection;
447   }
448
449   n = gst_caps_get_size (ret);
450   tmp = gst_caps_new_empty ();
451   for (i = 0; i < n; i++) {
452     GstStructure *s = gst_caps_get_structure (ret, i);
453     GstStructure *t;
454
455     gst_structure_set_name (s, "video/x-raw-gray");
456     gst_structure_remove_fields (s, "format", "framerate", NULL);
457
458     if (self->width && self->height)
459       gst_structure_set (s, "width", G_TYPE_INT, self->width, "height",
460           G_TYPE_INT, self->height, NULL);
461
462     gst_structure_set (s, "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
463
464     t = gst_structure_copy (s);
465
466     gst_structure_set (s, "bpp", G_TYPE_INT, 16, "depth", G_TYPE_INT, 16,
467         "endianness", G_TYPE_INT, G_BYTE_ORDER, NULL);
468     gst_structure_set (t, "bpp", G_TYPE_INT, 8, "depth", G_TYPE_INT, 8, NULL);
469
470     gst_caps_append_structure (tmp, t);
471   }
472   gst_caps_merge (ret, tmp);
473
474   tmp = gst_pad_peer_get_caps (pad);
475   if (tmp) {
476     GstCaps *intersection;
477
478     intersection = gst_caps_intersect (tmp, ret);
479     gst_caps_unref (tmp);
480     gst_caps_unref (ret);
481     ret = intersection;
482   }
483
484   gst_object_unref (self);
485
486   GST_DEBUG_OBJECT (pad, "Returning caps: %" GST_PTR_FORMAT, ret);
487
488   return ret;
489 }
490
491 static GstCaps *
492 gst_shape_wipe_src_getcaps (GstPad * pad)
493 {
494   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
495   GstCaps *ret, *tmp;
496
497   if (GST_PAD_CAPS (pad))
498     return gst_caps_copy (GST_PAD_CAPS (pad));
499   else if (GST_PAD_CAPS (self->video_sinkpad))
500     return gst_caps_copy (GST_PAD_CAPS (self->video_sinkpad));
501
502   tmp = gst_pad_peer_get_caps (self->video_sinkpad);
503   if (tmp) {
504     ret =
505         gst_caps_intersect (tmp,
506         gst_pad_get_pad_template_caps (self->video_sinkpad));
507     gst_caps_unref (tmp);
508   } else {
509     ret = gst_caps_copy (gst_pad_get_pad_template_caps (self->video_sinkpad));
510   }
511
512   tmp = gst_pad_peer_get_caps (pad);
513   if (tmp) {
514     GstCaps *intersection;
515
516     intersection = gst_caps_intersect (tmp, ret);
517     gst_caps_unref (tmp);
518     gst_caps_unref (ret);
519     ret = intersection;
520   }
521
522   if (self->height && self->width) {
523     guint i, n;
524
525     n = gst_caps_get_size (ret);
526     for (i = 0; i < n; i++) {
527       GstStructure *s = gst_caps_get_structure (ret, i);
528
529       gst_structure_set (s, "width", G_TYPE_INT, self->width, "height",
530           G_TYPE_INT, self->height, NULL);
531     }
532   }
533
534   tmp = gst_pad_peer_get_caps (self->mask_sinkpad);
535   if (tmp) {
536     GstCaps *intersection, *tmp2;
537     guint i, n;
538
539     tmp = gst_caps_make_writable (tmp);
540     tmp2 = gst_caps_copy (gst_pad_get_pad_template_caps (self->mask_sinkpad));
541
542     intersection = gst_caps_intersect (tmp, tmp2);
543     gst_caps_unref (tmp);
544     gst_caps_unref (tmp2);
545
546     tmp = intersection;
547     n = gst_caps_get_size (tmp);
548
549     for (i = 0; i < n; i++) {
550       GstStructure *s = gst_caps_get_structure (tmp, i);
551
552       gst_structure_remove_fields (s, "bpp", "depth", "endianness", "framerate",
553           NULL);
554       gst_structure_set_name (s, "video/x-raw-yuv");
555     }
556
557     intersection = gst_caps_intersect (tmp, ret);
558     gst_caps_unref (tmp);
559     gst_caps_unref (ret);
560     ret = intersection;
561   }
562
563   gst_object_unref (self);
564
565   GST_DEBUG_OBJECT (pad, "Returning caps: %" GST_PTR_FORMAT, ret);
566
567   return ret;
568 }
569
570 static GstFlowReturn
571 gst_shape_wipe_blend_16 (GstShapeWipe * self, GstBuffer * inbuf,
572     GstBuffer * maskbuf, GstBuffer * outbuf)
573 {
574   const guint16 *mask = (const guint16 *) GST_BUFFER_DATA (maskbuf);
575   const guint8 *input = (const guint8 *) GST_BUFFER_DATA (inbuf);
576   guint8 *output = (guint8 *) GST_BUFFER_DATA (outbuf);
577   guint i, j;
578   guint mask_increment = GST_ROUND_UP_2 (self->width) - self->width;
579   gfloat position = self->mask_position;
580   gfloat low = position - (self->mask_border / 2.0f);
581   gfloat high = position + (self->mask_border / 2.0f);
582
583   if (low < 0.0f) {
584     high = 0.0f;
585     low = 0.0f;
586   }
587
588   if (high > 1.0f) {
589     low = 1.0f;
590     high = 1.0f;
591   }
592
593   for (i = 0; i < self->height; i++) {
594     for (j = 0; j < self->width; j++) {
595       gfloat in = *mask / 65536.0f;
596
597       if (in < low) {
598         output[0] = 0x00;       /* A */
599         output[1] = 0x00;       /* Y */
600         output[2] = 0x80;       /* U */
601         output[3] = 0x80;       /* V */
602       } else if (in >= high) {
603         output[0] = 0xff;       /* A */
604         output[1] = input[1];   /* Y */
605         output[2] = input[2];   /* U */
606         output[3] = input[3];   /* V */
607       } else {
608         gfloat val = 255.0f * ((in - low) / (high - low));
609
610         output[0] = CLAMP (val, 0, 255);        /* A */
611         output[1] = input[1];   /* Y */
612         output[2] = input[2];   /* U */
613         output[3] = input[3];   /* V */
614       }
615
616       mask++;
617       input += 4;
618       output += 4;
619     }
620     mask += mask_increment;
621   }
622
623   return GST_FLOW_OK;
624 }
625
626 static GstFlowReturn
627 gst_shape_wipe_blend_8 (GstShapeWipe * self, GstBuffer * inbuf,
628     GstBuffer * maskbuf, GstBuffer * outbuf)
629 {
630   const guint8 *mask = (const guint8 *) GST_BUFFER_DATA (maskbuf);
631   const guint8 *input = (const guint8 *) GST_BUFFER_DATA (inbuf);
632   guint8 *output = (guint8 *) GST_BUFFER_DATA (outbuf);
633   guint i, j;
634   guint mask_increment = GST_ROUND_UP_4 (self->width) - self->width;
635   gfloat position = self->mask_position;
636   gfloat low = position - (self->mask_border / 2.0f);
637   gfloat high = position + (self->mask_border / 2.0f);
638
639   if (low < 0.0f) {
640     high = 0.0f;
641     low = 0.0f;
642   }
643
644   if (high > 1.0f) {
645     low = 1.0f;
646     high = 1.0f;
647   }
648
649   for (i = 0; i < self->height; i++) {
650     for (j = 0; j < self->width; j++) {
651       gfloat in = *mask / 256.0f;
652
653       if (in < low) {
654         output[0] = 0x00;       /* A */
655         output[1] = 0x00;       /* Y */
656         output[2] = 0x80;       /* U */
657         output[3] = 0x80;       /* V */
658       } else if (in >= high) {
659         output[0] = 0xff;       /* A */
660         output[1] = input[1];   /* Y */
661         output[2] = input[2];   /* U */
662         output[3] = input[3];   /* V */
663       } else {
664         gfloat val = 255.0f * ((in - low) / (high - low));
665
666         output[0] = CLAMP (val, 0, 255);        /* A */
667         output[1] = input[1];   /* Y */
668         output[2] = input[2];   /* U */
669         output[3] = input[3];   /* V */
670       }
671
672       mask++;
673       input += 4;
674       output += 4;
675     }
676     mask += mask_increment;
677   }
678
679   return GST_FLOW_OK;
680 }
681
682 static GstFlowReturn
683 gst_shape_wipe_video_sink_chain (GstPad * pad, GstBuffer * buffer)
684 {
685   GstShapeWipe *self = GST_SHAPE_WIPE (GST_PAD_PARENT (pad));
686   GstFlowReturn ret = GST_FLOW_OK;
687   GstBuffer *mask = NULL, *outbuf = NULL;
688   GstClockTime timestamp;
689
690   timestamp = GST_BUFFER_TIMESTAMP (buffer);
691   timestamp =
692       gst_segment_to_stream_time (&self->segment, GST_FORMAT_TIME, timestamp);
693
694   if (GST_CLOCK_TIME_IS_VALID (timestamp))
695     gst_object_sync_values (G_OBJECT (self), timestamp);
696
697   GST_DEBUG_OBJECT (self,
698       "Blending buffer with timestamp %" GST_TIME_FORMAT " at position %lf",
699       GST_TIME_ARGS (timestamp), self->mask_position);
700
701   g_mutex_lock (self->mask_mutex);
702   if (!self->mask)
703     g_cond_wait (self->mask_cond, self->mask_mutex);
704
705   if (self->mask == NULL) {
706     g_mutex_unlock (self->mask_mutex);
707     gst_buffer_unref (buffer);
708     return GST_FLOW_UNEXPECTED;
709   } else {
710     mask = gst_buffer_ref (self->mask);
711   }
712   g_mutex_unlock (self->mask_mutex);
713
714   ret =
715       gst_pad_alloc_buffer_and_set_caps (self->srcpad, GST_BUFFER_OFFSET_NONE,
716       GST_BUFFER_SIZE (buffer), GST_PAD_CAPS (self->srcpad), &outbuf);
717   if (G_UNLIKELY (ret != GST_FLOW_OK)) {
718     gst_buffer_unref (buffer);
719     gst_buffer_unref (mask);
720     return ret;
721   }
722
723   if (self->mask_bpp == 16)
724     ret = gst_shape_wipe_blend_16 (self, buffer, mask, outbuf);
725   else
726     ret = gst_shape_wipe_blend_8 (self, buffer, mask, outbuf);
727
728   gst_buffer_unref (mask);
729   gst_buffer_unref (buffer);
730   if (ret != GST_FLOW_OK) {
731     gst_buffer_unref (outbuf);
732     return ret;
733   }
734
735   ret = gst_pad_push (self->srcpad, outbuf);
736   return ret;
737 }
738
739 static GstFlowReturn
740 gst_shape_wipe_mask_sink_chain (GstPad * pad, GstBuffer * buffer)
741 {
742   GstShapeWipe *self = GST_SHAPE_WIPE (GST_PAD_PARENT (pad));
743   GstFlowReturn ret = GST_FLOW_OK;
744
745   g_mutex_lock (self->mask_mutex);
746   GST_DEBUG_OBJECT (self, "Setting new mask buffer: %" GST_PTR_FORMAT, buffer);
747
748   gst_buffer_replace (&self->mask, buffer);
749   g_cond_signal (self->mask_cond);
750   g_mutex_unlock (self->mask_mutex);
751
752   gst_buffer_unref (buffer);
753
754   return ret;
755 }
756
757 static GstStateChangeReturn
758 gst_shape_wipe_change_state (GstElement * element, GstStateChange transition)
759 {
760   GstShapeWipe *self = GST_SHAPE_WIPE (element);
761   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
762
763   switch (transition) {
764     case GST_STATE_CHANGE_READY_TO_PAUSED:
765     default:
766       break;
767   }
768
769   /* Unblock video sink chain function */
770   if (transition == GST_STATE_CHANGE_PAUSED_TO_READY)
771     g_cond_signal (self->mask_cond);
772
773   if (GST_ELEMENT_CLASS (parent_class)->change_state)
774     ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
775
776   switch (transition) {
777     case GST_STATE_CHANGE_PAUSED_TO_READY:
778       gst_shape_wipe_reset (self);
779       break;
780     default:
781       break;
782   }
783
784   return ret;
785 }
786
787 static gboolean
788 gst_shape_wipe_video_sink_event (GstPad * pad, GstEvent * event)
789 {
790   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
791   gboolean ret;
792
793   GST_DEBUG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
794
795   switch (GST_EVENT_TYPE (event)) {
796     case GST_EVENT_NEWSEGMENT:{
797       GstFormat fmt;
798       gboolean is_update;
799       gint64 start, end, base;
800       gdouble rate;
801
802       gst_event_parse_new_segment (event, &is_update, &rate, &fmt, &start,
803           &end, &base);
804       if (fmt == GST_FORMAT_TIME) {
805         GST_DEBUG_OBJECT (pad,
806             "Got NEWSEGMENT event in GST_FORMAT_TIME, passing on (%"
807             GST_TIME_FORMAT " - %" GST_TIME_FORMAT ")", GST_TIME_ARGS (start),
808             GST_TIME_ARGS (end));
809         gst_segment_set_newsegment (&self->segment, is_update, rate, fmt, start,
810             end, base);
811       } else {
812         gst_segment_init (&self->segment, GST_FORMAT_TIME);
813       }
814     }
815       /* fall through */
816     default:
817       ret = gst_pad_push_event (self->srcpad, event);
818       break;
819   }
820
821   gst_object_unref (self);
822   return ret;
823 }
824
825 static gboolean
826 gst_shape_wipe_mask_sink_event (GstPad * pad, GstEvent * event)
827 {
828   GST_DEBUG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
829
830   /* Dropping all events here */
831   gst_event_unref (event);
832   return TRUE;
833 }
834
835 static gboolean
836 gst_shape_wipe_src_event (GstPad * pad, GstEvent * event)
837 {
838   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
839   gboolean ret;
840
841   switch (GST_EVENT_TYPE (event)) {
842     default:
843       ret = gst_pad_push_event (self->video_sinkpad, event);
844       break;
845   }
846
847   gst_object_unref (self);
848   return ret;
849 }
850
851 static gboolean
852 plugin_init (GstPlugin * plugin)
853 {
854   GST_DEBUG_CATEGORY_INIT (gst_shape_wipe_debug, "shapewipe", 0,
855       "shapewipe element");
856
857   gst_controller_init (NULL, NULL);
858
859   if (!gst_element_register (plugin, "shapewipe", GST_RANK_NONE,
860           GST_TYPE_SHAPE_WIPE))
861     return FALSE;
862
863   return TRUE;
864 }
865
866 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
867     GST_VERSION_MINOR,
868     "shapewipe",
869     "Shape Wipe transition filter",
870     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)