mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-29 15:22:11 +02:00
Rotation can be set by passing a GstVideoOrientationMethod to the gst_clapper_paintable_set_rotation() function. The actual rotation is done through GskTransforms directly on the Snapshot. Signed-off-by: Eugenio Paolantonio (g7) <me@medesimo.eu>
531 lines
16 KiB
C
531 lines
16 KiB
C
/*
|
|
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "gstclapperpaintable.h"
|
|
|
|
#include "gstgtkutils.h"
|
|
|
|
#define DEFAULT_PAR_N 1
|
|
#define DEFAULT_PAR_D 1
|
|
|
|
#define GST_CAT_DEFAULT gst_clapper_paintable_debug
|
|
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
|
|
|
static void gst_clapper_paintable_iface_init (GdkPaintableInterface *iface);
|
|
static void gst_clapper_paintable_dispose (GObject *object);
|
|
static void gst_clapper_paintable_finalize (GObject *object);
|
|
|
|
#define parent_class gst_clapper_paintable_parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstClapperPaintable, gst_clapper_paintable, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
|
|
gst_clapper_paintable_iface_init));
|
|
|
|
static void
|
|
gst_clapper_paintable_class_init (GstClapperPaintableClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperpaintable", 0,
|
|
"Clapper Paintable");
|
|
|
|
gobject_class->dispose = gst_clapper_paintable_dispose;
|
|
gobject_class->finalize = gst_clapper_paintable_finalize;
|
|
}
|
|
|
|
static void
|
|
gst_clapper_paintable_init (GstClapperPaintable *self)
|
|
{
|
|
self->display_width = 1;
|
|
self->display_height = 1;
|
|
self->display_aspect_ratio = 1.0;
|
|
|
|
self->rotation = GST_VIDEO_ORIENTATION_IDENTITY;
|
|
|
|
self->par_n = DEFAULT_PAR_N;
|
|
self->par_d = DEFAULT_PAR_D;
|
|
|
|
g_mutex_init (&self->lock);
|
|
g_mutex_init (&self->importer_lock);
|
|
|
|
gst_video_info_init (&self->v_info);
|
|
g_weak_ref_init (&self->widget, NULL);
|
|
|
|
gdk_rgba_parse (&self->bg, "black");
|
|
}
|
|
|
|
static void
|
|
gst_clapper_paintable_dispose (GObject *object)
|
|
{
|
|
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE (object);
|
|
|
|
GST_CLAPPER_PAINTABLE_LOCK (self);
|
|
|
|
if (self->draw_id > 0) {
|
|
g_source_remove (self->draw_id);
|
|
self->draw_id = 0;
|
|
}
|
|
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
|
|
GST_CLAPPER_PAINTABLE_IMPORTER_LOCK (self);
|
|
gst_clear_object (&self->importer);
|
|
GST_CLAPPER_PAINTABLE_IMPORTER_UNLOCK (self);
|
|
|
|
GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
|
|
}
|
|
|
|
static void
|
|
gst_clapper_paintable_finalize (GObject *object)
|
|
{
|
|
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE (object);
|
|
|
|
GST_TRACE ("Finalize");
|
|
|
|
g_weak_ref_clear (&self->widget);
|
|
|
|
g_mutex_clear (&self->lock);
|
|
g_mutex_clear (&self->importer_lock);
|
|
|
|
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
|
|
}
|
|
|
|
static gboolean
|
|
calculate_display_par (GstClapperPaintable *self, const GstVideoInfo *info)
|
|
{
|
|
gint width, height, par_n, par_d, req_par_n, req_par_d;
|
|
gboolean success;
|
|
|
|
gst_gtk_get_width_height_for_rotation (GST_VIDEO_INFO_WIDTH (info),
|
|
GST_VIDEO_INFO_HEIGHT (info), &width, &height, self->rotation);
|
|
|
|
/* Cannot apply aspect ratio if there is no video */
|
|
if (width == 0 || height == 0)
|
|
return FALSE;
|
|
|
|
par_n = GST_VIDEO_INFO_PAR_N (info);
|
|
par_d = GST_VIDEO_INFO_PAR_D (info);
|
|
|
|
req_par_n = self->par_n;
|
|
req_par_d = self->par_d;
|
|
|
|
if (par_n == 0)
|
|
par_n = 1;
|
|
|
|
/* Use defaults if user set zero */
|
|
if (req_par_n == 0 || req_par_d == 0) {
|
|
req_par_n = DEFAULT_PAR_N;
|
|
req_par_d = DEFAULT_PAR_D;
|
|
}
|
|
|
|
GST_LOG_OBJECT (self, "PAR: %u/%u, DAR: %u/%u", par_n, par_d, req_par_n, req_par_d);
|
|
|
|
if (!(success = gst_video_calculate_display_ratio (&self->display_ratio_num,
|
|
&self->display_ratio_den, width, height, par_n, par_d,
|
|
req_par_n, req_par_d))) {
|
|
GST_ERROR_OBJECT (self, "Could not calculate display ratio values");
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
static void
|
|
invalidate_paintable_size_internal (GstClapperPaintable *self)
|
|
{
|
|
gint video_width, video_height;
|
|
guint display_ratio_num, display_ratio_den;
|
|
|
|
GST_CLAPPER_PAINTABLE_LOCK (self);
|
|
|
|
gst_gtk_get_width_height_for_rotation (GST_VIDEO_INFO_WIDTH (&self->v_info),
|
|
GST_VIDEO_INFO_HEIGHT (&self->v_info), &video_height, &video_width,
|
|
self->rotation);
|
|
|
|
display_ratio_num = self->display_ratio_num;
|
|
display_ratio_den = self->display_ratio_den;
|
|
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
|
|
if (video_height % display_ratio_den == 0) {
|
|
GST_LOG ("Keeping video height");
|
|
|
|
self->display_width = (guint)
|
|
gst_util_uint64_scale_int (video_height, display_ratio_num, display_ratio_den);
|
|
self->display_height = video_height;
|
|
} else if (video_width % display_ratio_num == 0) {
|
|
GST_LOG ("Keeping video width");
|
|
|
|
self->display_width = video_width;
|
|
self->display_height = (guint)
|
|
gst_util_uint64_scale_int (video_width, display_ratio_den, display_ratio_num);
|
|
} else {
|
|
GST_LOG ("Approximating while keeping video height");
|
|
|
|
self->display_width = (guint)
|
|
gst_util_uint64_scale_int (video_height, display_ratio_num, display_ratio_den);
|
|
self->display_height = video_height;
|
|
}
|
|
|
|
self->display_aspect_ratio = ((gdouble) self->display_width
|
|
/ (gdouble) self->display_height);
|
|
|
|
GST_DEBUG_OBJECT (self, "Invalidate paintable size, display: %dx%d",
|
|
self->display_width, self->display_height);
|
|
gdk_paintable_invalidate_size ((GdkPaintable *) self);
|
|
}
|
|
|
|
static gboolean
|
|
invalidate_paintable_size_on_main_cb (GstClapperPaintable *self)
|
|
{
|
|
GST_CLAPPER_PAINTABLE_LOCK (self);
|
|
self->draw_id = 0;
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
|
|
invalidate_paintable_size_internal (self);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
update_paintable_on_main_cb (GstClapperPaintable *self)
|
|
{
|
|
gboolean size_changed;
|
|
|
|
GST_CLAPPER_PAINTABLE_LOCK (self);
|
|
|
|
/* Check if we will need to invalidate size */
|
|
if ((size_changed = self->pending_resize))
|
|
self->pending_resize = FALSE;
|
|
|
|
self->draw_id = 0;
|
|
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
|
|
if (size_changed)
|
|
invalidate_paintable_size_internal (self);
|
|
|
|
GST_LOG_OBJECT (self, "Invalidate paintable contents");
|
|
gdk_paintable_invalidate_contents ((GdkPaintable *) self);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
GstClapperPaintable *
|
|
gst_clapper_paintable_new (void)
|
|
{
|
|
return g_object_new (GST_TYPE_CLAPPER_PAINTABLE, NULL);
|
|
}
|
|
|
|
void
|
|
gst_clapper_paintable_set_widget (GstClapperPaintable *self, GtkWidget *widget)
|
|
{
|
|
g_weak_ref_set (&self->widget, widget);
|
|
}
|
|
|
|
void
|
|
gst_clapper_paintable_set_importer (GstClapperPaintable *self, GstClapperImporter *importer)
|
|
{
|
|
GST_CLAPPER_PAINTABLE_IMPORTER_LOCK (self);
|
|
gst_object_replace ((GstObject **) &self->importer, GST_OBJECT_CAST (importer));
|
|
GST_CLAPPER_PAINTABLE_IMPORTER_UNLOCK (self);
|
|
}
|
|
|
|
void
|
|
gst_clapper_paintable_queue_draw (GstClapperPaintable *self)
|
|
{
|
|
GST_CLAPPER_PAINTABLE_LOCK (self);
|
|
|
|
if (self->draw_id > 0) {
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
GST_TRACE ("Already have pending draw");
|
|
|
|
return;
|
|
}
|
|
|
|
self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
|
|
(GSourceFunc) update_paintable_on_main_cb, self, NULL);
|
|
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
}
|
|
|
|
gboolean
|
|
gst_clapper_paintable_set_video_info (GstClapperPaintable *self, const GstVideoInfo *v_info)
|
|
{
|
|
GST_CLAPPER_PAINTABLE_LOCK (self);
|
|
|
|
if (gst_video_info_is_equal (&self->v_info, v_info)) {
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Reject info if values would cause integer overflow */
|
|
if (G_UNLIKELY (!calculate_display_par (self, v_info))) {
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
return FALSE;
|
|
}
|
|
|
|
self->pending_resize = TRUE;
|
|
self->v_info = *v_info;
|
|
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
gst_clapper_paintable_set_pixel_aspect_ratio (GstClapperPaintable *self,
|
|
gint par_n, gint par_d)
|
|
{
|
|
gboolean success;
|
|
|
|
GST_CLAPPER_PAINTABLE_LOCK (self);
|
|
|
|
/* No change */
|
|
if (self->par_n == par_n && self->par_d == par_d) {
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
return;
|
|
}
|
|
|
|
self->par_n = par_n;
|
|
self->par_d = par_d;
|
|
|
|
/* Check if we can accept new values. This will update
|
|
* display `ratio_num` and `ratio_den` only when successful */
|
|
success = calculate_display_par (self, &self->v_info);
|
|
|
|
/* If paintable update is queued, wait for it, otherwise invalidate
|
|
* size only for change to be applied even when paused */
|
|
if (!success || self->draw_id > 0) {
|
|
self->pending_resize = success;
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
|
|
return;
|
|
}
|
|
|
|
self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
|
|
(GSourceFunc) invalidate_paintable_size_on_main_cb, self, NULL);
|
|
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
}
|
|
|
|
void
|
|
gst_clapper_paintable_set_rotation (GstClapperPaintable *self,
|
|
GstVideoOrientationMethod rotation)
|
|
{
|
|
GST_CLAPPER_PAINTABLE_LOCK (self);
|
|
|
|
self->rotation = rotation;
|
|
|
|
if (G_UNLIKELY (!calculate_display_par (self, &self->v_info))) {
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
return;
|
|
}
|
|
|
|
self->pending_resize = TRUE;
|
|
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
}
|
|
|
|
GstVideoOrientationMethod
|
|
gst_clapper_paintable_get_rotation (GstClapperPaintable *self)
|
|
{
|
|
GstVideoOrientationMethod rotation;
|
|
|
|
GST_CLAPPER_PAINTABLE_LOCK (self);
|
|
|
|
rotation = self->rotation;
|
|
|
|
GST_CLAPPER_PAINTABLE_UNLOCK (self);
|
|
|
|
return rotation;
|
|
}
|
|
|
|
/*
|
|
* GdkPaintableInterface
|
|
*/
|
|
static void
|
|
gst_clapper_paintable_snapshot_internal (GstClapperPaintable *self,
|
|
GdkSnapshot *snapshot, gdouble width, gdouble height,
|
|
gint widget_width, gint widget_height)
|
|
{
|
|
gfloat scale_x, scale_y;
|
|
gdouble snapshot_width, snapshot_height;
|
|
GskTransform *transform = NULL;
|
|
|
|
GST_LOG_OBJECT (self, "Snapshot");
|
|
|
|
scale_x = (gfloat) width / self->display_width;
|
|
scale_y = (gfloat) height / self->display_height;
|
|
|
|
/* Apply black borders when keeping aspect ratio */
|
|
if (scale_x == scale_y || abs (scale_x - scale_y) <= FLT_EPSILON) {
|
|
if (widget_height - height > 0) {
|
|
/* XXX: Top uses integer to work with GTK rounding (not going offscreen) */
|
|
gint top_bar_height = (widget_height - height) / 2;
|
|
gdouble bottom_bar_height = (widget_height - top_bar_height - height);
|
|
|
|
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, -top_bar_height));
|
|
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, height, width, bottom_bar_height));
|
|
} else if (widget_width - width > 0) {
|
|
gint left_bar_width = (widget_width - width) / 2;
|
|
gdouble right_bar_width = (widget_width - left_bar_width - width);
|
|
|
|
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, -left_bar_width, height));
|
|
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (width, 0, right_bar_width, height));
|
|
}
|
|
}
|
|
|
|
GST_CLAPPER_PAINTABLE_IMPORTER_LOCK (self);
|
|
|
|
if (self->importer) {
|
|
switch (self->rotation) {
|
|
case GST_VIDEO_ORIENTATION_IDENTITY:
|
|
default:
|
|
snapshot_width = width;
|
|
snapshot_height = height;
|
|
break;
|
|
case GST_VIDEO_ORIENTATION_90R:
|
|
transform = gsk_transform_rotate (transform, 90);
|
|
transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, -width));
|
|
snapshot_width = height;
|
|
snapshot_height = width;
|
|
break;
|
|
case GST_VIDEO_ORIENTATION_180:
|
|
transform = gsk_transform_rotate (transform, 180);
|
|
transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-width, -height));
|
|
snapshot_width = width;
|
|
snapshot_height = height;
|
|
break;
|
|
case GST_VIDEO_ORIENTATION_90L:
|
|
transform = gsk_transform_rotate (transform, 270);
|
|
transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-height, 0));
|
|
snapshot_width = height;
|
|
snapshot_height = width;
|
|
break;
|
|
case GST_VIDEO_ORIENTATION_HORIZ:
|
|
transform = gsk_transform_rotate_3d (transform, 180, graphene_vec3_y_axis ());
|
|
transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-width, 0));
|
|
snapshot_width = width;
|
|
snapshot_height = height;
|
|
break;
|
|
case GST_VIDEO_ORIENTATION_VERT:
|
|
transform = gsk_transform_rotate_3d (transform, 180, graphene_vec3_x_axis ());
|
|
transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, -height));
|
|
snapshot_width = width;
|
|
snapshot_height = height;
|
|
break;
|
|
case GST_VIDEO_ORIENTATION_UL_LR:
|
|
transform = gsk_transform_rotate (transform, 90);
|
|
transform = gsk_transform_rotate_3d (transform, 180, graphene_vec3_x_axis ());
|
|
snapshot_width = height;
|
|
snapshot_height = width;
|
|
break;
|
|
case GST_VIDEO_ORIENTATION_UR_LL:
|
|
transform = gsk_transform_rotate (transform, 90);
|
|
transform = gsk_transform_rotate_3d (transform, 180, graphene_vec3_y_axis ());
|
|
transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-height, -width));
|
|
snapshot_width = height;
|
|
snapshot_height = width;
|
|
break;
|
|
}
|
|
|
|
if (transform) {
|
|
gtk_snapshot_transform (snapshot, transform);
|
|
gsk_transform_unref (transform);
|
|
}
|
|
|
|
gst_clapper_importer_snapshot (self->importer, snapshot, snapshot_width, snapshot_height);
|
|
} else {
|
|
GST_LOG_OBJECT (self, "No texture importer, drawing black");
|
|
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height));
|
|
}
|
|
|
|
GST_CLAPPER_PAINTABLE_IMPORTER_UNLOCK (self);
|
|
}
|
|
|
|
static void
|
|
gst_clapper_paintable_snapshot (GdkPaintable *paintable,
|
|
GdkSnapshot *snapshot, gdouble width, gdouble height)
|
|
{
|
|
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
|
|
GtkWidget *widget;
|
|
gint widget_width = 0, widget_height = 0;
|
|
|
|
if ((widget = g_weak_ref_get (&self->widget))) {
|
|
widget_width = gtk_widget_get_width (widget);
|
|
widget_height = gtk_widget_get_height (widget);
|
|
|
|
g_object_unref (widget);
|
|
}
|
|
|
|
gst_clapper_paintable_snapshot_internal (self, snapshot,
|
|
width, height, widget_width, widget_height);
|
|
}
|
|
|
|
static GdkPaintable *
|
|
gst_clapper_paintable_get_current_image (GdkPaintable *paintable)
|
|
{
|
|
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
|
|
GtkSnapshot *snapshot = gtk_snapshot_new ();
|
|
|
|
/* Snapshot without widget size in order to get
|
|
* paintable without black borders */
|
|
gst_clapper_paintable_snapshot_internal (self, snapshot,
|
|
self->display_width, self->display_height, 0, 0);
|
|
|
|
return gtk_snapshot_free_to_paintable (snapshot, NULL);
|
|
}
|
|
|
|
static gint
|
|
gst_clapper_paintable_get_intrinsic_width (GdkPaintable *paintable)
|
|
{
|
|
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
|
|
|
|
return self->display_width;
|
|
}
|
|
|
|
static gint
|
|
gst_clapper_paintable_get_intrinsic_height (GdkPaintable *paintable)
|
|
{
|
|
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
|
|
|
|
return self->display_height;
|
|
}
|
|
|
|
static gdouble
|
|
gst_clapper_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
|
|
{
|
|
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
|
|
|
|
return self->display_aspect_ratio;
|
|
}
|
|
|
|
static void
|
|
gst_clapper_paintable_iface_init (GdkPaintableInterface *iface)
|
|
{
|
|
iface->snapshot = gst_clapper_paintable_snapshot;
|
|
iface->get_current_image = gst_clapper_paintable_get_current_image;
|
|
iface->get_intrinsic_width = gst_clapper_paintable_get_intrinsic_width;
|
|
iface->get_intrinsic_height = gst_clapper_paintable_get_intrinsic_height;
|
|
iface->get_intrinsic_aspect_ratio = gst_clapper_paintable_get_intrinsic_aspect_ratio;
|
|
}
|