/* GStreamer * Copyright (C) 1999 Erik Walthinsen * Copyright (C) 2007 Wim Taymans * Copyright (C) 2007 Edward Hervey * Copyright (C) 2007 Jan Schmidt * Copyright (C) 2010 Sebastian Dröge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:element-chromahold * * The chromahold element will remove all color information for * all colors except a single one and converts them to grayscale. * * Sample pipeline: * |[ * gst-launch videotestsrc pattern=smpte75 ! \ * chromahold target-r=0 target-g=0 target-b=255 ! \ * videoconvert ! autovideosink \ * ]| This pipeline only keeps the red color. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* FIXME 0.11: suppress warnings for deprecated API such as GStaticRecMutex * with newer GLib versions (>= 2.31.0) */ #define GLIB_DISABLE_DEPRECATION_WARNINGS #include "gstchromahold.h" #include #include #include GST_DEBUG_CATEGORY_STATIC (gst_chroma_hold_debug); #define GST_CAT_DEFAULT gst_chroma_hold_debug #define DEFAULT_TARGET_R 255 #define DEFAULT_TARGET_G 0 #define DEFAULT_TARGET_B 0 #define DEFAULT_TOLERANCE 30 enum { PROP_0, PROP_TARGET_R, PROP_TARGET_G, PROP_TARGET_B, PROP_TOLERANCE, PROP_LAST }; static GstStaticPadTemplate gst_chroma_hold_src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ ARGB, BGRA, ABGR, RGBA, xRGB, BGRx, xBGR, RGBx}")) ); static GstStaticPadTemplate gst_chroma_hold_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ ARGB, BGRA, ABGR, RGBA, xRGB, BGRx, xBGR, RGBx}")) ); #define GST_CHROMA_HOLD_LOCK(self) G_STMT_START { \ GST_LOG_OBJECT (self, "Locking chromahold from thread %p", g_thread_self ()); \ g_static_mutex_lock (&self->lock); \ GST_LOG_OBJECT (self, "Locked chromahold from thread %p", g_thread_self ()); \ } G_STMT_END #define GST_CHROMA_HOLD_UNLOCK(self) G_STMT_START { \ GST_LOG_OBJECT (self, "Unlocking chromahold from thread %p", \ g_thread_self ()); \ g_static_mutex_unlock (&self->lock); \ } G_STMT_END static gboolean gst_chroma_hold_start (GstBaseTransform * trans); static gboolean gst_chroma_hold_set_info (GstVideoFilter * vfilter, GstCaps * incaps, GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info); static GstFlowReturn gst_chroma_hold_transform_frame_ip (GstVideoFilter * vfilter, GstVideoFrame * frame); static void gst_chroma_hold_before_transform (GstBaseTransform * btrans, GstBuffer * buf); static void gst_chroma_hold_init_params (GstChromaHold * self); static gboolean gst_chroma_hold_set_process_function (GstChromaHold * self); static void gst_chroma_hold_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_chroma_hold_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void gst_chroma_hold_finalize (GObject * object); #define gst_chroma_hold_parent_class parent_class G_DEFINE_TYPE (GstChromaHold, gst_chroma_hold, GST_TYPE_VIDEO_FILTER); static void gst_chroma_hold_class_init (GstChromaHoldClass * klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GstElementClass *gstelement_class = (GstElementClass *) klass; GstBaseTransformClass *btrans_class = (GstBaseTransformClass *) klass; GstVideoFilterClass *vfilter_class = (GstVideoFilterClass *) klass; gobject_class->set_property = gst_chroma_hold_set_property; gobject_class->get_property = gst_chroma_hold_get_property; gobject_class->finalize = gst_chroma_hold_finalize; g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TARGET_R, g_param_spec_uint ("target-r", "Target Red", "The Red target", 0, 255, DEFAULT_TARGET_R, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TARGET_G, g_param_spec_uint ("target-g", "Target Green", "The Green target", 0, 255, DEFAULT_TARGET_G, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TARGET_B, g_param_spec_uint ("target-b", "Target Blue", "The Blue target", 0, 255, DEFAULT_TARGET_B, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TOLERANCE, g_param_spec_uint ("tolerance", "Tolerance", "Tolerance for the target color", 0, 180, DEFAULT_TOLERANCE, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); btrans_class->start = GST_DEBUG_FUNCPTR (gst_chroma_hold_start); btrans_class->before_transform = GST_DEBUG_FUNCPTR (gst_chroma_hold_before_transform); vfilter_class->transform_frame_ip = GST_DEBUG_FUNCPTR (gst_chroma_hold_transform_frame_ip); vfilter_class->set_info = GST_DEBUG_FUNCPTR (gst_chroma_hold_set_info); gst_element_class_set_static_metadata (gstelement_class, "Chroma hold filter", "Filter/Effect/Video", "Removes all color information except for one color", "Sebastian Dröge "); gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&gst_chroma_hold_sink_template)); gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&gst_chroma_hold_src_template)); GST_DEBUG_CATEGORY_INIT (gst_chroma_hold_debug, "chromahold", 0, "chromahold - Removes all color information except for one color"); } static void gst_chroma_hold_init (GstChromaHold * self) { self->target_r = DEFAULT_TARGET_R; self->target_g = DEFAULT_TARGET_G; self->target_b = DEFAULT_TARGET_B; self->tolerance = DEFAULT_TOLERANCE; g_static_mutex_init (&self->lock); } static void gst_chroma_hold_finalize (GObject * object) { GstChromaHold *self = GST_CHROMA_HOLD (object); g_static_mutex_free (&self->lock); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_chroma_hold_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstChromaHold *self = GST_CHROMA_HOLD (object); GST_CHROMA_HOLD_LOCK (self); switch (prop_id) { case PROP_TARGET_R: self->target_r = g_value_get_uint (value); gst_chroma_hold_init_params (self); break; case PROP_TARGET_G: self->target_g = g_value_get_uint (value); gst_chroma_hold_init_params (self); break; case PROP_TARGET_B: self->target_b = g_value_get_uint (value); gst_chroma_hold_init_params (self); break; case PROP_TOLERANCE: self->tolerance = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } GST_CHROMA_HOLD_UNLOCK (self); } static void gst_chroma_hold_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstChromaHold *self = GST_CHROMA_HOLD (object); switch (prop_id) { case PROP_TARGET_R: g_value_set_uint (value, self->target_r); break; case PROP_TARGET_G: g_value_set_uint (value, self->target_g); break; case PROP_TARGET_B: g_value_set_uint (value, self->target_b); break; case PROP_TOLERANCE: g_value_set_uint (value, self->tolerance); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean gst_chroma_hold_set_info (GstVideoFilter * vfilter, GstCaps * incaps, GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info) { GstChromaHold *self = GST_CHROMA_HOLD (vfilter); GST_CHROMA_HOLD_LOCK (self); GST_DEBUG_OBJECT (self, "Setting caps %" GST_PTR_FORMAT " -> %" GST_PTR_FORMAT, incaps, outcaps); self->format = GST_VIDEO_INFO_FORMAT (in_info); self->width = GST_VIDEO_INFO_WIDTH (in_info); self->height = GST_VIDEO_INFO_HEIGHT (in_info); if (!gst_chroma_hold_set_process_function (self)) { GST_WARNING_OBJECT (self, "No processing function for this caps"); GST_CHROMA_HOLD_UNLOCK (self); return FALSE; } GST_CHROMA_HOLD_UNLOCK (self); return TRUE; } static inline gint rgb_to_hue (gint r, gint g, gint b) { gint m, M, C, C2, h; m = MIN (MIN (r, g), b); M = MAX (MAX (r, g), b); C = M - m; C2 = C >> 1; if (C == 0) { return G_MAXUINT; } else if (M == r) { h = ((256 * 60 * (g - b) + C2) / C); } else if (M == g) { h = ((256 * 60 * (b - r) + C2) / C) + 120 * 256; } else { /* if (M == b) */ h = ((256 * 60 * (r - g) + C2) / C) + 240 * 256; } h >>= 8; if (h >= 360) h -= 360; else if (h < 0) h += 360; return h; } static inline gint hue_dist (gint h1, gint h2) { gint d1, d2; d1 = h1 - h2; d2 = h2 - h1; if (d1 < 0) d1 += 360; if (d2 < 0) d2 += 360; return MIN (d1, d2); } static void gst_chroma_hold_process_xrgb (GstVideoFrame * frame, gint width, gint height, GstChromaHold * self) { gint i, j; gint r, g, b; gint grey; gint h1, h2; gint tolerance = self->tolerance; gint p[4]; gint diff; gint row_wrap; guint8 *dest; dest = GST_VIDEO_FRAME_PLANE_DATA (frame, 0); p[0] = GST_VIDEO_FRAME_COMP_POFFSET (frame, 3); p[1] = GST_VIDEO_FRAME_COMP_POFFSET (frame, 0); p[2] = GST_VIDEO_FRAME_COMP_POFFSET (frame, 1); p[3] = GST_VIDEO_FRAME_COMP_POFFSET (frame, 2); row_wrap = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0) - 4 * width; h1 = self->hue; for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { r = dest[p[1]]; g = dest[p[2]]; b = dest[p[3]]; h2 = rgb_to_hue (r, g, b); diff = hue_dist (h1, h2); if (h1 == G_MAXUINT || diff > tolerance) { grey = (13938 * r + 46869 * g + 4730 * b) >> 16; grey = CLAMP (grey, 0, 255); dest[p[1]] = grey; dest[p[2]] = grey; dest[p[3]] = grey; } dest += 4; } dest += row_wrap; } } /* Protected with the chroma hold lock */ static void gst_chroma_hold_init_params (GstChromaHold * self) { self->hue = rgb_to_hue (self->target_r, self->target_g, self->target_b); } /* Protected with the chroma hold lock */ static gboolean gst_chroma_hold_set_process_function (GstChromaHold * self) { self->process = NULL; switch (self->format) { case GST_VIDEO_FORMAT_ARGB: case GST_VIDEO_FORMAT_ABGR: case GST_VIDEO_FORMAT_RGBA: case GST_VIDEO_FORMAT_BGRA: case GST_VIDEO_FORMAT_xRGB: case GST_VIDEO_FORMAT_xBGR: case GST_VIDEO_FORMAT_RGBx: case GST_VIDEO_FORMAT_BGRx: self->process = gst_chroma_hold_process_xrgb; break; default: break; } return self->process != NULL; } static gboolean gst_chroma_hold_start (GstBaseTransform * btrans) { GstChromaHold *self = GST_CHROMA_HOLD (btrans); GST_CHROMA_HOLD_LOCK (self); gst_chroma_hold_init_params (self); GST_CHROMA_HOLD_UNLOCK (self); return TRUE; } static void gst_chroma_hold_before_transform (GstBaseTransform * btrans, GstBuffer * buf) { GstChromaHold *self = GST_CHROMA_HOLD (btrans); GstClockTime timestamp; timestamp = gst_segment_to_stream_time (&btrans->segment, GST_FORMAT_TIME, GST_BUFFER_TIMESTAMP (buf)); GST_LOG ("Got stream time of %" GST_TIME_FORMAT, GST_TIME_ARGS (timestamp)); if (GST_CLOCK_TIME_IS_VALID (timestamp)) gst_object_sync_values (GST_OBJECT (self), timestamp); } static GstFlowReturn gst_chroma_hold_transform_frame_ip (GstVideoFilter * vfilter, GstVideoFrame * frame) { GstChromaHold *self = GST_CHROMA_HOLD (vfilter); GST_CHROMA_HOLD_LOCK (self); if (G_UNLIKELY (!self->process)) { GST_ERROR_OBJECT (self, "Not negotiated yet"); GST_CHROMA_HOLD_UNLOCK (self); return GST_FLOW_NOT_NEGOTIATED; } self->process (frame, self->width, self->height, self); GST_CHROMA_HOLD_UNLOCK (self); return GST_FLOW_OK; }