1 /*
2  * Clutter-GStreamer.
3  *
4  * GStreamer integration library for Clutter.
5  *
6  * clutter-gst-aspectratio.c - An actor rendering a video with respect
7  * to its aspect ratio.
8  *
9  * Authored by Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com>
10  *
11  * Copyright (C) 2013 Intel Corporation
12  *
13  * This library is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU Lesser General Public
15  * License as published by the Free Software Foundation; either
16  * version 2 of the License, or (at your option) any later version.
17  *
18  * This library is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21  * Lesser General Public License for more details.
22  *
23  * You should have received a copy of the GNU Lesser General Public
24  * License along with this library; if not, write to the
25  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26  * Boston, MA 02111-1307, USA.
27  */
28 
29 /**
30  * SECTION:clutter-gst-crop
31  * @short_description: A #ClutterContent for displaying part of video frames
32  *
33  * #ClutterGstCrop sub-classes #ClutterGstContent.
34  */
35 
36 #include "clutter-gst-crop.h"
37 #include "clutter-gst-private.h"
38 
39 static void content_iface_init (ClutterContentIface *iface);
40 
41 G_DEFINE_TYPE_WITH_CODE (ClutterGstCrop,
42                          clutter_gst_crop,
43                          CLUTTER_GST_TYPE_CONTENT,
44                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
45                                                 content_iface_init))
46 
47 #define CROP_PRIVATE(o) \
48   (G_TYPE_INSTANCE_GET_PRIVATE ((o), CLUTTER_GST_TYPE_CROP, ClutterGstCropPrivate))
49 
50 struct _ClutterGstCropPrivate
51 {
52   ClutterGstBox input_region;
53   ClutterGstBox output_region;
54 
55   gboolean paint_borders;
56   gboolean cull_backface;
57 };
58 
59 enum
60 {
61   PROP_0,
62 
63   PROP_PAINT_BORDERS,
64   PROP_CULL_BACKFACE,
65   PROP_INPUT_REGION,
66   PROP_OUTPUT_REGION
67 };
68 
69 /**/
70 
71 static gboolean
clutter_gst_crop_get_preferred_size(ClutterContent * content,gfloat * width,gfloat * height)72 clutter_gst_crop_get_preferred_size (ClutterContent *content,
73                                      gfloat         *width,
74                                      gfloat         *height)
75 {
76   ClutterGstFrame *frame =
77     clutter_gst_content_get_frame (CLUTTER_GST_CONTENT (content));
78 
79   if (!frame)
80     return FALSE;
81 
82   if (width)
83     *width = frame->resolution.width;
84   if (height)
85     *height = frame->resolution.height;
86 
87   return TRUE;
88 }
89 
90 static gboolean
clutter_gst_crop_get_overlay_box(ClutterGstCrop * self,ClutterGstBox * input_box,ClutterGstBox * paint_box,const ClutterGstBox * frame_box,ClutterGstFrame * frame,ClutterGstOverlay * overlay)91 clutter_gst_crop_get_overlay_box (ClutterGstCrop      *self,
92                                   ClutterGstBox       *input_box,
93                                   ClutterGstBox       *paint_box,
94                                   const ClutterGstBox *frame_box,
95                                   ClutterGstFrame     *frame,
96                                   ClutterGstOverlay   *overlay)
97 {
98   ClutterGstCropPrivate *priv = self->priv;
99   ClutterGstBox overlay_input_box;
100   ClutterGstBox frame_input_box;
101 
102   /* Clamped frame input */
103   frame_input_box.x1 = priv->input_region.x1 * frame->resolution.width;
104   frame_input_box.y1 = priv->input_region.y1 * frame->resolution.height;
105   frame_input_box.x2 = priv->input_region.x2 * frame->resolution.width;
106   frame_input_box.y2 = priv->input_region.y2 * frame->resolution.height;
107 
108   /* Clamp overlay box to frame's clamping */
109   overlay_input_box.x1 = MAX (priv->input_region.x1 * frame->resolution.width, overlay->position.x1);
110   overlay_input_box.y1 = MAX (priv->input_region.y1 * frame->resolution.height, overlay->position.y1);
111   overlay_input_box.x2 = MIN (priv->input_region.x2 * frame->resolution.width, overlay->position.x2);
112   overlay_input_box.y2 = MIN (priv->input_region.y2 * frame->resolution.height, overlay->position.y2);
113 
114   /* normalize overlay input */
115   input_box->x1 = (overlay_input_box.x1 - overlay->position.x1) / (overlay->position.x2 - overlay->position.x1);
116   input_box->y1 = (overlay_input_box.y1 - overlay->position.y1) / (overlay->position.y2 - overlay->position.y1);
117   input_box->x2 = (overlay_input_box.x2 - overlay->position.x1) / (overlay->position.x2 - overlay->position.x1);
118   input_box->y2 = (overlay_input_box.y2 - overlay->position.y1) / (overlay->position.y2 - overlay->position.y1);
119 
120   /* bail if not in the visible scope */
121   if (input_box->x1 >= input_box->x2 ||
122       input_box->y1 >= input_box->y2)
123     return FALSE;
124 
125   /* Clamp overlay output */
126   paint_box->x1 = frame_box->x1 + (frame_box->x2 - frame_box->x1) * ((overlay_input_box.x1 - frame_input_box.x1) / (frame_input_box.x2 - frame_input_box.x1));
127   paint_box->y1 = frame_box->y1 + (frame_box->y2 - frame_box->y1) * ((overlay_input_box.y1 - frame_input_box.y1) / (frame_input_box.y2 - frame_input_box.y1));
128   paint_box->x2 = frame_box->x1 + (frame_box->x2 - frame_box->x1) * ((overlay_input_box.x2 - frame_input_box.x1) / (frame_input_box.x2 - frame_input_box.x1));
129   paint_box->y2 = frame_box->y1 + (frame_box->y2 - frame_box->y1) * ((overlay_input_box.y2 - frame_input_box.y1) / (frame_input_box.y2 - frame_input_box.y1));
130 
131   return TRUE;
132 }
133 
134 static void
clutter_gst_crop_paint_content(ClutterContent * content,ClutterActor * actor,ClutterPaintNode * root)135 clutter_gst_crop_paint_content (ClutterContent   *content,
136                                 ClutterActor     *actor,
137                                 ClutterPaintNode *root)
138 {
139   ClutterGstCrop *self = CLUTTER_GST_CROP (content);
140   ClutterGstCropPrivate *priv = self->priv;
141   ClutterGstContent *gst_content = CLUTTER_GST_CONTENT (content);
142   ClutterGstFrame *frame = clutter_gst_content_get_frame (gst_content);
143   guint8 paint_opacity = clutter_actor_get_paint_opacity (actor);
144   ClutterActorBox content_box;
145   ClutterGstBox frame_box;
146   gfloat box_width, box_height;
147   ClutterColor color;
148   ClutterPaintNode *node;
149 
150   clutter_actor_get_content_box (actor, &content_box);
151 
152   if (!frame)
153     {
154       /* No frame to paint, just paint the background color of the
155          actor. */
156       if (priv->paint_borders)
157         {
158           clutter_actor_get_background_color (actor, &color);
159           color.alpha = paint_opacity;
160 
161           node = clutter_color_node_new (&color);
162           clutter_paint_node_set_name (node, "CropIdleVideo");
163 
164           clutter_paint_node_add_rectangle_custom (node,
165                                                    content_box.x1, content_box.y1,
166                                                    content_box.x2, content_box.y2);
167           clutter_paint_node_add_child (root, node);
168           clutter_paint_node_unref (node);
169         }
170 
171       return;
172     }
173 
174   box_width = clutter_actor_box_get_width (&content_box);
175   box_height = clutter_actor_box_get_height (&content_box);
176 
177   if (priv->paint_borders &&
178       (priv->output_region.x1 > 0 ||
179        priv->output_region.x2 < 1 ||
180        priv->output_region.y1 > 0 ||
181        priv->output_region.y2 < 1))
182     {
183       clutter_actor_get_background_color (actor, &color);
184       color.alpha = paint_opacity;
185 
186       node = clutter_color_node_new (&color);
187       clutter_paint_node_set_name (node, "CropVideoBorders");
188 
189       if (priv->output_region.x1 > 0)
190         clutter_paint_node_add_rectangle_custom (node,
191                                                  content_box.x1,
192                                                  content_box.y1,
193                                                  content_box.x1 + box_width * priv->output_region.x1,
194                                                  content_box.y2);
195       if (priv->output_region.x2 < 1)
196         clutter_paint_node_add_rectangle_custom (node,
197                                                  content_box.x1 + box_width * priv->output_region.x2,
198                                                  content_box.y1,
199                                                  content_box.x2,
200                                                  content_box.y2);
201       if (priv->output_region.y1 > 0)
202         clutter_paint_node_add_rectangle_custom (node,
203                                                  content_box.x1 + box_width * priv->output_region.x1,
204                                                  content_box.y1,
205                                                  content_box.x1 + box_width * priv->output_region.x2,
206                                                  content_box.y1 + box_height * priv->output_region.y1);
207       if (priv->output_region.y2 < 1)
208         clutter_paint_node_add_rectangle_custom (node,
209                                                  content_box.x1 + box_width * priv->output_region.x1,
210                                                  content_box.y1 + box_height * priv->output_region.y2,
211                                                  content_box.x1 + box_width * priv->output_region.x2,
212                                                  content_box.y2);
213 
214       clutter_paint_node_add_child (root, node);
215       clutter_paint_node_unref (node);
216     }
217 
218 
219   frame_box.x1 = content_box.x1 + box_width * priv->output_region.x1;
220   frame_box.y1 = content_box.y1 + box_height * priv->output_region.y1;
221   frame_box.x2 = content_box.x1 + box_width * priv->output_region.x2;
222   frame_box.y2 = content_box.y1 + box_height * priv->output_region.y2;
223 
224   if (clutter_gst_content_get_paint_frame (gst_content))
225     {
226       cogl_pipeline_set_color4ub (frame->pipeline,
227                                   paint_opacity,
228                                   paint_opacity,
229                                   paint_opacity,
230                                   paint_opacity);
231       if (priv->cull_backface)
232         cogl_pipeline_set_cull_face_mode (frame->pipeline,
233                                           COGL_PIPELINE_CULL_FACE_MODE_BACK);
234 
235       node = clutter_pipeline_node_new (frame->pipeline);
236       clutter_paint_node_set_name (node, "CropVideoFrame");
237 
238       clutter_paint_node_add_texture_rectangle_custom (node,
239                                                        frame_box.x1,
240                                                        frame_box.y1,
241                                                        frame_box.x2,
242                                                        frame_box.y2,
243                                                        priv->input_region.x1,
244                                                        priv->input_region.y1,
245                                                        priv->input_region.x2,
246                                                        priv->input_region.y2);
247       clutter_paint_node_add_child (root, node);
248       clutter_paint_node_unref (node);
249     }
250 
251 
252   if (clutter_gst_content_get_paint_overlays (gst_content))
253     {
254       ClutterGstOverlays *overlays = clutter_gst_content_get_overlays (gst_content);
255 
256       if (overlays)
257         {
258           guint i;
259 
260           for (i = 0; i < overlays->overlays->len; i++)
261             {
262               ClutterGstOverlay *overlay =
263                 g_ptr_array_index (overlays->overlays, i);
264               ClutterGstBox overlay_box;
265               ClutterGstBox overlay_input_box;
266 
267               /* overlay outside the visible scope? -> next */
268               if (!clutter_gst_crop_get_overlay_box (self,
269                                                      &overlay_input_box,
270                                                      &overlay_box,
271                                                      /* &content_box, */
272                                                      &frame_box,
273                                                      frame,
274                                                      overlay))
275                 continue;
276 
277               cogl_pipeline_set_color4ub (overlay->pipeline,
278                                           paint_opacity, paint_opacity,
279                                           paint_opacity, paint_opacity);
280 
281               node = clutter_pipeline_node_new (overlay->pipeline);
282               clutter_paint_node_set_name (node, "CropVideoOverlay");
283 
284               clutter_paint_node_add_texture_rectangle_custom (node,
285                                                                overlay_box.x1, overlay_box.y1,
286                                                                overlay_box.x2, overlay_box.y2,
287                                                                overlay_input_box.x1, overlay_input_box.y1,
288                                                                overlay_input_box.x2, overlay_input_box.y2);
289 
290               clutter_paint_node_add_child (root, node);
291               clutter_paint_node_unref (node);
292             }
293         }
294     }
295 }
296 
297 static void
content_iface_init(ClutterContentIface * iface)298 content_iface_init (ClutterContentIface *iface)
299 {
300   iface->get_preferred_size = clutter_gst_crop_get_preferred_size;
301   iface->paint_content = clutter_gst_crop_paint_content;
302 }
303 
304 /**/
305 
306 static gboolean
_validate_box(ClutterGstBox * box)307 _validate_box (ClutterGstBox *box)
308 {
309   if (box->x1 >= 0 &&
310       box->x1 <= 1 &&
311       box->y1 >= 0 &&
312       box->y1 <= 1 &&
313       box->x2 >= 0 &&
314       box->x2 <= 1 &&
315       box->y2 >= 0 &&
316       box->y2 <= 1)
317     return TRUE;
318 
319   return FALSE;
320 }
321 
322 static void
clutter_gst_crop_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)323 clutter_gst_crop_get_property (GObject    *object,
324                                guint       property_id,
325                                GValue     *value,
326                                GParamSpec *pspec)
327 {
328   ClutterGstCropPrivate *priv = CLUTTER_GST_CROP (object)->priv;
329   ClutterGstBox *box;
330 
331   switch (property_id)
332     {
333     case PROP_PAINT_BORDERS:
334       g_value_set_boolean (value, priv->paint_borders);
335       break;
336     case PROP_CULL_BACKFACE:
337       g_value_set_boolean (value, priv->cull_backface);
338       break;
339     case PROP_INPUT_REGION:
340       box = (ClutterGstBox *) g_value_get_boxed (value);
341       *box = priv->input_region;
342       break;
343     case PROP_OUTPUT_REGION:
344       box = (ClutterGstBox *) g_value_get_boxed (value);
345       *box = priv->output_region;
346       break;
347     default:
348       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
349     }
350 }
351 
352 static void
clutter_gst_crop_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)353 clutter_gst_crop_set_property (GObject      *object,
354                                guint         property_id,
355                                const GValue *value,
356                                GParamSpec   *pspec)
357 {
358   ClutterGstCropPrivate *priv = CLUTTER_GST_CROP (object)->priv;
359   ClutterGstBox *box;
360 
361   switch (property_id)
362     {
363     case PROP_PAINT_BORDERS:
364       if (priv->paint_borders != g_value_get_boolean (value))
365         {
366           priv->paint_borders = g_value_get_boolean (value);
367           clutter_content_invalidate (CLUTTER_CONTENT (object));
368         }
369       break;
370     case PROP_CULL_BACKFACE:
371       priv->cull_backface = g_value_get_boolean (value);
372       break;
373     case PROP_INPUT_REGION:
374       box = (ClutterGstBox *) g_value_get_boxed (value);
375       if (_validate_box (box)) {
376         priv->input_region = *box;
377         clutter_content_invalidate (CLUTTER_CONTENT (object));
378       } else
379         g_warning ("Input region must be given in [0, 1] values.");
380       break;
381     case PROP_OUTPUT_REGION:
382       box = (ClutterGstBox *) g_value_get_boxed (value);
383       if (_validate_box (box)) {
384         priv->output_region = *box;
385         clutter_content_invalidate (CLUTTER_CONTENT (object));
386       } else
387         g_warning ("Output region must be given in [0, 1] values.");
388       break;
389     default:
390       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
391     }
392 }
393 
394 static void
clutter_gst_crop_dispose(GObject * object)395 clutter_gst_crop_dispose (GObject *object)
396 {
397   G_OBJECT_CLASS (clutter_gst_crop_parent_class)->dispose (object);
398 }
399 
400 static void
clutter_gst_crop_finalize(GObject * object)401 clutter_gst_crop_finalize (GObject *object)
402 {
403   G_OBJECT_CLASS (clutter_gst_crop_parent_class)->finalize (object);
404 }
405 
406 static void
clutter_gst_crop_class_init(ClutterGstCropClass * klass)407 clutter_gst_crop_class_init (ClutterGstCropClass *klass)
408 {
409   GObjectClass *object_class = G_OBJECT_CLASS (klass);
410   GParamSpec *pspec;
411 
412   g_type_class_add_private (klass, sizeof (ClutterGstCropPrivate));
413 
414   object_class->get_property = clutter_gst_crop_get_property;
415   object_class->set_property = clutter_gst_crop_set_property;
416   object_class->dispose = clutter_gst_crop_dispose;
417   object_class->finalize = clutter_gst_crop_finalize;
418 
419   /**
420    * ClutterGstCrop:paint-borders:
421    *
422    * Whether or not paint borders on the sides of the video
423    *
424    * Since: 3.0
425    */
426   pspec = g_param_spec_boolean ("paint-borders",
427                                 "Paint borders",
428                                 "Paint borders on side of video",
429                                 FALSE,
430                                 CLUTTER_GST_PARAM_READWRITE);
431   g_object_class_install_property (object_class, PROP_PAINT_BORDERS, pspec);
432 
433   /**
434    * ClutterGstCrop:cull-backface:
435    *
436    * Whether to cull the backface of the actor
437    *
438    * Since: 3.0
439    */
440   pspec = g_param_spec_boolean ("cull-backface",
441                                 "Cull Backface",
442                                 "Cull the backface of the actor",
443                                 FALSE,
444                                 CLUTTER_GST_PARAM_READWRITE);
445   g_object_class_install_property (object_class, PROP_CULL_BACKFACE, pspec);
446 
447   /**
448    * ClutterGstCrop:input-region:
449    *
450    * Input region in the video frame (all values between 0 and 1).
451    *
452    * Since: 3.0
453    */
454   pspec = g_param_spec_boxed ("input-region",
455                               "Input Region",
456                               "Input Region",
457                               CLUTTER_GST_TYPE_BOX,
458                               CLUTTER_GST_PARAM_READWRITE);
459   g_object_class_install_property (object_class, PROP_INPUT_REGION, pspec);
460 
461   /**
462    * ClutterGstCrop:output-region:
463    *
464    * Output region in the actor's allocation (all values between 0 and 1).
465    *
466    * Since: 3.0
467    */
468   pspec = g_param_spec_boxed ("output-region",
469                               "Output Region",
470                               "Output Region",
471                               CLUTTER_GST_TYPE_BOX,
472                               CLUTTER_GST_PARAM_READWRITE);
473   g_object_class_install_property (object_class, PROP_OUTPUT_REGION, pspec);
474 }
475 
476 static void
clutter_gst_crop_init(ClutterGstCrop * self)477 clutter_gst_crop_init (ClutterGstCrop *self)
478 {
479   ClutterGstCropPrivate *priv;
480 
481   priv = self->priv = CROP_PRIVATE (self);
482 
483   priv->input_region.x1 = 0;
484   priv->input_region.y1 = 0;
485   priv->input_region.x2 = 1;
486   priv->input_region.y2 = 1;
487 
488   priv->output_region = priv->input_region;
489 }
490 
491 /**
492  * clutter_gst_crop_new:
493  *
494  * Returns: (transfer full): a new #ClutterGstCrop instance
495  */
496 ClutterActor *
clutter_gst_crop_new(void)497 clutter_gst_crop_new (void)
498 {
499   return g_object_new (CLUTTER_GST_TYPE_CROP, NULL);
500 }
501