Merge branch 'master' into 0.11
[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 -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, Y800, 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_BASE_TRANSFORM);
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 GstFlowReturn gst_video_crop_transform (GstBaseTransform * trans,
110     GstBuffer * inbuf, GstBuffer * outbuf);
111 static gboolean gst_video_crop_get_unit_size (GstBaseTransform * trans,
112     GstCaps * caps, gsize * size);
113 static gboolean gst_video_crop_set_caps (GstBaseTransform * trans,
114     GstCaps * in_caps, GstCaps * outcaps);
115 static gboolean gst_video_crop_src_event (GstBaseTransform * trans,
116     GstEvent * event);
117
118 static gboolean
119 gst_video_crop_src_event (GstBaseTransform * trans, GstEvent * event)
120 {
121   GstEvent *new_event;
122   GstStructure *new_structure;
123   const GstStructure *structure;
124   const gchar *event_name;
125   double pointer_x;
126   double pointer_y;
127
128   GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
129   new_event = NULL;
130
131   GST_OBJECT_LOCK (vcrop);
132   if (GST_EVENT_TYPE (event) == GST_EVENT_NAVIGATION &&
133       (vcrop->crop_left != 0 || vcrop->crop_top != 0)) {
134     structure = gst_event_get_structure (event);
135     event_name = gst_structure_get_string (structure, "event");
136
137     if (event_name &&
138         (strcmp (event_name, "mouse-move") == 0 ||
139             strcmp (event_name, "mouse-button-press") == 0 ||
140             strcmp (event_name, "mouse-button-release") == 0)) {
141
142       if (gst_structure_get_double (structure, "pointer_x", &pointer_x) &&
143           gst_structure_get_double (structure, "pointer_y", &pointer_y)) {
144
145         new_structure = gst_structure_copy (structure);
146         gst_structure_set (new_structure,
147             "pointer_x", G_TYPE_DOUBLE, (double) (pointer_x + vcrop->crop_left),
148             "pointer_y", G_TYPE_DOUBLE, (double) (pointer_y + vcrop->crop_top),
149             NULL);
150
151         new_event = gst_event_new_navigation (new_structure);
152         gst_event_unref (event);
153       } else {
154         GST_WARNING_OBJECT (vcrop, "Failed to read navigation event");
155       }
156     }
157   }
158
159   GST_OBJECT_UNLOCK (vcrop);
160
161   return GST_BASE_TRANSFORM_CLASS (parent_class)->src_event (trans,
162       (new_event ? new_event : event));
163 }
164
165 static void
166 gst_video_crop_class_init (GstVideoCropClass * klass)
167 {
168   GObjectClass *gobject_class;
169   GstElementClass *element_class;
170   GstBaseTransformClass *basetransform_class;
171
172   gobject_class = (GObjectClass *) klass;
173   element_class = (GstElementClass *) klass;
174   basetransform_class = (GstBaseTransformClass *) klass;
175
176   gobject_class->finalize = gst_video_crop_finalize;
177   gobject_class->set_property = gst_video_crop_set_property;
178   gobject_class->get_property = gst_video_crop_get_property;
179
180   g_object_class_install_property (gobject_class, ARG_LEFT,
181       g_param_spec_int ("left", "Left", "Pixels to crop at left",
182           0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
183   g_object_class_install_property (gobject_class, ARG_RIGHT,
184       g_param_spec_int ("right", "Right", "Pixels to crop at right",
185           0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
186   g_object_class_install_property (gobject_class, ARG_TOP,
187       g_param_spec_int ("top", "Top", "Pixels to crop at top",
188           0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
189   g_object_class_install_property (gobject_class, ARG_BOTTOM,
190       g_param_spec_int ("bottom", "Bottom", "Pixels to crop at bottom",
191           0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
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   gst_element_class_set_details_simple (element_class, "Crop",
198       "Filter/Effect/Video",
199       "Crops video into a user-defined region",
200       "Tim-Philipp Müller <tim centricular net>");
201
202   basetransform_class->transform = GST_DEBUG_FUNCPTR (gst_video_crop_transform);
203   basetransform_class->transform_caps =
204       GST_DEBUG_FUNCPTR (gst_video_crop_transform_caps);
205   basetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_video_crop_set_caps);
206   basetransform_class->get_unit_size =
207       GST_DEBUG_FUNCPTR (gst_video_crop_get_unit_size);
208
209   basetransform_class->passthrough_on_same_caps = FALSE;
210   basetransform_class->src_event = GST_DEBUG_FUNCPTR (gst_video_crop_src_event);
211 }
212
213 static void
214 gst_video_crop_init (GstVideoCrop * vcrop)
215 {
216   vcrop->crop_right = 0;
217   vcrop->crop_left = 0;
218   vcrop->crop_top = 0;
219   vcrop->crop_bottom = 0;
220
221   g_mutex_init (&vcrop->lock);
222 }
223
224 static void
225 gst_video_crop_finalize (GObject * object)
226 {
227   GstVideoCrop *vcrop;
228
229   vcrop = GST_VIDEO_CROP (object);
230
231   g_mutex_clear (&vcrop->lock);
232
233   G_OBJECT_CLASS (parent_class)->finalize (object);
234 }
235
236 static gboolean
237 gst_video_crop_get_image_details_from_caps (GstVideoCrop * vcrop,
238     GstVideoCropImageDetails * details, GstCaps * caps)
239 {
240   gst_video_info_init (&details->info);
241   if (!gst_video_info_from_caps (&details->info, caps)) {
242     goto incomplete_format;
243   }
244
245   if (details->info.width == 0 && details->info.height == 0) {
246     goto incomplete_format;
247   }
248
249   if (GST_VIDEO_INFO_IS_RGB (&details->info)
250       || GST_VIDEO_INFO_IS_GRAY (&details->info)) {
251     details->packing = VIDEO_CROP_PIXEL_FORMAT_PACKED_SIMPLE;
252   } else {
253     switch (GST_VIDEO_INFO_FORMAT (&details->info)) {
254       case GST_VIDEO_FORMAT_AYUV:
255         details->packing = VIDEO_CROP_PIXEL_FORMAT_PACKED_SIMPLE;
256         break;
257       case GST_VIDEO_FORMAT_YVYU:
258       case GST_VIDEO_FORMAT_YUY2:
259       case GST_VIDEO_FORMAT_UYVY:
260         details->packing = VIDEO_CROP_PIXEL_FORMAT_PACKED_COMPLEX;
261         if (GST_VIDEO_INFO_FORMAT (&details->info) == GST_VIDEO_FORMAT_UYVY) {
262           /* UYVY = 4:2:2 - [U0 Y0 V0 Y1] [U2 Y2 V2 Y3] [U4 Y4 V4 Y5] */
263           details->macro_y_off = 1;
264         } else {
265           /* YUYV = 4:2:2 - [Y0 U0 Y1 V0] [Y2 U2 Y3 V2] [Y4 U4 Y5 V4] = YUY2 */
266           details->macro_y_off = 0;
267         }
268         break;
269       case GST_VIDEO_FORMAT_Y800:
270         details->packing = VIDEO_CROP_PIXEL_FORMAT_PACKED_SIMPLE;
271         break;
272       case GST_VIDEO_FORMAT_I420:
273       case GST_VIDEO_FORMAT_YV12:
274         details->packing = VIDEO_CROP_PIXEL_FORMAT_PLANAR;
275         break;
276       default:
277         goto unknown_format;
278     }
279   }
280
281   return TRUE;
282
283   /* ERRORS */
284 unknown_format:
285   {
286     GST_ELEMENT_ERROR (vcrop, STREAM, NOT_IMPLEMENTED, (NULL),
287         ("Unsupported format"));
288     return FALSE;
289   }
290
291 incomplete_format:
292   {
293     GST_ELEMENT_ERROR (vcrop, CORE, NEGOTIATION, (NULL),
294         ("Incomplete caps, some required field is missing"));
295     return FALSE;
296   }
297 }
298
299 static gboolean
300 gst_video_crop_get_unit_size (GstBaseTransform * trans, GstCaps * caps,
301     gsize * size)
302 {
303   GstVideoCropImageDetails img_details = { 0, };
304   GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
305
306   if (!gst_video_crop_get_image_details_from_caps (vcrop, &img_details, caps))
307     return FALSE;
308
309   *size = GST_VIDEO_INFO_SIZE (&img_details.info);
310   return TRUE;
311 }
312
313 #define ROUND_DOWN_2(n)  ((n)&(~1))
314
315 static void
316 gst_video_crop_transform_packed_complex (GstVideoCrop * vcrop,
317     GstBuffer * inbuf, GstBuffer * outbuf)
318 {
319   GstMapInfo in_map, out_map;
320   guint8 *in_data, *out_data;
321   guint i, dx;
322   gint in_stride;
323   gint out_stride;
324
325   gst_buffer_map (inbuf, &in_map, GST_MAP_READ);
326   gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE);
327
328   in_data = in_map.data;
329   out_data = out_map.data;
330
331   in_stride = GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->in.info, 0);
332   out_stride = GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->out.info, 0);
333
334   in_data += vcrop->crop_top * in_stride;
335
336   /* rounding down here so we end up at the start of a macro-pixel and not
337    * in the middle of one */
338   in_data +=
339       ROUND_DOWN_2 (vcrop->crop_left) *
340       GST_VIDEO_INFO_COMP_PSTRIDE (&vcrop->in.info, 0);
341
342   dx = GST_VIDEO_INFO_WIDTH (&vcrop->out.info) *
343       GST_VIDEO_INFO_COMP_PSTRIDE (&vcrop->out.info, 0);
344
345   /* UYVY = 4:2:2 - [U0 Y0 V0 Y1] [U2 Y2 V2 Y3] [U4 Y4 V4 Y5]
346    * YUYV = 4:2:2 - [Y0 U0 Y1 V0] [Y2 U2 Y3 V2] [Y4 U4 Y5 V4] = YUY2 */
347   if ((vcrop->crop_left % 2) != 0) {
348     for (i = 0; i < GST_VIDEO_INFO_HEIGHT (&vcrop->out.info); ++i) {
349       gint j;
350
351       memcpy (out_data, in_data, dx);
352
353       /* move just the Y samples one pixel to the left, don't worry about
354        * chroma shift */
355       for (j = vcrop->in.macro_y_off; j < out_stride - 2; j += 2)
356         out_data[j] = in_data[j + 2];
357
358       in_data += in_stride;
359       out_data += out_stride;
360     }
361   } else {
362     for (i = 0; i < GST_VIDEO_INFO_HEIGHT (&vcrop->out.info); ++i) {
363       memcpy (out_data, in_data, dx);
364       in_data += in_stride;
365       out_data += out_stride;
366     }
367   }
368   gst_buffer_unmap (inbuf, &in_map);
369   gst_buffer_unmap (outbuf, &out_map);
370 }
371
372 static void
373 gst_video_crop_transform_packed_simple (GstVideoCrop * vcrop,
374     GstBuffer * inbuf, GstBuffer * outbuf)
375 {
376   GstMapInfo in_map, out_map;
377   guint8 *in_data, *out_data;
378   guint i, dx;
379   gint in_stride, out_stride;
380
381   gst_buffer_map (inbuf, &in_map, GST_MAP_READ);
382   gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE);
383
384   in_data = in_map.data;
385   out_data = out_map.data;
386
387   in_stride = GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->in.info, 0);
388   out_stride = GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->out.info, 0);
389
390   in_data += vcrop->crop_top * in_stride;
391   in_data +=
392       vcrop->crop_left * GST_VIDEO_INFO_COMP_PSTRIDE (&vcrop->in.info, 0);
393
394   dx = GST_VIDEO_INFO_WIDTH (&vcrop->out.info) *
395       GST_VIDEO_INFO_COMP_PSTRIDE (&vcrop->out.info, 0);
396
397   for (i = 0; i < GST_VIDEO_INFO_HEIGHT (&vcrop->out.info); ++i) {
398     memcpy (out_data, in_data, dx);
399     in_data += in_stride;
400     out_data += out_stride;
401   }
402   gst_buffer_unmap (inbuf, &in_map);
403   gst_buffer_unmap (outbuf, &out_map);
404 }
405
406 static void
407 gst_video_crop_transform_planar (GstVideoCrop * vcrop, GstBuffer * inbuf,
408     GstBuffer * outbuf)
409 {
410   GstMapInfo in_map, out_map;
411   guint8 *y_out, *u_out, *v_out;
412   guint8 *y_in, *u_in, *v_in;
413   guint i, dx;
414
415   gst_buffer_map (inbuf, &in_map, GST_MAP_READ);
416   gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE);
417
418   /* Y plane */
419   y_in = in_map.data;
420   y_out = out_map.data;
421
422   y_in +=
423       (vcrop->crop_top * GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->in.info,
424           0)) + vcrop->crop_left;
425   dx = GST_VIDEO_INFO_WIDTH (&vcrop->out.info) * 1;
426
427   for (i = 0; i < GST_VIDEO_INFO_HEIGHT (&vcrop->out.info); ++i) {
428     memcpy (y_out, y_in, dx);
429     y_in += GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->in.info, 0);
430     y_out += GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->out.info, 0);
431   }
432
433   /* U + V planes */
434   u_in =
435       (guint8 *) in_map.data + GST_VIDEO_INFO_PLANE_OFFSET (&vcrop->in.info, 1);
436   u_out =
437       (guint8 *) out_map.data + GST_VIDEO_INFO_PLANE_OFFSET (&vcrop->out.info,
438       1);
439
440   u_in +=
441       (vcrop->crop_top / 2) * GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->in.info, 1);
442   u_in += vcrop->crop_left / 2;
443
444   v_in =
445       (guint8 *) in_map.data + GST_VIDEO_INFO_PLANE_OFFSET (&vcrop->in.info, 2);
446   v_out =
447       (guint8 *) out_map.data + GST_VIDEO_INFO_PLANE_OFFSET (&vcrop->out.info,
448       2);
449
450   v_in +=
451       (vcrop->crop_top / 2) * GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->in.info, 2);
452   v_in += vcrop->crop_left / 2;
453
454   dx = GST_ROUND_UP_2 (GST_VIDEO_INFO_WIDTH (&vcrop->out.info)) / 2;
455
456   for (i = 0; i < GST_ROUND_UP_2 (GST_VIDEO_INFO_HEIGHT (&vcrop->out.info)) / 2;
457       ++i) {
458     memcpy (u_out, u_in, dx);
459     memcpy (v_out, v_in, dx);
460     u_in += GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->in.info, 1);
461     u_out += GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->out.info, 1);
462     v_in += GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->in.info, 2);
463     v_out += GST_VIDEO_INFO_PLANE_STRIDE (&vcrop->out.info, 2);
464   }
465
466   gst_buffer_unmap (inbuf, &in_map);
467   gst_buffer_unmap (outbuf, &out_map);
468 }
469
470 static GstFlowReturn
471 gst_video_crop_transform (GstBaseTransform * trans, GstBuffer * inbuf,
472     GstBuffer * outbuf)
473 {
474   GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
475
476   g_mutex_lock (&vcrop->lock);
477   switch (vcrop->in.packing) {
478     case VIDEO_CROP_PIXEL_FORMAT_PACKED_SIMPLE:
479       gst_video_crop_transform_packed_simple (vcrop, inbuf, outbuf);
480       break;
481     case VIDEO_CROP_PIXEL_FORMAT_PACKED_COMPLEX:
482       gst_video_crop_transform_packed_complex (vcrop, inbuf, outbuf);
483       break;
484     case VIDEO_CROP_PIXEL_FORMAT_PLANAR:
485       gst_video_crop_transform_planar (vcrop, inbuf, outbuf);
486       break;
487     default:
488       g_assert_not_reached ();
489   }
490   g_mutex_unlock (&vcrop->lock);
491
492   return GST_FLOW_OK;
493 }
494
495 static gint
496 gst_video_crop_transform_dimension (gint val, gint delta)
497 {
498   gint64 new_val = (gint64) val + (gint64) delta;
499
500   new_val = CLAMP (new_val, 1, G_MAXINT);
501
502   return (gint) new_val;
503 }
504
505 static gboolean
506 gst_video_crop_transform_dimension_value (const GValue * src_val,
507     gint delta, GValue * dest_val)
508 {
509   gboolean ret = TRUE;
510
511   g_value_init (dest_val, G_VALUE_TYPE (src_val));
512
513   if (G_VALUE_HOLDS_INT (src_val)) {
514     gint ival = g_value_get_int (src_val);
515
516     ival = gst_video_crop_transform_dimension (ival, delta);
517     g_value_set_int (dest_val, ival);
518   } else if (GST_VALUE_HOLDS_INT_RANGE (src_val)) {
519     gint min = gst_value_get_int_range_min (src_val);
520     gint max = gst_value_get_int_range_max (src_val);
521
522     min = gst_video_crop_transform_dimension (min, delta);
523     max = gst_video_crop_transform_dimension (max, delta);
524     gst_value_set_int_range (dest_val, min, max);
525   } else if (GST_VALUE_HOLDS_LIST (src_val)) {
526     gint i;
527
528     for (i = 0; i < gst_value_list_get_size (src_val); ++i) {
529       const GValue *list_val;
530       GValue newval = { 0, };
531
532       list_val = gst_value_list_get_value (src_val, i);
533       if (gst_video_crop_transform_dimension_value (list_val, delta, &newval))
534         gst_value_list_append_value (dest_val, &newval);
535       g_value_unset (&newval);
536     }
537
538     if (gst_value_list_get_size (dest_val) == 0) {
539       g_value_unset (dest_val);
540       ret = FALSE;
541     }
542   } else {
543     g_value_unset (dest_val);
544     ret = FALSE;
545   }
546
547   return ret;
548 }
549
550 /* TODO use filter_caps */
551 static GstCaps *
552 gst_video_crop_transform_caps (GstBaseTransform * trans,
553     GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps)
554 {
555   GstVideoCrop *vcrop;
556   GstCaps *other_caps;
557   gint dy, dx, i;
558
559   vcrop = GST_VIDEO_CROP (trans);
560
561   GST_OBJECT_LOCK (vcrop);
562
563   GST_LOG_OBJECT (vcrop, "l=%d,r=%d,b=%d,t=%d",
564       vcrop->crop_left, vcrop->crop_right, vcrop->crop_bottom, vcrop->crop_top);
565
566   if (direction == GST_PAD_SRC) {
567     dx = vcrop->crop_left + vcrop->crop_right;
568     dy = vcrop->crop_top + vcrop->crop_bottom;
569   } else {
570     dx = 0 - (vcrop->crop_left + vcrop->crop_right);
571     dy = 0 - (vcrop->crop_top + vcrop->crop_bottom);
572   }
573   GST_OBJECT_UNLOCK (vcrop);
574
575   GST_LOG_OBJECT (vcrop, "transforming caps %" GST_PTR_FORMAT, caps);
576
577   other_caps = gst_caps_new_empty ();
578
579   for (i = 0; i < gst_caps_get_size (caps); ++i) {
580     const GValue *v;
581     GstStructure *structure, *new_structure;
582     GValue w_val = { 0, }, h_val = {
583     0,};
584
585     structure = gst_caps_get_structure (caps, i);
586
587     v = gst_structure_get_value (structure, "width");
588     if (!gst_video_crop_transform_dimension_value (v, dx, &w_val)) {
589       GST_WARNING_OBJECT (vcrop, "could not tranform width value with dx=%d"
590           ", caps structure=%" GST_PTR_FORMAT, dx, structure);
591       continue;
592     }
593
594     v = gst_structure_get_value (structure, "height");
595     if (!gst_video_crop_transform_dimension_value (v, dy, &h_val)) {
596       g_value_unset (&w_val);
597       GST_WARNING_OBJECT (vcrop, "could not tranform height value with dy=%d"
598           ", caps structure=%" GST_PTR_FORMAT, dy, structure);
599       continue;
600     }
601
602     new_structure = gst_structure_copy (structure);
603     gst_structure_set_value (new_structure, "width", &w_val);
604     gst_structure_set_value (new_structure, "height", &h_val);
605     g_value_unset (&w_val);
606     g_value_unset (&h_val);
607     GST_LOG_OBJECT (vcrop, "transformed structure %2d: %" GST_PTR_FORMAT
608         " => %" GST_PTR_FORMAT, i, structure, new_structure);
609     gst_caps_append_structure (other_caps, new_structure);
610   }
611
612   if (gst_caps_is_empty (other_caps)) {
613     gst_caps_unref (other_caps);
614     other_caps = NULL;
615   }
616
617   if (other_caps && filter_caps) {
618     GstCaps *tmp = gst_caps_intersect_full (filter_caps, other_caps,
619         GST_CAPS_INTERSECT_FIRST);
620     gst_caps_replace (&other_caps, tmp);
621     gst_caps_unref (tmp);
622   }
623
624   return other_caps;
625 }
626
627 static gboolean
628 gst_video_crop_set_caps (GstBaseTransform * trans, GstCaps * incaps,
629     GstCaps * outcaps)
630 {
631   GstVideoCrop *crop = GST_VIDEO_CROP (trans);
632
633   if (!gst_video_crop_get_image_details_from_caps (crop, &crop->in, incaps))
634     goto wrong_input;
635
636   if (!gst_video_crop_get_image_details_from_caps (crop, &crop->out, outcaps))
637     goto wrong_output;
638
639   if (G_UNLIKELY ((crop->crop_left + crop->crop_right) >=
640           GST_VIDEO_INFO_WIDTH (&crop->in.info)
641           || (crop->crop_top + crop->crop_bottom) >=
642           GST_VIDEO_INFO_HEIGHT (&crop->in.info)))
643     goto cropping_too_much;
644
645   GST_LOG_OBJECT (crop, "incaps = %" GST_PTR_FORMAT ", outcaps = %"
646       GST_PTR_FORMAT, incaps, outcaps);
647
648   if ((crop->crop_left | crop->crop_right | crop->
649           crop_top | crop->crop_bottom) == 0) {
650     GST_LOG_OBJECT (crop, "we are using passthrough");
651     gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (crop), TRUE);
652   } else {
653     GST_LOG_OBJECT (crop, "we are not using passthrough");
654     gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (crop), FALSE);
655   }
656
657   return TRUE;
658
659   /* ERROR */
660 wrong_input:
661   {
662     GST_DEBUG_OBJECT (crop, "failed to parse input caps %" GST_PTR_FORMAT,
663         incaps);
664     return FALSE;
665   }
666 wrong_output:
667   {
668     GST_DEBUG_OBJECT (crop, "failed to parse output caps %" GST_PTR_FORMAT,
669         outcaps);
670     return FALSE;
671   }
672 cropping_too_much:
673   {
674     GST_DEBUG_OBJECT (crop, "we are cropping too much");
675     return FALSE;
676   }
677 }
678
679 static void
680 gst_video_crop_set_property (GObject * object, guint prop_id,
681     const GValue * value, GParamSpec * pspec)
682 {
683   GstVideoCrop *video_crop;
684
685   video_crop = GST_VIDEO_CROP (object);
686
687   /* don't modify while we are transforming */
688   g_mutex_lock (&video_crop->lock);
689
690   /* protect with the object lock so that we can read them */
691   GST_OBJECT_LOCK (video_crop);
692   switch (prop_id) {
693     case ARG_LEFT:
694       video_crop->crop_left = g_value_get_int (value);
695       break;
696     case ARG_RIGHT:
697       video_crop->crop_right = g_value_get_int (value);
698       break;
699     case ARG_TOP:
700       video_crop->crop_top = g_value_get_int (value);
701       break;
702     case ARG_BOTTOM:
703       video_crop->crop_bottom = g_value_get_int (value);
704       break;
705     default:
706       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
707       break;
708   }
709   GST_LOG_OBJECT (video_crop, "l=%d,r=%d,b=%d,t=%d",
710       video_crop->crop_left, video_crop->crop_right, video_crop->crop_bottom,
711       video_crop->crop_top);
712   GST_OBJECT_UNLOCK (video_crop);
713
714   gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (video_crop));
715   g_mutex_unlock (&video_crop->lock);
716 }
717
718 static void
719 gst_video_crop_get_property (GObject * object, guint prop_id, GValue * value,
720     GParamSpec * pspec)
721 {
722   GstVideoCrop *video_crop;
723
724   video_crop = GST_VIDEO_CROP (object);
725
726   GST_OBJECT_LOCK (video_crop);
727   switch (prop_id) {
728     case ARG_LEFT:
729       g_value_set_int (value, video_crop->crop_left);
730       break;
731     case ARG_RIGHT:
732       g_value_set_int (value, video_crop->crop_right);
733       break;
734     case ARG_TOP:
735       g_value_set_int (value, video_crop->crop_top);
736       break;
737     case ARG_BOTTOM:
738       g_value_set_int (value, video_crop->crop_bottom);
739       break;
740     default:
741       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
742       break;
743   }
744   GST_OBJECT_UNLOCK (video_crop);
745 }
746
747 static gboolean
748 plugin_init (GstPlugin * plugin)
749 {
750   GST_DEBUG_CATEGORY_INIT (videocrop_debug, "videocrop", 0, "videocrop");
751
752   if (gst_element_register (plugin, "videocrop", GST_RANK_NONE,
753           GST_TYPE_VIDEO_CROP)
754       && gst_element_register (plugin, "aspectratiocrop", GST_RANK_NONE,
755           GST_TYPE_ASPECT_RATIO_CROP))
756     return TRUE;
757
758   return FALSE;
759 }
760
761 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
762     GST_VERSION_MINOR,
763     "videocrop",
764     "Crops video into a user-defined region",
765     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)