1 /* GStreamer video frame cropping
2 * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
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.
21 * SECTION:element-videocrop
22 * @see_also: GstVideoBox
26 * This element crops video frames, meaning it can remove parts of the
27 * picture on the left, right, top or bottom of the picture and output
28 * a smaller picture than the input picture, with the unwanted parts at the
32 * The videocrop element is similar to the videobox element, but its main
33 * goal is to support a multitude of formats as efficiently as possible.
34 * Unlike videbox, it cannot add borders to the picture and unlike videbox
35 * it will always output images in exactly the same format as the input image.
38 * If there is nothing to crop, the element will operate in pass-through mode.
40 * <title>Example launch line</title>
43 * gst-launch -v videotestsrc ! videocrop top=42 left=1 right=4 bottom=0 ! ximagesink
50 * - for packed formats, we could avoid memcpy() in case crop_left
51 * and crop_right are 0 and just create a sub-buffer of the input
60 #include <gst/video/video.h>
61 #include <gst/base/gstbasetransform.h>
62 #include <liboil/liboil.h>
65 GST_DEBUG_CATEGORY_STATIC (videocrop_debug);
66 #define GST_CAT_DEFAULT videocrop_debug
68 #define GST_TYPE_VIDEO_CROP \
69 (gst_video_crop_get_type())
70 #define GST_VIDEO_CROP(obj) \
71 (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VIDEO_CROP,GstVideoCrop))
72 #define GST_VIDEO_CROP_CLASS(klass) \
73 (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VIDEO_CROP,GstVideoCropClass))
74 #define GST_IS_VIDEO_CROP(obj) \
75 (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VIDEO_CROP))
76 #define GST_IS_VIDEO_CROP_CLASS(klass) \
77 (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VIDEO_CROP))
79 typedef struct _GstVideoCropImageDetails GstVideoCropImageDetails;
80 struct _GstVideoCropImageDetails
82 gboolean packed; /* TRUE if packed, FALSE if planar */
88 /* for packed RGB and YUV */
90 guint bytes_per_pixel;
93 guint y_stride, y_off;
94 guint u_stride, u_off;
95 guint v_stride, v_off;
98 typedef struct _GstVideoCrop GstVideoCrop;
99 typedef struct _GstVideoCropClass GstVideoCropClass;
103 GstBaseTransform basetransform;
105 gboolean noop; /* TRUE if crop_left,_right,_top and _bottom are all 0 */
112 GstVideoCropImageDetails in; /* details of input image */
113 GstVideoCropImageDetails out; /* details of output image */
116 struct _GstVideoCropClass
118 GstBaseTransformClass basetransform_class;
121 static const GstElementDetails video_crop_details = GST_ELEMENT_DETAILS ("Crop",
122 "Filter/Effect/Video",
123 "Crops video into a user-defined region",
124 "Tim-Philipp Müller <tim centricular net>");
135 /* the formats we support */
136 #define VIDEO_CROP_CAPS \
137 GST_VIDEO_CAPS_RGBx ";" \
138 GST_VIDEO_CAPS_xRGB ";" \
139 GST_VIDEO_CAPS_BGRx ";" \
140 GST_VIDEO_CAPS_xBGR ";" \
141 GST_VIDEO_CAPS_RGBA ";" \
142 GST_VIDEO_CAPS_ARGB ";" \
143 GST_VIDEO_CAPS_BGRA ";" \
144 GST_VIDEO_CAPS_ABGR ";" \
145 GST_VIDEO_CAPS_RGB ";" \
146 GST_VIDEO_CAPS_BGR ";" \
147 GST_VIDEO_CAPS_YUV ("AYUV") ";" \
148 GST_VIDEO_CAPS_YUV ("YUY2") ";" \
149 GST_VIDEO_CAPS_YUV ("YVYU") ";" \
150 GST_VIDEO_CAPS_YUV ("UYVY") ";" \
151 GST_VIDEO_CAPS_YUV ("Y800") ";" \
152 GST_VIDEO_CAPS_YUV ("I420") ";" \
153 GST_VIDEO_CAPS_YUV ("YV12") ";" \
154 GST_VIDEO_CAPS_RGB_16 ";" \
155 GST_VIDEO_CAPS_RGB_15
157 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
160 GST_STATIC_CAPS (VIDEO_CROP_CAPS)
163 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
166 GST_STATIC_CAPS (VIDEO_CROP_CAPS)
169 GST_BOILERPLATE (GstVideoCrop, gst_video_crop, GstBaseTransform,
170 GST_TYPE_BASE_TRANSFORM);
172 static void gst_video_crop_set_property (GObject * object, guint prop_id,
173 const GValue * value, GParamSpec * pspec);
174 static void gst_video_crop_get_property (GObject * object, guint prop_id,
175 GValue * value, GParamSpec * pspec);
177 static GstCaps *gst_video_crop_transform_caps (GstBaseTransform * trans,
178 GstPadDirection direction, GstCaps * caps);
179 static GstFlowReturn gst_video_crop_transform (GstBaseTransform * trans,
180 GstBuffer * inbuf, GstBuffer * outbuf);
181 static gboolean gst_video_crop_get_unit_size (GstBaseTransform * trans,
182 GstCaps * caps, guint * size);
183 static gboolean gst_video_crop_set_caps (GstBaseTransform * trans,
184 GstCaps * in_caps, GstCaps * outcaps);
187 gst_video_crop_base_init (gpointer g_class)
189 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
191 gst_element_class_set_details (element_class, &video_crop_details);
193 gst_element_class_add_pad_template (element_class,
194 gst_static_pad_template_get (&sink_template));
195 gst_element_class_add_pad_template (element_class,
196 gst_static_pad_template_get (&src_template));
200 gst_video_crop_class_init (GstVideoCropClass * klass)
202 GObjectClass *gobject_class;
203 GstBaseTransformClass *basetransform_class;
205 gobject_class = (GObjectClass *) klass;
206 basetransform_class = (GstBaseTransformClass *) klass;
208 gobject_class->set_property = gst_video_crop_set_property;
209 gobject_class->get_property = gst_video_crop_get_property;
211 g_object_class_install_property (gobject_class, ARG_LEFT,
212 g_param_spec_int ("left", "Left", "Pixels to crop at left",
213 0, G_MAXINT, 0, G_PARAM_READWRITE));
214 g_object_class_install_property (gobject_class, ARG_RIGHT,
215 g_param_spec_int ("right", "Right", "Pixels to crop at right",
216 0, G_MAXINT, 0, G_PARAM_READWRITE));
217 g_object_class_install_property (gobject_class, ARG_TOP,
218 g_param_spec_int ("top", "Top", "Pixels to crop at top",
219 0, G_MAXINT, 0, G_PARAM_READWRITE));
220 g_object_class_install_property (gobject_class, ARG_BOTTOM,
221 g_param_spec_int ("bottom", "Bottom", "Pixels to crop at bottom",
222 0, G_MAXINT, 0, G_PARAM_READWRITE));
224 basetransform_class->transform = GST_DEBUG_FUNCPTR (gst_video_crop_transform);
225 basetransform_class->transform_caps =
226 GST_DEBUG_FUNCPTR (gst_video_crop_transform_caps);
227 basetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_video_crop_set_caps);
228 basetransform_class->get_unit_size =
229 GST_DEBUG_FUNCPTR (gst_video_crop_get_unit_size);
231 basetransform_class->passthrough_on_same_caps = TRUE;
237 gst_video_crop_init (GstVideoCrop * vcrop, GstVideoCropClass * klass)
239 vcrop->crop_right = 0;
240 vcrop->crop_left = 0;
242 vcrop->crop_bottom = 0;
247 gst_video_crop_get_image_details_from_caps (GstVideoCrop * vcrop,
248 GstVideoCropImageDetails * details, GstCaps * caps)
250 GstStructure *structure;
253 structure = gst_caps_get_structure (caps, 0);
254 if (!gst_structure_get_int (structure, "width", &width) ||
255 !gst_structure_get_int (structure, "height", &height)) {
256 goto incomplete_format;
259 details->width = width;
260 details->height = height;
262 if (gst_structure_has_name (structure, "video/x-raw-rgb")) {
265 if (!gst_structure_get_int (structure, "bpp", &bpp) || (bpp & 0x07) != 0)
266 goto incomplete_format;
268 details->packed = TRUE;
269 details->bytes_per_pixel = bpp / 8;
270 details->stride = GST_ROUND_UP_4 (width * details->bytes_per_pixel);
271 details->size = details->stride * height;
272 } else if (gst_structure_has_name (structure, "video/x-raw-yuv")) {
275 if (!gst_structure_get_fourcc (structure, "format", &format))
276 goto incomplete_format;
279 case GST_MAKE_FOURCC ('A', 'Y', 'U', 'V'):
280 details->packed = TRUE;
281 details->bytes_per_pixel = 4;
282 details->stride = GST_ROUND_UP_4 (width * 4);
283 details->size = details->stride * height;
285 case GST_MAKE_FOURCC ('Y', 'V', 'Y', 'U'):
286 case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'):
287 case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
288 details->packed = TRUE;
289 details->bytes_per_pixel = 2;
290 details->stride = GST_ROUND_UP_4 (width * 2);
291 details->size = details->stride * height;
293 case GST_MAKE_FOURCC ('Y', '8', '0', '0'):
294 details->packed = TRUE;
295 details->bytes_per_pixel = 1;
296 details->stride = GST_ROUND_UP_4 (width);
297 details->size = details->stride * height;
299 case GST_MAKE_FOURCC ('I', '4', '2', '0'):
300 case GST_MAKE_FOURCC ('Y', 'V', '1', '2'):{
301 details->packed = FALSE;
303 details->y_stride = GST_ROUND_UP_4 (width);
304 details->u_stride = GST_ROUND_UP_8 (width) / 2;
305 details->v_stride = GST_ROUND_UP_8 (width) / 2;
307 /* I420 and YV12 have U/V planes swapped, but doesn't matter for us */
309 details->u_off = 0 + details->y_stride * GST_ROUND_UP_2 (height);
310 details->v_off = details->u_off +
311 details->u_stride * (GST_ROUND_UP_2 (height) / 2);
312 details->size = details->v_off +
313 details->v_stride * (GST_ROUND_UP_2 (height) / 2);
328 GST_ELEMENT_ERROR (vcrop, STREAM, NOT_IMPLEMENTED, (NULL),
329 ("Unsupported format"));
335 GST_ELEMENT_ERROR (vcrop, CORE, NEGOTIATION, (NULL),
336 ("Incomplete caps, some required field is missing"));
342 gst_video_crop_get_unit_size (GstBaseTransform * trans, GstCaps * caps,
345 GstVideoCropImageDetails img_details = { 0, };
346 GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
348 if (!gst_video_crop_get_image_details_from_caps (vcrop, &img_details, caps))
351 *size = img_details.size;
356 gst_video_crop_transform_packed (GstVideoCrop * vcrop, GstBuffer * inbuf,
359 guint8 *in_data, *out_data;
362 in_data = GST_BUFFER_DATA (inbuf);
363 out_data = GST_BUFFER_DATA (outbuf);
365 in_data += vcrop->crop_top * vcrop->in.stride;
366 in_data += vcrop->crop_left * vcrop->in.bytes_per_pixel;
368 dx = vcrop->out.width * vcrop->out.bytes_per_pixel;
370 for (i = 0; i < vcrop->out.height; ++i) {
371 oil_memcpy (out_data, in_data, dx);
372 in_data += vcrop->in.stride;
373 out_data += vcrop->out.stride;
378 gst_video_crop_transform_planar (GstVideoCrop * vcrop, GstBuffer * inbuf,
381 guint8 *y_out, *u_out, *v_out;
382 guint8 *y_in, *u_in, *v_in;
386 y_in = GST_BUFFER_DATA (inbuf);
387 y_out = GST_BUFFER_DATA (outbuf);
389 y_in += (vcrop->crop_top * vcrop->in.y_stride) + vcrop->crop_left;
390 dx = vcrop->out.width * 1;
392 for (i = 0; i < vcrop->out.height; ++i) {
393 oil_memcpy (y_out, y_in, dx);
394 y_in += vcrop->in.y_stride;
395 y_out += vcrop->out.y_stride;
399 u_in = GST_BUFFER_DATA (inbuf) + vcrop->in.u_off;
400 u_out = GST_BUFFER_DATA (outbuf) + vcrop->out.u_off;
402 u_in += (vcrop->crop_top / 2) * vcrop->in.u_stride;
403 u_in += vcrop->crop_left / 2;
405 v_in = GST_BUFFER_DATA (inbuf) + vcrop->in.v_off;
406 v_out = GST_BUFFER_DATA (outbuf) + vcrop->out.v_off;
408 v_in += (vcrop->crop_top / 2) * vcrop->in.v_stride;
409 v_in += vcrop->crop_left / 2;
411 dx = GST_ROUND_UP_2 (vcrop->out.width) / 2;
413 for (i = 0; i < GST_ROUND_UP_2 (vcrop->out.height) / 2; ++i) {
414 oil_memcpy (u_out, u_in, dx);
415 oil_memcpy (v_out, v_in, dx);
416 u_in += vcrop->in.u_stride;
417 u_out += vcrop->out.u_stride;
418 v_in += vcrop->in.v_stride;
419 v_out += vcrop->out.v_stride;
424 gst_video_crop_transform (GstBaseTransform * trans, GstBuffer * inbuf,
427 GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
429 /* we should be operating in passthrough mode if there's nothing to do */
430 g_assert (vcrop->noop == FALSE);
432 GST_OBJECT_LOCK (vcrop);
434 if (G_UNLIKELY ((vcrop->crop_left + vcrop->crop_right) >= vcrop->in.width ||
435 (vcrop->crop_top + vcrop->crop_bottom) >= vcrop->in.height)) {
436 GST_OBJECT_UNLOCK (vcrop);
437 goto cropping_too_much;
440 if (vcrop->in.packed) {
441 gst_video_crop_transform_packed (vcrop, inbuf, outbuf);
443 gst_video_crop_transform_planar (vcrop, inbuf, outbuf);
446 GST_OBJECT_UNLOCK (vcrop);
452 /* is there a better error code for this? */
453 GST_ELEMENT_ERROR (vcrop, LIBRARY, SETTINGS, (NULL),
454 ("Can't crop more pixels than there are"));
455 return GST_FLOW_ERROR;
460 gst_video_crop_transform_dimension (gint val, gint delta)
462 gint64 new_val = (gint64) val + (gint64) delta;
464 new_val = CLAMP (new_val, 1, G_MAXINT);
466 return (gint) new_val;
470 gst_video_crop_transform_dimension_value (const GValue * src_val,
471 gint delta, GValue * dest_val)
475 g_value_init (dest_val, G_VALUE_TYPE (src_val));
477 if (G_VALUE_HOLDS_INT (src_val)) {
478 gint ival = g_value_get_int (src_val);
480 ival = gst_video_crop_transform_dimension (ival, delta);
481 g_value_set_int (dest_val, ival);
482 } else if (GST_VALUE_HOLDS_INT_RANGE (src_val)) {
483 gint min = gst_value_get_int_range_min (src_val);
484 gint max = gst_value_get_int_range_max (src_val);
486 min = gst_video_crop_transform_dimension (min, delta);
487 max = gst_video_crop_transform_dimension (max, delta);
488 gst_value_set_int_range (dest_val, min, max);
489 } else if (GST_VALUE_HOLDS_LIST (src_val)) {
492 for (i = 0; i < gst_value_list_get_size (src_val); ++i) {
493 const GValue *list_val;
494 GValue newval = { 0, };
496 list_val = gst_value_list_get_value (src_val, i);
497 if (gst_video_crop_transform_dimension_value (list_val, delta, &newval))
498 gst_value_list_append_value (dest_val, &newval);
499 g_value_unset (&newval);
502 if (gst_value_list_get_size (dest_val) == 0) {
503 g_value_unset (dest_val);
507 g_value_unset (dest_val);
515 gst_video_crop_transform_caps (GstBaseTransform * trans,
516 GstPadDirection direction, GstCaps * caps)
522 vcrop = GST_VIDEO_CROP (trans);
525 return gst_caps_ref (caps);
527 GST_OBJECT_LOCK (vcrop);
528 if (direction == GST_PAD_SRC) {
529 dx = vcrop->crop_left + vcrop->crop_right;
530 dy = vcrop->crop_top + vcrop->crop_bottom;
532 dx = 0 - (vcrop->crop_left + vcrop->crop_right);
533 dy = 0 - (vcrop->crop_top + vcrop->crop_bottom);
535 GST_OBJECT_UNLOCK (vcrop);
537 GST_LOG_OBJECT (vcrop, "transforming caps %" GST_PTR_FORMAT, caps);
539 other_caps = gst_caps_new_empty ();
541 for (i = 0; i < gst_caps_get_size (caps); ++i) {
543 GstStructure *structure, *new_structure;
544 GValue w_val = { 0, }, h_val = {
547 structure = gst_caps_get_structure (caps, i);
549 v = gst_structure_get_value (structure, "width");
550 if (!gst_video_crop_transform_dimension_value (v, dx, &w_val)) {
551 GST_WARNING_OBJECT (vcrop, "could not tranform width value with dx=%d"
552 ", caps structure=%" GST_PTR_FORMAT, dx, structure);
556 v = gst_structure_get_value (structure, "height");
557 if (!gst_video_crop_transform_dimension_value (v, dy, &h_val)) {
558 g_value_unset (&w_val);
559 GST_WARNING_OBJECT (vcrop, "could not tranform height value with dy=%d"
560 ", caps structure=%" GST_PTR_FORMAT, dy, structure);
564 new_structure = gst_structure_copy (structure);
565 gst_structure_set_value (new_structure, "width", &w_val);
566 gst_structure_set_value (new_structure, "height", &h_val);
567 g_value_unset (&w_val);
568 g_value_unset (&h_val);
569 GST_LOG_OBJECT (vcrop, "transformed structure %2d: %" GST_PTR_FORMAT
570 " => %" GST_PTR_FORMAT, i, structure, new_structure);
571 gst_caps_append_structure (other_caps, new_structure);
574 if (gst_caps_is_empty (other_caps)) {
575 gst_caps_unref (other_caps);
583 gst_video_crop_set_caps (GstBaseTransform * trans, GstCaps * incaps,
586 GstVideoCrop *crop = GST_VIDEO_CROP (trans);
588 if (!gst_video_crop_get_image_details_from_caps (crop, &crop->in, incaps)) {
589 GST_DEBUG_OBJECT (crop, "failed to parse input caps %" GST_PTR_FORMAT,
594 if (!gst_video_crop_get_image_details_from_caps (crop, &crop->out, outcaps)) {
595 GST_DEBUG_OBJECT (crop, "failed to parse output caps %" GST_PTR_FORMAT,
604 gst_video_crop_set_property (GObject * object, guint prop_id,
605 const GValue * value, GParamSpec * pspec)
607 GstVideoCrop *video_crop;
609 video_crop = GST_VIDEO_CROP (object);
611 GST_OBJECT_LOCK (video_crop);
614 video_crop->crop_left = g_value_get_int (value);
617 video_crop->crop_right = g_value_get_int (value);
620 video_crop->crop_top = g_value_get_int (value);
623 video_crop->crop_bottom = g_value_get_int (value);
626 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
630 video_crop->noop = ((video_crop->crop_left | video_crop->crop_right |
631 video_crop->crop_top | video_crop->crop_bottom) == 0);
633 GST_OBJECT_UNLOCK (video_crop);
637 gst_video_crop_get_property (GObject * object, guint prop_id, GValue * value,
640 GstVideoCrop *video_crop;
642 video_crop = GST_VIDEO_CROP (object);
644 GST_OBJECT_LOCK (video_crop);
647 g_value_set_int (value, video_crop->crop_left);
650 g_value_set_int (value, video_crop->crop_right);
653 g_value_set_int (value, video_crop->crop_top);
656 g_value_set_int (value, video_crop->crop_bottom);
659 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
662 GST_OBJECT_UNLOCK (video_crop);
666 plugin_init (GstPlugin * plugin)
668 GST_DEBUG_CATEGORY_INIT (videocrop_debug, "videocrop", 0, "videocrop");
670 return gst_element_register (plugin, "videocrop", GST_RANK_NONE,
671 GST_TYPE_VIDEO_CROP);
674 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
677 "Crops video into a user-defined region",
678 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)