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_4(x) (((x) + 3) & ~3)
53 #define ROUND_UP_2(x) (((x) + 1) & ~1)
64 gint in_width, in_height;
65 gint out_width, out_height;
73 GstAlphaMethod method;
78 gfloat y; /* chroma color */
81 gfloat accept_angle_cos;
82 gfloat accept_angle_sin;
83 guint8 accept_angle_tg;
84 guint8 accept_angle_ctg;
91 GstElementClass parent_class;
94 /* elementfactory information */
95 static GstElementDetails gst_alpha_details =
96 GST_ELEMENT_DETAILS ("alpha filter",
97 "Filter/Effect/Video",
98 "Adds an alpha channel to video",
99 "Wim Taymans <wim@fluendo.com>");
102 /* Alpha signals and args */
109 #define DEFAULT_METHOD ALPHA_METHOD_ADD
110 #define DEFAULT_ALPHA 1.0
111 #define DEFAULT_TARGET_R 0
112 #define DEFAULT_TARGET_G 255
113 #define DEFAULT_TARGET_B 0
114 #define DEFAULT_ANGLE 20.0
115 #define DEFAULT_NOISE_LEVEL 2.0
130 static GstStaticPadTemplate gst_alpha_src_template =
131 GST_STATIC_PAD_TEMPLATE ("src",
134 GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV"))
137 static GstStaticPadTemplate gst_alpha_sink_template =
138 GST_STATIC_PAD_TEMPLATE ("sink",
141 GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
145 static void gst_alpha_base_init (gpointer g_class);
146 static void gst_alpha_class_init (GstAlphaClass * klass);
147 static void gst_alpha_init (GstAlpha * alpha);
148 static void gst_alpha_init_params (GstAlpha * alpha);
150 static void gst_alpha_set_property (GObject * object, guint prop_id,
151 const GValue * value, GParamSpec * pspec);
152 static void gst_alpha_get_property (GObject * object, guint prop_id,
153 GValue * value, GParamSpec * pspec);
155 static GstPadLinkReturn
156 gst_alpha_sink_link (GstPad * pad, const GstCaps * caps);
157 static void gst_alpha_chain (GstPad * pad, GstData * _data);
159 static GstElementStateReturn gst_alpha_change_state (GstElement * element);
162 static GstElementClass *parent_class = NULL;
164 #define GST_TYPE_ALPHA_METHOD (gst_alpha_method_get_type())
166 gst_alpha_method_get_type (void)
168 static GType alpha_method_type = 0;
169 static GEnumValue alpha_method[] = {
170 {ALPHA_METHOD_ADD, "0", "Add alpha channel"},
171 {ALPHA_METHOD_GREEN, "1", "Chroma Key green"},
172 {ALPHA_METHOD_BLUE, "2", "Chroma Key blue"},
173 {ALPHA_METHOD_CUSTOM, "3", "Chroma Key on target_r/g/b"},
177 if (!alpha_method_type) {
178 alpha_method_type = g_enum_register_static ("GstAlphaMethod", alpha_method);
180 return alpha_method_type;
183 /* static guint gst_alpha_signals[LAST_SIGNAL] = { 0 }; */
186 gst_alpha_get_type (void)
188 static GType alpha_type = 0;
191 static const GTypeInfo alpha_info = {
192 sizeof (GstAlphaClass),
195 (GClassInitFunc) gst_alpha_class_init,
200 (GInstanceInitFunc) gst_alpha_init,
204 g_type_register_static (GST_TYPE_ELEMENT, "GstAlpha", &alpha_info, 0);
210 gst_alpha_base_init (gpointer g_class)
212 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
214 gst_element_class_set_details (element_class, &gst_alpha_details);
216 gst_element_class_add_pad_template (element_class,
217 gst_static_pad_template_get (&gst_alpha_sink_template));
218 gst_element_class_add_pad_template (element_class,
219 gst_static_pad_template_get (&gst_alpha_src_template));
222 gst_alpha_class_init (GstAlphaClass * klass)
224 GObjectClass *gobject_class;
225 GstElementClass *gstelement_class;
227 gobject_class = (GObjectClass *) klass;
228 gstelement_class = (GstElementClass *) klass;
230 parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
232 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_METHOD,
233 g_param_spec_enum ("method", "Method",
234 "How the alpha channels should be created", GST_TYPE_ALPHA_METHOD,
235 DEFAULT_METHOD, (GParamFlags) G_PARAM_READWRITE));
236 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_ALPHA,
237 g_param_spec_double ("alpha", "Alpha", "The value for the alpha channel",
238 0.0, 1.0, DEFAULT_ALPHA, (GParamFlags) G_PARAM_READWRITE));
239 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TARGET_R,
240 g_param_spec_uint ("target_r", "Target Red", "The Red target", 0,
241 255, DEFAULT_TARGET_R, (GParamFlags) G_PARAM_READWRITE));
242 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TARGET_G,
243 g_param_spec_uint ("target_g", "Target Green", "The Green target", 0,
244 255, DEFAULT_TARGET_G, (GParamFlags) G_PARAM_READWRITE));
245 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TARGET_B,
246 g_param_spec_uint ("target_b", "Target Blue", "The Blue target",
247 0, 255, DEFAULT_TARGET_B, (GParamFlags) G_PARAM_READWRITE));
248 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_ANGLE,
249 g_param_spec_float ("angle", "Angle", "Size of the colorcube to change",
250 0.0, 90.0, DEFAULT_ANGLE, (GParamFlags) G_PARAM_READWRITE));
251 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_NOISE_LEVEL,
252 g_param_spec_float ("noise_level", "Noise Level", "Size of noise radius",
253 0.0, 64.0, DEFAULT_NOISE_LEVEL, (GParamFlags) G_PARAM_READWRITE));
255 gobject_class->set_property = gst_alpha_set_property;
256 gobject_class->get_property = gst_alpha_get_property;
258 gstelement_class->change_state = gst_alpha_change_state;
262 gst_alpha_init (GstAlpha * alpha)
264 /* create the sink and src pads */
266 gst_pad_new_from_template (gst_static_pad_template_get
267 (&gst_alpha_sink_template), "sink");
268 gst_element_add_pad (GST_ELEMENT (alpha), alpha->sinkpad);
269 gst_pad_set_chain_function (alpha->sinkpad, gst_alpha_chain);
270 gst_pad_set_link_function (alpha->sinkpad, gst_alpha_sink_link);
273 gst_pad_new_from_template (gst_static_pad_template_get
274 (&gst_alpha_src_template), "src");
275 gst_element_add_pad (GST_ELEMENT (alpha), alpha->srcpad);
277 alpha->alpha = DEFAULT_ALPHA;
278 alpha->method = DEFAULT_METHOD;
279 alpha->target_r = DEFAULT_TARGET_R;
280 alpha->target_g = DEFAULT_TARGET_G;
281 alpha->target_b = DEFAULT_TARGET_B;
282 alpha->angle = DEFAULT_ANGLE;
283 alpha->noise_level = DEFAULT_NOISE_LEVEL;
285 GST_FLAG_SET (alpha, GST_ELEMENT_EVENT_AWARE);
288 /* do we need this function? */
290 gst_alpha_set_property (GObject * object, guint prop_id,
291 const GValue * value, GParamSpec * pspec)
295 /* it's not null if we got it, but it might not be ours */
296 g_return_if_fail (GST_IS_ALPHA (object));
298 alpha = GST_ALPHA (object);
302 alpha->method = g_value_get_enum (value);
303 switch (alpha->method) {
304 case ALPHA_METHOD_GREEN:
306 alpha->target_g = 255;
309 case ALPHA_METHOD_BLUE:
312 alpha->target_b = 255;
317 gst_alpha_init_params (alpha);
320 alpha->alpha = g_value_get_double (value);
323 alpha->target_r = g_value_get_uint (value);
324 gst_alpha_init_params (alpha);
327 alpha->target_g = g_value_get_uint (value);
328 gst_alpha_init_params (alpha);
331 alpha->target_b = g_value_get_uint (value);
332 gst_alpha_init_params (alpha);
335 alpha->angle = g_value_get_float (value);
336 gst_alpha_init_params (alpha);
338 case ARG_NOISE_LEVEL:
339 alpha->noise_level = g_value_get_float (value);
340 gst_alpha_init_params (alpha);
343 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
348 gst_alpha_get_property (GObject * object, guint prop_id, GValue * value,
353 /* it's not null if we got it, but it might not be ours */
354 g_return_if_fail (GST_IS_ALPHA (object));
356 alpha = GST_ALPHA (object);
360 g_value_set_enum (value, alpha->method);
363 g_value_set_double (value, alpha->alpha);
366 g_value_set_uint (value, alpha->target_r);
369 g_value_set_uint (value, alpha->target_g);
372 g_value_set_uint (value, alpha->target_b);
375 g_value_set_float (value, alpha->angle);
377 case ARG_NOISE_LEVEL:
378 g_value_set_float (value, alpha->noise_level);
381 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
386 static GstPadLinkReturn
387 gst_alpha_sink_link (GstPad * pad, const GstCaps * caps)
390 GstStructure *structure;
393 alpha = GST_ALPHA (gst_pad_get_parent (pad));
394 structure = gst_caps_get_structure (caps, 0);
396 ret = gst_structure_get_int (structure, "width", &alpha->in_width);
397 ret &= gst_structure_get_int (structure, "height", &alpha->in_height);
399 return GST_PAD_LINK_OK;
402 #define ROUND_UP_4(x) (((x) + 3) & ~3)
405 gst_alpha_add (guint8 * src, guint8 * dest, gint width, gint height,
408 gint b_alpha = (gint) (alpha * 255);
415 gint stride, stride2;
418 stride = ROUND_UP_4 (width);
419 size = stride * height;
420 w2 = (width + 1) >> 1;
421 stride2 = ROUND_UP_4 (w2);
422 h2 = (height + 1) >> 1;
423 size2 = stride2 * h2;
425 wrap = stride - 2 * (width / 2);
426 wrap2 = stride2 - width / 2;
432 for (i = 0; i < height; i++) {
433 for (j = 0; j < width / 2; j++) {
455 /* based on http://www.cs.utah.edu/~michael/chroma/
458 gst_alpha_chroma_key (gchar * src, gchar * dest, gint width, gint height,
462 guint8 *srcY1, *srcY2, *srcU, *srcV;
463 guint8 *dest1, *dest2;
465 gint x, z, u, v, y11, y12, y21, y22;
468 gint stride, stride2;
469 gint wrap, wrap2, wrap3;
473 stride = ROUND_UP_4 (width);
474 size = stride * height;
475 w2 = (width + 1) >> 1;
476 stride2 = ROUND_UP_4 (w2);
477 h2 = (height + 1) >> 1;
478 size2 = stride2 * h2;
481 srcY2 = src + stride;
486 dest2 = dest + width * 4;
488 wrap = 2 * stride - 2 * (width / 2);
489 wrap2 = stride2 - width / 2;
490 wrap3 = 8 * width - 8 * (ROUND_UP_2 (width) / 2);
492 for (i = 0; i < height / 2; i++) {
493 for (j = 0; j < width / 2; j++) {
501 /* Convert foreground to XZ coords where X direction is defined by
503 tmp = ((short) u * alpha->cb + (short) v * alpha->cr) >> 7;
504 x = CLAMP (tmp, -128, 127);
505 tmp = ((short) v * alpha->cb - (short) u * alpha->cr) >> 7;
506 z = CLAMP (tmp, -128, 127);
508 /* WARNING: accept angle should never be set greater than "somewhat less
509 than 90 degrees" to avoid dealing with negative/infinite tg. In reality,
510 80 degrees should be enough if foreground is reasonable. If this seems
511 to be a problem, go to alternative ways of checking point position
512 (scalar product or line equations). This angle should not be too small
513 either to avoid infinite ctg (used to suppress foreground without use of
516 tmp = ((short) (x) * alpha->accept_angle_tg) >> 4;
517 tmp = MIN (tmp, 127);
520 /* keep foreground Kfg = 0 */
523 /* Compute Kfg (implicitly) and Kbg, suppress foreground in XZ coord
525 tmp = ((short) (z) * alpha->accept_angle_ctg) >> 4;
526 tmp = CLAMP (tmp, -128, 127);
531 tmp1 = MAX (tmp1, 0);
532 b_alpha = (((unsigned char) (tmp1) *
533 (unsigned short) (alpha->one_over_kc)) / 2);
534 b_alpha = 255 - CLAMP (b_alpha, 0, 255);
536 tmp = ((unsigned short) (tmp1) * alpha->kfgy_scale) >> 4;
537 tmp1 = MIN (tmp, 255);
548 /* Convert suppressed foreground back to CbCr */
549 tmp = ((char) (x1) * (short) (alpha->cb) -
550 (char) (y1) * (short) (alpha->cr)) >> 7;
551 u = CLAMP (tmp, -128, 127);
553 tmp = ((char) (x1) * (short) (alpha->cr) +
554 (char) (y1) * (short) (alpha->cb)) >> 7;
555 v = CLAMP (tmp, -128, 127);
557 /* Deal with noise. For now, a circle around the key color with
558 radius of noise_level treated as exact key color. Introduces
561 tmp = z * (short) (z) + (x - alpha->kg) * (short) (x - alpha->kg);
562 tmp = MIN (tmp, 0xffff);
564 if (tmp < alpha->noise_level * alpha->noise_level) {
565 /* Uncomment this if you want total suppression within the noise circle */
600 gst_alpha_init_params (GstAlpha * alpha)
607 0.257 * alpha->target_r + 0.504 * alpha->target_g +
608 0.098 * alpha->target_b;
610 -0.148 * alpha->target_r - 0.291 * alpha->target_g +
611 0.439 * alpha->target_b;
613 0.439 * alpha->target_r - 0.368 * alpha->target_g -
614 0.071 * alpha->target_b;
615 kgl = sqrt (tmp1 * tmp1 + tmp2 * tmp2);
616 alpha->cb = 127 * (tmp1 / kgl);
617 alpha->cr = 127 * (tmp2 / kgl);
619 alpha->accept_angle_cos = cos (M_PI * alpha->angle / 180);
620 alpha->accept_angle_sin = sin (M_PI * alpha->angle / 180);
621 tmp = 15 * tan (M_PI * alpha->angle / 180);
622 tmp = MIN (tmp, 255);
623 alpha->accept_angle_tg = tmp;
624 tmp = 15 / tan (M_PI * alpha->angle / 180);
625 tmp = MIN (tmp, 255);
626 alpha->accept_angle_ctg = tmp;
628 alpha->one_over_kc = 255 * 2 * tmp - 255;
629 tmp = 15 * (float) (alpha->y) / kgl;
630 tmp = MIN (tmp, 255);
631 alpha->kfgy_scale = tmp;
632 alpha->kg = MIN (kgl, 127);
636 gst_alpha_chain (GstPad * pad, GstData * _data)
641 gint new_width, new_height;
643 alpha = GST_ALPHA (gst_pad_get_parent (pad));
645 if (GST_IS_EVENT (_data)) {
646 GstEvent *event = GST_EVENT (_data);
648 switch (GST_EVENT_TYPE (event)) {
650 gst_pad_event_default (pad, event);
656 buffer = GST_BUFFER (_data);
658 new_width = alpha->in_width;
659 new_height = alpha->in_height;
661 if (new_width != alpha->out_width ||
662 new_height != alpha->out_height || !GST_PAD_CAPS (alpha->srcpad)) {
665 newcaps = gst_caps_copy (gst_pad_get_negotiated_caps (alpha->sinkpad));
666 gst_caps_set_simple (newcaps,
667 "format", GST_TYPE_FOURCC, GST_STR_FOURCC ("AYUV"),
668 "width", G_TYPE_INT, new_width, "height", G_TYPE_INT, new_height, NULL);
670 if (!gst_pad_try_set_caps (alpha->srcpad, newcaps)) {
671 GST_ELEMENT_ERROR (alpha, CORE, NEGOTIATION, (NULL), (NULL));
675 alpha->out_width = new_width;
676 alpha->out_height = new_height;
679 outbuf = gst_buffer_new_and_alloc (new_width * new_height * 4);
680 GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (buffer);
681 GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (buffer);
683 switch (alpha->method) {
684 case ALPHA_METHOD_ADD:
685 gst_alpha_add (GST_BUFFER_DATA (buffer),
686 GST_BUFFER_DATA (outbuf), new_width, new_height, alpha->alpha);
688 case ALPHA_METHOD_GREEN:
689 gst_alpha_chroma_key (GST_BUFFER_DATA (buffer),
690 GST_BUFFER_DATA (outbuf), new_width, new_height, alpha);
692 case ALPHA_METHOD_BLUE:
693 gst_alpha_chroma_key (GST_BUFFER_DATA (buffer),
694 GST_BUFFER_DATA (outbuf), new_width, new_height, alpha);
696 case ALPHA_METHOD_CUSTOM:
697 gst_alpha_chroma_key (GST_BUFFER_DATA (buffer),
698 GST_BUFFER_DATA (outbuf), new_width, new_height, alpha);
704 gst_buffer_unref (buffer);
706 gst_pad_push (alpha->srcpad, GST_DATA (outbuf));
709 static GstElementStateReturn
710 gst_alpha_change_state (GstElement * element)
714 alpha = GST_ALPHA (element);
716 switch (GST_STATE_TRANSITION (element)) {
717 case GST_STATE_NULL_TO_READY:
719 case GST_STATE_READY_TO_PAUSED:
720 gst_alpha_init_params (alpha);
722 case GST_STATE_PAUSED_TO_PLAYING:
724 case GST_STATE_PLAYING_TO_PAUSED:
726 case GST_STATE_PAUSED_TO_READY:
728 case GST_STATE_READY_TO_NULL:
732 parent_class->change_state (element);
734 return GST_STATE_SUCCESS;
738 plugin_init (GstPlugin * plugin)
740 return gst_element_register (plugin, "alpha", GST_RANK_NONE, GST_TYPE_ALPHA);
743 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
746 "resizes a video by adding borders or cropping",
747 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE, GST_ORIGIN)