Port/rewrite videocrop from scratch for GStreamer-0.10, and make it support all forma...
[platform/upstream/gst-plugins-good.git] / gst / videocrop / gstvideocrop.c
1 /* GStreamer video frame cropping
2  * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
3  *
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.
8  *
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.
13  *
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.
18  */
19
20 /**
21  * SECTION:element-videocrop
22  * @see_also: GstVideoBox
23  *
24  * <refsect2>
25  * <para>
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
29  * border removed.
30  * </para>
31  * <para>
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.
36  * </para>
37  * <para>
38  * If there is nothing to crop, the element will operate in pass-through mode.
39  * </para>
40  * <title>Example launch line</title>
41  * <para>
42  * <programlisting>
43  * gst-launch -v videotestsrc ! videocrop top=42 left=1 right=4 bottom=0 ! ximagesink
44  * </programlisting>
45  * </para>
46  * </refsect2>
47  */
48
49 /* TODO:
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
52  *    buffer
53  */
54
55 #ifdef HAVE_CONFIG_H
56 #include "config.h"
57 #endif
58
59 #include <gst/gst.h>
60 #include <gst/video/video.h>
61 #include <gst/base/gstbasetransform.h>
62 #include <liboil/liboil.h>
63 #include <string.h>
64
65 GST_DEBUG_CATEGORY_STATIC (videocrop_debug);
66 #define GST_CAT_DEFAULT videocrop_debug
67
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))
78
79 typedef struct _GstVideoCropImageDetails GstVideoCropImageDetails;
80 struct _GstVideoCropImageDetails
81 {
82   gboolean packed;              /* TRUE if packed, FALSE if planar */
83
84   guint width;
85   guint height;
86   guint size;
87
88   /* for packed RGB and YUV */
89   guint stride;
90   guint bytes_per_pixel;
91
92   /* for planar YUV */
93   guint y_stride, y_off;
94   guint u_stride, u_off;
95   guint v_stride, v_off;
96 };
97
98 typedef struct _GstVideoCrop GstVideoCrop;
99 typedef struct _GstVideoCropClass GstVideoCropClass;
100
101 struct _GstVideoCrop
102 {
103   GstBaseTransform basetransform;
104
105   gboolean noop;                /* TRUE if crop_left,_right,_top and _bottom are all 0 */
106
107   gint crop_left;
108   gint crop_right;
109   gint crop_top;
110   gint crop_bottom;
111
112   GstVideoCropImageDetails in;  /* details of input image */
113   GstVideoCropImageDetails out; /* details of output image */
114 };
115
116 struct _GstVideoCropClass
117 {
118   GstBaseTransformClass basetransform_class;
119 };
120
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>");
125
126 enum
127 {
128   ARG_0,
129   ARG_LEFT,
130   ARG_RIGHT,
131   ARG_TOP,
132   ARG_BOTTOM
133 };
134
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
156
157 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
158     GST_PAD_SRC,
159     GST_PAD_ALWAYS,
160     GST_STATIC_CAPS (VIDEO_CROP_CAPS)
161     );
162
163 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
164     GST_PAD_SINK,
165     GST_PAD_ALWAYS,
166     GST_STATIC_CAPS (VIDEO_CROP_CAPS)
167     );
168
169 GST_BOILERPLATE (GstVideoCrop, gst_video_crop, GstBaseTransform,
170     GST_TYPE_BASE_TRANSFORM);
171
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);
176
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);
185
186 static void
187 gst_video_crop_base_init (gpointer g_class)
188 {
189   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
190
191   gst_element_class_set_details (element_class, &video_crop_details);
192
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));
197 }
198
199 static void
200 gst_video_crop_class_init (GstVideoCropClass * klass)
201 {
202   GObjectClass *gobject_class;
203   GstBaseTransformClass *basetransform_class;
204
205   gobject_class = (GObjectClass *) klass;
206   basetransform_class = (GstBaseTransformClass *) klass;
207
208   gobject_class->set_property = gst_video_crop_set_property;
209   gobject_class->get_property = gst_video_crop_get_property;
210
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));
223
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);
230
231   basetransform_class->passthrough_on_same_caps = TRUE;
232
233   oil_init ();
234 }
235
236 static void
237 gst_video_crop_init (GstVideoCrop * vcrop, GstVideoCropClass * klass)
238 {
239   vcrop->crop_right = 0;
240   vcrop->crop_left = 0;
241   vcrop->crop_top = 0;
242   vcrop->crop_bottom = 0;
243   vcrop->noop = TRUE;
244 }
245
246 static gboolean
247 gst_video_crop_get_image_details_from_caps (GstVideoCrop * vcrop,
248     GstVideoCropImageDetails * details, GstCaps * caps)
249 {
250   GstStructure *structure;
251   gint width, height;
252
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;
257   }
258
259   details->width = width;
260   details->height = height;
261
262   if (gst_structure_has_name (structure, "video/x-raw-rgb")) {
263     gint bpp = 0;
264
265     if (!gst_structure_get_int (structure, "bpp", &bpp) || (bpp & 0x07) != 0)
266       goto incomplete_format;
267
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")) {
273     guint32 format = 0;
274
275     if (!gst_structure_get_fourcc (structure, "format", &format))
276       goto incomplete_format;
277
278     switch (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;
284         break;
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;
292         break;
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;
298         break;
299       case GST_MAKE_FOURCC ('I', '4', '2', '0'):
300       case GST_MAKE_FOURCC ('Y', 'V', '1', '2'):{
301         details->packed = FALSE;
302
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;
306
307         /* I420 and YV12 have U/V planes swapped, but doesn't matter for us */
308         details->y_off = 0;
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);
314         break;
315       }
316       default:
317         goto unknown_format;
318     }
319   } else {
320     goto unknown_format;
321   }
322
323   return TRUE;
324
325   /* ERRORS */
326 unknown_format:
327   {
328     GST_ELEMENT_ERROR (vcrop, STREAM, NOT_IMPLEMENTED, (NULL),
329         ("Unsupported format"));
330     return FALSE;
331   }
332
333 incomplete_format:
334   {
335     GST_ELEMENT_ERROR (vcrop, CORE, NEGOTIATION, (NULL),
336         ("Incomplete caps, some required field is missing"));
337     return FALSE;
338   }
339 }
340
341 static gboolean
342 gst_video_crop_get_unit_size (GstBaseTransform * trans, GstCaps * caps,
343     guint * size)
344 {
345   GstVideoCropImageDetails img_details = { 0, };
346   GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
347
348   if (!gst_video_crop_get_image_details_from_caps (vcrop, &img_details, caps))
349     return FALSE;
350
351   *size = img_details.size;
352   return TRUE;
353 }
354
355 static void
356 gst_video_crop_transform_packed (GstVideoCrop * vcrop, GstBuffer * inbuf,
357     GstBuffer * outbuf)
358 {
359   guint8 *in_data, *out_data;
360   guint i, dx;
361
362   in_data = GST_BUFFER_DATA (inbuf);
363   out_data = GST_BUFFER_DATA (outbuf);
364
365   in_data += vcrop->crop_top * vcrop->in.stride;
366   in_data += vcrop->crop_left * vcrop->in.bytes_per_pixel;
367
368   dx = vcrop->out.width * vcrop->out.bytes_per_pixel;
369
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;
374   }
375 }
376
377 static void
378 gst_video_crop_transform_planar (GstVideoCrop * vcrop, GstBuffer * inbuf,
379     GstBuffer * outbuf)
380 {
381   guint8 *y_out, *u_out, *v_out;
382   guint8 *y_in, *u_in, *v_in;
383   guint i, dx;
384
385   /* Y plane */
386   y_in = GST_BUFFER_DATA (inbuf);
387   y_out = GST_BUFFER_DATA (outbuf);
388
389   y_in += (vcrop->crop_top * vcrop->in.y_stride) + vcrop->crop_left;
390   dx = vcrop->out.width * 1;
391
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;
396   }
397
398   /* U + V planes */
399   u_in = GST_BUFFER_DATA (inbuf) + vcrop->in.u_off;
400   u_out = GST_BUFFER_DATA (outbuf) + vcrop->out.u_off;
401
402   u_in += (vcrop->crop_top / 2) * vcrop->in.u_stride;
403   u_in += vcrop->crop_left / 2;
404
405   v_in = GST_BUFFER_DATA (inbuf) + vcrop->in.v_off;
406   v_out = GST_BUFFER_DATA (outbuf) + vcrop->out.v_off;
407
408   v_in += (vcrop->crop_top / 2) * vcrop->in.v_stride;
409   v_in += vcrop->crop_left / 2;
410
411   dx = GST_ROUND_UP_2 (vcrop->out.width) / 2;
412
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;
420   }
421 }
422
423 static GstFlowReturn
424 gst_video_crop_transform (GstBaseTransform * trans, GstBuffer * inbuf,
425     GstBuffer * outbuf)
426 {
427   GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
428
429   /* we should be operating in passthrough mode if there's nothing to do */
430   g_assert (vcrop->noop == FALSE);
431
432   GST_OBJECT_LOCK (vcrop);
433
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;
438   }
439
440   if (vcrop->in.packed) {
441     gst_video_crop_transform_packed (vcrop, inbuf, outbuf);
442   } else {
443     gst_video_crop_transform_planar (vcrop, inbuf, outbuf);
444   }
445
446   GST_OBJECT_UNLOCK (vcrop);
447
448   return GST_FLOW_OK;
449
450 cropping_too_much:
451   {
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;
456   }
457 }
458
459 static gint
460 gst_video_crop_transform_dimension (gint val, gint delta)
461 {
462   gint64 new_val = (gint64) val + (gint64) delta;
463
464   new_val = CLAMP (new_val, 1, G_MAXINT);
465
466   return (gint) new_val;
467 }
468
469 static gboolean
470 gst_video_crop_transform_dimension_value (const GValue * src_val,
471     gint delta, GValue * dest_val)
472 {
473   gboolean ret = TRUE;
474
475   g_value_init (dest_val, G_VALUE_TYPE (src_val));
476
477   if (G_VALUE_HOLDS_INT (src_val)) {
478     gint ival = g_value_get_int (src_val);
479
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);
485
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)) {
490     gint i;
491
492     for (i = 0; i < gst_value_list_get_size (src_val); ++i) {
493       const GValue *list_val;
494       GValue newval = { 0, };
495
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);
500     }
501
502     if (gst_value_list_get_size (dest_val) == 0) {
503       g_value_unset (dest_val);
504       ret = FALSE;
505     }
506   } else {
507     g_value_unset (dest_val);
508     ret = FALSE;
509   }
510
511   return ret;
512 }
513
514 static GstCaps *
515 gst_video_crop_transform_caps (GstBaseTransform * trans,
516     GstPadDirection direction, GstCaps * caps)
517 {
518   GstVideoCrop *vcrop;
519   GstCaps *other_caps;
520   gint dy, dx, i;
521
522   vcrop = GST_VIDEO_CROP (trans);
523
524   if (vcrop->noop)
525     return gst_caps_ref (caps);
526
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;
531   } else {
532     dx = 0 - (vcrop->crop_left + vcrop->crop_right);
533     dy = 0 - (vcrop->crop_top + vcrop->crop_bottom);
534   }
535   GST_OBJECT_UNLOCK (vcrop);
536
537   GST_LOG_OBJECT (vcrop, "transforming caps %" GST_PTR_FORMAT, caps);
538
539   other_caps = gst_caps_new_empty ();
540
541   for (i = 0; i < gst_caps_get_size (caps); ++i) {
542     const GValue *v;
543     GstStructure *structure, *new_structure;
544     GValue w_val = { 0, }, h_val = {
545     0,};
546
547     structure = gst_caps_get_structure (caps, i);
548
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);
553       continue;
554     }
555
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);
561       continue;
562     }
563
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);
572   }
573
574   if (gst_caps_is_empty (other_caps)) {
575     gst_caps_unref (other_caps);
576     other_caps = NULL;
577   }
578
579   return other_caps;
580 }
581
582 static gboolean
583 gst_video_crop_set_caps (GstBaseTransform * trans, GstCaps * incaps,
584     GstCaps * outcaps)
585 {
586   GstVideoCrop *crop = GST_VIDEO_CROP (trans);
587
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,
590         incaps);
591     return FALSE;
592   }
593
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,
596         outcaps);
597     return FALSE;
598   }
599
600   return TRUE;
601 }
602
603 static void
604 gst_video_crop_set_property (GObject * object, guint prop_id,
605     const GValue * value, GParamSpec * pspec)
606 {
607   GstVideoCrop *video_crop;
608
609   video_crop = GST_VIDEO_CROP (object);
610
611   GST_OBJECT_LOCK (video_crop);
612   switch (prop_id) {
613     case ARG_LEFT:
614       video_crop->crop_left = g_value_get_int (value);
615       break;
616     case ARG_RIGHT:
617       video_crop->crop_right = g_value_get_int (value);
618       break;
619     case ARG_TOP:
620       video_crop->crop_top = g_value_get_int (value);
621       break;
622     case ARG_BOTTOM:
623       video_crop->crop_bottom = g_value_get_int (value);
624       break;
625     default:
626       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
627       break;
628   }
629
630   video_crop->noop = ((video_crop->crop_left | video_crop->crop_right |
631           video_crop->crop_top | video_crop->crop_bottom) == 0);
632
633   GST_OBJECT_UNLOCK (video_crop);
634 }
635
636 static void
637 gst_video_crop_get_property (GObject * object, guint prop_id, GValue * value,
638     GParamSpec * pspec)
639 {
640   GstVideoCrop *video_crop;
641
642   video_crop = GST_VIDEO_CROP (object);
643
644   GST_OBJECT_LOCK (video_crop);
645   switch (prop_id) {
646     case ARG_LEFT:
647       g_value_set_int (value, video_crop->crop_left);
648       break;
649     case ARG_RIGHT:
650       g_value_set_int (value, video_crop->crop_right);
651       break;
652     case ARG_TOP:
653       g_value_set_int (value, video_crop->crop_top);
654       break;
655     case ARG_BOTTOM:
656       g_value_set_int (value, video_crop->crop_bottom);
657       break;
658     default:
659       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
660       break;
661   }
662   GST_OBJECT_UNLOCK (video_crop);
663 }
664
665 static gboolean
666 plugin_init (GstPlugin * plugin)
667 {
668   GST_DEBUG_CATEGORY_INIT (videocrop_debug, "videocrop", 0, "videocrop");
669
670   return gst_element_register (plugin, "videocrop", GST_RANK_NONE,
671       GST_TYPE_VIDEO_CROP);
672 }
673
674 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
675     GST_VERSION_MINOR,
676     "videocrop",
677     "Crops video into a user-defined region",
678     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)