21738c570c98983f1ddaa8f81f325651dd50a074
[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  * This element crops video frames, meaning it can remove parts of the
25  * picture on the left, right, top or bottom of the picture and output
26  * a smaller picture than the input picture, with the unwanted parts at the
27  * border removed.
28  *
29  * The videocrop element is similar to the videobox element, but its main
30  * goal is to support a multitude of formats as efficiently as possible.
31  * Unlike videbox, it cannot add borders to the picture and unlike videbox
32  * it will always output images in exactly the same format as the input image.
33  *
34  * If there is nothing to crop, the element will operate in pass-through mode.
35  *
36  * Note that no special efforts are made to handle chroma-subsampled formats
37  * in the case of odd-valued cropping and compensate for sub-unit chroma plane
38  * shifts for such formats in the case where the #GstVideoCrop:left or
39  * #GstVideoCrop:top property is set to an odd number. This doesn't matter for 
40  * most use cases, but it might matter for yours.
41  *
42  * <refsect2>
43  * <title>Example launch line</title>
44  * |[
45  * gst-launch-1.0 -v videotestsrc ! videocrop top=42 left=1 right=4 bottom=0 ! ximagesink
46  * ]|
47  * </refsect2>
48  */
49
50 /* TODO:
51  *  - for packed formats, we could avoid memcpy() in case crop_left
52  *    and crop_right are 0 and just create a sub-buffer of the input
53  *    buffer
54  */
55
56 #ifdef HAVE_CONFIG_H
57 #include "config.h"
58 #endif
59
60 #include <gst/gst.h>
61 #include <gst/video/video.h>
62
63 #include "gstvideocrop.h"
64 #include "gstaspectratiocrop.h"
65
66 #include <string.h>
67
68 GST_DEBUG_CATEGORY_STATIC (videocrop_debug);
69 #define GST_CAT_DEFAULT videocrop_debug
70
71 enum
72 {
73   ARG_0,
74   ARG_LEFT,
75   ARG_RIGHT,
76   ARG_TOP,
77   ARG_BOTTOM
78 };
79
80 #define VIDEO_CROP_CAPS                                \
81   GST_VIDEO_CAPS_MAKE ("{ RGBx, xRGB, BGRx, xBGR, "    \
82       "RGBA, ARGB, BGRA, ABGR, RGB, BGR, AYUV, YUY2, " \
83       "YVYU, UYVY, I420, RGB16, RGB15, GRAY8 }")
84
85 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
86     GST_PAD_SRC,
87     GST_PAD_ALWAYS,
88     GST_STATIC_CAPS (VIDEO_CROP_CAPS)
89     );
90
91 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
92     GST_PAD_SINK,
93     GST_PAD_ALWAYS,
94     GST_STATIC_CAPS (VIDEO_CROP_CAPS)
95     );
96
97 #define gst_video_crop_parent_class parent_class
98 G_DEFINE_TYPE (GstVideoCrop, gst_video_crop, GST_TYPE_VIDEO_FILTER);
99
100 static void gst_video_crop_finalize (GObject * object);
101
102 static void gst_video_crop_set_property (GObject * object, guint prop_id,
103     const GValue * value, GParamSpec * pspec);
104 static void gst_video_crop_get_property (GObject * object, guint prop_id,
105     GValue * value, GParamSpec * pspec);
106
107 static GstCaps *gst_video_crop_transform_caps (GstBaseTransform * trans,
108     GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps);
109 static gboolean gst_video_crop_src_event (GstBaseTransform * trans,
110     GstEvent * event);
111
112 static gboolean gst_video_crop_set_info (GstVideoFilter * vfilter, GstCaps * in,
113     GstVideoInfo * in_info, GstCaps * out, GstVideoInfo * out_info);
114 static GstFlowReturn gst_video_crop_transform_frame (GstVideoFilter * vfilter,
115     GstVideoFrame * in_frame, GstVideoFrame * out_frame);
116
117 static gboolean
118 gst_video_crop_src_event (GstBaseTransform * trans, GstEvent * event)
119 {
120   GstEvent *new_event;
121   GstStructure *new_structure;
122   const GstStructure *structure;
123   const gchar *event_name;
124   double pointer_x;
125   double pointer_y;
126
127   GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
128   new_event = NULL;
129
130   GST_OBJECT_LOCK (vcrop);
131   if (GST_EVENT_TYPE (event) == GST_EVENT_NAVIGATION &&
132       (vcrop->crop_left != 0 || vcrop->crop_top != 0)) {
133     structure = gst_event_get_structure (event);
134     event_name = gst_structure_get_string (structure, "event");
135
136     if (event_name &&
137         (strcmp (event_name, "mouse-move") == 0 ||
138             strcmp (event_name, "mouse-button-press") == 0 ||
139             strcmp (event_name, "mouse-button-release") == 0)) {
140
141       if (gst_structure_get_double (structure, "pointer_x", &pointer_x) &&
142           gst_structure_get_double (structure, "pointer_y", &pointer_y)) {
143
144         new_structure = gst_structure_copy (structure);
145         gst_structure_set (new_structure,
146             "pointer_x", G_TYPE_DOUBLE, (double) (pointer_x + vcrop->crop_left),
147             "pointer_y", G_TYPE_DOUBLE, (double) (pointer_y + vcrop->crop_top),
148             NULL);
149
150         new_event = gst_event_new_navigation (new_structure);
151         gst_event_unref (event);
152       } else {
153         GST_WARNING_OBJECT (vcrop, "Failed to read navigation event");
154       }
155     }
156   }
157
158   GST_OBJECT_UNLOCK (vcrop);
159
160   return GST_BASE_TRANSFORM_CLASS (parent_class)->src_event (trans,
161       (new_event ? new_event : event));
162 }
163
164 static void
165 gst_video_crop_class_init (GstVideoCropClass * klass)
166 {
167   GObjectClass *gobject_class;
168   GstElementClass *element_class;
169   GstBaseTransformClass *basetransform_class;
170   GstVideoFilterClass *vfilter_class;
171
172   gobject_class = (GObjectClass *) klass;
173   element_class = (GstElementClass *) klass;
174   basetransform_class = (GstBaseTransformClass *) klass;
175   vfilter_class = (GstVideoFilterClass *) klass;
176
177   gobject_class->finalize = gst_video_crop_finalize;
178   gobject_class->set_property = gst_video_crop_set_property;
179   gobject_class->get_property = gst_video_crop_get_property;
180
181   g_object_class_install_property (gobject_class, ARG_LEFT,
182       g_param_spec_int ("left", "Left", "Pixels to crop at left",
183           0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
184   g_object_class_install_property (gobject_class, ARG_RIGHT,
185       g_param_spec_int ("right", "Right", "Pixels to crop at right",
186           0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
187   g_object_class_install_property (gobject_class, ARG_TOP,
188       g_param_spec_int ("top", "Top", "Pixels to crop at top",
189           0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
190   g_object_class_install_property (gobject_class, ARG_BOTTOM,
191       g_param_spec_int ("bottom", "Bottom", "Pixels to crop at bottom",
192           0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
193
194   gst_element_class_add_pad_template (element_class,
195       gst_static_pad_template_get (&sink_template));
196   gst_element_class_add_pad_template (element_class,
197       gst_static_pad_template_get (&src_template));
198   gst_element_class_set_static_metadata (element_class, "Crop",
199       "Filter/Effect/Video",
200       "Crops video into a user-defined region",
201       "Tim-Philipp Müller <tim centricular net>");
202
203   basetransform_class->transform_caps =
204       GST_DEBUG_FUNCPTR (gst_video_crop_transform_caps);
205   basetransform_class->src_event = GST_DEBUG_FUNCPTR (gst_video_crop_src_event);
206
207   vfilter_class->set_info = GST_DEBUG_FUNCPTR (gst_video_crop_set_info);
208   vfilter_class->transform_frame =
209       GST_DEBUG_FUNCPTR (gst_video_crop_transform_frame);
210 }
211
212 static void
213 gst_video_crop_init (GstVideoCrop * vcrop)
214 {
215   vcrop->crop_right = 0;
216   vcrop->crop_left = 0;
217   vcrop->crop_top = 0;
218   vcrop->crop_bottom = 0;
219
220   g_mutex_init (&vcrop->lock);
221 }
222
223 static void
224 gst_video_crop_finalize (GObject * object)
225 {
226   GstVideoCrop *vcrop;
227
228   vcrop = GST_VIDEO_CROP (object);
229
230   g_mutex_clear (&vcrop->lock);
231
232   G_OBJECT_CLASS (parent_class)->finalize (object);
233 }
234
235 #define ROUND_DOWN_2(n)  ((n)&(~1))
236
237 static void
238 gst_video_crop_transform_packed_complex (GstVideoCrop * vcrop,
239     GstVideoFrame * in_frame, GstVideoFrame * out_frame)
240 {
241   guint8 *in_data, *out_data;
242   guint i, dx;
243   gint width, height;
244   gint in_stride;
245   gint out_stride;
246
247   width = GST_VIDEO_FRAME_WIDTH (out_frame);
248   height = GST_VIDEO_FRAME_HEIGHT (out_frame);
249
250   in_data = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
251   out_data = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);
252
253   in_stride = GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 0);
254   out_stride = GST_VIDEO_FRAME_PLANE_STRIDE (out_frame, 0);
255
256   in_data += vcrop->crop_top * in_stride;
257
258   /* rounding down here so we end up at the start of a macro-pixel and not
259    * in the middle of one */
260   in_data += ROUND_DOWN_2 (vcrop->crop_left) *
261       GST_VIDEO_FRAME_COMP_PSTRIDE (in_frame, 0);
262
263   dx = width * GST_VIDEO_FRAME_COMP_PSTRIDE (out_frame, 0);
264
265   /* UYVY = 4:2:2 - [U0 Y0 V0 Y1] [U2 Y2 V2 Y3] [U4 Y4 V4 Y5]
266    * YUYV = 4:2:2 - [Y0 U0 Y1 V0] [Y2 U2 Y3 V2] [Y4 U4 Y5 V4] = YUY2 */
267   if ((vcrop->crop_left % 2) != 0) {
268     for (i = 0; i < height; ++i) {
269       gint j;
270
271       memcpy (out_data, in_data, dx);
272
273       /* move just the Y samples one pixel to the left, don't worry about
274        * chroma shift */
275       for (j = vcrop->macro_y_off; j < out_stride - 2; j += 2)
276         out_data[j] = in_data[j + 2];
277
278       in_data += in_stride;
279       out_data += out_stride;
280     }
281   } else {
282     for (i = 0; i < height; ++i) {
283       memcpy (out_data, in_data, dx);
284       in_data += in_stride;
285       out_data += out_stride;
286     }
287   }
288 }
289
290 static void
291 gst_video_crop_transform_packed_simple (GstVideoCrop * vcrop,
292     GstVideoFrame * in_frame, GstVideoFrame * out_frame)
293 {
294   guint8 *in_data, *out_data;
295   gint width, height;
296   guint i, dx;
297   gint in_stride, out_stride;
298
299   width = GST_VIDEO_FRAME_WIDTH (out_frame);
300   height = GST_VIDEO_FRAME_HEIGHT (out_frame);
301
302   in_data = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
303   out_data = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);
304
305   in_stride = GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 0);
306   out_stride = GST_VIDEO_FRAME_PLANE_STRIDE (out_frame, 0);
307
308   in_data += vcrop->crop_top * in_stride;
309   in_data += vcrop->crop_left * GST_VIDEO_FRAME_COMP_PSTRIDE (in_frame, 0);
310
311   dx = width * GST_VIDEO_FRAME_COMP_PSTRIDE (out_frame, 0);
312
313   for (i = 0; i < height; ++i) {
314     memcpy (out_data, in_data, dx);
315     in_data += in_stride;
316     out_data += out_stride;
317   }
318 }
319
320 static void
321 gst_video_crop_transform_planar (GstVideoCrop * vcrop,
322     GstVideoFrame * in_frame, GstVideoFrame * out_frame)
323 {
324   gint width, height;
325   guint8 *y_out, *u_out, *v_out;
326   guint8 *y_in, *u_in, *v_in;
327   guint i, dx;
328
329   width = GST_VIDEO_FRAME_WIDTH (out_frame);
330   height = GST_VIDEO_FRAME_HEIGHT (out_frame);
331
332   /* Y plane */
333   y_in = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
334   y_out = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);
335
336   y_in +=
337       (vcrop->crop_top * GST_VIDEO_FRAME_PLANE_STRIDE (in_frame,
338           0)) + vcrop->crop_left;
339   dx = width;
340
341   for (i = 0; i < height; ++i) {
342     memcpy (y_out, y_in, dx);
343     y_in += GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 0);
344     y_out += GST_VIDEO_FRAME_PLANE_STRIDE (out_frame, 0);
345   }
346
347   /* U + V planes */
348   u_in = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 1);
349   u_out = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 1);
350
351   u_in += (vcrop->crop_top / 2) * GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 1);
352   u_in += vcrop->crop_left / 2;
353
354   v_in = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 2);
355   v_out = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 2);
356
357   v_in += (vcrop->crop_top / 2) * GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 2);
358   v_in += vcrop->crop_left / 2;
359
360   dx = GST_ROUND_UP_2 (width) / 2;
361
362   for (i = 0; i < GST_ROUND_UP_2 (height) / 2; ++i) {
363     memcpy (u_out, u_in, dx);
364     memcpy (v_out, v_in, dx);
365     u_in += GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 1);
366     u_out += GST_VIDEO_FRAME_PLANE_STRIDE (out_frame, 1);
367     v_in += GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 2);
368     v_out += GST_VIDEO_FRAME_PLANE_STRIDE (out_frame, 2);
369   }
370 }
371
372 static GstFlowReturn
373 gst_video_crop_transform_frame (GstVideoFilter * vfilter,
374     GstVideoFrame * in_frame, GstVideoFrame * out_frame)
375 {
376   GstVideoCrop *vcrop = GST_VIDEO_CROP (vfilter);
377
378   g_mutex_lock (&vcrop->lock);
379   switch (vcrop->packing) {
380     case VIDEO_CROP_PIXEL_FORMAT_PACKED_SIMPLE:
381       gst_video_crop_transform_packed_simple (vcrop, in_frame, out_frame);
382       break;
383     case VIDEO_CROP_PIXEL_FORMAT_PACKED_COMPLEX:
384       gst_video_crop_transform_packed_complex (vcrop, in_frame, out_frame);
385       break;
386     case VIDEO_CROP_PIXEL_FORMAT_PLANAR:
387       gst_video_crop_transform_planar (vcrop, in_frame, out_frame);
388       break;
389     default:
390       g_assert_not_reached ();
391   }
392   g_mutex_unlock (&vcrop->lock);
393
394   return GST_FLOW_OK;
395 }
396
397 static gint
398 gst_video_crop_transform_dimension (gint val, gint delta)
399 {
400   gint64 new_val = (gint64) val + (gint64) delta;
401
402   new_val = CLAMP (new_val, 1, G_MAXINT);
403
404   return (gint) new_val;
405 }
406
407 static gboolean
408 gst_video_crop_transform_dimension_value (const GValue * src_val,
409     gint delta, GValue * dest_val)
410 {
411   gboolean ret = TRUE;
412
413   g_value_init (dest_val, G_VALUE_TYPE (src_val));
414
415   if (G_VALUE_HOLDS_INT (src_val)) {
416     gint ival = g_value_get_int (src_val);
417
418     ival = gst_video_crop_transform_dimension (ival, delta);
419     g_value_set_int (dest_val, ival);
420   } else if (GST_VALUE_HOLDS_INT_RANGE (src_val)) {
421     gint min = gst_value_get_int_range_min (src_val);
422     gint max = gst_value_get_int_range_max (src_val);
423
424     min = gst_video_crop_transform_dimension (min, delta);
425     max = gst_video_crop_transform_dimension (max, delta);
426     gst_value_set_int_range (dest_val, min, max);
427   } else if (GST_VALUE_HOLDS_LIST (src_val)) {
428     gint i;
429
430     for (i = 0; i < gst_value_list_get_size (src_val); ++i) {
431       const GValue *list_val;
432       GValue newval = { 0, };
433
434       list_val = gst_value_list_get_value (src_val, i);
435       if (gst_video_crop_transform_dimension_value (list_val, delta, &newval))
436         gst_value_list_append_value (dest_val, &newval);
437       g_value_unset (&newval);
438     }
439
440     if (gst_value_list_get_size (dest_val) == 0) {
441       g_value_unset (dest_val);
442       ret = FALSE;
443     }
444   } else {
445     g_value_unset (dest_val);
446     ret = FALSE;
447   }
448
449   return ret;
450 }
451
452 /* TODO use filter_caps */
453 static GstCaps *
454 gst_video_crop_transform_caps (GstBaseTransform * trans,
455     GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps)
456 {
457   GstVideoCrop *vcrop;
458   GstCaps *other_caps;
459   gint dy, dx, i;
460
461   vcrop = GST_VIDEO_CROP (trans);
462
463   GST_OBJECT_LOCK (vcrop);
464
465   GST_LOG_OBJECT (vcrop, "l=%d,r=%d,b=%d,t=%d",
466       vcrop->crop_left, vcrop->crop_right, vcrop->crop_bottom, vcrop->crop_top);
467
468   if (direction == GST_PAD_SRC) {
469     dx = vcrop->crop_left + vcrop->crop_right;
470     dy = vcrop->crop_top + vcrop->crop_bottom;
471   } else {
472     dx = 0 - (vcrop->crop_left + vcrop->crop_right);
473     dy = 0 - (vcrop->crop_top + vcrop->crop_bottom);
474   }
475   GST_OBJECT_UNLOCK (vcrop);
476
477   GST_LOG_OBJECT (vcrop, "transforming caps %" GST_PTR_FORMAT, caps);
478
479   other_caps = gst_caps_new_empty ();
480
481   for (i = 0; i < gst_caps_get_size (caps); ++i) {
482     const GValue *v;
483     GstStructure *structure, *new_structure;
484     GValue w_val = { 0, }, h_val = {
485     0,};
486
487     structure = gst_caps_get_structure (caps, i);
488
489     v = gst_structure_get_value (structure, "width");
490     if (!gst_video_crop_transform_dimension_value (v, dx, &w_val)) {
491       GST_WARNING_OBJECT (vcrop, "could not tranform width value with dx=%d"
492           ", caps structure=%" GST_PTR_FORMAT, dx, structure);
493       continue;
494     }
495
496     v = gst_structure_get_value (structure, "height");
497     if (!gst_video_crop_transform_dimension_value (v, dy, &h_val)) {
498       g_value_unset (&w_val);
499       GST_WARNING_OBJECT (vcrop, "could not tranform height value with dy=%d"
500           ", caps structure=%" GST_PTR_FORMAT, dy, structure);
501       continue;
502     }
503
504     new_structure = gst_structure_copy (structure);
505     gst_structure_set_value (new_structure, "width", &w_val);
506     gst_structure_set_value (new_structure, "height", &h_val);
507     g_value_unset (&w_val);
508     g_value_unset (&h_val);
509     GST_LOG_OBJECT (vcrop, "transformed structure %2d: %" GST_PTR_FORMAT
510         " => %" GST_PTR_FORMAT, i, structure, new_structure);
511     gst_caps_append_structure (other_caps, new_structure);
512   }
513
514   if (!gst_caps_is_empty (other_caps) && filter_caps) {
515     GstCaps *tmp = gst_caps_intersect_full (filter_caps, other_caps,
516         GST_CAPS_INTERSECT_FIRST);
517     gst_caps_replace (&other_caps, tmp);
518     gst_caps_unref (tmp);
519   }
520
521   return other_caps;
522 }
523
524 static gboolean
525 gst_video_crop_set_info (GstVideoFilter * vfilter, GstCaps * in,
526     GstVideoInfo * in_info, GstCaps * out, GstVideoInfo * out_info)
527 {
528   GstVideoCrop *crop = GST_VIDEO_CROP (vfilter);
529
530   if (G_UNLIKELY ((crop->crop_left + crop->crop_right) >=
531           GST_VIDEO_INFO_WIDTH (in_info)
532           || (crop->crop_top + crop->crop_bottom) >=
533           GST_VIDEO_INFO_HEIGHT (in_info)))
534     goto cropping_too_much;
535
536   GST_LOG_OBJECT (crop, "incaps = %" GST_PTR_FORMAT ", outcaps = %"
537       GST_PTR_FORMAT, in, out);
538
539   if ((crop->crop_left | crop->crop_right | crop->crop_top | crop->
540           crop_bottom) == 0) {
541     GST_LOG_OBJECT (crop, "we are using passthrough");
542     gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (crop), TRUE);
543   } else {
544     GST_LOG_OBJECT (crop, "we are not using passthrough");
545     gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (crop), FALSE);
546   }
547
548   if (GST_VIDEO_INFO_IS_RGB (in_info)
549       || GST_VIDEO_INFO_IS_GRAY (in_info)) {
550     crop->packing = VIDEO_CROP_PIXEL_FORMAT_PACKED_SIMPLE;
551   } else {
552     switch (GST_VIDEO_INFO_FORMAT (in_info)) {
553       case GST_VIDEO_FORMAT_AYUV:
554         crop->packing = VIDEO_CROP_PIXEL_FORMAT_PACKED_SIMPLE;
555         break;
556       case GST_VIDEO_FORMAT_YVYU:
557       case GST_VIDEO_FORMAT_YUY2:
558       case GST_VIDEO_FORMAT_UYVY:
559         crop->packing = VIDEO_CROP_PIXEL_FORMAT_PACKED_COMPLEX;
560         if (GST_VIDEO_INFO_FORMAT (in_info) == GST_VIDEO_FORMAT_UYVY) {
561           /* UYVY = 4:2:2 - [U0 Y0 V0 Y1] [U2 Y2 V2 Y3] [U4 Y4 V4 Y5] */
562           crop->macro_y_off = 1;
563         } else {
564           /* YUYV = 4:2:2 - [Y0 U0 Y1 V0] [Y2 U2 Y3 V2] [Y4 U4 Y5 V4] = YUY2 */
565           crop->macro_y_off = 0;
566         }
567         break;
568       case GST_VIDEO_FORMAT_GRAY8:
569         crop->packing = VIDEO_CROP_PIXEL_FORMAT_PACKED_SIMPLE;
570         break;
571       case GST_VIDEO_FORMAT_I420:
572       case GST_VIDEO_FORMAT_YV12:
573         crop->packing = VIDEO_CROP_PIXEL_FORMAT_PLANAR;
574         break;
575       default:
576         goto unknown_format;
577     }
578   }
579
580   return TRUE;
581
582   /* ERROR */
583 cropping_too_much:
584   {
585     GST_DEBUG_OBJECT (crop, "we are cropping too much");
586     return FALSE;
587   }
588 unknown_format:
589   {
590     GST_DEBUG_OBJECT (crop, "Unsupported format");
591     return FALSE;
592   }
593 }
594
595 static void
596 gst_video_crop_set_property (GObject * object, guint prop_id,
597     const GValue * value, GParamSpec * pspec)
598 {
599   GstVideoCrop *video_crop;
600
601   video_crop = GST_VIDEO_CROP (object);
602
603   /* don't modify while we are transforming */
604   g_mutex_lock (&video_crop->lock);
605
606   /* protect with the object lock so that we can read them */
607   GST_OBJECT_LOCK (video_crop);
608   switch (prop_id) {
609     case ARG_LEFT:
610       video_crop->crop_left = g_value_get_int (value);
611       break;
612     case ARG_RIGHT:
613       video_crop->crop_right = g_value_get_int (value);
614       break;
615     case ARG_TOP:
616       video_crop->crop_top = g_value_get_int (value);
617       break;
618     case ARG_BOTTOM:
619       video_crop->crop_bottom = g_value_get_int (value);
620       break;
621     default:
622       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
623       break;
624   }
625   GST_LOG_OBJECT (video_crop, "l=%d,r=%d,b=%d,t=%d",
626       video_crop->crop_left, video_crop->crop_right, video_crop->crop_bottom,
627       video_crop->crop_top);
628   GST_OBJECT_UNLOCK (video_crop);
629
630   gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (video_crop));
631   g_mutex_unlock (&video_crop->lock);
632 }
633
634 static void
635 gst_video_crop_get_property (GObject * object, guint prop_id, GValue * value,
636     GParamSpec * pspec)
637 {
638   GstVideoCrop *video_crop;
639
640   video_crop = GST_VIDEO_CROP (object);
641
642   GST_OBJECT_LOCK (video_crop);
643   switch (prop_id) {
644     case ARG_LEFT:
645       g_value_set_int (value, video_crop->crop_left);
646       break;
647     case ARG_RIGHT:
648       g_value_set_int (value, video_crop->crop_right);
649       break;
650     case ARG_TOP:
651       g_value_set_int (value, video_crop->crop_top);
652       break;
653     case ARG_BOTTOM:
654       g_value_set_int (value, video_crop->crop_bottom);
655       break;
656     default:
657       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
658       break;
659   }
660   GST_OBJECT_UNLOCK (video_crop);
661 }
662
663 static gboolean
664 plugin_init (GstPlugin * plugin)
665 {
666   GST_DEBUG_CATEGORY_INIT (videocrop_debug, "videocrop", 0, "videocrop");
667
668   if (gst_element_register (plugin, "videocrop", GST_RANK_NONE,
669           GST_TYPE_VIDEO_CROP)
670       && gst_element_register (plugin, "aspectratiocrop", GST_RANK_NONE,
671           GST_TYPE_ASPECT_RATIO_CROP))
672     return TRUE;
673
674   return FALSE;
675 }
676
677 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
678     GST_VERSION_MINOR,
679     videocrop,
680     "Crops video into a user-defined region",
681     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)