/* * Clutter-GStreamer. * * GStreamer integration library for Clutter. * * clutter-gst-aspectratio.c - An actor rendering a video with respect * to its aspect ratio. * * Authored by Lionel Landwerlin * * Copyright (C) 2013 Intel Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /** * SECTION:clutter-gst-crop * @short_description: A #ClutterContent for displaying part of video frames * * #ClutterGstCrop sub-classes #ClutterGstContent. */ #include "clutter-gst-crop.h" #include "clutter-gst-private.h" static void content_iface_init (ClutterContentIface *iface); G_DEFINE_TYPE_WITH_CODE (ClutterGstCrop, clutter_gst_crop, CLUTTER_GST_TYPE_CONTENT, G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT, content_iface_init)) #define CROP_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), CLUTTER_GST_TYPE_CROP, ClutterGstCropPrivate)) struct _ClutterGstCropPrivate { ClutterGstBox input_region; ClutterGstBox output_region; gboolean paint_borders; gboolean cull_backface; }; enum { PROP_0, PROP_PAINT_BORDERS, PROP_CULL_BACKFACE, PROP_INPUT_REGION, PROP_OUTPUT_REGION }; /**/ static gboolean clutter_gst_crop_get_preferred_size (ClutterContent *content, gfloat *width, gfloat *height) { ClutterGstFrame *frame = clutter_gst_content_get_frame (CLUTTER_GST_CONTENT (content)); if (!frame) return FALSE; if (width) *width = frame->resolution.width; if (height) *height = frame->resolution.height; return TRUE; } static gboolean clutter_gst_crop_get_overlay_box (ClutterGstCrop *self, ClutterGstBox *input_box, ClutterGstBox *paint_box, const ClutterGstBox *frame_box, ClutterGstFrame *frame, ClutterGstOverlay *overlay) { ClutterGstCropPrivate *priv = self->priv; ClutterGstBox overlay_input_box; ClutterGstBox frame_input_box; /* Clamped frame input */ frame_input_box.x1 = priv->input_region.x1 * frame->resolution.width; frame_input_box.y1 = priv->input_region.y1 * frame->resolution.height; frame_input_box.x2 = priv->input_region.x2 * frame->resolution.width; frame_input_box.y2 = priv->input_region.y2 * frame->resolution.height; /* Clamp overlay box to frame's clamping */ overlay_input_box.x1 = MAX (priv->input_region.x1 * frame->resolution.width, overlay->position.x1); overlay_input_box.y1 = MAX (priv->input_region.y1 * frame->resolution.height, overlay->position.y1); overlay_input_box.x2 = MIN (priv->input_region.x2 * frame->resolution.width, overlay->position.x2); overlay_input_box.y2 = MIN (priv->input_region.y2 * frame->resolution.height, overlay->position.y2); /* normalize overlay input */ input_box->x1 = (overlay_input_box.x1 - overlay->position.x1) / (overlay->position.x2 - overlay->position.x1); input_box->y1 = (overlay_input_box.y1 - overlay->position.y1) / (overlay->position.y2 - overlay->position.y1); input_box->x2 = (overlay_input_box.x2 - overlay->position.x1) / (overlay->position.x2 - overlay->position.x1); input_box->y2 = (overlay_input_box.y2 - overlay->position.y1) / (overlay->position.y2 - overlay->position.y1); /* bail if not in the visible scope */ if (input_box->x1 >= input_box->x2 || input_box->y1 >= input_box->y2) return FALSE; /* Clamp overlay output */ 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)); 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)); 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)); 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)); return TRUE; } static void clutter_gst_crop_paint_content (ClutterContent *content, ClutterActor *actor, ClutterPaintNode *root) { ClutterGstCrop *self = CLUTTER_GST_CROP (content); ClutterGstCropPrivate *priv = self->priv; ClutterGstContent *gst_content = CLUTTER_GST_CONTENT (content); ClutterGstFrame *frame = clutter_gst_content_get_frame (gst_content); guint8 paint_opacity = clutter_actor_get_paint_opacity (actor); ClutterActorBox content_box; ClutterGstBox frame_box; gfloat box_width, box_height; ClutterColor color; ClutterPaintNode *node; clutter_actor_get_content_box (actor, &content_box); if (!frame) { /* No frame to paint, just paint the background color of the actor. */ if (priv->paint_borders) { clutter_actor_get_background_color (actor, &color); color.alpha = paint_opacity; node = clutter_color_node_new (&color); clutter_paint_node_set_name (node, "CropIdleVideo"); clutter_paint_node_add_rectangle_custom (node, content_box.x1, content_box.y1, content_box.x2, content_box.y2); clutter_paint_node_add_child (root, node); clutter_paint_node_unref (node); } return; } box_width = clutter_actor_box_get_width (&content_box); box_height = clutter_actor_box_get_height (&content_box); if (priv->paint_borders && (priv->output_region.x1 > 0 || priv->output_region.x2 < 1 || priv->output_region.y1 > 0 || priv->output_region.y2 < 1)) { clutter_actor_get_background_color (actor, &color); color.alpha = paint_opacity; node = clutter_color_node_new (&color); clutter_paint_node_set_name (node, "CropVideoBorders"); if (priv->output_region.x1 > 0) clutter_paint_node_add_rectangle_custom (node, content_box.x1, content_box.y1, content_box.x1 + box_width * priv->output_region.x1, content_box.y2); if (priv->output_region.x2 < 1) clutter_paint_node_add_rectangle_custom (node, content_box.x1 + box_width * priv->output_region.x2, content_box.y1, content_box.x2, content_box.y2); if (priv->output_region.y1 > 0) clutter_paint_node_add_rectangle_custom (node, content_box.x1 + box_width * priv->output_region.x1, content_box.y1, content_box.x1 + box_width * priv->output_region.x2, content_box.y1 + box_height * priv->output_region.y1); if (priv->output_region.y2 < 1) clutter_paint_node_add_rectangle_custom (node, content_box.x1 + box_width * priv->output_region.x1, content_box.y1 + box_height * priv->output_region.y2, content_box.x1 + box_width * priv->output_region.x2, content_box.y2); clutter_paint_node_add_child (root, node); clutter_paint_node_unref (node); } frame_box.x1 = content_box.x1 + box_width * priv->output_region.x1; frame_box.y1 = content_box.y1 + box_height * priv->output_region.y1; frame_box.x2 = content_box.x1 + box_width * priv->output_region.x2; frame_box.y2 = content_box.y1 + box_height * priv->output_region.y2; if (clutter_gst_content_get_paint_frame (gst_content)) { cogl_pipeline_set_color4ub (frame->pipeline, paint_opacity, paint_opacity, paint_opacity, paint_opacity); if (priv->cull_backface) cogl_pipeline_set_cull_face_mode (frame->pipeline, COGL_PIPELINE_CULL_FACE_MODE_BACK); node = clutter_pipeline_node_new (frame->pipeline); clutter_paint_node_set_name (node, "CropVideoFrame"); clutter_paint_node_add_texture_rectangle_custom (node, frame_box.x1, frame_box.y1, frame_box.x2, frame_box.y2, priv->input_region.x1, priv->input_region.y1, priv->input_region.x2, priv->input_region.y2); clutter_paint_node_add_child (root, node); clutter_paint_node_unref (node); } if (clutter_gst_content_get_paint_overlays (gst_content)) { ClutterGstOverlays *overlays = clutter_gst_content_get_overlays (gst_content); if (overlays) { guint i; for (i = 0; i < overlays->overlays->len; i++) { ClutterGstOverlay *overlay = g_ptr_array_index (overlays->overlays, i); ClutterGstBox overlay_box; ClutterGstBox overlay_input_box; /* overlay outside the visible scope? -> next */ if (!clutter_gst_crop_get_overlay_box (self, &overlay_input_box, &overlay_box, /* &content_box, */ &frame_box, frame, overlay)) continue; cogl_pipeline_set_color4ub (overlay->pipeline, paint_opacity, paint_opacity, paint_opacity, paint_opacity); node = clutter_pipeline_node_new (overlay->pipeline); clutter_paint_node_set_name (node, "CropVideoOverlay"); clutter_paint_node_add_texture_rectangle_custom (node, overlay_box.x1, overlay_box.y1, overlay_box.x2, overlay_box.y2, overlay_input_box.x1, overlay_input_box.y1, overlay_input_box.x2, overlay_input_box.y2); clutter_paint_node_add_child (root, node); clutter_paint_node_unref (node); } } } } static void content_iface_init (ClutterContentIface *iface) { iface->get_preferred_size = clutter_gst_crop_get_preferred_size; iface->paint_content = clutter_gst_crop_paint_content; } /**/ static gboolean _validate_box (ClutterGstBox *box) { if (box->x1 >= 0 && box->x1 <= 1 && box->y1 >= 0 && box->y1 <= 1 && box->x2 >= 0 && box->x2 <= 1 && box->y2 >= 0 && box->y2 <= 1) return TRUE; return FALSE; } static void clutter_gst_crop_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { ClutterGstCropPrivate *priv = CLUTTER_GST_CROP (object)->priv; ClutterGstBox *box; switch (property_id) { case PROP_PAINT_BORDERS: g_value_set_boolean (value, priv->paint_borders); break; case PROP_CULL_BACKFACE: g_value_set_boolean (value, priv->cull_backface); break; case PROP_INPUT_REGION: box = (ClutterGstBox *) g_value_get_boxed (value); *box = priv->input_region; break; case PROP_OUTPUT_REGION: box = (ClutterGstBox *) g_value_get_boxed (value); *box = priv->output_region; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void clutter_gst_crop_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { ClutterGstCropPrivate *priv = CLUTTER_GST_CROP (object)->priv; ClutterGstBox *box; switch (property_id) { case PROP_PAINT_BORDERS: if (priv->paint_borders != g_value_get_boolean (value)) { priv->paint_borders = g_value_get_boolean (value); clutter_content_invalidate (CLUTTER_CONTENT (object)); } break; case PROP_CULL_BACKFACE: priv->cull_backface = g_value_get_boolean (value); break; case PROP_INPUT_REGION: box = (ClutterGstBox *) g_value_get_boxed (value); if (_validate_box (box)) { priv->input_region = *box; clutter_content_invalidate (CLUTTER_CONTENT (object)); } else g_warning ("Input region must be given in [0, 1] values."); break; case PROP_OUTPUT_REGION: box = (ClutterGstBox *) g_value_get_boxed (value); if (_validate_box (box)) { priv->output_region = *box; clutter_content_invalidate (CLUTTER_CONTENT (object)); } else g_warning ("Output region must be given in [0, 1] values."); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void clutter_gst_crop_dispose (GObject *object) { G_OBJECT_CLASS (clutter_gst_crop_parent_class)->dispose (object); } static void clutter_gst_crop_finalize (GObject *object) { G_OBJECT_CLASS (clutter_gst_crop_parent_class)->finalize (object); } static void clutter_gst_crop_class_init (ClutterGstCropClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; g_type_class_add_private (klass, sizeof (ClutterGstCropPrivate)); object_class->get_property = clutter_gst_crop_get_property; object_class->set_property = clutter_gst_crop_set_property; object_class->dispose = clutter_gst_crop_dispose; object_class->finalize = clutter_gst_crop_finalize; /** * ClutterGstCrop:paint-borders: * * Whether or not paint borders on the sides of the video * * Since: 3.0 */ pspec = g_param_spec_boolean ("paint-borders", "Paint borders", "Paint borders on side of video", FALSE, CLUTTER_GST_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_PAINT_BORDERS, pspec); /** * ClutterGstCrop:cull-backface: * * Whether to cull the backface of the actor * * Since: 3.0 */ pspec = g_param_spec_boolean ("cull-backface", "Cull Backface", "Cull the backface of the actor", FALSE, CLUTTER_GST_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_CULL_BACKFACE, pspec); /** * ClutterGstCrop:input-region: * * Input region in the video frame (all values between 0 and 1). * * Since: 3.0 */ pspec = g_param_spec_boxed ("input-region", "Input Region", "Input Region", CLUTTER_GST_TYPE_BOX, CLUTTER_GST_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_INPUT_REGION, pspec); /** * ClutterGstCrop:output-region: * * Output region in the actor's allocation (all values between 0 and 1). * * Since: 3.0 */ pspec = g_param_spec_boxed ("output-region", "Output Region", "Output Region", CLUTTER_GST_TYPE_BOX, CLUTTER_GST_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_OUTPUT_REGION, pspec); } static void clutter_gst_crop_init (ClutterGstCrop *self) { ClutterGstCropPrivate *priv; priv = self->priv = CROP_PRIVATE (self); priv->input_region.x1 = 0; priv->input_region.y1 = 0; priv->input_region.x2 = 1; priv->input_region.y2 = 1; priv->output_region = priv->input_region; } /** * clutter_gst_crop_new: * * Returns: (transfer full): a new #ClutterGstCrop instance */ ClutterActor * clutter_gst_crop_new (void) { return g_object_new (CLUTTER_GST_TYPE_CROP, NULL); }