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.
41 * Note that no special efforts are made to handle chroma-subsampled formats
42 * in the case of odd-valued cropping and compensate for sub-unit chroma plane
43 * shifts for such formats in the case where the "left" or "top" property is
44 * set to an odd number. This doesn't matter for most use cases, but it might
47 * <title>Example launch line</title>
50 * gst-launch -v videotestsrc ! videocrop top=42 left=1 right=4 bottom=0 ! ximagesink
57 * - for packed formats, we could avoid memcpy() in case crop_left
58 * and crop_right are 0 and just create a sub-buffer of the input
67 #include <gst/video/video.h>
69 #include "gstvideocrop.h"
73 GST_DEBUG_CATEGORY_STATIC (videocrop_debug);
74 #define GST_CAT_DEFAULT videocrop_debug
76 static const GstElementDetails video_crop_details = GST_ELEMENT_DETAILS ("Crop",
77 "Filter/Effect/Video",
78 "Crops video into a user-defined region",
79 "Tim-Philipp Müller <tim centricular net>");
90 /* the formats we support */
91 #define VIDEO_CROP_CAPS \
92 GST_VIDEO_CAPS_RGBx ";" \
93 GST_VIDEO_CAPS_xRGB ";" \
94 GST_VIDEO_CAPS_BGRx ";" \
95 GST_VIDEO_CAPS_xBGR ";" \
96 GST_VIDEO_CAPS_RGBA ";" \
97 GST_VIDEO_CAPS_ARGB ";" \
98 GST_VIDEO_CAPS_BGRA ";" \
99 GST_VIDEO_CAPS_ABGR ";" \
100 GST_VIDEO_CAPS_RGB ";" \
101 GST_VIDEO_CAPS_BGR ";" \
102 GST_VIDEO_CAPS_YUV ("AYUV") ";" \
103 GST_VIDEO_CAPS_YUV ("YUY2") ";" \
104 GST_VIDEO_CAPS_YUV ("YVYU") ";" \
105 GST_VIDEO_CAPS_YUV ("UYVY") ";" \
106 GST_VIDEO_CAPS_YUV ("Y800") ";" \
107 GST_VIDEO_CAPS_YUV ("I420") ";" \
108 GST_VIDEO_CAPS_YUV ("YV12") ";" \
109 GST_VIDEO_CAPS_RGB_16 ";" \
110 GST_VIDEO_CAPS_RGB_15
112 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
115 GST_STATIC_CAPS (VIDEO_CROP_CAPS)
118 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
121 GST_STATIC_CAPS (VIDEO_CROP_CAPS)
124 GST_BOILERPLATE (GstVideoCrop, gst_video_crop, GstBaseTransform,
125 GST_TYPE_BASE_TRANSFORM);
127 static void gst_video_crop_set_property (GObject * object, guint prop_id,
128 const GValue * value, GParamSpec * pspec);
129 static void gst_video_crop_get_property (GObject * object, guint prop_id,
130 GValue * value, GParamSpec * pspec);
132 static GstCaps *gst_video_crop_transform_caps (GstBaseTransform * trans,
133 GstPadDirection direction, GstCaps * caps);
134 static GstFlowReturn gst_video_crop_transform (GstBaseTransform * trans,
135 GstBuffer * inbuf, GstBuffer * outbuf);
136 static gboolean gst_video_crop_get_unit_size (GstBaseTransform * trans,
137 GstCaps * caps, guint * size);
138 static gboolean gst_video_crop_set_caps (GstBaseTransform * trans,
139 GstCaps * in_caps, GstCaps * outcaps);
142 gst_video_crop_base_init (gpointer g_class)
144 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
146 gst_element_class_set_details (element_class, &video_crop_details);
148 gst_element_class_add_pad_template (element_class,
149 gst_static_pad_template_get (&sink_template));
150 gst_element_class_add_pad_template (element_class,
151 gst_static_pad_template_get (&src_template));
155 gst_video_crop_class_init (GstVideoCropClass * klass)
157 GObjectClass *gobject_class;
158 GstBaseTransformClass *basetransform_class;
160 gobject_class = (GObjectClass *) klass;
161 basetransform_class = (GstBaseTransformClass *) klass;
163 gobject_class->set_property = gst_video_crop_set_property;
164 gobject_class->get_property = gst_video_crop_get_property;
166 g_object_class_install_property (gobject_class, ARG_LEFT,
167 g_param_spec_int ("left", "Left", "Pixels to crop at left",
168 0, G_MAXINT, 0, G_PARAM_READWRITE));
169 g_object_class_install_property (gobject_class, ARG_RIGHT,
170 g_param_spec_int ("right", "Right", "Pixels to crop at right",
171 0, G_MAXINT, 0, G_PARAM_READWRITE));
172 g_object_class_install_property (gobject_class, ARG_TOP,
173 g_param_spec_int ("top", "Top", "Pixels to crop at top",
174 0, G_MAXINT, 0, G_PARAM_READWRITE));
175 g_object_class_install_property (gobject_class, ARG_BOTTOM,
176 g_param_spec_int ("bottom", "Bottom", "Pixels to crop at bottom",
177 0, G_MAXINT, 0, G_PARAM_READWRITE));
179 basetransform_class->transform = GST_DEBUG_FUNCPTR (gst_video_crop_transform);
180 basetransform_class->transform_caps =
181 GST_DEBUG_FUNCPTR (gst_video_crop_transform_caps);
182 basetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_video_crop_set_caps);
183 basetransform_class->get_unit_size =
184 GST_DEBUG_FUNCPTR (gst_video_crop_get_unit_size);
186 basetransform_class->passthrough_on_same_caps = TRUE;
190 gst_video_crop_init (GstVideoCrop * vcrop, GstVideoCropClass * klass)
192 vcrop->crop_right = 0;
193 vcrop->crop_left = 0;
195 vcrop->crop_bottom = 0;
200 gst_video_crop_get_image_details_from_caps (GstVideoCrop * vcrop,
201 GstVideoCropImageDetails * details, GstCaps * caps)
203 GstStructure *structure;
206 structure = gst_caps_get_structure (caps, 0);
207 if (!gst_structure_get_int (structure, "width", &width) ||
208 !gst_structure_get_int (structure, "height", &height)) {
209 goto incomplete_format;
212 details->width = width;
213 details->height = height;
215 if (gst_structure_has_name (structure, "video/x-raw-rgb")) {
218 if (!gst_structure_get_int (structure, "bpp", &bpp) || (bpp & 0x07) != 0)
219 goto incomplete_format;
221 details->packed = TRUE;
222 details->bytes_per_pixel = bpp / 8;
223 details->stride = GST_ROUND_UP_4 (width * details->bytes_per_pixel);
224 details->size = details->stride * height;
225 } else if (gst_structure_has_name (structure, "video/x-raw-yuv")) {
228 if (!gst_structure_get_fourcc (structure, "format", &format))
229 goto incomplete_format;
232 case GST_MAKE_FOURCC ('A', 'Y', 'U', 'V'):
233 details->packed = TRUE;
234 details->bytes_per_pixel = 4;
235 details->stride = GST_ROUND_UP_4 (width * 4);
236 details->size = details->stride * height;
238 case GST_MAKE_FOURCC ('Y', 'V', 'Y', 'U'):
239 case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'):
240 case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
241 details->packed = TRUE;
242 details->bytes_per_pixel = 2;
243 details->stride = GST_ROUND_UP_4 (width * 2);
244 details->size = details->stride * height;
246 case GST_MAKE_FOURCC ('Y', '8', '0', '0'):
247 details->packed = TRUE;
248 details->bytes_per_pixel = 1;
249 details->stride = GST_ROUND_UP_4 (width);
250 details->size = details->stride * height;
252 case GST_MAKE_FOURCC ('I', '4', '2', '0'):
253 case GST_MAKE_FOURCC ('Y', 'V', '1', '2'):{
254 details->packed = FALSE;
256 details->y_stride = GST_ROUND_UP_4 (width);
257 details->u_stride = GST_ROUND_UP_8 (width) / 2;
258 details->v_stride = GST_ROUND_UP_8 (width) / 2;
260 /* I420 and YV12 have U/V planes swapped, but doesn't matter for us */
262 details->u_off = 0 + details->y_stride * GST_ROUND_UP_2 (height);
263 details->v_off = details->u_off +
264 details->u_stride * (GST_ROUND_UP_2 (height) / 2);
265 details->size = details->v_off +
266 details->v_stride * (GST_ROUND_UP_2 (height) / 2);
281 GST_ELEMENT_ERROR (vcrop, STREAM, NOT_IMPLEMENTED, (NULL),
282 ("Unsupported format"));
288 GST_ELEMENT_ERROR (vcrop, CORE, NEGOTIATION, (NULL),
289 ("Incomplete caps, some required field is missing"));
295 gst_video_crop_get_unit_size (GstBaseTransform * trans, GstCaps * caps,
298 GstVideoCropImageDetails img_details = { 0, };
299 GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
301 if (!gst_video_crop_get_image_details_from_caps (vcrop, &img_details, caps))
304 *size = img_details.size;
309 gst_video_crop_transform_packed (GstVideoCrop * vcrop, GstBuffer * inbuf,
312 guint8 *in_data, *out_data;
315 in_data = GST_BUFFER_DATA (inbuf);
316 out_data = GST_BUFFER_DATA (outbuf);
318 in_data += vcrop->crop_top * vcrop->in.stride;
319 in_data += vcrop->crop_left * vcrop->in.bytes_per_pixel;
321 dx = vcrop->out.width * vcrop->out.bytes_per_pixel;
323 for (i = 0; i < vcrop->out.height; ++i) {
324 memcpy (out_data, in_data, dx);
325 in_data += vcrop->in.stride;
326 out_data += vcrop->out.stride;
331 gst_video_crop_transform_planar (GstVideoCrop * vcrop, GstBuffer * inbuf,
334 guint8 *y_out, *u_out, *v_out;
335 guint8 *y_in, *u_in, *v_in;
339 y_in = GST_BUFFER_DATA (inbuf);
340 y_out = GST_BUFFER_DATA (outbuf);
342 y_in += (vcrop->crop_top * vcrop->in.y_stride) + vcrop->crop_left;
343 dx = vcrop->out.width * 1;
345 for (i = 0; i < vcrop->out.height; ++i) {
346 memcpy (y_out, y_in, dx);
347 y_in += vcrop->in.y_stride;
348 y_out += vcrop->out.y_stride;
352 u_in = GST_BUFFER_DATA (inbuf) + vcrop->in.u_off;
353 u_out = GST_BUFFER_DATA (outbuf) + vcrop->out.u_off;
355 u_in += (vcrop->crop_top / 2) * vcrop->in.u_stride;
356 u_in += vcrop->crop_left / 2;
358 v_in = GST_BUFFER_DATA (inbuf) + vcrop->in.v_off;
359 v_out = GST_BUFFER_DATA (outbuf) + vcrop->out.v_off;
361 v_in += (vcrop->crop_top / 2) * vcrop->in.v_stride;
362 v_in += vcrop->crop_left / 2;
364 dx = GST_ROUND_UP_2 (vcrop->out.width) / 2;
366 for (i = 0; i < GST_ROUND_UP_2 (vcrop->out.height) / 2; ++i) {
367 memcpy (u_out, u_in, dx);
368 memcpy (v_out, v_in, dx);
369 u_in += vcrop->in.u_stride;
370 u_out += vcrop->out.u_stride;
371 v_in += vcrop->in.v_stride;
372 v_out += vcrop->out.v_stride;
377 gst_video_crop_transform (GstBaseTransform * trans, GstBuffer * inbuf,
380 GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
382 /* we should be operating in passthrough mode if there's nothing to do */
383 g_assert (vcrop->noop == FALSE);
385 GST_OBJECT_LOCK (vcrop);
387 if (G_UNLIKELY ((vcrop->crop_left + vcrop->crop_right) >= vcrop->in.width ||
388 (vcrop->crop_top + vcrop->crop_bottom) >= vcrop->in.height)) {
389 GST_OBJECT_UNLOCK (vcrop);
390 goto cropping_too_much;
393 if (vcrop->in.packed) {
394 gst_video_crop_transform_packed (vcrop, inbuf, outbuf);
396 gst_video_crop_transform_planar (vcrop, inbuf, outbuf);
399 GST_OBJECT_UNLOCK (vcrop);
405 /* is there a better error code for this? */
406 GST_ELEMENT_ERROR (vcrop, LIBRARY, SETTINGS, (NULL),
407 ("Can't crop more pixels than there are"));
408 return GST_FLOW_ERROR;
413 gst_video_crop_transform_dimension (gint val, gint delta)
415 gint64 new_val = (gint64) val + (gint64) delta;
417 new_val = CLAMP (new_val, 1, G_MAXINT);
419 return (gint) new_val;
423 gst_video_crop_transform_dimension_value (const GValue * src_val,
424 gint delta, GValue * dest_val)
428 g_value_init (dest_val, G_VALUE_TYPE (src_val));
430 if (G_VALUE_HOLDS_INT (src_val)) {
431 gint ival = g_value_get_int (src_val);
433 ival = gst_video_crop_transform_dimension (ival, delta);
434 g_value_set_int (dest_val, ival);
435 } else if (GST_VALUE_HOLDS_INT_RANGE (src_val)) {
436 gint min = gst_value_get_int_range_min (src_val);
437 gint max = gst_value_get_int_range_max (src_val);
439 min = gst_video_crop_transform_dimension (min, delta);
440 max = gst_video_crop_transform_dimension (max, delta);
441 gst_value_set_int_range (dest_val, min, max);
442 } else if (GST_VALUE_HOLDS_LIST (src_val)) {
445 for (i = 0; i < gst_value_list_get_size (src_val); ++i) {
446 const GValue *list_val;
447 GValue newval = { 0, };
449 list_val = gst_value_list_get_value (src_val, i);
450 if (gst_video_crop_transform_dimension_value (list_val, delta, &newval))
451 gst_value_list_append_value (dest_val, &newval);
452 g_value_unset (&newval);
455 if (gst_value_list_get_size (dest_val) == 0) {
456 g_value_unset (dest_val);
460 g_value_unset (dest_val);
468 gst_video_crop_transform_caps (GstBaseTransform * trans,
469 GstPadDirection direction, GstCaps * caps)
475 vcrop = GST_VIDEO_CROP (trans);
478 return gst_caps_ref (caps);
480 GST_OBJECT_LOCK (vcrop);
481 if (direction == GST_PAD_SRC) {
482 dx = vcrop->crop_left + vcrop->crop_right;
483 dy = vcrop->crop_top + vcrop->crop_bottom;
485 dx = 0 - (vcrop->crop_left + vcrop->crop_right);
486 dy = 0 - (vcrop->crop_top + vcrop->crop_bottom);
488 GST_OBJECT_UNLOCK (vcrop);
490 GST_LOG_OBJECT (vcrop, "transforming caps %" GST_PTR_FORMAT, caps);
492 other_caps = gst_caps_new_empty ();
494 for (i = 0; i < gst_caps_get_size (caps); ++i) {
496 GstStructure *structure, *new_structure;
497 GValue w_val = { 0, }, h_val = {
500 structure = gst_caps_get_structure (caps, i);
502 v = gst_structure_get_value (structure, "width");
503 if (!gst_video_crop_transform_dimension_value (v, dx, &w_val)) {
504 GST_WARNING_OBJECT (vcrop, "could not tranform width value with dx=%d"
505 ", caps structure=%" GST_PTR_FORMAT, dx, structure);
509 v = gst_structure_get_value (structure, "height");
510 if (!gst_video_crop_transform_dimension_value (v, dy, &h_val)) {
511 g_value_unset (&w_val);
512 GST_WARNING_OBJECT (vcrop, "could not tranform height value with dy=%d"
513 ", caps structure=%" GST_PTR_FORMAT, dy, structure);
517 new_structure = gst_structure_copy (structure);
518 gst_structure_set_value (new_structure, "width", &w_val);
519 gst_structure_set_value (new_structure, "height", &h_val);
520 g_value_unset (&w_val);
521 g_value_unset (&h_val);
522 GST_LOG_OBJECT (vcrop, "transformed structure %2d: %" GST_PTR_FORMAT
523 " => %" GST_PTR_FORMAT, i, structure, new_structure);
524 gst_caps_append_structure (other_caps, new_structure);
527 if (gst_caps_is_empty (other_caps)) {
528 gst_caps_unref (other_caps);
536 gst_video_crop_set_caps (GstBaseTransform * trans, GstCaps * incaps,
539 GstVideoCrop *crop = GST_VIDEO_CROP (trans);
541 if (!gst_video_crop_get_image_details_from_caps (crop, &crop->in, incaps)) {
542 GST_DEBUG_OBJECT (crop, "failed to parse input caps %" GST_PTR_FORMAT,
547 if (!gst_video_crop_get_image_details_from_caps (crop, &crop->out, outcaps)) {
548 GST_DEBUG_OBJECT (crop, "failed to parse output caps %" GST_PTR_FORMAT,
557 gst_video_crop_set_property (GObject * object, guint prop_id,
558 const GValue * value, GParamSpec * pspec)
560 GstVideoCrop *video_crop;
562 video_crop = GST_VIDEO_CROP (object);
564 GST_OBJECT_LOCK (video_crop);
567 video_crop->crop_left = g_value_get_int (value);
570 video_crop->crop_right = g_value_get_int (value);
573 video_crop->crop_top = g_value_get_int (value);
576 video_crop->crop_bottom = g_value_get_int (value);
579 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
583 video_crop->noop = ((video_crop->crop_left | video_crop->crop_right |
584 video_crop->crop_top | video_crop->crop_bottom) == 0);
586 GST_OBJECT_UNLOCK (video_crop);
590 gst_video_crop_get_property (GObject * object, guint prop_id, GValue * value,
593 GstVideoCrop *video_crop;
595 video_crop = GST_VIDEO_CROP (object);
597 GST_OBJECT_LOCK (video_crop);
600 g_value_set_int (value, video_crop->crop_left);
603 g_value_set_int (value, video_crop->crop_right);
606 g_value_set_int (value, video_crop->crop_top);
609 g_value_set_int (value, video_crop->crop_bottom);
612 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
615 GST_OBJECT_UNLOCK (video_crop);
619 plugin_init (GstPlugin * plugin)
621 GST_DEBUG_CATEGORY_INIT (videocrop_debug, "videocrop", 0, "videocrop");
623 return gst_element_register (plugin, "videocrop", GST_RANK_NONE,
624 GST_TYPE_VIDEO_CROP);
627 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
630 "Crops video into a user-defined region",
631 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)