Merging gst-plugins-bad
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / gst / coloreffects / gstchromahold.c
1 /* GStreamer
2  * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) 2007 Wim Taymans <wim.taymans@collabora.co.uk>
4  * Copyright (C) 2007 Edward Hervey <edward.hervey@collabora.co.uk>
5  * Copyright (C) 2007 Jan Schmidt <thaytan@noraisin.net>
6  * Copyright (C) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 /**
25  * SECTION:element-chromahold
26  * @title: chromahold
27  *
28  * The chromahold element will remove all color information for
29  * all colors except a single one and converts them to grayscale.
30  *
31  * Sample pipeline:
32  * |[
33  * gst-launch-1.0 videotestsrc pattern=smpte75 ! \
34  *   chromahold target-r=0 target-g=0 target-b=255 ! \
35  *   videoconvert ! autovideosink     \
36  * ]| This pipeline only keeps the red color.
37  */
38
39 #ifdef HAVE_CONFIG_H
40 #include "config.h"
41 #endif
42
43 #include "gstchromahold.h"
44
45 #include <stdlib.h>
46 #include <string.h>
47 #include <math.h>
48
49 GST_DEBUG_CATEGORY_STATIC (gst_chroma_hold_debug);
50 #define GST_CAT_DEFAULT gst_chroma_hold_debug
51
52 #define DEFAULT_TARGET_R 255
53 #define DEFAULT_TARGET_G 0
54 #define DEFAULT_TARGET_B 0
55 #define DEFAULT_TOLERANCE 30
56
57 enum
58 {
59   PROP_0,
60   PROP_TARGET_R,
61   PROP_TARGET_G,
62   PROP_TARGET_B,
63   PROP_TOLERANCE
64 };
65
66 static GstStaticPadTemplate gst_chroma_hold_src_template =
67 GST_STATIC_PAD_TEMPLATE ("src",
68     GST_PAD_SRC,
69     GST_PAD_ALWAYS,
70     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE
71         ("{ ARGB, BGRA, ABGR, RGBA, xRGB, BGRx, xBGR, RGBx}"))
72     );
73
74 static GstStaticPadTemplate gst_chroma_hold_sink_template =
75 GST_STATIC_PAD_TEMPLATE ("sink",
76     GST_PAD_SINK,
77     GST_PAD_ALWAYS,
78     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE
79         ("{ ARGB, BGRA, ABGR, RGBA, xRGB, BGRx, xBGR, RGBx}"))
80     );
81
82 #define GST_CHROMA_HOLD_LOCK(self) G_STMT_START { \
83   GST_LOG_OBJECT (self, "Locking chromahold from thread %p", g_thread_self ()); \
84   g_mutex_lock (&self->lock); \
85   GST_LOG_OBJECT (self, "Locked chromahold from thread %p", g_thread_self ()); \
86 } G_STMT_END
87
88 #define GST_CHROMA_HOLD_UNLOCK(self) G_STMT_START { \
89   GST_LOG_OBJECT (self, "Unlocking chromahold from thread %p", \
90       g_thread_self ()); \
91   g_mutex_unlock (&self->lock); \
92 } G_STMT_END
93
94 static gboolean gst_chroma_hold_start (GstBaseTransform * trans);
95 static gboolean gst_chroma_hold_set_info (GstVideoFilter * vfilter,
96     GstCaps * incaps, GstVideoInfo * in_info, GstCaps * outcaps,
97     GstVideoInfo * out_info);
98 static GstFlowReturn gst_chroma_hold_transform_frame_ip (GstVideoFilter *
99     vfilter, GstVideoFrame * frame);
100 static void gst_chroma_hold_before_transform (GstBaseTransform * btrans,
101     GstBuffer * buf);
102
103 static void gst_chroma_hold_init_params (GstChromaHold * self);
104 static gboolean gst_chroma_hold_set_process_function (GstChromaHold * self);
105
106 static void gst_chroma_hold_set_property (GObject * object, guint prop_id,
107     const GValue * value, GParamSpec * pspec);
108 static void gst_chroma_hold_get_property (GObject * object, guint prop_id,
109     GValue * value, GParamSpec * pspec);
110 static void gst_chroma_hold_finalize (GObject * object);
111
112 #define gst_chroma_hold_parent_class parent_class
113 G_DEFINE_TYPE (GstChromaHold, gst_chroma_hold, GST_TYPE_VIDEO_FILTER);
114 GST_ELEMENT_REGISTER_DEFINE (chromahold, "chromahold",
115     GST_RANK_NONE, gst_chroma_hold_get_type ());
116
117 static void
118 gst_chroma_hold_class_init (GstChromaHoldClass * klass)
119 {
120   GObjectClass *gobject_class = (GObjectClass *) klass;
121   GstElementClass *gstelement_class = (GstElementClass *) klass;
122   GstBaseTransformClass *btrans_class = (GstBaseTransformClass *) klass;
123   GstVideoFilterClass *vfilter_class = (GstVideoFilterClass *) klass;
124
125   gobject_class->set_property = gst_chroma_hold_set_property;
126   gobject_class->get_property = gst_chroma_hold_get_property;
127   gobject_class->finalize = gst_chroma_hold_finalize;
128
129   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TARGET_R,
130       g_param_spec_uint ("target-r", "Target Red", "The Red target", 0, 255,
131           DEFAULT_TARGET_R,
132           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
133   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TARGET_G,
134       g_param_spec_uint ("target-g", "Target Green", "The Green target", 0, 255,
135           DEFAULT_TARGET_G,
136           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
137   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TARGET_B,
138       g_param_spec_uint ("target-b", "Target Blue", "The Blue target", 0, 255,
139           DEFAULT_TARGET_B,
140           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
141   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TOLERANCE,
142       g_param_spec_uint ("tolerance", "Tolerance",
143           "Tolerance for the target color", 0, 180, DEFAULT_TOLERANCE,
144           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
145
146   btrans_class->start = GST_DEBUG_FUNCPTR (gst_chroma_hold_start);
147   btrans_class->before_transform =
148       GST_DEBUG_FUNCPTR (gst_chroma_hold_before_transform);
149
150   vfilter_class->transform_frame_ip =
151       GST_DEBUG_FUNCPTR (gst_chroma_hold_transform_frame_ip);
152   vfilter_class->set_info = GST_DEBUG_FUNCPTR (gst_chroma_hold_set_info);
153
154   gst_element_class_set_static_metadata (gstelement_class, "Chroma hold filter",
155       "Filter/Effect/Video",
156       "Removes all color information except for one color",
157       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
158
159   gst_element_class_add_static_pad_template (gstelement_class,
160       &gst_chroma_hold_sink_template);
161   gst_element_class_add_static_pad_template (gstelement_class,
162       &gst_chroma_hold_src_template);
163
164   GST_DEBUG_CATEGORY_INIT (gst_chroma_hold_debug, "chromahold", 0,
165       "chromahold - Removes all color information except for one color");
166 }
167
168 static void
169 gst_chroma_hold_init (GstChromaHold * self)
170 {
171   self->target_r = DEFAULT_TARGET_R;
172   self->target_g = DEFAULT_TARGET_G;
173   self->target_b = DEFAULT_TARGET_B;
174   self->tolerance = DEFAULT_TOLERANCE;
175
176   g_mutex_init (&self->lock);
177 }
178
179 static void
180 gst_chroma_hold_finalize (GObject * object)
181 {
182   GstChromaHold *self = GST_CHROMA_HOLD (object);
183
184   g_mutex_clear (&self->lock);
185
186   G_OBJECT_CLASS (parent_class)->finalize (object);
187 }
188
189 static void
190 gst_chroma_hold_set_property (GObject * object, guint prop_id,
191     const GValue * value, GParamSpec * pspec)
192 {
193   GstChromaHold *self = GST_CHROMA_HOLD (object);
194
195   GST_CHROMA_HOLD_LOCK (self);
196   switch (prop_id) {
197     case PROP_TARGET_R:
198       self->target_r = g_value_get_uint (value);
199       gst_chroma_hold_init_params (self);
200       break;
201     case PROP_TARGET_G:
202       self->target_g = g_value_get_uint (value);
203       gst_chroma_hold_init_params (self);
204       break;
205     case PROP_TARGET_B:
206       self->target_b = g_value_get_uint (value);
207       gst_chroma_hold_init_params (self);
208       break;
209     case PROP_TOLERANCE:
210       self->tolerance = g_value_get_uint (value);
211       break;
212     default:
213       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
214       break;
215   }
216
217   GST_CHROMA_HOLD_UNLOCK (self);
218 }
219
220 static void
221 gst_chroma_hold_get_property (GObject * object, guint prop_id, GValue * value,
222     GParamSpec * pspec)
223 {
224   GstChromaHold *self = GST_CHROMA_HOLD (object);
225
226   switch (prop_id) {
227     case PROP_TARGET_R:
228       g_value_set_uint (value, self->target_r);
229       break;
230     case PROP_TARGET_G:
231       g_value_set_uint (value, self->target_g);
232       break;
233     case PROP_TARGET_B:
234       g_value_set_uint (value, self->target_b);
235       break;
236     case PROP_TOLERANCE:
237       g_value_set_uint (value, self->tolerance);
238       break;
239     default:
240       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
241       break;
242   }
243 }
244
245 static gboolean
246 gst_chroma_hold_set_info (GstVideoFilter * vfilter, GstCaps * incaps,
247     GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info)
248 {
249   GstChromaHold *self = GST_CHROMA_HOLD (vfilter);
250
251   GST_CHROMA_HOLD_LOCK (self);
252
253   GST_DEBUG_OBJECT (self,
254       "Setting caps %" GST_PTR_FORMAT " -> %" GST_PTR_FORMAT, incaps, outcaps);
255
256   self->format = GST_VIDEO_INFO_FORMAT (in_info);
257   self->width = GST_VIDEO_INFO_WIDTH (in_info);
258   self->height = GST_VIDEO_INFO_HEIGHT (in_info);
259
260   if (!gst_chroma_hold_set_process_function (self)) {
261     GST_WARNING_OBJECT (self, "No processing function for this caps");
262     GST_CHROMA_HOLD_UNLOCK (self);
263     return FALSE;
264   }
265
266   GST_CHROMA_HOLD_UNLOCK (self);
267
268   return TRUE;
269 }
270
271 static inline gint
272 rgb_to_hue (gint r, gint g, gint b)
273 {
274   gint m, M, C, C2, h;
275
276   m = MIN (MIN (r, g), b);
277   M = MAX (MAX (r, g), b);
278   C = M - m;
279   C2 = C >> 1;
280
281   if (C == 0) {
282     return G_MAXUINT;
283   } else if (M == r) {
284     h = ((256 * 60 * (g - b) + C2) / C);
285   } else if (M == g) {
286     h = ((256 * 60 * (b - r) + C2) / C) + 120 * 256;
287   } else {
288     /* if (M == b) */
289     h = ((256 * 60 * (r - g) + C2) / C) + 240 * 256;
290   }
291   h >>= 8;
292
293   if (h >= 360)
294     h -= 360;
295   else if (h < 0)
296     h += 360;
297
298   return h;
299 }
300
301 static inline gint
302 hue_dist (gint h1, gint h2)
303 {
304   gint d1, d2;
305
306   d1 = h1 - h2;
307   d2 = h2 - h1;
308
309   if (d1 < 0)
310     d1 += 360;
311   if (d2 < 0)
312     d2 += 360;
313
314   return MIN (d1, d2);
315 }
316
317 static void
318 gst_chroma_hold_process_xrgb (GstVideoFrame * frame, gint width,
319     gint height, GstChromaHold * self)
320 {
321   gint i, j;
322   gint r, g, b;
323   gint grey;
324   gint h1, h2;
325   gint tolerance = self->tolerance;
326   gint p[4];
327   gint diff;
328   gint row_wrap;
329   guint8 *dest;
330
331   dest = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
332   p[0] = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3);
333   p[1] = GST_VIDEO_FRAME_COMP_POFFSET (frame, 0);
334   p[2] = GST_VIDEO_FRAME_COMP_POFFSET (frame, 1);
335   p[3] = GST_VIDEO_FRAME_COMP_POFFSET (frame, 2);
336   row_wrap = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) - 4 * width;
337
338   h1 = self->hue;
339
340   for (i = 0; i < height; i++) {
341     for (j = 0; j < width; j++) {
342       r = dest[p[1]];
343       g = dest[p[2]];
344       b = dest[p[3]];
345
346       h2 = rgb_to_hue (r, g, b);
347       diff = hue_dist (h1, h2);
348       if (h1 == G_MAXUINT || diff > tolerance) {
349         grey = (13938 * r + 46869 * g + 4730 * b) >> 16;
350         grey = CLAMP (grey, 0, 255);
351         dest[p[1]] = grey;
352         dest[p[2]] = grey;
353         dest[p[3]] = grey;
354       }
355
356       dest += 4;
357     }
358     dest += row_wrap;
359   }
360 }
361
362 /* Protected with the chroma hold lock */
363 static void
364 gst_chroma_hold_init_params (GstChromaHold * self)
365 {
366   self->hue = rgb_to_hue (self->target_r, self->target_g, self->target_b);
367 }
368
369 /* Protected with the chroma hold lock */
370 static gboolean
371 gst_chroma_hold_set_process_function (GstChromaHold * self)
372 {
373   self->process = NULL;
374
375   switch (self->format) {
376     case GST_VIDEO_FORMAT_ARGB:
377     case GST_VIDEO_FORMAT_ABGR:
378     case GST_VIDEO_FORMAT_RGBA:
379     case GST_VIDEO_FORMAT_BGRA:
380     case GST_VIDEO_FORMAT_xRGB:
381     case GST_VIDEO_FORMAT_xBGR:
382     case GST_VIDEO_FORMAT_RGBx:
383     case GST_VIDEO_FORMAT_BGRx:
384       self->process = gst_chroma_hold_process_xrgb;
385       break;
386     default:
387       break;
388   }
389   return self->process != NULL;
390 }
391
392 static gboolean
393 gst_chroma_hold_start (GstBaseTransform * btrans)
394 {
395   GstChromaHold *self = GST_CHROMA_HOLD (btrans);
396
397   GST_CHROMA_HOLD_LOCK (self);
398   gst_chroma_hold_init_params (self);
399   GST_CHROMA_HOLD_UNLOCK (self);
400
401   return TRUE;
402 }
403
404 static void
405 gst_chroma_hold_before_transform (GstBaseTransform * btrans, GstBuffer * buf)
406 {
407   GstChromaHold *self = GST_CHROMA_HOLD (btrans);
408   GstClockTime timestamp;
409
410   timestamp = gst_segment_to_stream_time (&btrans->segment, GST_FORMAT_TIME,
411       GST_BUFFER_TIMESTAMP (buf));
412   GST_LOG ("Got stream time of %" GST_TIME_FORMAT, GST_TIME_ARGS (timestamp));
413   if (GST_CLOCK_TIME_IS_VALID (timestamp))
414     gst_object_sync_values (GST_OBJECT (self), timestamp);
415 }
416
417 static GstFlowReturn
418 gst_chroma_hold_transform_frame_ip (GstVideoFilter * vfilter,
419     GstVideoFrame * frame)
420 {
421   GstChromaHold *self = GST_CHROMA_HOLD (vfilter);
422
423   GST_CHROMA_HOLD_LOCK (self);
424
425   if (G_UNLIKELY (!self->process)) {
426     GST_ERROR_OBJECT (self, "Not negotiated yet");
427     GST_CHROMA_HOLD_UNLOCK (self);
428     return GST_FLOW_NOT_NEGOTIATED;
429   }
430
431   self->process (frame, self->width, self->height, self);
432
433   GST_CHROMA_HOLD_UNLOCK (self);
434
435   return GST_FLOW_OK;
436 }