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