cairorender: Correctly set srccaps
[platform/upstream/gst-plugins-good.git] / ext / cairo / gsttimeoverlay.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) <2003> David Schleef <ds@schleef.org>
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., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /**
22  * SECTION:element-cairotimeoverlay
23  *
24  * cairotimeoverlay renders the buffer timestamp for each frame on top of
25  * the frame.
26  *
27  * <refsect2>
28  * <title>Example launch line</title>
29  * |[
30  * gst-launch videotestsrc ! cairotimeoverlay ! autovideosink
31  * ]|
32  * </refsect2>
33  */
34
35 #ifdef HAVE_CONFIG_H
36 #include "config.h"
37 #endif
38
39 #include <gsttimeoverlay.h>
40
41 #include <string.h>
42 #include <math.h>
43
44 #include <cairo.h>
45
46 #include <gst/video/video.h>
47
48 #ifndef HAVE_RINT
49 #define rint(x) ((double) floor((x)+(((x) < 0)? -0.5 : 0.5)))
50 #endif
51
52 static const GstElementDetails cairo_time_overlay_details =
53 GST_ELEMENT_DETAILS ("Time overlay",
54     "Filter/Editor/Video",
55     "Overlays the time on a video stream",
56     "David Schleef <ds@schleef.org>");
57
58 static GstStaticPadTemplate gst_cairo_time_overlay_src_template =
59 GST_STATIC_PAD_TEMPLATE ("src",
60     GST_PAD_SRC,
61     GST_PAD_ALWAYS,
62     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
63     );
64
65 static GstStaticPadTemplate gst_cairo_time_overlay_sink_template =
66 GST_STATIC_PAD_TEMPLATE ("sink",
67     GST_PAD_SINK,
68     GST_PAD_ALWAYS,
69     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
70     );
71
72 static GstBaseTransformClass *parent_class = NULL;
73
74 static void
75 gst_cairo_time_overlay_update_font_height (GstCairoTimeOverlay * timeoverlay)
76 {
77   gint width, height;
78   cairo_surface_t *font_surface;
79   cairo_t *font_cairo;
80   cairo_font_extents_t font_extents;
81
82   width = timeoverlay->width;
83   height = timeoverlay->height;
84
85   font_surface =
86       cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
87   font_cairo = cairo_create (font_surface);
88   cairo_surface_destroy (font_surface);
89   font_surface = NULL;
90
91   cairo_select_font_face (font_cairo, "monospace", 0, 0);
92   cairo_set_font_size (font_cairo, 20);
93   cairo_font_extents (font_cairo, &font_extents);
94   timeoverlay->text_height = font_extents.height;
95   GST_DEBUG_OBJECT (timeoverlay, "font height is %f", font_extents.height);
96   cairo_destroy (font_cairo);
97   font_cairo = NULL;
98 }
99
100 static gboolean
101 gst_cairo_time_overlay_set_caps (GstBaseTransform * btrans, GstCaps * incaps,
102     GstCaps * outcaps)
103 {
104   GstCairoTimeOverlay *filter = GST_CAIRO_TIME_OVERLAY (btrans);
105   GstStructure *structure;
106   gboolean ret = FALSE;
107
108   structure = gst_caps_get_structure (incaps, 0);
109
110   if (gst_structure_get_int (structure, "width", &filter->width) &&
111       gst_structure_get_int (structure, "height", &filter->height)) {
112     gst_cairo_time_overlay_update_font_height (filter);
113     ret = TRUE;
114   }
115
116   return ret;
117 }
118
119 /* Useful macros */
120 #define GST_VIDEO_I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width))
121 #define GST_VIDEO_I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2)
122 #define GST_VIDEO_I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(GST_VIDEO_I420_Y_ROWSTRIDE(width)))/2)
123
124 #define GST_VIDEO_I420_Y_OFFSET(w,h) (0)
125 #define GST_VIDEO_I420_U_OFFSET(w,h) (GST_VIDEO_I420_Y_OFFSET(w,h)+(GST_VIDEO_I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h)))
126 #define GST_VIDEO_I420_V_OFFSET(w,h) (GST_VIDEO_I420_U_OFFSET(w,h)+(GST_VIDEO_I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
127
128 #define GST_VIDEO_I420_SIZE(w,h)     (GST_VIDEO_I420_V_OFFSET(w,h)+(GST_VIDEO_I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
129
130 static gboolean
131 gst_cairo_time_overlay_get_unit_size (GstBaseTransform * btrans, GstCaps * caps,
132     guint * size)
133 {
134   GstCairoTimeOverlay *filter;
135   GstStructure *structure;
136   gboolean ret = FALSE;
137   gint width, height;
138
139   filter = GST_CAIRO_TIME_OVERLAY (btrans);
140
141   structure = gst_caps_get_structure (caps, 0);
142
143   if (gst_structure_get_int (structure, "width", &width) &&
144       gst_structure_get_int (structure, "height", &height)) {
145     *size = GST_VIDEO_I420_SIZE (width, height);
146     ret = TRUE;
147     GST_DEBUG_OBJECT (filter, "our frame size is %d bytes (%dx%d)", *size,
148         width, height);
149   }
150
151   return ret;
152 }
153
154 static char *
155 gst_cairo_time_overlay_print_smpte_time (guint64 time)
156 {
157   int hours;
158   int minutes;
159   int seconds;
160   int ms;
161   double x;
162
163   x = rint (gst_util_guint64_to_gdouble (time + 500000) * 1e-6);
164
165   hours = floor (x / (60 * 60 * 1000));
166   x -= hours * 60 * 60 * 1000;
167   minutes = floor (x / (60 * 1000));
168   x -= minutes * 60 * 1000;
169   seconds = floor (x / (1000));
170   x -= seconds * 1000;
171   ms = rint (x);
172
173   return g_strdup_printf ("%02d:%02d:%02d.%03d", hours, minutes, seconds, ms);
174 }
175
176
177 static GstFlowReturn
178 gst_cairo_time_overlay_transform (GstBaseTransform * trans, GstBuffer * in,
179     GstBuffer * out)
180 {
181   GstCairoTimeOverlay *timeoverlay;
182   int width;
183   int height;
184   int b_width;
185   int stride_y, stride_u, stride_v;
186   char *string;
187   int i, j;
188   unsigned char *image;
189   cairo_text_extents_t extents;
190   guint8 *dest, *src;
191   cairo_surface_t *font_surface;
192   cairo_t *text_cairo;
193   GstFlowReturn ret = GST_FLOW_OK;
194
195   timeoverlay = GST_CAIRO_TIME_OVERLAY (trans);
196
197   gst_buffer_copy_metadata (out, in, GST_BUFFER_COPY_TIMESTAMPS);
198
199   src = GST_BUFFER_DATA (in);
200   dest = GST_BUFFER_DATA (out);
201
202   width = timeoverlay->width;
203   height = timeoverlay->height;
204
205   /* create surface for font rendering */
206   /* FIXME: preparation of the surface could also be done once when settings
207    * change */
208   image = g_malloc (4 * width * timeoverlay->text_height);
209
210   font_surface =
211       cairo_image_surface_create_for_data (image, CAIRO_FORMAT_ARGB32, width,
212       timeoverlay->text_height, width * 4);
213   text_cairo = cairo_create (font_surface);
214   cairo_surface_destroy (font_surface);
215   font_surface = NULL;
216
217   /* we draw a rectangle because the compositing on the buffer below
218    * doesn't do alpha */
219   cairo_save (text_cairo);
220   cairo_rectangle (text_cairo, 0, 0, width, timeoverlay->text_height);
221   cairo_set_source_rgba (text_cairo, 0, 0, 0, 1);
222   cairo_set_operator (text_cairo, CAIRO_OPERATOR_SOURCE);
223   cairo_fill (text_cairo);
224   cairo_restore (text_cairo);
225
226   string = gst_cairo_time_overlay_print_smpte_time (GST_BUFFER_TIMESTAMP (in));
227   cairo_save (text_cairo);
228   cairo_select_font_face (text_cairo, "monospace", 0, 0);
229   cairo_set_font_size (text_cairo, 20);
230   cairo_text_extents (text_cairo, string, &extents);
231   cairo_set_source_rgb (text_cairo, 1, 1, 1);
232   cairo_move_to (text_cairo, 0, timeoverlay->text_height - 2);
233   cairo_show_text (text_cairo, string);
234   g_free (string);
235
236   cairo_restore (text_cairo);
237
238   /* blend width; should retain a max text width so it doesn't jitter */
239   b_width = extents.width;
240   if (b_width > width)
241     b_width = width;
242
243   stride_y = GST_VIDEO_I420_Y_ROWSTRIDE (width);
244   stride_u = GST_VIDEO_I420_U_ROWSTRIDE (width);
245   stride_v = GST_VIDEO_I420_V_ROWSTRIDE (width);
246
247   memcpy (dest, src, GST_BUFFER_SIZE (in));
248   for (i = 0; i < timeoverlay->text_height; i++) {
249     for (j = 0; j < b_width; j++) {
250       ((unsigned char *) dest)[i * stride_y + j] =
251           image[(i * width + j) * 4 + 0];
252     }
253   }
254   for (i = 0; i < timeoverlay->text_height / 2; i++) {
255     memset (dest + GST_VIDEO_I420_U_OFFSET (width, height) + i * stride_u, 128,
256         b_width / 2);
257     memset (dest + GST_VIDEO_I420_V_OFFSET (width, height) + i * stride_v, 128,
258         b_width / 2);
259   }
260
261   cairo_destroy (text_cairo);
262   text_cairo = NULL;
263   g_free (image);
264
265   return ret;
266 }
267
268 static void
269 gst_cairo_time_overlay_base_init (gpointer g_class)
270 {
271   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
272
273   gst_element_class_set_details (element_class, &cairo_time_overlay_details);
274
275   gst_element_class_add_pad_template (element_class,
276       gst_static_pad_template_get (&gst_cairo_time_overlay_sink_template));
277   gst_element_class_add_pad_template (element_class,
278       gst_static_pad_template_get (&gst_cairo_time_overlay_src_template));
279 }
280
281 static void
282 gst_cairo_time_overlay_class_init (gpointer klass, gpointer class_data)
283 {
284   GstBaseTransformClass *trans_class;
285
286   trans_class = (GstBaseTransformClass *) klass;
287
288   parent_class = g_type_class_peek_parent (klass);
289
290   trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_cairo_time_overlay_set_caps);
291   trans_class->get_unit_size =
292       GST_DEBUG_FUNCPTR (gst_cairo_time_overlay_get_unit_size);
293   trans_class->transform = GST_DEBUG_FUNCPTR (gst_cairo_time_overlay_transform);
294 }
295
296 static void
297 gst_cairo_time_overlay_init (GTypeInstance * instance, gpointer g_class)
298 {
299 }
300
301 GType
302 gst_cairo_time_overlay_get_type (void)
303 {
304   static GType cairo_time_overlay_type = 0;
305
306   if (!cairo_time_overlay_type) {
307     static const GTypeInfo cairo_time_overlay_info = {
308       sizeof (GstCairoTimeOverlayClass),
309       gst_cairo_time_overlay_base_init,
310       NULL,
311       gst_cairo_time_overlay_class_init,
312       NULL,
313       NULL,
314       sizeof (GstCairoTimeOverlay),
315       0,
316       gst_cairo_time_overlay_init,
317     };
318
319     cairo_time_overlay_type = g_type_register_static (GST_TYPE_BASE_TRANSFORM,
320         "GstCairoTimeOverlay", &cairo_time_overlay_info, 0);
321   }
322   return cairo_time_overlay_type;
323 }