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., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, 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   PROP_0,
74   PROP_LEFT,
75   PROP_RIGHT,
76   PROP_TOP,
77   PROP_BOTTOM
78 };
79 
80 /* we support the same caps as aspectratiocrop (sync changes) */
81 #define VIDEO_CROP_CAPS                                \
82   GST_VIDEO_CAPS_MAKE ("{ RGBx, xRGB, BGRx, xBGR, "    \
83       "RGBA, ARGB, BGRA, ABGR, RGB, BGR, AYUV, YUY2, " \
84       "YVYU, UYVY, I420, YV12, RGB16, RGB15, GRAY8, "  \
85       "NV12, NV21, GRAY16_LE, GRAY16_BE }")
86 
87 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
88     GST_PAD_SRC,
89     GST_PAD_ALWAYS,
90     GST_STATIC_CAPS (VIDEO_CROP_CAPS)
91     );
92 
93 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
94     GST_PAD_SINK,
95     GST_PAD_ALWAYS,
96     GST_STATIC_CAPS (VIDEO_CROP_CAPS)
97     );
98 
99 #define gst_video_crop_parent_class parent_class
100 G_DEFINE_TYPE (GstVideoCrop, gst_video_crop, GST_TYPE_VIDEO_FILTER);
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 gst_video_crop_decide_allocation (GstBaseTransform * trans,
118     GstQuery * query);
119 static gboolean gst_video_crop_propose_allocation (GstBaseTransform * trans,
120     GstQuery * decide_query, GstQuery * query);
121 static GstFlowReturn gst_video_crop_transform_ip (GstBaseTransform * trans,
122     GstBuffer * buf);
123 
124 static gboolean
gst_video_crop_src_event(GstBaseTransform * trans,GstEvent * event)125 gst_video_crop_src_event (GstBaseTransform * trans, GstEvent * event)
126 {
127   GstEvent *new_event;
128   GstStructure *new_structure;
129   const GstStructure *structure;
130   const gchar *event_name;
131   double pointer_x;
132   double pointer_y;
133 
134   GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
135   new_event = NULL;
136 
137   GST_OBJECT_LOCK (vcrop);
138   if (GST_EVENT_TYPE (event) == GST_EVENT_NAVIGATION &&
139       (vcrop->crop_left != 0 || vcrop->crop_top != 0)) {
140     structure = gst_event_get_structure (event);
141     event_name = gst_structure_get_string (structure, "event");
142 
143     if (event_name &&
144         (strcmp (event_name, "mouse-move") == 0 ||
145             strcmp (event_name, "mouse-button-press") == 0 ||
146             strcmp (event_name, "mouse-button-release") == 0)) {
147 
148       if (gst_structure_get_double (structure, "pointer_x", &pointer_x) &&
149           gst_structure_get_double (structure, "pointer_y", &pointer_y)) {
150 
151         new_structure = gst_structure_copy (structure);
152         gst_structure_set (new_structure,
153             "pointer_x", G_TYPE_DOUBLE, (double) (pointer_x + vcrop->crop_left),
154             "pointer_y", G_TYPE_DOUBLE, (double) (pointer_y + vcrop->crop_top),
155             NULL);
156 
157         new_event = gst_event_new_navigation (new_structure);
158         gst_event_unref (event);
159       } else {
160         GST_WARNING_OBJECT (vcrop, "Failed to read navigation event");
161       }
162     }
163   }
164 
165   GST_OBJECT_UNLOCK (vcrop);
166 
167   return GST_BASE_TRANSFORM_CLASS (parent_class)->src_event (trans,
168       (new_event ? new_event : event));
169 }
170 
171 static void
gst_video_crop_class_init(GstVideoCropClass * klass)172 gst_video_crop_class_init (GstVideoCropClass * klass)
173 {
174   GObjectClass *gobject_class;
175   GstElementClass *element_class;
176   GstBaseTransformClass *basetransform_class;
177   GstVideoFilterClass *vfilter_class;
178 
179   gobject_class = (GObjectClass *) klass;
180   element_class = (GstElementClass *) klass;
181   basetransform_class = (GstBaseTransformClass *) klass;
182   vfilter_class = (GstVideoFilterClass *) klass;
183 
184   gobject_class->set_property = gst_video_crop_set_property;
185   gobject_class->get_property = gst_video_crop_get_property;
186 
187   g_object_class_install_property (gobject_class, PROP_LEFT,
188       g_param_spec_int ("left", "Left",
189           "Pixels to crop at left (-1 to auto-crop)", -1, G_MAXINT, 0,
190           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
191           GST_PARAM_MUTABLE_PLAYING));
192   g_object_class_install_property (gobject_class, PROP_RIGHT,
193       g_param_spec_int ("right", "Right",
194           "Pixels to crop at right (-1 to auto-crop)", -1, G_MAXINT, 0,
195           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
196           GST_PARAM_MUTABLE_PLAYING));
197   g_object_class_install_property (gobject_class, PROP_TOP,
198       g_param_spec_int ("top", "Top", "Pixels to crop at top (-1 to auto-crop)",
199           -1, G_MAXINT, 0,
200           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
201           GST_PARAM_MUTABLE_PLAYING));
202   g_object_class_install_property (gobject_class, PROP_BOTTOM,
203       g_param_spec_int ("bottom", "Bottom",
204           "Pixels to crop at bottom (-1 to auto-crop)", -1, G_MAXINT, 0,
205           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
206           GST_PARAM_MUTABLE_PLAYING));
207 
208   gst_element_class_add_static_pad_template (element_class, &sink_template);
209   gst_element_class_add_static_pad_template (element_class, &src_template);
210   gst_element_class_set_static_metadata (element_class, "Crop",
211       "Filter/Effect/Video",
212       "Crops video into a user-defined region",
213       "Tim-Philipp Müller <tim centricular net>");
214 
215   basetransform_class->transform_ip_on_passthrough = FALSE;
216   basetransform_class->transform_caps =
217       GST_DEBUG_FUNCPTR (gst_video_crop_transform_caps);
218   basetransform_class->src_event = GST_DEBUG_FUNCPTR (gst_video_crop_src_event);
219   basetransform_class->decide_allocation =
220       GST_DEBUG_FUNCPTR (gst_video_crop_decide_allocation);
221   basetransform_class->propose_allocation =
222       GST_DEBUG_FUNCPTR (gst_video_crop_propose_allocation);
223   basetransform_class->transform_ip =
224       GST_DEBUG_FUNCPTR (gst_video_crop_transform_ip);
225 
226   vfilter_class->set_info = GST_DEBUG_FUNCPTR (gst_video_crop_set_info);
227   vfilter_class->transform_frame =
228       GST_DEBUG_FUNCPTR (gst_video_crop_transform_frame);
229 }
230 
231 static void
gst_video_crop_init(GstVideoCrop * vcrop)232 gst_video_crop_init (GstVideoCrop * vcrop)
233 {
234   vcrop->crop_right = 0;
235   vcrop->crop_left = 0;
236   vcrop->crop_top = 0;
237   vcrop->crop_bottom = 0;
238 }
239 
240 #define ROUND_DOWN_2(n)  ((n)&(~1))
241 
242 static void
gst_video_crop_transform_packed_complex(GstVideoCrop * vcrop,GstVideoFrame * in_frame,GstVideoFrame * out_frame,gint x,gint y)243 gst_video_crop_transform_packed_complex (GstVideoCrop * vcrop,
244     GstVideoFrame * in_frame, GstVideoFrame * out_frame, gint x, gint y)
245 {
246   guint8 *in_data, *out_data;
247   guint i, dx;
248   gint width, height;
249   gint in_stride;
250   gint out_stride;
251 
252   width = GST_VIDEO_FRAME_WIDTH (out_frame);
253   height = GST_VIDEO_FRAME_HEIGHT (out_frame);
254 
255   in_data = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
256   out_data = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);
257 
258   in_stride = GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 0);
259   out_stride = GST_VIDEO_FRAME_PLANE_STRIDE (out_frame, 0);
260 
261   in_data += vcrop->crop_top * in_stride;
262 
263   /* rounding down here so we end up at the start of a macro-pixel and not
264    * in the middle of one */
265   in_data += ROUND_DOWN_2 (vcrop->crop_left) *
266       GST_VIDEO_FRAME_COMP_PSTRIDE (in_frame, 0);
267 
268   dx = width * GST_VIDEO_FRAME_COMP_PSTRIDE (out_frame, 0);
269 
270   /* UYVY = 4:2:2 - [U0 Y0 V0 Y1] [U2 Y2 V2 Y3] [U4 Y4 V4 Y5]
271    * YUYV = 4:2:2 - [Y0 U0 Y1 V0] [Y2 U2 Y3 V2] [Y4 U4 Y5 V4] = YUY2 */
272   if ((vcrop->crop_left % 2) != 0) {
273     for (i = 0; i < height; ++i) {
274       gint j;
275 
276       memcpy (out_data, in_data, dx);
277 
278       /* move just the Y samples one pixel to the left, don't worry about
279        * chroma shift */
280       for (j = vcrop->macro_y_off; j < out_stride - 2; j += 2)
281         out_data[j] = in_data[j + 2];
282 
283       in_data += in_stride;
284       out_data += out_stride;
285     }
286   } else {
287     for (i = 0; i < height; ++i) {
288       memcpy (out_data, in_data, dx);
289       in_data += in_stride;
290       out_data += out_stride;
291     }
292   }
293 }
294 
295 static void
gst_video_crop_transform_packed_simple(GstVideoCrop * vcrop,GstVideoFrame * in_frame,GstVideoFrame * out_frame,gint x,gint y)296 gst_video_crop_transform_packed_simple (GstVideoCrop * vcrop,
297     GstVideoFrame * in_frame, GstVideoFrame * out_frame, gint x, gint y)
298 {
299   guint8 *in_data, *out_data;
300   gint width, height;
301   guint i, dx;
302   gint in_stride, out_stride;
303 
304   width = GST_VIDEO_FRAME_WIDTH (out_frame);
305   height = GST_VIDEO_FRAME_HEIGHT (out_frame);
306 
307   in_data = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
308   out_data = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);
309 
310   in_stride = GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 0);
311   out_stride = GST_VIDEO_FRAME_PLANE_STRIDE (out_frame, 0);
312 
313   in_data += (vcrop->crop_top + y) * in_stride;
314   in_data +=
315       (vcrop->crop_left + x) * GST_VIDEO_FRAME_COMP_PSTRIDE (in_frame, 0);
316 
317   dx = width * GST_VIDEO_FRAME_COMP_PSTRIDE (out_frame, 0);
318 
319   for (i = 0; i < height; ++i) {
320     memcpy (out_data, in_data, dx);
321     in_data += in_stride;
322     out_data += out_stride;
323   }
324 }
325 
326 static void
gst_video_crop_transform_planar(GstVideoCrop * vcrop,GstVideoFrame * in_frame,GstVideoFrame * out_frame,gint x,gint y)327 gst_video_crop_transform_planar (GstVideoCrop * vcrop,
328     GstVideoFrame * in_frame, GstVideoFrame * out_frame, gint x, gint y)
329 {
330   gint width, height;
331   gint crop_top, crop_left;
332   guint8 *y_out, *u_out, *v_out;
333   guint8 *y_in, *u_in, *v_in;
334   guint i, dx;
335 
336   width = GST_VIDEO_FRAME_WIDTH (out_frame);
337   height = GST_VIDEO_FRAME_HEIGHT (out_frame);
338   crop_left = vcrop->crop_left + x;
339   crop_top = vcrop->crop_top + y;
340 
341   /* Y plane */
342   y_in = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
343   y_out = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);
344 
345   y_in += (crop_top * GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 0)) + crop_left;
346   dx = width;
347 
348   for (i = 0; i < height; ++i) {
349     memcpy (y_out, y_in, dx);
350     y_in += GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 0);
351     y_out += GST_VIDEO_FRAME_PLANE_STRIDE (out_frame, 0);
352   }
353 
354   /* U + V planes */
355   u_in = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 1);
356   u_out = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 1);
357 
358   u_in += (crop_top / 2) * GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 1);
359   u_in += crop_left / 2;
360 
361   v_in = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 2);
362   v_out = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 2);
363 
364   v_in += (crop_top / 2) * GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 2);
365   v_in += crop_left / 2;
366 
367   dx = GST_ROUND_UP_2 (width) / 2;
368 
369   for (i = 0; i < GST_ROUND_UP_2 (height) / 2; ++i) {
370     memcpy (u_out, u_in, dx);
371     memcpy (v_out, v_in, dx);
372     u_in += GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 1);
373     u_out += GST_VIDEO_FRAME_PLANE_STRIDE (out_frame, 1);
374     v_in += GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 2);
375     v_out += GST_VIDEO_FRAME_PLANE_STRIDE (out_frame, 2);
376   }
377 }
378 
379 static void
gst_video_crop_transform_semi_planar(GstVideoCrop * vcrop,GstVideoFrame * in_frame,GstVideoFrame * out_frame,gint x,gint y)380 gst_video_crop_transform_semi_planar (GstVideoCrop * vcrop,
381     GstVideoFrame * in_frame, GstVideoFrame * out_frame, gint x, gint y)
382 {
383   gint width, height;
384   gint crop_top, crop_left;
385   guint8 *y_out, *uv_out;
386   guint8 *y_in, *uv_in;
387   guint i, dx;
388 
389   width = GST_VIDEO_FRAME_WIDTH (out_frame);
390   height = GST_VIDEO_FRAME_HEIGHT (out_frame);
391   crop_left = vcrop->crop_left + x;
392   crop_top = vcrop->crop_top + y;
393 
394   /* Y plane */
395   y_in = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
396   y_out = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);
397 
398   /* UV plane */
399   uv_in = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 1);
400   uv_out = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 1);
401 
402   y_in += crop_top * GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 0) + crop_left;
403   dx = width;
404 
405   for (i = 0; i < height; ++i) {
406     memcpy (y_out, y_in, dx);
407     y_in += GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 0);
408     y_out += GST_VIDEO_FRAME_PLANE_STRIDE (out_frame, 0);
409   }
410 
411   uv_in += (crop_top / 2) * GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 1);
412   uv_in += GST_ROUND_DOWN_2 (crop_left);
413   dx = GST_ROUND_UP_2 (width);
414 
415   for (i = 0; i < GST_ROUND_UP_2 (height) / 2; i++) {
416     memcpy (uv_out, uv_in, dx);
417     uv_in += GST_VIDEO_FRAME_PLANE_STRIDE (in_frame, 1);
418     uv_out += GST_VIDEO_FRAME_PLANE_STRIDE (out_frame, 1);
419   }
420 }
421 
422 static GstFlowReturn
gst_video_crop_transform_frame(GstVideoFilter * vfilter,GstVideoFrame * in_frame,GstVideoFrame * out_frame)423 gst_video_crop_transform_frame (GstVideoFilter * vfilter,
424     GstVideoFrame * in_frame, GstVideoFrame * out_frame)
425 {
426   GstVideoCrop *vcrop = GST_VIDEO_CROP (vfilter);
427   GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta (in_frame->buffer);
428   gint x = 0, y = 0;
429 
430   if (G_UNLIKELY (vcrop->need_update)) {
431     if (!gst_video_crop_set_info (vfilter, NULL, &vcrop->in_info, NULL,
432             &vcrop->out_info)) {
433       return GST_FLOW_ERROR;
434     }
435   }
436 
437   if (meta) {
438     x = meta->x;
439     y = meta->y;
440   }
441 
442   switch (vcrop->packing) {
443     case VIDEO_CROP_PIXEL_FORMAT_PACKED_SIMPLE:
444       gst_video_crop_transform_packed_simple (vcrop, in_frame, out_frame, x, y);
445       break;
446     case VIDEO_CROP_PIXEL_FORMAT_PACKED_COMPLEX:
447       gst_video_crop_transform_packed_complex (vcrop, in_frame, out_frame, x,
448           y);
449       break;
450     case VIDEO_CROP_PIXEL_FORMAT_PLANAR:
451       gst_video_crop_transform_planar (vcrop, in_frame, out_frame, x, y);
452       break;
453     case VIDEO_CROP_PIXEL_FORMAT_SEMI_PLANAR:
454       gst_video_crop_transform_semi_planar (vcrop, in_frame, out_frame, x, y);
455       break;
456     default:
457       g_assert_not_reached ();
458   }
459 
460   return GST_FLOW_OK;
461 }
462 
463 static gboolean
gst_video_crop_decide_allocation(GstBaseTransform * trans,GstQuery * query)464 gst_video_crop_decide_allocation (GstBaseTransform * trans, GstQuery * query)
465 {
466   GstVideoCrop *crop = GST_VIDEO_CROP (trans);
467   gboolean use_crop_meta;
468 
469   use_crop_meta = (gst_query_find_allocation_meta (query,
470           GST_VIDEO_CROP_META_API_TYPE, NULL) &&
471       gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL));
472 
473   if ((crop->crop_left | crop->crop_right | crop->crop_top | crop->
474           crop_bottom) == 0) {
475     GST_INFO_OBJECT (crop, "we are using passthrough");
476     gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (crop), TRUE);
477     gst_base_transform_set_in_place (GST_BASE_TRANSFORM (crop), FALSE);
478   } else if (use_crop_meta) {
479     GST_INFO_OBJECT (crop, "we are doing in-place transform using crop meta");
480     gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (crop), FALSE);
481     gst_base_transform_set_in_place (GST_BASE_TRANSFORM (crop), TRUE);
482   } else {
483     GST_INFO_OBJECT (crop, "we are not using passthrough");
484     gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (crop), FALSE);
485     gst_base_transform_set_in_place (GST_BASE_TRANSFORM (crop), FALSE);
486   }
487 
488   return GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (trans,
489       query);
490 }
491 
492 static gboolean
gst_video_crop_propose_allocation(GstBaseTransform * trans,GstQuery * decide_query,GstQuery * query)493 gst_video_crop_propose_allocation (GstBaseTransform * trans,
494     GstQuery * decide_query, GstQuery * query)
495 {
496   /* if we are not passthrough, we can handle video meta and crop meta */
497   if (decide_query) {
498     GST_DEBUG_OBJECT (trans, "Advertising video meta and crop meta support");
499     gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
500     gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE, NULL);
501   }
502 
503   return GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (trans,
504       decide_query, query);
505 }
506 
507 static GstFlowReturn
gst_video_crop_transform_ip(GstBaseTransform * trans,GstBuffer * buf)508 gst_video_crop_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
509 {
510   GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
511   GstVideoFilter *vfilter = GST_VIDEO_FILTER (trans);
512   GstVideoMeta *video_meta;
513   GstVideoCropMeta *crop_meta;
514 
515   GST_LOG_OBJECT (trans, "Transforming in-place");
516 
517   if (G_UNLIKELY (vcrop->need_update)) {
518     if (!gst_video_crop_set_info (vfilter, NULL, &vcrop->in_info, NULL,
519             &vcrop->out_info)) {
520       return GST_FLOW_ERROR;
521     }
522   }
523 
524   /* The video meta is required since we are going to make the caps
525    * width/height smaller, which would not result in a usable GstVideoInfo for
526    * mapping the buffer. */
527   video_meta = gst_buffer_get_video_meta (buf);
528   if (!video_meta) {
529     video_meta = gst_buffer_add_video_meta (buf, GST_VIDEO_FRAME_FLAG_NONE,
530         GST_VIDEO_INFO_FORMAT (&vcrop->in_info), vcrop->in_info.width,
531         vcrop->in_info.height);
532   }
533 
534   crop_meta = gst_buffer_get_video_crop_meta (buf);
535   if (!crop_meta) {
536     crop_meta = gst_buffer_add_video_crop_meta (buf);
537     crop_meta->width = vcrop->in_info.width;
538     crop_meta->height = vcrop->in_info.height;
539   }
540 
541   crop_meta->x += vcrop->crop_left;
542   crop_meta->y += vcrop->crop_top;
543   crop_meta->width = GST_VIDEO_INFO_WIDTH (&vcrop->out_info);
544   crop_meta->height = GST_VIDEO_INFO_HEIGHT (&vcrop->out_info);
545 
546   return GST_FLOW_OK;
547 }
548 
549 static gint
gst_video_crop_transform_dimension(gint val,gint delta)550 gst_video_crop_transform_dimension (gint val, gint delta)
551 {
552   gint64 new_val = (gint64) val + (gint64) delta;
553 
554   new_val = CLAMP (new_val, 1, G_MAXINT);
555 
556   return (gint) new_val;
557 }
558 
559 static gboolean
gst_video_crop_transform_dimension_value(const GValue * src_val,gint delta,GValue * dest_val,GstPadDirection direction,gboolean dynamic)560 gst_video_crop_transform_dimension_value (const GValue * src_val,
561     gint delta, GValue * dest_val, GstPadDirection direction, gboolean dynamic)
562 {
563   gboolean ret = TRUE;
564 
565   if (G_VALUE_HOLDS_INT (src_val)) {
566     gint ival = g_value_get_int (src_val);
567     ival = gst_video_crop_transform_dimension (ival, delta);
568 
569     if (dynamic) {
570       if (direction == GST_PAD_SRC) {
571         if (ival == G_MAXINT) {
572           g_value_init (dest_val, G_TYPE_INT);
573           g_value_set_int (dest_val, ival);
574         } else {
575           g_value_init (dest_val, GST_TYPE_INT_RANGE);
576           gst_value_set_int_range (dest_val, ival, G_MAXINT);
577         }
578       } else {
579         if (ival == 1) {
580           g_value_init (dest_val, G_TYPE_INT);
581           g_value_set_int (dest_val, ival);
582         } else {
583           g_value_init (dest_val, GST_TYPE_INT_RANGE);
584           gst_value_set_int_range (dest_val, 1, ival);
585         }
586       }
587     } else {
588       g_value_init (dest_val, G_TYPE_INT);
589       g_value_set_int (dest_val, ival);
590     }
591   } else if (GST_VALUE_HOLDS_INT_RANGE (src_val)) {
592     gint min = gst_value_get_int_range_min (src_val);
593     gint max = gst_value_get_int_range_max (src_val);
594 
595     min = gst_video_crop_transform_dimension (min, delta);
596     max = gst_video_crop_transform_dimension (max, delta);
597 
598     if (dynamic) {
599       if (direction == GST_PAD_SRC)
600         max = G_MAXINT;
601       else
602         min = 1;
603     }
604 
605     if (min == max) {
606       g_value_init (dest_val, G_TYPE_INT);
607       g_value_set_int (dest_val, min);
608     } else {
609       g_value_init (dest_val, GST_TYPE_INT_RANGE);
610       gst_value_set_int_range (dest_val, min, max);
611     }
612   } else if (GST_VALUE_HOLDS_LIST (src_val)) {
613     gint i;
614 
615     g_value_init (dest_val, GST_TYPE_LIST);
616 
617     for (i = 0; i < gst_value_list_get_size (src_val); ++i) {
618       const GValue *list_val;
619       GValue newval = { 0, };
620 
621       list_val = gst_value_list_get_value (src_val, i);
622       if (gst_video_crop_transform_dimension_value (list_val, delta, &newval,
623               direction, dynamic))
624         gst_value_list_append_value (dest_val, &newval);
625       g_value_unset (&newval);
626     }
627 
628     if (gst_value_list_get_size (dest_val) == 0) {
629       g_value_unset (dest_val);
630       ret = FALSE;
631     }
632   } else {
633     ret = FALSE;
634   }
635 
636   return ret;
637 }
638 
639 static GstCaps *
gst_video_crop_transform_caps(GstBaseTransform * trans,GstPadDirection direction,GstCaps * caps,GstCaps * filter_caps)640 gst_video_crop_transform_caps (GstBaseTransform * trans,
641     GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps)
642 {
643   GstVideoCrop *vcrop;
644   GstCaps *other_caps;
645   gint dy, dx, i, left, right, bottom, top;
646   gboolean w_dynamic, h_dynamic;
647 
648   vcrop = GST_VIDEO_CROP (trans);
649 
650   GST_OBJECT_LOCK (vcrop);
651 
652   GST_LOG_OBJECT (vcrop, "l=%d,r=%d,b=%d,t=%d",
653       vcrop->prop_left, vcrop->prop_right, vcrop->prop_bottom, vcrop->prop_top);
654 
655   w_dynamic = (vcrop->prop_left == -1 || vcrop->prop_right == -1);
656   h_dynamic = (vcrop->prop_top == -1 || vcrop->prop_bottom == -1);
657 
658   left = (vcrop->prop_left == -1) ? 0 : vcrop->prop_left;
659   right = (vcrop->prop_right == -1) ? 0 : vcrop->prop_right;
660   bottom = (vcrop->prop_bottom == -1) ? 0 : vcrop->prop_bottom;
661   top = (vcrop->prop_top == -1) ? 0 : vcrop->prop_top;
662 
663   GST_OBJECT_UNLOCK (vcrop);
664 
665   if (direction == GST_PAD_SRC) {
666     dx = left + right;
667     dy = top + bottom;
668   } else {
669     dx = 0 - (left + right);
670     dy = 0 - (top + bottom);
671   }
672 
673   GST_LOG_OBJECT (vcrop, "transforming caps %" GST_PTR_FORMAT, caps);
674 
675   other_caps = gst_caps_new_empty ();
676 
677   for (i = 0; i < gst_caps_get_size (caps); ++i) {
678     const GValue *v;
679     GstStructure *structure, *new_structure;
680     GValue w_val = { 0, }, h_val = {
681     0,};
682 
683     structure = gst_caps_get_structure (caps, i);
684 
685     v = gst_structure_get_value (structure, "width");
686     if (!gst_video_crop_transform_dimension_value (v, dx, &w_val, direction,
687             w_dynamic)) {
688       GST_WARNING_OBJECT (vcrop, "could not tranform width value with dx=%d"
689           ", caps structure=%" GST_PTR_FORMAT, dx, structure);
690       continue;
691     }
692 
693     v = gst_structure_get_value (structure, "height");
694     if (!gst_video_crop_transform_dimension_value (v, dy, &h_val, direction,
695             h_dynamic)) {
696       g_value_unset (&w_val);
697       GST_WARNING_OBJECT (vcrop, "could not tranform height value with dy=%d"
698           ", caps structure=%" GST_PTR_FORMAT, dy, structure);
699       continue;
700     }
701 
702     new_structure = gst_structure_copy (structure);
703     gst_structure_set_value (new_structure, "width", &w_val);
704     gst_structure_set_value (new_structure, "height", &h_val);
705     g_value_unset (&w_val);
706     g_value_unset (&h_val);
707     GST_LOG_OBJECT (vcrop, "transformed structure %2d: %" GST_PTR_FORMAT
708         " => %" GST_PTR_FORMAT, i, structure, new_structure);
709     gst_caps_append_structure (other_caps, new_structure);
710   }
711 
712   if (!gst_caps_is_empty (other_caps) && filter_caps) {
713     GstCaps *tmp = gst_caps_intersect_full (filter_caps, other_caps,
714         GST_CAPS_INTERSECT_FIRST);
715     gst_caps_replace (&other_caps, tmp);
716     gst_caps_unref (tmp);
717   }
718 
719   return other_caps;
720 }
721 
722 static gboolean
gst_video_crop_set_info(GstVideoFilter * vfilter,GstCaps * in,GstVideoInfo * in_info,GstCaps * out,GstVideoInfo * out_info)723 gst_video_crop_set_info (GstVideoFilter * vfilter, GstCaps * in,
724     GstVideoInfo * in_info, GstCaps * out, GstVideoInfo * out_info)
725 {
726   GstVideoCrop *crop = GST_VIDEO_CROP (vfilter);
727   int dx, dy;
728 
729   GST_OBJECT_LOCK (crop);
730   crop->need_update = FALSE;
731   crop->crop_left = crop->prop_left;
732   crop->crop_right = crop->prop_right;
733   crop->crop_top = crop->prop_top;
734   crop->crop_bottom = crop->prop_bottom;
735   GST_OBJECT_UNLOCK (crop);
736 
737   dx = GST_VIDEO_INFO_WIDTH (in_info) - GST_VIDEO_INFO_WIDTH (out_info);
738   dy = GST_VIDEO_INFO_HEIGHT (in_info) - GST_VIDEO_INFO_HEIGHT (out_info);
739 
740   if (crop->crop_left == -1 && crop->crop_right == -1) {
741     crop->crop_left = dx / 2;
742     crop->crop_right = dx / 2 + (dx & 1);
743   } else if (crop->crop_left == -1) {
744     if (G_UNLIKELY (crop->crop_right > dx))
745       goto cropping_too_much;
746     crop->crop_left = dx - crop->crop_right;
747   } else if (crop->crop_right == -1) {
748     if (G_UNLIKELY (crop->crop_left > dx))
749       goto cropping_too_much;
750     crop->crop_right = dx - crop->crop_left;
751   }
752 
753   if (crop->crop_top == -1 && crop->crop_bottom == -1) {
754     crop->crop_top = dy / 2;
755     crop->crop_bottom = dy / 2 + (dy & 1);
756   } else if (crop->crop_top == -1) {
757     if (G_UNLIKELY (crop->crop_bottom > dy))
758       goto cropping_too_much;
759     crop->crop_top = dy - crop->crop_bottom;
760   } else if (crop->crop_bottom == -1) {
761     if (G_UNLIKELY (crop->crop_top > dy))
762       goto cropping_too_much;
763     crop->crop_bottom = dy - crop->crop_top;
764   }
765 
766   if (G_UNLIKELY ((crop->crop_left + crop->crop_right) >=
767           GST_VIDEO_INFO_WIDTH (in_info)
768           || (crop->crop_top + crop->crop_bottom) >=
769           GST_VIDEO_INFO_HEIGHT (in_info)))
770     goto cropping_too_much;
771 
772   if (in && out)
773     GST_LOG_OBJECT (crop, "incaps = %" GST_PTR_FORMAT ", outcaps = %"
774         GST_PTR_FORMAT, in, out);
775 
776   if (GST_VIDEO_INFO_IS_RGB (in_info)
777       || GST_VIDEO_INFO_IS_GRAY (in_info)) {
778     crop->packing = VIDEO_CROP_PIXEL_FORMAT_PACKED_SIMPLE;
779   } else {
780     switch (GST_VIDEO_INFO_FORMAT (in_info)) {
781       case GST_VIDEO_FORMAT_AYUV:
782         crop->packing = VIDEO_CROP_PIXEL_FORMAT_PACKED_SIMPLE;
783         break;
784       case GST_VIDEO_FORMAT_YVYU:
785       case GST_VIDEO_FORMAT_YUY2:
786       case GST_VIDEO_FORMAT_UYVY:
787         crop->packing = VIDEO_CROP_PIXEL_FORMAT_PACKED_COMPLEX;
788         if (GST_VIDEO_INFO_FORMAT (in_info) == GST_VIDEO_FORMAT_UYVY) {
789           /* UYVY = 4:2:2 - [U0 Y0 V0 Y1] [U2 Y2 V2 Y3] [U4 Y4 V4 Y5] */
790           crop->macro_y_off = 1;
791         } else {
792           /* YUYV = 4:2:2 - [Y0 U0 Y1 V0] [Y2 U2 Y3 V2] [Y4 U4 Y5 V4] = YUY2 */
793           crop->macro_y_off = 0;
794         }
795         break;
796       case GST_VIDEO_FORMAT_I420:
797       case GST_VIDEO_FORMAT_YV12:
798         crop->packing = VIDEO_CROP_PIXEL_FORMAT_PLANAR;
799         break;
800       case GST_VIDEO_FORMAT_NV12:
801       case GST_VIDEO_FORMAT_NV21:
802         crop->packing = VIDEO_CROP_PIXEL_FORMAT_SEMI_PLANAR;
803         break;
804       default:
805         goto unknown_format;
806     }
807   }
808 
809   crop->in_info = *in_info;
810   crop->out_info = *out_info;
811 
812   /* Ensure our decide_allocation will be called again */
813   gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (crop), FALSE);
814   gst_base_transform_set_in_place (GST_BASE_TRANSFORM (crop), FALSE);
815 
816   return TRUE;
817 
818   /* ERROR */
819 cropping_too_much:
820   {
821     GST_WARNING_OBJECT (crop, "we are cropping too much");
822     return FALSE;
823   }
824 unknown_format:
825   {
826     GST_WARNING_OBJECT (crop, "Unsupported format");
827     return FALSE;
828   }
829 }
830 
831 /* called with object lock */
832 static inline void
gst_video_crop_set_crop(GstVideoCrop * vcrop,gint new_value,gint * prop)833 gst_video_crop_set_crop (GstVideoCrop * vcrop, gint new_value, gint * prop)
834 {
835   if (*prop != new_value) {
836     *prop = new_value;
837     vcrop->need_update = TRUE;
838   }
839 }
840 
841 static void
gst_video_crop_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)842 gst_video_crop_set_property (GObject * object, guint prop_id,
843     const GValue * value, GParamSpec * pspec)
844 {
845   GstVideoCrop *video_crop;
846 
847   video_crop = GST_VIDEO_CROP (object);
848 
849   GST_OBJECT_LOCK (video_crop);
850   switch (prop_id) {
851     case PROP_LEFT:
852       gst_video_crop_set_crop (video_crop, g_value_get_int (value),
853           &video_crop->prop_left);
854       break;
855     case PROP_RIGHT:
856       gst_video_crop_set_crop (video_crop, g_value_get_int (value),
857           &video_crop->prop_right);
858       break;
859     case PROP_TOP:
860       gst_video_crop_set_crop (video_crop, g_value_get_int (value),
861           &video_crop->prop_top);
862       break;
863     case PROP_BOTTOM:
864       gst_video_crop_set_crop (video_crop, g_value_get_int (value),
865           &video_crop->prop_bottom);
866       break;
867     default:
868       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
869       break;
870   }
871   GST_LOG_OBJECT (video_crop, "l=%d,r=%d,b=%d,t=%d, need_update:%d",
872       video_crop->prop_left, video_crop->prop_right, video_crop->prop_bottom,
873       video_crop->prop_top, video_crop->need_update);
874 
875   GST_OBJECT_UNLOCK (video_crop);
876 
877   gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (video_crop));
878 }
879 
880 static void
gst_video_crop_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)881 gst_video_crop_get_property (GObject * object, guint prop_id, GValue * value,
882     GParamSpec * pspec)
883 {
884   GstVideoCrop *video_crop;
885 
886   video_crop = GST_VIDEO_CROP (object);
887 
888   GST_OBJECT_LOCK (video_crop);
889   switch (prop_id) {
890     case PROP_LEFT:
891       g_value_set_int (value, video_crop->prop_left);
892       break;
893     case PROP_RIGHT:
894       g_value_set_int (value, video_crop->prop_right);
895       break;
896     case PROP_TOP:
897       g_value_set_int (value, video_crop->prop_top);
898       break;
899     case PROP_BOTTOM:
900       g_value_set_int (value, video_crop->prop_bottom);
901       break;
902     default:
903       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
904       break;
905   }
906   GST_OBJECT_UNLOCK (video_crop);
907 }
908 
909 static gboolean
plugin_init(GstPlugin * plugin)910 plugin_init (GstPlugin * plugin)
911 {
912   GST_DEBUG_CATEGORY_INIT (videocrop_debug, "videocrop", 0, "videocrop");
913 
914   if (gst_element_register (plugin, "videocrop", GST_RANK_NONE,
915           GST_TYPE_VIDEO_CROP)
916       && gst_element_register (plugin, "aspectratiocrop", GST_RANK_NONE,
917           GST_TYPE_ASPECT_RATIO_CROP))
918     return TRUE;
919 
920   return FALSE;
921 }
922 
923 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
924     GST_VERSION_MINOR,
925     videocrop,
926     "Crops video into a user-defined region",
927     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
928