Implement gtk sinks
[platform/upstream/gst-plugins-good.git] / ext / gtk / gtkgstwidget.c
1 /*
2  * GStreamer
3  * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <stdio.h>
26
27 #include "gtkgstwidget.h"
28 #include <gst/video/video.h>
29
30 /**
31  * SECTION:gtkgstwidget
32  * @short_description: a #GtkWidget that renders GStreamer video #GstBuffers
33  * @see_also: #GtkDrawingArea, #GstBuffer
34  *
35  * #GtkGstWidget is an #GtkWidget that renders GStreamer video buffers.
36  */
37
38 G_DEFINE_TYPE (GtkGstWidget, gtk_gst_widget, GTK_TYPE_DRAWING_AREA);
39
40 #define GTK_GST_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
41     GTK_TYPE_GST_WIDGET, GtkGstWidgetPrivate))
42
43 struct _GtkGstWidgetPrivate
44 {
45   GMutex lock;
46
47   gboolean negotiated;
48   GstBuffer *buffer;
49   GstCaps *caps;
50   GstVideoInfo v_info;
51 };
52
53 static void
54 gtk_gst_widget_get_preferred_width (GtkWidget * widget, gint * min,
55     gint * natural)
56 {
57   GtkGstWidget *gst_widget = (GtkGstWidget *) widget;
58   gint video_width = GST_VIDEO_INFO_WIDTH (&gst_widget->priv->v_info);
59
60   if (!gst_widget->priv->negotiated)
61     video_width = 10;
62
63   if (min)
64     *min = 1;
65   if (natural)
66     *natural = video_width;
67 }
68
69 static void
70 gtk_gst_widget_get_preferred_height (GtkWidget * widget, gint * min,
71     gint * natural)
72 {
73   GtkGstWidget *gst_widget = (GtkGstWidget *) widget;
74   gint video_height = GST_VIDEO_INFO_HEIGHT (&gst_widget->priv->v_info);
75
76   if (!gst_widget->priv->negotiated)
77     video_height = 10;
78
79   if (min)
80     *min = 1;
81   if (natural)
82     *natural = video_height;
83 }
84
85 static gboolean
86 gtk_gst_widget_draw (GtkWidget * widget, cairo_t * cr)
87 {
88   GtkGstWidget *gst_widget = (GtkGstWidget *) widget;
89   guint widget_width, widget_height;
90   cairo_surface_t *surface;
91   GstVideoFrame frame;
92
93   widget_width = gtk_widget_get_allocated_width (widget);
94   widget_height = gtk_widget_get_allocated_height (widget);
95
96   g_mutex_lock (&gst_widget->priv->lock);
97
98   /* failed to map the video frame */
99   if (gst_widget->priv->negotiated && gst_widget->priv->buffer
100       && gst_video_frame_map (&frame, &gst_widget->priv->v_info,
101           gst_widget->priv->buffer, GST_MAP_READ)) {
102     gdouble scale_x =
103         (gdouble) widget_width / GST_VIDEO_INFO_WIDTH (&frame.info);
104     gdouble scale_y =
105         (gdouble) widget_height / GST_VIDEO_INFO_HEIGHT (&frame.info);
106
107     gst_widget->priv->v_info = frame.info;
108
109     surface = cairo_image_surface_create_for_data (frame.data[0],
110         CAIRO_FORMAT_ARGB32, frame.info.width, frame.info.height,
111         frame.info.stride[0]);
112
113     cairo_scale (cr, scale_x, scale_y);
114     cairo_rectangle (cr, 0, 0, widget_width, widget_height);
115     cairo_set_source_surface (cr, surface, 0, 0);
116     cairo_paint (cr);
117
118     cairo_surface_destroy (surface);
119
120     gst_video_frame_unmap (&frame);
121   } else {
122     GdkRGBA color;
123
124     gtk_style_context_get_color (gtk_widget_get_style_context (widget), 0,
125         &color);
126     gdk_cairo_set_source_rgba (cr, &color);
127     cairo_rectangle (cr, 0, 0, widget_width, widget_height);
128     cairo_fill (cr);
129   }
130
131   g_mutex_unlock (&gst_widget->priv->lock);
132   return FALSE;
133 }
134
135 static void
136 gtk_gst_widget_finalize (GObject * object)
137 {
138   GtkGstWidget *widget = GTK_GST_WIDGET_CAST (object);
139
140   gst_buffer_replace (&widget->priv->buffer, NULL);
141   g_mutex_clear (&widget->priv->lock);
142
143   G_OBJECT_CLASS (gtk_gst_widget_parent_class)->finalize (object);
144 }
145
146 static void
147 gtk_gst_widget_class_init (GtkGstWidgetClass * klass)
148 {
149   GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass;
150
151   g_type_class_add_private (klass, sizeof (GtkGstWidgetPrivate));
152
153   widget_klass->draw = gtk_gst_widget_draw;
154   widget_klass->get_preferred_width = gtk_gst_widget_get_preferred_width;
155   widget_klass->get_preferred_height = gtk_gst_widget_get_preferred_height;
156
157   G_OBJECT_CLASS (klass)->finalize = gtk_gst_widget_finalize;
158 }
159
160 static void
161 gtk_gst_widget_init (GtkGstWidget * widget)
162 {
163   widget->priv = GTK_GST_WIDGET_GET_PRIVATE (widget);
164
165   g_mutex_init (&widget->priv->lock);
166 }
167
168 GtkWidget *
169 gtk_gst_widget_new (void)
170 {
171   return (GtkWidget *) g_object_new (GTK_TYPE_GST_WIDGET, NULL);
172 }
173
174 static gboolean
175 _queue_draw (GtkGstWidget * widget)
176 {
177   gtk_widget_queue_draw (GTK_WIDGET (widget));
178
179   return G_SOURCE_REMOVE;
180 }
181
182 void
183 gtk_gst_widget_set_buffer (GtkGstWidget * widget, GstBuffer * buffer)
184 {
185   GMainContext *main_context = g_main_context_default ();
186
187   g_return_if_fail (GTK_IS_GST_WIDGET (widget));
188   g_return_if_fail (widget->priv->negotiated);
189
190   g_mutex_lock (&widget->priv->lock);
191
192   gst_buffer_replace (&widget->priv->buffer, buffer);
193
194   g_mutex_unlock (&widget->priv->lock);
195
196   g_main_context_invoke (main_context, (GSourceFunc) _queue_draw, widget);
197 }
198
199 static gboolean
200 _queue_resize (GtkGstWidget * widget)
201 {
202   gtk_widget_queue_resize (GTK_WIDGET (widget));
203
204   return G_SOURCE_REMOVE;
205 }
206
207 gboolean
208 gtk_gst_widget_set_caps (GtkGstWidget * widget, GstCaps * caps)
209 {
210   GMainContext *main_context = g_main_context_default ();
211   GstVideoInfo v_info;
212
213   g_return_val_if_fail (GTK_IS_GST_WIDGET (widget), FALSE);
214   g_return_val_if_fail (GST_IS_CAPS (caps), FALSE);
215   g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
216
217   if (widget->priv->caps && gst_caps_is_equal_fixed (widget->priv->caps, caps))
218     return TRUE;
219
220   if (!gst_video_info_from_caps (&v_info, caps))
221     return FALSE;
222
223   /* FIXME: support other formats */
224   g_return_val_if_fail (GST_VIDEO_INFO_FORMAT (&v_info) ==
225       GST_VIDEO_FORMAT_BGRA, FALSE);
226
227   g_mutex_lock (&widget->priv->lock);
228
229   gst_caps_replace (&widget->priv->caps, caps);
230   widget->priv->v_info = v_info;
231   widget->priv->negotiated = TRUE;
232
233   g_mutex_unlock (&widget->priv->lock);
234
235   gtk_widget_queue_resize (GTK_WIDGET (widget));
236
237   g_main_context_invoke (main_context, (GSourceFunc) _queue_resize, widget);
238
239   return TRUE;
240 }