1 /*
2  * GStreamer
3  * Copyright (C) 2015 Thiago Santos <thiagoss@osg.samsung.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 
22 /**
23  * SECTION:element-digitalzoom
24  * @title: digitalzoom
25  *
26  * Does digital zooming by cropping and scaling an image.
27  *
28  * It is a bin that contains the internal pipeline:
29  * videocrop ! videoscale ! capsfilter
30  *
31  * It keeps monitoring the input caps and when it is set/updated
32  * the capsfilter gets set the same caps to guarantee that the same
33  * input resolution is provided as output.
34  *
35  * Exposes the 'zoom' property as a float to allow setting the amount
36  * of zoom desired. Zooming is done in the center.
37  */
38 
39 #ifdef HAVE_CONFIG_H
40 #  include <config.h>
41 #endif
42 
43 #include <gst/gst-i18n-plugin.h>
44 #include "gstdigitalzoom.h"
45 
46 enum
47 {
48   PROP_0,
49   PROP_ZOOM
50 };
51 
52 GST_DEBUG_CATEGORY (digital_zoom_debug);
53 #define GST_CAT_DEFAULT digital_zoom_debug
54 
55 #define gst_digital_zoom_parent_class parent_class
56 G_DEFINE_TYPE (GstDigitalZoom, gst_digital_zoom, GST_TYPE_BIN);
57 
58 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
59     GST_PAD_SRC,
60     GST_PAD_ALWAYS,
61     GST_STATIC_CAPS_ANY);
62 
63 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
64     GST_PAD_SINK,
65     GST_PAD_ALWAYS,
66     GST_STATIC_CAPS_ANY);
67 
68 static void
gst_digital_zoom_update_crop(GstDigitalZoom * self,GstCaps * caps)69 gst_digital_zoom_update_crop (GstDigitalZoom * self, GstCaps * caps)
70 {
71   gint w2_crop = 0, h2_crop = 0;
72   gint left = 0;
73   gint right = 0;
74   gint top = 0;
75   gint bottom = 0;
76   gint width, height;
77   gfloat zoom;
78   GstStructure *structure;
79 
80   if (caps == NULL || gst_caps_is_any (caps)) {
81     g_object_set (self->capsfilter, "caps", NULL, NULL);
82     return;
83   }
84 
85   structure = gst_caps_get_structure (caps, 0);
86   gst_structure_get (structure, "width", G_TYPE_INT, &width, "height",
87       G_TYPE_INT, &height, NULL);
88 
89   zoom = self->zoom;
90 
91   if (self->videocrop) {
92     /* Update capsfilters to apply the zoom */
93     GST_INFO_OBJECT (self, "zoom: %f, orig size: %dx%d", zoom, width, height);
94 
95     if (zoom != 1.0) {
96       w2_crop = (width - (gint) (width * 1.0 / zoom)) / 2;
97       h2_crop = (height - (gint) (height * 1.0 / zoom)) / 2;
98 
99       left += w2_crop;
100       right += w2_crop;
101       top += h2_crop;
102       bottom += h2_crop;
103 
104       /* force number of pixels cropped from left to be even, to avoid slow code
105        * path on videoscale */
106       left &= 0xFFFE;
107     }
108 
109     GST_INFO_OBJECT (self,
110         "sw cropping: left:%d, right:%d, top:%d, bottom:%d", left, right, top,
111         bottom);
112 
113     g_object_set (self->videocrop, "left", left, "right", right, "top",
114         top, "bottom", bottom, NULL);
115   }
116 }
117 
118 static void
gst_digital_zoom_update_zoom(GstDigitalZoom * self)119 gst_digital_zoom_update_zoom (GstDigitalZoom * self)
120 {
121   GstCaps *caps = NULL;
122 
123   if (!self->elements_created)
124     return;
125 
126   g_object_get (self->capsfilter, "caps", &caps, NULL);
127   if (caps) {
128     gst_digital_zoom_update_crop (self, caps);
129     gst_caps_unref (caps);
130   }
131 }
132 
133 static void
gst_digital_zoom_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)134 gst_digital_zoom_set_property (GObject * object,
135     guint prop_id, const GValue * value, GParamSpec * pspec)
136 {
137   GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (object);
138 
139   switch (prop_id) {
140     case PROP_ZOOM:
141       self->zoom = g_value_get_float (value);
142       GST_DEBUG_OBJECT (self, "Setting zoom: %f", self->zoom);
143       gst_digital_zoom_update_zoom (self);
144       break;
145     default:
146       G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
147       break;
148   }
149 }
150 
151 static void
gst_digital_zoom_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)152 gst_digital_zoom_get_property (GObject * object,
153     guint prop_id, GValue * value, GParamSpec * pspec)
154 {
155   GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (object);
156 
157   switch (prop_id) {
158     case PROP_ZOOM:
159       g_value_set_float (value, self->zoom);
160       break;
161     default:
162       G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
163       break;
164   }
165 }
166 
167 static gboolean
gst_digital_zoom_sink_query(GstPad * sink,GstObject * parent,GstQuery * query)168 gst_digital_zoom_sink_query (GstPad * sink, GstObject * parent,
169     GstQuery * query)
170 {
171   GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (parent);
172   switch (GST_QUERY_TYPE (query)) {
173       /* for caps related queries we want to skip videocrop ! videoscale
174        * as the digital zoom preserves input dimensions */
175     case GST_QUERY_CAPS:
176     case GST_QUERY_ACCEPT_CAPS:
177       if (self->elements_created)
178         return gst_pad_peer_query (self->srcpad, query);
179       /* fall through */
180     default:
181       return gst_pad_query_default (sink, parent, query);
182   }
183 }
184 
185 static gboolean
gst_digital_zoom_src_query(GstPad * sink,GstObject * parent,GstQuery * query)186 gst_digital_zoom_src_query (GstPad * sink, GstObject * parent, GstQuery * query)
187 {
188   GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (parent);
189   switch (GST_QUERY_TYPE (query)) {
190       /* for caps related queries we want to skip videocrop ! videoscale
191        * as the digital zoom preserves input dimensions */
192     case GST_QUERY_CAPS:
193     case GST_QUERY_ACCEPT_CAPS:
194       if (self->elements_created)
195         return gst_pad_peer_query (self->sinkpad, query);
196       /* fall through */
197     default:
198       return gst_pad_query_default (sink, parent, query);
199   }
200 }
201 
202 static gboolean
gst_digital_zoom_sink_event(GstPad * sink,GstObject * parent,GstEvent * event)203 gst_digital_zoom_sink_event (GstPad * sink, GstObject * parent,
204     GstEvent * event)
205 {
206   gboolean ret;
207   gboolean is_caps;
208   GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (parent);
209   GstCaps *old_caps = NULL;
210   GstCaps *caps = NULL;
211 
212   is_caps = GST_EVENT_TYPE (event) == GST_EVENT_CAPS;
213 
214   if (is_caps) {
215     gst_event_parse_caps (event, &caps);
216     g_object_get (self->capsfilter, "caps", &old_caps, NULL);
217     g_object_set (self->capsfilter, "caps", caps, NULL);
218     gst_digital_zoom_update_crop (self, caps);
219   }
220 
221   ret = gst_pad_event_default (sink, parent, event);
222 
223   if (is_caps) {
224     if (!ret) {
225       gst_digital_zoom_update_crop (self, old_caps);
226       g_object_set (self->capsfilter, "caps", old_caps, NULL);
227     }
228 
229     if (old_caps)
230       gst_caps_unref (old_caps);
231   }
232 
233   return ret;
234 }
235 
236 static void
gst_digital_zoom_dispose(GObject * object)237 gst_digital_zoom_dispose (GObject * object)
238 {
239   GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (object);
240 
241   if (self->capsfilter_sinkpad) {
242     gst_object_unref (self->capsfilter_sinkpad);
243     self->capsfilter_sinkpad = NULL;
244   }
245 
246   G_OBJECT_CLASS (parent_class)->dispose (object);
247 }
248 
249 static void
gst_digital_zoom_init(GstDigitalZoom * self)250 gst_digital_zoom_init (GstDigitalZoom * self)
251 {
252   GstPadTemplate *tmpl;
253 
254   tmpl = gst_static_pad_template_get (&src_template);
255   self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", tmpl);
256   gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
257   gst_object_unref (tmpl);
258 
259   tmpl = gst_static_pad_template_get (&sink_template);
260   self->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", tmpl);
261   gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
262   gst_object_unref (tmpl);
263 
264   gst_pad_set_event_function (self->sinkpad,
265       GST_DEBUG_FUNCPTR (gst_digital_zoom_sink_event));
266   gst_pad_set_query_function (self->sinkpad,
267       GST_DEBUG_FUNCPTR (gst_digital_zoom_sink_query));
268 
269   gst_pad_set_query_function (self->srcpad,
270       GST_DEBUG_FUNCPTR (gst_digital_zoom_src_query));
271 
272   self->zoom = 1;
273 }
274 
275 static GstElement *
zoom_create_element(GstDigitalZoom * self,const gchar * element_name,const gchar * name)276 zoom_create_element (GstDigitalZoom * self, const gchar * element_name,
277     const gchar * name)
278 {
279   GstElement *element;
280   element = gst_element_factory_make (element_name, name);
281   if (element == NULL) {
282     GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN,
283         (_("Missing element '%s' - check your GStreamer installation."),
284             element_name), (NULL));
285   }
286   return element;
287 }
288 
289 static gboolean
gst_digital_zoom_create_elements(GstDigitalZoom * self)290 gst_digital_zoom_create_elements (GstDigitalZoom * self)
291 {
292   GstPad *pad;
293 
294   if (self->elements_created)
295     return TRUE;
296 
297   self->videocrop = zoom_create_element (self, "videocrop", "zoom-videocrop");
298   if (self->videocrop == NULL)
299     return FALSE;
300   if (!gst_bin_add (GST_BIN_CAST (self), self->videocrop))
301     return FALSE;
302 
303   self->videoscale =
304       zoom_create_element (self, "videoscale", "zoom-videoscale");
305   if (self->videoscale == NULL)
306     return FALSE;
307   if (!gst_bin_add (GST_BIN_CAST (self), self->videoscale))
308     return FALSE;
309 
310   self->capsfilter =
311       zoom_create_element (self, "capsfilter", "zoom-capsfilter");
312   if (self->capsfilter == NULL)
313     return FALSE;
314   if (!gst_bin_add (GST_BIN_CAST (self), self->capsfilter))
315     return FALSE;
316 
317   if (!gst_element_link_pads_full (self->videocrop, "src", self->videoscale,
318           "sink", GST_PAD_LINK_CHECK_CAPS))
319     return FALSE;
320   if (!gst_element_link_pads_full (self->videoscale, "src", self->capsfilter,
321           "sink", GST_PAD_LINK_CHECK_CAPS))
322     return FALSE;
323 
324   pad = gst_element_get_static_pad (self->videocrop, "sink");
325   gst_ghost_pad_set_target (GST_GHOST_PAD (self->sinkpad), pad);
326   gst_object_unref (pad);
327 
328   pad = gst_element_get_static_pad (self->capsfilter, "src");
329   gst_ghost_pad_set_target (GST_GHOST_PAD (self->srcpad), pad);
330   gst_object_unref (pad);
331 
332   self->capsfilter_sinkpad =
333       gst_element_get_static_pad (self->capsfilter, "sink");
334 
335   self->elements_created = TRUE;
336   return TRUE;
337 }
338 
339 static GstStateChangeReturn
gst_digital_zoom_change_state(GstElement * element,GstStateChange trans)340 gst_digital_zoom_change_state (GstElement * element, GstStateChange trans)
341 {
342   GstDigitalZoom *self = GST_DIGITAL_ZOOM_CAST (element);
343 
344   switch (trans) {
345     case GST_STATE_CHANGE_NULL_TO_READY:
346       if (!gst_digital_zoom_create_elements (self)) {
347         return GST_STATE_CHANGE_FAILURE;
348       }
349       break;
350     default:
351       break;
352   }
353 
354   return GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
355 }
356 
357 static void
gst_digital_zoom_class_init(GstDigitalZoomClass * klass)358 gst_digital_zoom_class_init (GstDigitalZoomClass * klass)
359 {
360   GObjectClass *gobject_class;
361   GstElementClass *gstelement_class;
362 
363   gobject_class = G_OBJECT_CLASS (klass);
364   gstelement_class = GST_ELEMENT_CLASS (klass);
365 
366   gobject_class->dispose = gst_digital_zoom_dispose;
367   gobject_class->set_property = gst_digital_zoom_set_property;
368   gobject_class->get_property = gst_digital_zoom_get_property;
369 
370   /* g_object_class_install_property .... */
371   g_object_class_install_property (gobject_class, PROP_ZOOM,
372       g_param_spec_float ("zoom", "Zoom",
373           "Digital zoom level to be used", 1.0, G_MAXFLOAT, 1.0,
374           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
375   gstelement_class->change_state = gst_digital_zoom_change_state;
376 
377   GST_DEBUG_CATEGORY_INIT (digital_zoom_debug, "digitalzoom",
378       0, "digital zoom");
379 
380   gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
381   gst_element_class_add_static_pad_template (gstelement_class, &src_template);
382 
383   gst_element_class_set_static_metadata (gstelement_class,
384       "Digital zoom bin", "Generic/Video",
385       "Digital zoom bin", "Thiago Santos <thiagoss@osg.samsung.com>");
386 }
387