Way, way, way too many files: Remove crack comment from the 2000 era.
[platform/upstream/gst-plugins-good.git] / ext / pango / gsttextoverlay.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 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 #include <gst/gst.h>
25 #include "gsttextoverlay.h"
26
27 GST_DEBUG_CATEGORY_STATIC (pango_debug);
28 #define GST_CAT_DEFAULT pango_debug
29
30 static GstElementDetails textoverlay_details = {
31   "Text Overlay",
32   "Filter/Editor/Video",
33   "Adds text strings on top of a video buffer",
34   "David Schleef <ds@schleef.org>"
35 };
36
37 enum
38 {
39   ARG_0,
40   ARG_TEXT,
41   ARG_VALIGN,
42   ARG_HALIGN,
43   ARG_X0,
44   ARG_Y0,
45   ARG_FONT_DESC
46 };
47
48
49 static GstStaticPadTemplate textoverlay_src_template_factory =
50 GST_STATIC_PAD_TEMPLATE ("src",
51     GST_PAD_SRC,
52     GST_PAD_ALWAYS,
53     GST_STATIC_CAPS ("video/x-raw-yuv, "
54         "format = (fourcc) I420, "
55         "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
56     );
57
58 static GstStaticPadTemplate video_sink_template_factory =
59 GST_STATIC_PAD_TEMPLATE ("video_sink",
60     GST_PAD_SINK,
61     GST_PAD_ALWAYS,
62     GST_STATIC_CAPS ("video/x-raw-yuv, "
63         "format = (fourcc) I420, "
64         "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
65     );
66
67 static GstStaticPadTemplate text_sink_template_factory =
68     GST_STATIC_PAD_TEMPLATE ("text_sink",
69     GST_PAD_SINK,
70     GST_PAD_ALWAYS,
71     GST_STATIC_CAPS ("text/x-pango-markup; text/plain")
72     );
73
74 static void gst_textoverlay_base_init (gpointer g_class);
75 static void gst_textoverlay_class_init (GstTextOverlayClass * klass);
76 static void gst_textoverlay_init (GstTextOverlay * overlay);
77 static void gst_textoverlay_set_property (GObject * object,
78     guint prop_id, const GValue * value, GParamSpec * pspec);
79 static void gst_textoverlay_get_property (GObject * object,
80     guint prop_id, GValue * value, GParamSpec * pspec);
81 static GstElementStateReturn gst_textoverlay_change_state (GstElement *
82     element);
83 static void gst_textoverlay_finalize (GObject * object);
84
85
86 static GstElementClass *parent_class = NULL;
87
88 /*static guint gst_textoverlay_signals[LAST_SIGNAL] = { 0 }; */
89
90
91 GType
92 gst_textoverlay_get_type (void)
93 {
94   static GType textoverlay_type = 0;
95
96   if (!textoverlay_type) {
97     static const GTypeInfo textoverlay_info = {
98       sizeof (GstTextOverlayClass),
99       gst_textoverlay_base_init,
100       NULL,
101       (GClassInitFunc) gst_textoverlay_class_init,
102       NULL,
103       NULL,
104       sizeof (GstTextOverlay),
105       0,
106       (GInstanceInitFunc) gst_textoverlay_init,
107     };
108
109     textoverlay_type =
110         g_type_register_static (GST_TYPE_ELEMENT, "GstTextOverlay",
111         &textoverlay_info, 0);
112   }
113   return textoverlay_type;
114 }
115
116 static void
117 gst_textoverlay_base_init (gpointer g_class)
118 {
119   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
120
121   gst_element_class_add_pad_template (element_class,
122       gst_static_pad_template_get (&textoverlay_src_template_factory));
123   gst_element_class_add_pad_template (element_class,
124       gst_static_pad_template_get (&video_sink_template_factory));
125   gst_element_class_add_pad_template (element_class,
126       gst_static_pad_template_get (&text_sink_template_factory));
127
128   gst_element_class_set_details (element_class, &textoverlay_details);
129 }
130
131 static void
132 gst_textoverlay_class_init (GstTextOverlayClass * klass)
133 {
134   GObjectClass *gobject_class;
135   GstElementClass *gstelement_class;
136
137   gobject_class = (GObjectClass *) klass;
138   gstelement_class = (GstElementClass *) klass;
139
140   parent_class = g_type_class_peek_parent (klass);
141
142   gobject_class->finalize = gst_textoverlay_finalize;
143   gobject_class->set_property = gst_textoverlay_set_property;
144   gobject_class->get_property = gst_textoverlay_get_property;
145
146   gstelement_class->change_state = gst_textoverlay_change_state;
147   klass->pango_context = pango_ft2_get_context (72, 72);
148   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TEXT,
149       g_param_spec_string ("text", "text",
150           "Text to be display,"
151           " in pango markup format.", "", G_PARAM_WRITABLE));
152   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VALIGN,
153       g_param_spec_string ("valign", "vertical alignment",
154           "Vertical alignment of the text. "
155           "Can be either 'baseline', 'bottom', or 'top'",
156           "baseline", G_PARAM_WRITABLE));
157   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HALIGN,
158       g_param_spec_string ("halign", "horizontal alignment",
159           "Horizontal alignment of the text. "
160           "Can be either 'left', 'right', or 'center'",
161           "center", G_PARAM_WRITABLE));
162   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_X0,
163       g_param_spec_int ("x0", "X position",
164           "Initial X position."
165           " Horizontal aligment takes this point"
166           " as reference.", G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
167   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_Y0,
168       g_param_spec_int ("y0", "Y position",
169           "Initial Y position."
170           " Vertical aligment takes this point"
171           " as reference.", G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE));
172   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC,
173       g_param_spec_string ("font-desc", "font description",
174           "Pango font description of font "
175           "to be used for rendering. "
176           "See documentation of "
177           "pango_font_description_from_string"
178           " for syntax.", "", G_PARAM_WRITABLE));
179 }
180
181
182 static void
183 resize_bitmap (GstTextOverlay * overlay, int width, int height)
184 {
185   FT_Bitmap *bitmap = &overlay->bitmap;
186   int pitch = (width | 3) + 1;
187   int size = pitch * height;
188
189   /* no need to keep reallocating; just keep the maximum size so far */
190   if (size <= overlay->bitmap_buffer_size) {
191     bitmap->rows = height;
192     bitmap->width = width;
193     bitmap->pitch = pitch;
194     memset (bitmap->buffer, 0, overlay->bitmap_buffer_size);
195     return;
196   }
197   if (!bitmap->buffer) {
198     /* initialize */
199     bitmap->pixel_mode = ft_pixel_mode_grays;
200     bitmap->num_grays = 256;
201   }
202   if (bitmap->buffer)
203     bitmap->buffer = g_realloc (bitmap->buffer, size);
204   else
205     bitmap->buffer = g_malloc (size);
206   bitmap->rows = height;
207   bitmap->width = width;
208   bitmap->pitch = pitch;
209   memset (bitmap->buffer, 0, size);
210   overlay->bitmap_buffer_size = size;
211 }
212
213 static void
214 render_text (GstTextOverlay * overlay)
215 {
216   PangoRectangle ink_rect, logical_rect;
217
218   pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
219   resize_bitmap (overlay, ink_rect.width, ink_rect.height + ink_rect.y);
220   pango_ft2_render_layout (&overlay->bitmap, overlay->layout, 0, 0);
221   overlay->baseline_y = ink_rect.y;
222 }
223
224 /* static GstPadLinkReturn */
225 /* gst_textoverlay_text_sinkconnect (GstPad *pad, GstCaps *caps) */
226 /* { */
227 /*     return GST_PAD_LINK_DONE; */
228 /* } */
229
230 static GList *
231 gst_textoverlay_linkedpads (GstPad * pad)
232 {
233   GstPad *otherpad;
234   GstTextOverlay *overlay;
235
236   overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad));
237   if (pad == overlay->text_sinkpad)
238     return NULL;
239   otherpad = (pad == overlay->video_sinkpad) ?
240       overlay->srcpad : overlay->video_sinkpad;
241
242   return g_list_append (NULL, otherpad);
243 }
244
245 static GstPadLinkReturn
246 gst_textoverlay_link (GstPad * pad, const GstCaps * caps)
247 {
248   GstPad *otherpad;
249   GstTextOverlay *overlay;
250   GstStructure *structure;
251   GstPadLinkReturn ret;
252
253   overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad));
254   otherpad = (pad == overlay->video_sinkpad) ?
255       overlay->srcpad : overlay->video_sinkpad;
256
257   ret = gst_pad_try_set_caps (otherpad, caps);
258   if (GST_PAD_LINK_FAILED (ret))
259     return ret;
260
261   structure = gst_caps_get_structure (caps, 0);
262   overlay->width = overlay->height = 0;
263   gst_structure_get_int (structure, "width", &overlay->width);
264   gst_structure_get_int (structure, "height", &overlay->height);
265
266   return ret;
267 }
268
269 static GstCaps *
270 gst_textoverlay_getcaps (GstPad * pad)
271 {
272   GstPad *otherpad;
273   GstTextOverlay *overlay;
274   GstCaps *caps, *rcaps;
275   const GstCaps *tcaps;
276
277   overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad));
278   otherpad = (pad == overlay->video_sinkpad) ?
279       overlay->srcpad : overlay->video_sinkpad;
280
281   caps = gst_pad_get_allowed_caps (otherpad);
282   tcaps = gst_pad_get_pad_template_caps (pad);
283   rcaps = gst_caps_intersect (caps, tcaps);
284   gst_caps_free (caps);
285
286   return rcaps;
287 }
288
289 static gboolean
290 gst_textoverlay_event (GstPad * pad, GstEvent * event)
291 {
292   GstTextOverlay *overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad));
293
294   if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK &&
295       GST_PAD_IS_LINKED (overlay->text_sinkpad)) {
296     gst_event_ref (event);
297     gst_pad_send_event (GST_PAD_PEER (overlay->text_sinkpad), event);
298   }
299
300   return gst_pad_send_event (GST_PAD_PEER (overlay->video_sinkpad), event);
301 }
302
303 static void
304 gst_text_overlay_blit_yuv420 (GstTextOverlay * overlay, FT_Bitmap * bitmap,
305     guchar * pixbuf, int x0, int y0)
306 {
307   int y;                        /* text bitmap coordinates */
308   int x1, y1;                   /* video buffer coordinates */
309   int rowinc, bit_rowinc, uv_rowinc;
310   guchar *p, *bitp, *u_p;
311   int video_width = overlay->width, video_height = overlay->height;
312   int bitmap_x0 = x0 < 1 ? -(x0 - 1) : 1;       /* 1 pixel border */
313   int bitmap_y0 = y0 < 1 ? -(y0 - 1) : 1;       /* 1 pixel border */
314   int bitmap_width = bitmap->width - bitmap_x0;
315   int bitmap_height = bitmap->rows - bitmap_y0;
316   int u_plane_size;
317   int skip_y, skip_x;
318   guchar v;
319
320   if (x0 + bitmap_x0 + bitmap_width > video_width - 1)  /* 1 pixel border */
321     bitmap_width -= x0 + bitmap_x0 + bitmap_width - video_width + 1;
322   if (y0 + bitmap_y0 + bitmap_height > video_height - 1)        /* 1 pixel border */
323     bitmap_height -= y0 + bitmap_y0 + bitmap_height - video_height + 1;
324
325   rowinc = video_width - bitmap_width;
326   uv_rowinc = video_width / 2 - bitmap_width / 2;
327   bit_rowinc = bitmap->pitch - bitmap_width;
328   u_plane_size = (video_width / 2) * (video_height / 2);
329
330   y1 = y0 + bitmap_y0;
331   x1 = x0 + bitmap_x0;
332   p = pixbuf + video_width * y1 + x1;
333   bitp = bitmap->buffer + bitmap->pitch * bitmap_y0 + bitmap_x0;
334   for (y = bitmap_y0; y < bitmap_height; y++) {
335     int n;
336
337     for (n = bitmap_width; n > 0; --n) {
338       v = *bitp;
339       if (v) {
340         p[-1] = CLAMP (p[-1] - v, 0, 255);
341         p[1] = CLAMP (p[1] - v, 0, 255);
342         p[-video_width] = CLAMP (p[-video_width] - v, 0, 255);
343         p[video_width] = CLAMP (p[video_width] - v, 0, 255);
344       }
345       p++;
346       bitp++;
347     }
348     p += rowinc;
349     bitp += bit_rowinc;
350   }
351
352   y = bitmap_y0;
353   y1 = y0 + bitmap_y0;
354   x1 = x0 + bitmap_x0;
355   bitp = bitmap->buffer + bitmap->pitch * bitmap_y0 + bitmap_x0;
356   p = pixbuf + video_width * y1 + x1;
357   u_p =
358       pixbuf + video_width * video_height + (video_width >> 1) * (y1 >> 1) +
359       (x1 >> 1);
360   skip_y = 0;
361   skip_x = 0;
362
363   for (; y < bitmap_height; y++) {
364     int n;
365
366     x1 = x0 + bitmap_x0;
367     skip_x = 0;
368     for (n = bitmap_width; n > 0; --n) {
369       v = *bitp;
370       if (v) {
371         *p = v;
372         if (!skip_y) {
373           u_p[0] = u_p[u_plane_size] = 0x80;
374         }
375       }
376       if (!skip_y) {
377         skip_x = !skip_x;
378         if (!skip_x)
379           u_p++;
380       }
381       p++;
382       bitp++;
383     }
384     /*if (!skip_x && !skip_y) u_p--; */
385     p += rowinc;
386     bitp += bit_rowinc;
387     skip_y = !skip_y;
388     u_p += skip_y ? uv_rowinc : 0;
389   }
390 }
391
392
393 static void
394 gst_textoverlay_video_chain (GstPad * pad, GstData * _data)
395 {
396   GstBuffer *buf = GST_BUFFER (_data);
397   GstTextOverlay *overlay;
398   guchar *pixbuf;
399   gint x0, y0;
400
401   g_return_if_fail (pad != NULL);
402   g_return_if_fail (GST_IS_PAD (pad));
403   g_return_if_fail (buf != NULL);
404   overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad));
405   g_return_if_fail (overlay != NULL);
406   g_return_if_fail (GST_IS_TEXTOVERLAY (overlay));
407
408   pixbuf = GST_BUFFER_DATA (buf);
409
410   x0 = overlay->x0;
411   y0 = overlay->y0;
412   switch (overlay->valign) {
413     case GST_TEXT_OVERLAY_VALIGN_BOTTOM:
414       y0 = overlay->height - overlay->bitmap.rows - y0;
415       break;
416     case GST_TEXT_OVERLAY_VALIGN_BASELINE:     /* ? */
417       y0 -= (overlay->bitmap.rows - overlay->baseline_y);
418       break;
419     case GST_TEXT_OVERLAY_VALIGN_TOP:
420       break;
421   }
422
423   switch (overlay->halign) {
424     case GST_TEXT_OVERLAY_HALIGN_LEFT:
425       break;
426     case GST_TEXT_OVERLAY_HALIGN_RIGHT:
427       x0 = overlay->width - overlay->bitmap.width - x0;
428       break;
429     case GST_TEXT_OVERLAY_HALIGN_CENTER:
430       x0 = (overlay->width - overlay->bitmap.width) / 2;
431       break;
432   }
433
434   if (overlay->bitmap.buffer)
435     gst_text_overlay_blit_yuv420 (overlay, &overlay->bitmap, pixbuf, x0, y0);
436
437   gst_pad_push (overlay->srcpad, GST_DATA (buf));
438 }
439
440 #define GST_DATA_TIMESTAMP(data) \
441   (GST_IS_EVENT (data) ? \
442      GST_EVENT_TIMESTAMP (GST_EVENT (data)) : \
443      GST_BUFFER_TIMESTAMP (GST_BUFFER (data)))
444 #define GST_DATA_DURATION(data) \
445   (GST_IS_EVENT (data) ? \
446      ((GST_EVENT_TYPE (data) == GST_EVENT_FILLER) ? gst_event_filler_get_duration (GST_EVENT (data)) : \
447       GST_CLOCK_TIME_NONE) : \
448      GST_BUFFER_DURATION (GST_BUFFER (data)))
449
450 #define PAST_END(data, time) \
451   (GST_DATA_TIMESTAMP (data) != GST_CLOCK_TIME_NONE && \
452    GST_DATA_DURATION (data) != GST_CLOCK_TIME_NONE && \
453    GST_DATA_TIMESTAMP (data) + GST_DATA_DURATION (data) \
454      < (time))
455
456 static void
457 gst_textoverlay_loop (GstElement * element)
458 {
459   GstTextOverlay *overlay;
460   GstBuffer *video_frame;
461   guint64 now;
462
463   g_return_if_fail (element != NULL);
464   g_return_if_fail (GST_IS_TEXTOVERLAY (element));
465   overlay = GST_TEXTOVERLAY (element);
466
467   do {
468     GST_DEBUG ("Attempting to pull next video frame");
469     video_frame = GST_BUFFER (gst_pad_pull (overlay->video_sinkpad));
470     if (GST_IS_EVENT (video_frame)) {
471       GstEvent *event = GST_EVENT (video_frame);
472       GstEventType type = GST_EVENT_TYPE (event);
473
474       gst_pad_event_default (overlay->video_sinkpad, event);
475       GST_DEBUG ("Received event of type %d", type);
476       if (type == GST_EVENT_INTERRUPT)
477         return;
478       else if (type == GST_EVENT_EOS) {
479         /* EOS text stream */
480         GstData *data = NULL;
481
482         do {
483           if (data)
484             gst_data_unref (data);
485           data = gst_pad_pull (overlay->text_sinkpad);
486         } while (!GST_IS_EVENT (data) ||
487             GST_EVENT_TYPE (data) == GST_EVENT_EOS);
488         gst_data_unref (data);
489
490         return;
491       }
492       video_frame = NULL;
493     }
494   } while (!video_frame);
495   now = GST_BUFFER_TIMESTAMP (video_frame);
496   GST_DEBUG ("Got video frame, time=%" GST_TIME_FORMAT, GST_TIME_ARGS (now));
497
498   /*
499    * This state machine has a bug that can't be resolved easily.
500    * (Needs a more complicated state machine.)  Basically, if the
501    * text that came from a buffer from the sink pad is being
502    * displayed, and the default text is changed by set_parameter,
503    * we'll incorrectly display the default text.
504    *
505    * Otherwise, this is a pretty decent state machine that handles
506    * buffer timestamps and durations correctly.  (I think)
507    */
508
509   while ((!overlay->current_data ||
510           PAST_END (overlay->current_data, now)) &&
511       overlay->next_data == NULL) {
512     GST_DEBUG ("attempting to pull text data");
513
514     /* read all text buffers until we get one "in the future" */
515     if (!GST_PAD_IS_USABLE (overlay->text_sinkpad)) {
516       break;
517     }
518     do {
519       overlay->next_data = gst_pad_pull (overlay->text_sinkpad);
520       if (GST_IS_EVENT (overlay->next_data) &&
521           GST_EVENT_TYPE (overlay->next_data) != GST_EVENT_FILLER) {
522         GstEvent *event = GST_EVENT (overlay->next_data);
523         GstEventType type = GST_EVENT_TYPE (event);
524
525         gst_event_unref (event);
526         if (type == GST_EVENT_EOS)
527           break;
528         else if (type == GST_EVENT_INTERRUPT)
529           return;
530         overlay->next_data = NULL;
531       }
532     } while (!overlay->next_data);
533
534     if (PAST_END (overlay->next_data, now)) {
535       GST_DEBUG ("Received %s is past end (%" GST_TIME_FORMAT " + %"
536           GST_TIME_FORMAT " < %" GST_TIME_FORMAT ")",
537           GST_IS_EVENT (overlay->next_data) ? "event" : "buffer",
538           GST_TIME_ARGS (GST_DATA_TIMESTAMP (overlay->next_data)),
539           GST_TIME_ARGS (GST_DATA_DURATION (overlay->next_data)),
540           GST_TIME_ARGS (now));
541       gst_data_unref (overlay->next_data);
542       overlay->next_data = NULL;
543     } else {
544       GST_DEBUG ("Received new text %s of time %" GST_TIME_FORMAT
545           "and duration %" GST_TIME_FORMAT,
546           GST_IS_EVENT (overlay->next_data) ? "event" : "buffer",
547           GST_TIME_ARGS (GST_DATA_TIMESTAMP (overlay->next_data)),
548           GST_TIME_ARGS (GST_DATA_DURATION (overlay->next_data)));
549     }
550   }
551
552   if (overlay->next_data &&
553       (GST_DATA_TIMESTAMP (overlay->next_data) <= now ||
554           GST_DATA_TIMESTAMP (overlay->next_data) == GST_CLOCK_TIME_NONE)) {
555     GST_DEBUG ("using new %s",
556         GST_IS_EVENT (overlay->next_data) ? "event" : "buffer");
557
558     if (overlay->current_data) {
559       gst_data_unref (overlay->current_data);
560     }
561     overlay->current_data = overlay->next_data;
562     overlay->next_data = NULL;
563
564     if (GST_IS_BUFFER (overlay->current_data)) {
565       guint size = GST_BUFFER_SIZE (overlay->current_data);
566       guint8 *data = GST_BUFFER_DATA (overlay->current_data);
567
568       while (size > 0 &&
569           (data[size - 1] == '\r' ||
570               data[size - 1] == '\n' || data[size - 1] == '\0'))
571         size--;
572
573       GST_DEBUG ("rendering '%*s'", size,
574           GST_BUFFER_DATA (overlay->current_data));
575       /* somehow pango barfs over "\0" buffers... */
576       pango_layout_set_markup (overlay->layout,
577           GST_BUFFER_DATA (overlay->current_data), size);
578     } else {
579       GST_DEBUG ("Filler - no data");
580       pango_layout_set_markup (overlay->layout, "", 0);
581     }
582     render_text (overlay);
583     overlay->need_render = FALSE;
584   }
585
586   if (overlay->current_data && PAST_END (overlay->current_data, now)) {
587     GST_DEBUG ("dropping old %s",
588         GST_IS_EVENT (overlay->current_data) ? "event" : "buffer");
589
590     gst_buffer_unref (overlay->current_data);
591     overlay->current_data = NULL;
592
593     overlay->need_render = TRUE;
594   }
595
596   if (overlay->need_render) {
597     GST_DEBUG ("rendering '%s'", overlay->default_text);
598     pango_layout_set_markup (overlay->layout,
599         overlay->default_text, strlen (overlay->default_text));
600     render_text (overlay);
601
602     overlay->need_render = FALSE;
603   }
604
605   gst_textoverlay_video_chain (overlay->srcpad, GST_DATA (video_frame));
606 }
607
608
609 static GstElementStateReturn
610 gst_textoverlay_change_state (GstElement * element)
611 {
612   GstTextOverlay *overlay;
613
614   overlay = GST_TEXTOVERLAY (element);
615
616   switch (GST_STATE_TRANSITION (element)) {
617     case GST_STATE_PAUSED_TO_PLAYING:
618       break;
619     case GST_STATE_PLAYING_TO_PAUSED:
620       break;
621     case GST_STATE_PAUSED_TO_READY:
622       break;
623   }
624
625   parent_class->change_state (element);
626
627   return GST_STATE_SUCCESS;
628 }
629
630 static void
631 gst_textoverlay_finalize (GObject * object)
632 {
633   GstTextOverlay *overlay = GST_TEXTOVERLAY (object);
634
635   if (overlay->default_text) {
636     g_free (overlay->default_text);
637     overlay->default_text = NULL;
638   }
639
640   if (overlay->layout) {
641     g_object_unref (overlay->layout);
642     overlay->layout = NULL;
643   }
644   if (overlay->bitmap.buffer) {
645     g_free (overlay->bitmap.buffer);
646     overlay->bitmap.buffer = NULL;
647   }
648
649   G_OBJECT_CLASS (parent_class)->finalize (object);
650 }
651
652 static void
653 gst_textoverlay_init (GstTextOverlay * overlay)
654 {
655   /* video sink */
656   overlay->video_sinkpad =
657       gst_pad_new_from_template (gst_static_pad_template_get
658       (&video_sink_template_factory), "video_sink");
659   gst_pad_set_link_function (overlay->video_sinkpad, gst_textoverlay_link);
660   gst_pad_set_getcaps_function (overlay->video_sinkpad,
661       gst_textoverlay_getcaps);
662   gst_pad_set_internal_link_function (overlay->video_sinkpad,
663       gst_textoverlay_linkedpads);
664   gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
665
666   /* text sink */
667   overlay->text_sinkpad =
668       gst_pad_new_from_template (gst_static_pad_template_get
669       (&text_sink_template_factory), "text_sink");
670   gst_pad_set_internal_link_function (overlay->text_sinkpad,
671       gst_textoverlay_linkedpads);
672   gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
673
674   /* (video) source */
675   overlay->srcpad =
676       gst_pad_new_from_template (gst_static_pad_template_get
677       (&textoverlay_src_template_factory), "src");
678   gst_pad_set_link_function (overlay->srcpad, gst_textoverlay_link);
679   gst_pad_set_getcaps_function (overlay->srcpad, gst_textoverlay_getcaps);
680   gst_pad_set_internal_link_function (overlay->srcpad,
681       gst_textoverlay_linkedpads);
682   gst_pad_set_event_function (overlay->srcpad, gst_textoverlay_event);
683   gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
684
685   overlay->layout =
686       pango_layout_new (GST_TEXTOVERLAY_GET_CLASS (overlay)->pango_context);
687   memset (&overlay->bitmap, 0, sizeof (overlay->bitmap));
688
689   overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
690   overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
691   overlay->x0 = overlay->y0 = 25;
692
693   overlay->default_text = g_strdup ("");
694   overlay->need_render = TRUE;
695
696   gst_element_set_loop_function (GST_ELEMENT (overlay), gst_textoverlay_loop);
697
698   GST_FLAG_SET (overlay, GST_ELEMENT_EVENT_AWARE);
699 }
700
701
702 static void
703 gst_textoverlay_set_property (GObject * object, guint prop_id,
704     const GValue * value, GParamSpec * pspec)
705 {
706   GstTextOverlay *overlay;
707
708   g_return_if_fail (GST_IS_TEXTOVERLAY (object));
709   overlay = GST_TEXTOVERLAY (object);
710
711   switch (prop_id) {
712
713     case ARG_TEXT:
714       if (overlay->default_text) {
715         g_free (overlay->default_text);
716       }
717       overlay->default_text = g_strdup (g_value_get_string (value));
718       overlay->need_render = TRUE;
719       break;
720
721     case ARG_VALIGN:
722       if (strcasecmp (g_value_get_string (value), "baseline") == 0)
723         overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
724       else if (strcasecmp (g_value_get_string (value), "bottom") == 0)
725         overlay->valign = GST_TEXT_OVERLAY_VALIGN_BOTTOM;
726       else if (strcasecmp (g_value_get_string (value), "top") == 0)
727         overlay->valign = GST_TEXT_OVERLAY_VALIGN_TOP;
728       else
729         g_warning ("Invalid 'valign' property value: %s",
730             g_value_get_string (value));
731       break;
732
733     case ARG_HALIGN:
734       if (strcasecmp (g_value_get_string (value), "left") == 0)
735         overlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT;
736       else if (strcasecmp (g_value_get_string (value), "right") == 0)
737         overlay->halign = GST_TEXT_OVERLAY_HALIGN_RIGHT;
738       else if (strcasecmp (g_value_get_string (value), "center") == 0)
739         overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
740       else
741         g_warning ("Invalid 'halign' property value: %s",
742             g_value_get_string (value));
743       break;
744
745     case ARG_X0:
746       overlay->x0 = g_value_get_int (value);
747       break;
748
749     case ARG_Y0:
750       overlay->y0 = g_value_get_int (value);
751       break;
752
753     case ARG_FONT_DESC:
754     {
755       PangoFontDescription *desc;
756
757       desc = pango_font_description_from_string (g_value_get_string (value));
758       if (desc) {
759         GST_LOG ("font description set: %s", g_value_get_string (value));
760         pango_layout_set_font_description (overlay->layout, desc);
761         pango_font_description_free (desc);
762         render_text (overlay);
763       } else
764         GST_WARNING ("font description parse failed: %s",
765             g_value_get_string (value));
766       break;
767     }
768
769     default:
770       break;
771   }
772 }
773
774 static void
775 gst_textoverlay_get_property (GObject * object, guint prop_id, GValue * value,
776     GParamSpec * pspec)
777 {
778   GstTextOverlay *overlay;
779
780   g_return_if_fail (GST_IS_TEXTOVERLAY (object));
781   overlay = GST_TEXTOVERLAY (object);
782
783   switch (prop_id) {
784     default:
785       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
786       break;
787   }
788 }
789
790 static gboolean
791 plugin_init (GstPlugin * plugin)
792 {
793   if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
794           GST_TYPE_TEXTOVERLAY))
795     return FALSE;
796
797   /*texttestsrc_plugin_init(module, plugin); */
798   /*subparse_plugin_init(module, plugin); */
799
800   GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
801
802   return TRUE;
803 }
804
805 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
806     GST_VERSION_MINOR,
807     "textoverlay",
808     "Text overlay", plugin_init, VERSION, "LGPL", GST_PACKAGE, GST_ORIGIN)