2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
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.
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.
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.
24 #include <gst/video/video.h>
29 #define GST_TYPE_ALPHA \
30 (gst_alpha_get_type())
31 #define GST_ALPHA(obj) \
32 (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ALPHA,GstAlpha))
33 #define GST_ALPHA_CLASS(klass) \
34 (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ALPHA,GstAlphaClass))
35 #define GST_IS_ALPHA(obj) \
36 (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ALPHA))
37 #define GST_IS_ALPHA_CLASS(obj) \
38 (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ALPHA))
40 typedef struct _GstAlpha GstAlpha;
41 typedef struct _GstAlphaClass GstAlphaClass;
52 #define ROUND_UP_2(x) (((x) + 1) & ~1)
53 #define ROUND_UP_4(x) (((x) + 3) & ~3)
54 #define ROUND_UP_8(x) (((x) + 7) & ~7)
65 gint in_width, in_height;
66 gint out_width, out_height;
74 GstAlphaMethod method;
79 gfloat y; /* chroma color */
82 gfloat accept_angle_cos;
83 gfloat accept_angle_sin;
84 guint8 accept_angle_tg;
85 guint8 accept_angle_ctg;
92 GstElementClass parent_class;
95 /* elementfactory information */
96 static GstElementDetails gst_alpha_details =
97 GST_ELEMENT_DETAILS ("alpha filter",
98 "Filter/Effect/Video",
99 "Adds an alpha channel to video",
100 "Wim Taymans <wim@fluendo.com>");
103 /* Alpha signals and args */
110 #define DEFAULT_METHOD ALPHA_METHOD_ADD
111 #define DEFAULT_ALPHA 1.0
112 #define DEFAULT_TARGET_R 0
113 #define DEFAULT_TARGET_G 255
114 #define DEFAULT_TARGET_B 0
115 #define DEFAULT_ANGLE 20.0
116 #define DEFAULT_NOISE_LEVEL 2.0
131 static GstStaticPadTemplate gst_alpha_src_template =
132 GST_STATIC_PAD_TEMPLATE ("src",
135 GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV"))
138 static GstStaticPadTemplate gst_alpha_sink_template =
139 GST_STATIC_PAD_TEMPLATE ("sink",
142 GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
146 static void gst_alpha_base_init (gpointer g_class);
147 static void gst_alpha_class_init (GstAlphaClass * klass);
148 static void gst_alpha_init (GstAlpha * alpha);
149 static void gst_alpha_init_params (GstAlpha * alpha);
151 static void gst_alpha_set_property (GObject * object, guint prop_id,
152 const GValue * value, GParamSpec * pspec);
153 static void gst_alpha_get_property (GObject * object, guint prop_id,
154 GValue * value, GParamSpec * pspec);
156 static GstPadLinkReturn
157 gst_alpha_sink_link (GstPad * pad, const GstCaps * caps);
158 static void gst_alpha_chain (GstPad * pad, GstData * _data);
160 static GstElementStateReturn gst_alpha_change_state (GstElement * element);
163 static GstElementClass *parent_class = NULL;
165 #define GST_TYPE_ALPHA_METHOD (gst_alpha_method_get_type())
167 gst_alpha_method_get_type (void)
169 static GType alpha_method_type = 0;
170 static GEnumValue alpha_method[] = {
171 {ALPHA_METHOD_ADD, "0", "Add alpha channel"},
172 {ALPHA_METHOD_GREEN, "1", "Chroma Key green"},
173 {ALPHA_METHOD_BLUE, "2", "Chroma Key blue"},
174 {ALPHA_METHOD_CUSTOM, "3", "Chroma Key on target_r/g/b"},
178 if (!alpha_method_type) {
179 alpha_method_type = g_enum_register_static ("GstAlphaMethod", alpha_method);
181 return alpha_method_type;
184 /* static guint gst_alpha_signals[LAST_SIGNAL] = { 0 }; */
187 gst_alpha_get_type (void)
189 static GType alpha_type = 0;
192 static const GTypeInfo alpha_info = {
193 sizeof (GstAlphaClass),
196 (GClassInitFunc) gst_alpha_class_init,
201 (GInstanceInitFunc) gst_alpha_init,
205 g_type_register_static (GST_TYPE_ELEMENT, "GstAlpha", &alpha_info, 0);
211 gst_alpha_base_init (gpointer g_class)
213 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
215 gst_element_class_set_details (element_class, &gst_alpha_details);
217 gst_element_class_add_pad_template (element_class,
218 gst_static_pad_template_get (&gst_alpha_sink_template));
219 gst_element_class_add_pad_template (element_class,
220 gst_static_pad_template_get (&gst_alpha_src_template));
223 gst_alpha_class_init (GstAlphaClass * klass)
225 GObjectClass *gobject_class;
226 GstElementClass *gstelement_class;
228 gobject_class = (GObjectClass *) klass;
229 gstelement_class = (GstElementClass *) klass;
231 parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
233 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_METHOD,
234 g_param_spec_enum ("method", "Method",
235 "How the alpha channels should be created", GST_TYPE_ALPHA_METHOD,
236 DEFAULT_METHOD, (GParamFlags) G_PARAM_READWRITE));
237 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_ALPHA,
238 g_param_spec_double ("alpha", "Alpha", "The value for the alpha channel",
239 0.0, 1.0, DEFAULT_ALPHA, (GParamFlags) G_PARAM_READWRITE));
240 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TARGET_R,
241 g_param_spec_uint ("target_r", "Target Red", "The Red target", 0,
242 255, DEFAULT_TARGET_R, (GParamFlags) G_PARAM_READWRITE));
243 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TARGET_G,
244 g_param_spec_uint ("target_g", "Target Green", "The Green target", 0,
245 255, DEFAULT_TARGET_G, (GParamFlags) G_PARAM_READWRITE));
246 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TARGET_B,
247 g_param_spec_uint ("target_b", "Target Blue", "The Blue target",
248 0, 255, DEFAULT_TARGET_B, (GParamFlags) G_PARAM_READWRITE));
249 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_ANGLE,
250 g_param_spec_float ("angle", "Angle", "Size of the colorcube to change",
251 0.0, 90.0, DEFAULT_ANGLE, (GParamFlags) G_PARAM_READWRITE));
252 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_NOISE_LEVEL,
253 g_param_spec_float ("noise_level", "Noise Level", "Size of noise radius",
254 0.0, 64.0, DEFAULT_NOISE_LEVEL, (GParamFlags) G_PARAM_READWRITE));
256 gobject_class->set_property = gst_alpha_set_property;
257 gobject_class->get_property = gst_alpha_get_property;
259 gstelement_class->change_state = gst_alpha_change_state;
263 gst_alpha_init (GstAlpha * alpha)
265 /* create the sink and src pads */
267 gst_pad_new_from_template (gst_static_pad_template_get
268 (&gst_alpha_sink_template), "sink");
269 gst_element_add_pad (GST_ELEMENT (alpha), alpha->sinkpad);
270 gst_pad_set_chain_function (alpha->sinkpad, gst_alpha_chain);
271 gst_pad_set_link_function (alpha->sinkpad, gst_alpha_sink_link);
274 gst_pad_new_from_template (gst_static_pad_template_get
275 (&gst_alpha_src_template), "src");
276 gst_element_add_pad (GST_ELEMENT (alpha), alpha->srcpad);
278 alpha->alpha = DEFAULT_ALPHA;
279 alpha->method = DEFAULT_METHOD;
280 alpha->target_r = DEFAULT_TARGET_R;
281 alpha->target_g = DEFAULT_TARGET_G;
282 alpha->target_b = DEFAULT_TARGET_B;
283 alpha->angle = DEFAULT_ANGLE;
284 alpha->noise_level = DEFAULT_NOISE_LEVEL;
286 GST_FLAG_SET (alpha, GST_ELEMENT_EVENT_AWARE);
289 /* do we need this function? */
291 gst_alpha_set_property (GObject * object, guint prop_id,
292 const GValue * value, GParamSpec * pspec)
296 /* it's not null if we got it, but it might not be ours */
297 g_return_if_fail (GST_IS_ALPHA (object));
299 alpha = GST_ALPHA (object);
303 alpha->method = g_value_get_enum (value);
304 switch (alpha->method) {
305 case ALPHA_METHOD_GREEN:
307 alpha->target_g = 255;
310 case ALPHA_METHOD_BLUE:
313 alpha->target_b = 255;
318 gst_alpha_init_params (alpha);
321 alpha->alpha = g_value_get_double (value);
324 alpha->target_r = g_value_get_uint (value);
325 gst_alpha_init_params (alpha);
328 alpha->target_g = g_value_get_uint (value);
329 gst_alpha_init_params (alpha);
332 alpha->target_b = g_value_get_uint (value);
333 gst_alpha_init_params (alpha);
336 alpha->angle = g_value_get_float (value);
337 gst_alpha_init_params (alpha);
339 case ARG_NOISE_LEVEL:
340 alpha->noise_level = g_value_get_float (value);
341 gst_alpha_init_params (alpha);
344 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
349 gst_alpha_get_property (GObject * object, guint prop_id, GValue * value,
354 /* it's not null if we got it, but it might not be ours */
355 g_return_if_fail (GST_IS_ALPHA (object));
357 alpha = GST_ALPHA (object);
361 g_value_set_enum (value, alpha->method);
364 g_value_set_double (value, alpha->alpha);
367 g_value_set_uint (value, alpha->target_r);
370 g_value_set_uint (value, alpha->target_g);
373 g_value_set_uint (value, alpha->target_b);
376 g_value_set_float (value, alpha->angle);
378 case ARG_NOISE_LEVEL:
379 g_value_set_float (value, alpha->noise_level);
382 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
387 static GstPadLinkReturn
388 gst_alpha_sink_link (GstPad * pad, const GstCaps * caps)
391 GstStructure *structure;
394 alpha = GST_ALPHA (gst_pad_get_parent (pad));
395 structure = gst_caps_get_structure (caps, 0);
397 ret = gst_structure_get_int (structure, "width", &alpha->in_width);
398 ret &= gst_structure_get_int (structure, "height", &alpha->in_height);
400 return GST_PAD_LINK_OK;
404 gst_alpha_add (guint8 * src, guint8 * dest, gint width, gint height,
407 gint b_alpha = (gint) (alpha * 255);
413 gint stride, stride2;
416 width = ROUND_UP_2 (width);
417 height = ROUND_UP_2 (height);
419 stride = ROUND_UP_4 (width);
420 size = stride * height;
421 stride2 = ROUND_UP_8 (width) / 2;
422 size2 = stride2 * height / 2;
424 wrap = stride - 2 * (width / 2);
425 wrap2 = stride2 - width / 2;
431 for (i = 0; i < height; i++) {
432 for (j = 0; j < width / 2; j++) {
454 /* based on http://www.cs.utah.edu/~michael/chroma/
457 gst_alpha_chroma_key (gchar * src, gchar * dest, gint width, gint height,
461 guint8 *srcY1, *srcY2, *srcU, *srcV;
462 guint8 *dest1, *dest2;
464 gint x, z, u, v, y11, y12, y21, y22;
466 gint stride, stride2;
467 gint wrap, wrap2, wrap3;
471 width = ROUND_UP_2 (width);
472 height = ROUND_UP_2 (height);
474 stride = ROUND_UP_4 (width);
475 size = stride * height;
476 stride2 = ROUND_UP_8 (width) / 2;
477 size2 = stride2 * height / 2;
480 srcY2 = src + stride;
485 dest2 = dest + width * 4;
487 wrap = 2 * stride - 2 * (width / 2);
488 wrap2 = stride2 - width / 2;
489 wrap3 = 8 * width - 8 * (width / 2);
491 for (i = 0; i < height / 2; i++) {
492 for (j = 0; j < width / 2; j++) {
500 /* Convert foreground to XZ coords where X direction is defined by
502 tmp = ((short) u * alpha->cb + (short) v * alpha->cr) >> 7;
503 x = CLAMP (tmp, -128, 127);
504 tmp = ((short) v * alpha->cb - (short) u * alpha->cr) >> 7;
505 z = CLAMP (tmp, -128, 127);
507 /* WARNING: accept angle should never be set greater than "somewhat less
508 than 90 degrees" to avoid dealing with negative/infinite tg. In reality,
509 80 degrees should be enough if foreground is reasonable. If this seems
510 to be a problem, go to alternative ways of checking point position
511 (scalar product or line equations). This angle should not be too small
512 either to avoid infinite ctg (used to suppress foreground without use of
515 tmp = ((short) (x) * alpha->accept_angle_tg) >> 4;
516 tmp = MIN (tmp, 127);
519 /* keep foreground Kfg = 0 */
522 /* Compute Kfg (implicitly) and Kbg, suppress foreground in XZ coord
524 tmp = ((short) (z) * alpha->accept_angle_ctg) >> 4;
525 tmp = CLAMP (tmp, -128, 127);
530 tmp1 = MAX (tmp1, 0);
531 b_alpha = (((unsigned char) (tmp1) *
532 (unsigned short) (alpha->one_over_kc)) / 2);
533 b_alpha = 255 - CLAMP (b_alpha, 0, 255);
535 tmp = ((unsigned short) (tmp1) * alpha->kfgy_scale) >> 4;
536 tmp1 = MIN (tmp, 255);
547 /* Convert suppressed foreground back to CbCr */
548 tmp = ((char) (x1) * (short) (alpha->cb) -
549 (char) (y1) * (short) (alpha->cr)) >> 7;
550 u = CLAMP (tmp, -128, 127);
552 tmp = ((char) (x1) * (short) (alpha->cr) +
553 (char) (y1) * (short) (alpha->cb)) >> 7;
554 v = CLAMP (tmp, -128, 127);
556 /* Deal with noise. For now, a circle around the key color with
557 radius of noise_level treated as exact key color. Introduces
560 tmp = z * (short) (z) + (x - alpha->kg) * (short) (x - alpha->kg);
561 tmp = MIN (tmp, 0xffff);
563 if (tmp < alpha->noise_level * alpha->noise_level) {
564 /* Uncomment this if you want total suppression within the noise circle */
599 gst_alpha_init_params (GstAlpha * alpha)
606 0.257 * alpha->target_r + 0.504 * alpha->target_g +
607 0.098 * alpha->target_b;
609 -0.148 * alpha->target_r - 0.291 * alpha->target_g +
610 0.439 * alpha->target_b;
612 0.439 * alpha->target_r - 0.368 * alpha->target_g -
613 0.071 * alpha->target_b;
614 kgl = sqrt (tmp1 * tmp1 + tmp2 * tmp2);
615 alpha->cb = 127 * (tmp1 / kgl);
616 alpha->cr = 127 * (tmp2 / kgl);
618 alpha->accept_angle_cos = cos (M_PI * alpha->angle / 180);
619 alpha->accept_angle_sin = sin (M_PI * alpha->angle / 180);
620 tmp = 15 * tan (M_PI * alpha->angle / 180);
621 tmp = MIN (tmp, 255);
622 alpha->accept_angle_tg = tmp;
623 tmp = 15 / tan (M_PI * alpha->angle / 180);
624 tmp = MIN (tmp, 255);
625 alpha->accept_angle_ctg = tmp;
627 alpha->one_over_kc = 255 * 2 * tmp - 255;
628 tmp = 15 * (float) (alpha->y) / kgl;
629 tmp = MIN (tmp, 255);
630 alpha->kfgy_scale = tmp;
631 alpha->kg = MIN (kgl, 127);
635 gst_alpha_chain (GstPad * pad, GstData * _data)
640 gint new_width, new_height;
642 alpha = GST_ALPHA (gst_pad_get_parent (pad));
644 if (GST_IS_EVENT (_data)) {
645 GstEvent *event = GST_EVENT (_data);
647 switch (GST_EVENT_TYPE (event)) {
649 gst_pad_event_default (pad, event);
655 buffer = GST_BUFFER (_data);
657 new_width = alpha->in_width;
658 new_height = alpha->in_height;
660 if (new_width != alpha->out_width ||
661 new_height != alpha->out_height || !GST_PAD_CAPS (alpha->srcpad)) {
664 newcaps = gst_caps_copy (gst_pad_get_negotiated_caps (alpha->sinkpad));
665 gst_caps_set_simple (newcaps,
666 "format", GST_TYPE_FOURCC, GST_STR_FOURCC ("AYUV"),
667 "width", G_TYPE_INT, new_width, "height", G_TYPE_INT, new_height, NULL);
669 if (!gst_pad_try_set_caps (alpha->srcpad, newcaps)) {
670 GST_ELEMENT_ERROR (alpha, CORE, NEGOTIATION, (NULL), (NULL));
674 alpha->out_width = new_width;
675 alpha->out_height = new_height;
679 gst_buffer_new_and_alloc (ROUND_UP_2 (new_width) *
680 ROUND_UP_2 (new_height) * 4);
681 GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (buffer);
682 GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (buffer);
684 switch (alpha->method) {
685 case ALPHA_METHOD_ADD:
686 gst_alpha_add (GST_BUFFER_DATA (buffer),
687 GST_BUFFER_DATA (outbuf), new_width, new_height, alpha->alpha);
689 case ALPHA_METHOD_GREEN:
690 gst_alpha_chroma_key (GST_BUFFER_DATA (buffer),
691 GST_BUFFER_DATA (outbuf), new_width, new_height, alpha);
693 case ALPHA_METHOD_BLUE:
694 gst_alpha_chroma_key (GST_BUFFER_DATA (buffer),
695 GST_BUFFER_DATA (outbuf), new_width, new_height, alpha);
697 case ALPHA_METHOD_CUSTOM:
698 gst_alpha_chroma_key (GST_BUFFER_DATA (buffer),
699 GST_BUFFER_DATA (outbuf), new_width, new_height, alpha);
705 gst_buffer_unref (buffer);
707 gst_pad_push (alpha->srcpad, GST_DATA (outbuf));
710 static GstElementStateReturn
711 gst_alpha_change_state (GstElement * element)
715 alpha = GST_ALPHA (element);
717 switch (GST_STATE_TRANSITION (element)) {
718 case GST_STATE_NULL_TO_READY:
720 case GST_STATE_READY_TO_PAUSED:
721 gst_alpha_init_params (alpha);
723 case GST_STATE_PAUSED_TO_PLAYING:
725 case GST_STATE_PLAYING_TO_PAUSED:
727 case GST_STATE_PAUSED_TO_READY:
729 case GST_STATE_READY_TO_NULL:
733 parent_class->change_state (element);
735 return GST_STATE_SUCCESS;
739 plugin_init (GstPlugin * plugin)
741 return gst_element_register (plugin, "alpha", GST_RANK_NONE, GST_TYPE_ALPHA);
744 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
747 "resizes a video by adding borders or cropping",
748 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE, GST_ORIGIN)