pango: Use pango-cairo instead of pango-ft2

pango-cairo will always use the native font rendering backend
of the platform and provides better results.

Fixes bug #340887.
This commit is contained in:
Young-Ho Cha 2009-08-04 11:35:10 +02:00 committed by Sebastian Dröge
parent 76571840ef
commit 7608c31516
4 changed files with 639 additions and 322 deletions

View file

@ -4,6 +4,7 @@
* Copyright (C) <2006> Julien Moutte <julien@moutte.net>
* Copyright (C) <2006> Zeeshan Ali <zeeshan.ali@nokia.com>
* Copyright (C) <2006-2008> Tim-Philipp Müller <tim centricular net>
* Copyright (C) <2009> Young-Ho Cha <ganadist@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@ -80,6 +81,7 @@
#include "gsttimeoverlay.h"
#include "gstclockoverlay.h"
#include "gsttextrender.h"
#include <string.h>
/* FIXME:
* - use proper strides and offset for I420
@ -113,10 +115,38 @@ GST_ELEMENT_DETAILS ("Text overlay",
#define DEFAULT_PROP_SILENT FALSE
#define DEFAULT_PROP_LINE_ALIGNMENT GST_TEXT_OVERLAY_LINE_ALIGN_CENTER
#define DEFAULT_PROP_WAIT_TEXT TRUE
#define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
#define DEFAULT_PROP_VERTICAL_RENDER FALSE
/* make a property of me */
#define DEFAULT_SHADING_VALUE -80
#define MINIMUM_OUTLINE_OFFSET 1.0
#define DEFAULT_SCALE_BASIS 640
#define COMP_Y(ret, r, g, b) \
{ \
ret = (int) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16)); \
ret = CLAMP (ret, 0, 255); \
}
#define COMP_U(ret, r, g, b) \
{ \
ret = (int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) + 128); \
ret = CLAMP (ret, 0, 255); \
}
#define COMP_V(ret, r, g, b) \
{ \
ret = (int) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) + 128); \
ret = CLAMP (ret, 0, 255); \
}
#define BLEND(ret, alpha, v0, v1) \
{ \
ret = (v0 * alpha + v1 * (255 - alpha)) / 255; \
}
enum
{
PROP_0,
@ -135,6 +165,8 @@ enum
PROP_SILENT,
PROP_LINE_ALIGNMENT,
PROP_WAIT_TEXT,
PROP_AUTO_ADJUST_SIZE,
PROP_VERTICAL_RENDER,
PROP_LAST
};
@ -265,12 +297,15 @@ static GstPadLinkReturn gst_text_overlay_text_pad_link (GstPad * pad,
GstPad * peer);
static void gst_text_overlay_text_pad_unlink (GstPad * pad);
static void gst_text_overlay_pop_text (GstTextOverlay * overlay);
static void gst_text_overlay_update_render_mode (GstTextOverlay * overlay);
static void gst_text_overlay_finalize (GObject * object);
static void gst_text_overlay_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_text_overlay_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static void gst_text_overlay_adjust_values_with_fontdesc (GstTextOverlay *
overlay, PangoFontDescription * desc);
GST_BOILERPLATE (GstTextOverlay, gst_text_overlay, GstElement, GST_TYPE_ELEMENT)
@ -304,6 +339,7 @@ gst_text_overlay_class_init (GstTextOverlayClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
PangoFontMap *fontmap;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
@ -316,7 +352,9 @@ gst_text_overlay_class_init (GstTextOverlayClass * klass)
GST_DEBUG_FUNCPTR (gst_text_overlay_change_state);
klass->get_text = gst_text_overlay_get_text;
klass->pango_context = pango_ft2_get_context (72, 72);
fontmap = pango_cairo_font_map_get_default ();
klass->pango_context =
pango_cairo_font_map_create_context (PANGO_CAIRO_FONT_MAP (fontmap));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
g_param_spec_string ("text", "text",
@ -408,6 +446,16 @@ gst_text_overlay_class_init (GstTextOverlayClass * klass)
g_param_spec_boolean ("wait-text", "Wait Text",
"Whether to wait for subtitles",
DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (G_OBJECT_CLASS (klass),
PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
"Automatically adjust font size to screen-size.",
DEFAULT_PROP_AUTO_ADJUST_SIZE, G_PARAM_READWRITE));
g_object_class_install_property (G_OBJECT_CLASS (klass),
PROP_VERTICAL_RENDER, g_param_spec_boolean ("vertical-render",
"vertical render", "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
G_PARAM_READWRITE));
}
static void
@ -416,7 +464,11 @@ gst_text_overlay_finalize (GObject * object)
GstTextOverlay *overlay = GST_TEXT_OVERLAY (object);
g_free (overlay->default_text);
g_free (overlay->bitmap.buffer);
if (overlay->text_image) {
g_free (overlay->text_image);
overlay->text_image = NULL;
}
if (overlay->layout) {
g_object_unref (overlay->layout);
@ -445,6 +497,7 @@ static void
gst_text_overlay_init (GstTextOverlay * overlay, GstTextOverlayClass * klass)
{
GstPadTemplate *template;
PangoFontDescription *desc;
/* video sink */
template = gst_static_pad_template_get (&video_sink_template_factory);
@ -491,9 +544,10 @@ gst_text_overlay_init (GstTextOverlay * overlay, GstTextOverlayClass * klass)
overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
overlay->layout =
pango_layout_new (GST_TEXT_OVERLAY_GET_CLASS (overlay)->pango_context);
pango_layout_set_alignment (overlay->layout,
(PangoAlignment) overlay->line_align);
memset (&overlay->bitmap, 0, sizeof (overlay->bitmap));
desc =
pango_context_get_font_description (GST_TEXT_OVERLAY_GET_CLASS
(overlay)->pango_context);
gst_text_overlay_adjust_values_with_fontdesc (overlay, desc);
overlay->halign = DEFAULT_PROP_HALIGNMENT;
overlay->valign = DEFAULT_PROP_VALIGNMENT;
@ -508,9 +562,13 @@ gst_text_overlay_init (GstTextOverlay * overlay, GstTextOverlayClass * klass)
overlay->shading_value = DEFAULT_SHADING_VALUE;
overlay->silent = DEFAULT_PROP_SILENT;
overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
overlay->need_render = TRUE;
overlay->text_image = NULL;
overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
gst_text_overlay_update_render_mode (overlay);
overlay->fps_n = 0;
overlay->fps_d = 1;
@ -534,13 +592,48 @@ gst_text_overlay_update_wrap_mode (GstTextOverlay * overlay)
GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
pango_layout_set_width (overlay->layout, -1);
} else {
int width;
if (overlay->auto_adjust_size) {
width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
if (overlay->use_vertical_render) {
width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
}
} else {
width =
(overlay->use_vertical_render ? overlay->height : overlay->width) *
PANGO_SCALE;
}
GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
GST_DEBUG_OBJECT (overlay, "Set wrap mode %d", overlay->wrap_mode);
pango_layout_set_width (overlay->layout, overlay->width * PANGO_SCALE);
pango_layout_set_width (overlay->layout, width);
pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
}
}
static void
gst_text_overlay_update_render_mode (GstTextOverlay * overlay)
{
#if HAVE_PANGO_VERTICAL_WRITING
PangoMatrix matrix = PANGO_MATRIX_INIT;
PangoContext *context = pango_layout_get_context (overlay->layout);
if (overlay->use_vertical_render) {
pango_matrix_rotate (&matrix, -90);
pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
pango_context_set_matrix (context, &matrix);
pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
} else {
pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
pango_context_set_matrix (context, &matrix);
#endif
pango_layout_set_alignment (overlay->layout, overlay->line_align);
#if HAVE_PANGO_VERTICAL_WRITING
}
#endif
}
static gboolean
gst_text_overlay_setcaps_txt (GstPad * pad, GstCaps * caps)
{
@ -677,6 +770,7 @@ gst_text_overlay_set_property (GObject * object, guint prop_id,
if (desc) {
GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
pango_layout_set_font_description (overlay->layout, desc);
gst_text_overlay_adjust_values_with_fontdesc (overlay, desc);
pango_font_description_free (desc);
} else {
GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
@ -695,6 +789,19 @@ gst_text_overlay_set_property (GObject * object, guint prop_id,
case PROP_WAIT_TEXT:
overlay->wait_text = g_value_get_boolean (value);
break;
case PROP_AUTO_ADJUST_SIZE:
{
overlay->auto_adjust_size = g_value_get_boolean (value);
overlay->need_render = TRUE;
}
#ifdef HAVE_PANGO_VERTICAL_WRITING
case PROP_VERTICAL_RENDER:
{
overlay->use_vertical_render = g_value_get_boolean (value);
gst_text_overlay_update_render_mode (overlay);
overlay->need_render = TRUE;
}
#endif
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -748,6 +855,16 @@ gst_text_overlay_get_property (GObject * object, guint prop_id,
case PROP_WAIT_TEXT:
g_value_set_boolean (value, overlay->wait_text);
break;
case PROP_AUTO_ADJUST_SIZE:
g_value_set_boolean (value, overlay->auto_adjust_size);
break;
case PROP_VERTICAL_RENDER:
#ifdef HAVE_PANGO_VERTICAL_WRITING
g_value_set_boolean (value, overlay->use_vertical_render);
#else
g_value_set_boolean (value, FALSE);
#endif
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -854,6 +971,285 @@ gst_text_overlay_getcaps (GstPad * pad)
return caps;
}
static void
gst_text_overlay_adjust_values_with_fontdesc (GstTextOverlay * overlay,
PangoFontDescription * desc)
{
gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
overlay->shadow_offset = (double) (font_size) / 13.0;
overlay->outline_offset = (double) (font_size) / 15.0;
if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
}
static inline void
gst_text_overlay_blit_1 (GstTextOverlay * overlay, guchar * dest, gint xpos,
gint ypos, guchar * text_image, guint dest_stride)
{
gint i, j = 0;
gint x, y;
guchar r, g, b, a;
guchar *pimage;
guchar *py;
gint width = overlay->image_width;
gint height = overlay->image_height;
if (xpos < 0) {
xpos = 0;
}
if (xpos + width > overlay->width) {
width = overlay->width - xpos;
}
if (ypos + height > overlay->height) {
height = overlay->height - ypos;
}
dest += (ypos / 1) * dest_stride;
for (i = 0; i < height; i++) {
pimage = text_image + 4 * (i * overlay->image_width);
py = dest + i * dest_stride + xpos;
for (j = 0; j < width; j++) {
b = *pimage++;
g = *pimage++;
r = *pimage++;
a = *pimage++;
if (a == 0) {
py++;
continue;
}
COMP_Y (y, r, g, b);
x = *py;
BLEND (*py++, a, y, x);
}
}
}
static inline void
gst_text_overlay_blit_sub2x2cbcr (GstTextOverlay * overlay,
guchar * destcb, guchar * destcr, gint xpos, gint ypos, guchar * text_image,
guint destcb_stride, guint destcr_stride)
{
gint i, j;
gint x, cb, cr;
gushort r, g, b, a;
guchar *pimage1, *pimage2;
guchar *pcb, *pcr;
gint width = overlay->image_width - 2;
gint height = overlay->image_height - 2;
if (xpos < 0) {
xpos = 0;
}
if (xpos + width > overlay->width) {
width = overlay->width - xpos;
}
if (ypos + height > overlay->height) {
height = overlay->height - ypos;
}
destcb += (ypos / 2) * destcb_stride;
destcr += (ypos / 2) * destcr_stride;
for (i = 0; i < height; i += 2) {
pimage1 = text_image + 4 * (i * overlay->image_width);
pimage2 = pimage1 + 4 * overlay->image_width;
pcb = destcb + (i / 2) * destcb_stride + xpos / 2;
pcr = destcr + (i / 2) * destcr_stride + xpos / 2;
for (j = 0; j < width; j += 2) {
b = *pimage1++;
g = *pimage1++;
r = *pimage1++;
a = *pimage1++;
b += *pimage1++;
g += *pimage1++;
r += *pimage1++;
a += *pimage1++;
b += *pimage2++;
g += *pimage2++;
r += *pimage2++;
a += *pimage2++;
/* for rounding */
b += *pimage2++ + 2;
g += *pimage2++ + 2;
r += *pimage2++ + 2;
a += *pimage2++ + 2;
b /= 4;
g /= 4;
r /= 4;
a /= 4;
if (a == 0) {
pcb++;
pcr++;
continue;
}
COMP_U (cb, r, g, b);
COMP_V (cr, r, g, b)
x = *pcb;
BLEND (*pcb++, a, cb, x);
x = *pcr;
BLEND (*pcr++, a, cr, x);
}
}
}
static void
gst_text_overlay_render_pangocairo (GstTextOverlay * overlay,
const gchar * string, gint textlen)
{
cairo_t *cr;
cairo_surface_t *surface;
cairo_t *cr_shadow;
cairo_surface_t *surface_shadow;
PangoRectangle ink_rect, logical_rect;
cairo_matrix_t cairo_matrix;
int width, height;
double scalef = 1.0;
if (overlay->auto_adjust_size) {
/* 640 pixel is default */
scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
}
pango_layout_set_width (overlay->layout, -1);
/* set text on pango layout */
pango_layout_set_markup (overlay->layout, string, textlen);
/* get subtitle image size */
pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
width = (logical_rect.width + overlay->shadow_offset) * scalef;
if (width + overlay->deltax >
(overlay->use_vertical_render ? overlay->height : overlay->width)) {
/*
* subtitle image width is larger then overlay width
* so rearrange overlay wrap mode.
*/
gst_text_overlay_update_wrap_mode (overlay);
pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
width = overlay->width;
}
height =
(logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
if (height > overlay->height) {
height = overlay->height;
}
#ifdef HAVE_PANGO_VERTICAL_WRITING
if (overlay->use_vertical_render) {
PangoRectangle rect;
PangoContext *context;
PangoMatrix matrix = PANGO_MATRIX_INIT;
int tmp;
context = pango_layout_get_context (overlay->layout);
pango_matrix_rotate (&matrix, -90);
rect.x = rect.y = 0;
rect.width = width;
rect.height = height;
pango_matrix_transform_pixel_rectangle (&matrix, &rect);
matrix.x0 = -rect.x;
matrix.y0 = -rect.y;
pango_context_set_matrix (context, &matrix);
cairo_matrix.xx = matrix.xx;
cairo_matrix.yx = matrix.yx;
cairo_matrix.xy = matrix.xy;
cairo_matrix.yy = matrix.yy;
cairo_matrix.x0 = matrix.x0;
cairo_matrix.y0 = matrix.y0;
cairo_matrix_scale (&cairo_matrix, scalef, scalef);
tmp = height;
height = width;
width = tmp;
} else
#endif
{
cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
}
/* clear shadow surface */
surface_shadow = cairo_image_surface_create (CAIRO_FORMAT_A8, width, height);
cr_shadow = cairo_create (surface_shadow);
cairo_set_operator (cr_shadow, CAIRO_OPERATOR_CLEAR);
cairo_paint (cr_shadow);
cairo_set_operator (cr_shadow, CAIRO_OPERATOR_OVER);
cairo_save (cr_shadow);
cairo_set_matrix (cr_shadow, &cairo_matrix);
cairo_save (cr_shadow);
/* draw shadow text */
cairo_set_source_rgba (cr_shadow, 0.0, 0.0, 0.0, 0.5);
cairo_translate (cr_shadow, overlay->shadow_offset, overlay->shadow_offset);
pango_cairo_show_layout (cr_shadow, overlay->layout);
cairo_restore (cr_shadow);
/* draw outline text */
cairo_save (cr_shadow);
cairo_set_source_rgb (cr_shadow, 0.0, 0.0, 0.0);
cairo_set_line_width (cr_shadow, overlay->outline_offset);
pango_cairo_layout_path (cr_shadow, overlay->layout);
cairo_stroke (cr_shadow);
cairo_restore (cr_shadow);
if (overlay->want_shading) {
cairo_paint_with_alpha (cr_shadow, overlay->shading_value);
}
cairo_restore (cr_shadow);
cairo_destroy (cr_shadow);
/* clear image surface */
overlay->text_image = g_realloc (overlay->text_image, 4 * width * height);
surface = cairo_image_surface_create_for_data (overlay->text_image,
CAIRO_FORMAT_ARGB32, width, height, width * 4);
cr = cairo_create (surface);
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
cairo_paint (cr);
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
/* set default color */
cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
cairo_save (cr);
cairo_set_matrix (cr, &cairo_matrix);
/* draw text */
cairo_set_matrix (cr, &cairo_matrix);
/* draw text */
pango_cairo_show_layout (cr, overlay->layout);
cairo_restore (cr);
/* composite outline, shadow, and text */
cairo_set_operator (cr, CAIRO_OPERATOR_DEST_OVER);
cairo_set_source_surface (cr, surface_shadow, 0.0, 0.0);
cairo_paint (cr);
cairo_destroy (cr);
cairo_surface_destroy (surface_shadow);
cairo_surface_destroy (surface);
overlay->image_width = width;
overlay->image_height = height;
overlay->baseline_y = ink_rect.y;
}
#define BOX_XPAD 6
#define BOX_YPAD 6
@ -915,21 +1311,12 @@ gst_text_overlay_shade_UYVY_y (GstTextOverlay * overlay, guchar * dest,
*/
static inline void
gst_text_overlay_blit_I420 (GstTextOverlay * overlay, FT_Bitmap * bitmap,
guint8 * yuv_pixels, gint x0, gint y0)
gst_text_overlay_blit_I420 (GstTextOverlay * overlay,
guint8 * yuv_pixels, gint xpos, gint ypos)
{
int y; /* text bitmap coordinates */
int x1; /* video buffer coordinates */
guint8 *y_p, *bitp, *u_p, *v_p;
int bitmap_x0 = 0; //x0 < 1 ? -(x0 - 1) : 1; /* 1 pixel border */
int bitmap_y0 = y0 < 1 ? -(y0 - 1) : 1; /* 1 pixel border */
int bitmap_width = bitmap->width - bitmap_x0;
int bitmap_height = bitmap->rows - bitmap_y0;
int skip_y, skip_x;
int y_stride, u_stride, v_stride;
int u_offset, v_offset;
int h, w;
guint8 v;
w = overlay->width;
h = overlay->height;
@ -942,177 +1329,90 @@ gst_text_overlay_blit_I420 (GstTextOverlay * overlay, FT_Bitmap * bitmap,
v_offset =
gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 2, w, h);
/*
if (x0 < 0 && abs (x0) < bitmap_width) {
bitmap_x0 = abs (x0);
x0 = 0;
}
*/
if (x0 + bitmap_x0 + bitmap_width > w - 1) /* 1 pixel border */
bitmap_width -= x0 + bitmap_x0 + bitmap_width - w + 1;
if (y0 + bitmap_y0 + bitmap_height > h - 1) /* 1 pixel border */
bitmap_height -= y0 + bitmap_y0 + bitmap_height - h + 1;
x1 = x0 + bitmap_x0;
/* draw an outline around the text */
for (y = bitmap_y0; y < bitmap_y0 + bitmap_height; y++) {
int n;
bitp = bitmap->buffer + (y * bitmap->pitch) + bitmap_x0;
y_p = yuv_pixels + ((y + y0) * y_stride) + x1;
for (n = bitmap_width; n > 0; --n) {
v = *bitp;
if (v) {
y_p[-1] = CLAMP (y_p[-1] - v, 0, 255);
y_p[1] = CLAMP (y_p[1] - v, 0, 255);
y_p[-w] = CLAMP (y_p[-w] - v, 0, 255);
y_p[w] = CLAMP (y_p[w] - v, 0, 255);
}
y_p++;
bitp++;
}
}
/* now blit text */
x1 = x0 + bitmap_x0;
skip_y = 0;
for (y = bitmap_y0; y < bitmap_y0 + bitmap_height; y++) {
int n;
bitp = bitmap->buffer + (y * bitmap->pitch) + bitmap_x0;
y_p = yuv_pixels + 0 + ((y0 + y) * y_stride) + x1;
u_p = yuv_pixels + u_offset + (((y0 + y) / 2) * u_stride) + (x1 / 2);
v_p = yuv_pixels + v_offset + (((y0 + y) / 2) * v_stride) + (x1 / 2);
skip_x = 0;
for (n = bitmap_width; n > 0; --n) {
v = *bitp;
if (v) {
*y_p = v;
if (!skip_y) {
*u_p = 0x80;
*v_p = 0x80;
}
}
if (!skip_y) {
if (!skip_x) {
u_p++;
v_p++;
}
skip_x = !skip_x;
}
y_p++;
bitp++;
}
skip_y = !skip_y;
}
gst_text_overlay_blit_1 (overlay, yuv_pixels, xpos, ypos, overlay->text_image,
y_stride);
gst_text_overlay_blit_sub2x2cbcr (overlay, yuv_pixels + u_offset,
yuv_pixels + v_offset, xpos, ypos, overlay->text_image, u_stride,
v_stride);
}
static inline void
gst_text_overlay_blit_UYVY (GstTextOverlay * overlay, FT_Bitmap * bitmap,
guint8 * yuv_pixels, gint x0, gint y0)
gst_text_overlay_blit_UYVY (GstTextOverlay * overlay,
guint8 * yuv_pixels, gint xpos, gint ypos)
{
int y; /* text bitmap coordinates */
int x1, y1; /* video buffer coordinates */
guint8 *p, *bitp;
int video_width, video_height;
int bitmap_x0 = 0; //x0 < 1 ? -(x0 - 1) : 1; /* 1 pixel border */
int bitmap_y0 = y0 < 1 ? -(y0 - 1) : 1; /* 1 pixel border */
int bitmap_width = bitmap->width - bitmap_x0;
int bitmap_height = bitmap->rows - bitmap_y0;
int a0, r0, g0, b0;
int a1, r1, g1, b1;
int y0, y1, u, v;
int i, j;
int h, w;
guchar *pimage, *dest;
video_width =
gst_video_format_get_row_stride (GST_VIDEO_FORMAT_UYVY, 0,
overlay->width);
video_height = overlay->height;
w = overlay->image_width - 2;
h = overlay->image_height - 2;
/*g_debug ("bliting bitmap(%d, %d) on yuv (%d,%d) at %d,%d",
bitmap_width, bitmap_height,
video_width, video_height,
x0, y0); */
if (xpos < 0) {
xpos = 0;
}
if (x0 + bitmap_x0 + bitmap_width > overlay->width - 1) /* 1 pixel border */
bitmap_width -= x0 + bitmap_x0 + bitmap_width - overlay->width + 1;
if (y0 + bitmap_y0 + bitmap_height > video_height - 1) /* 1 pixel border */
bitmap_height -= y0 + bitmap_y0 + bitmap_height - video_height + 1;
if (xpos + w > overlay->width) {
w = overlay->width - xpos;
}
x1 = x0 + bitmap_x0;
y1 = y0 + bitmap_y0;
if (ypos + h > overlay->height) {
h = overlay->height - ypos;
}
/* draw an outline around the text */
for (y = bitmap_y0; y < bitmap_y0 + bitmap_height; y++) {
int n;
for (i = 0; i < h; i++) {
pimage = overlay->text_image + i * overlay->image_width * 4;
dest = yuv_pixels + (i + ypos) * overlay->width * 2 + xpos * 2;
for (j = 0; j < w; j += 2) {
b0 = *pimage++;
g0 = *pimage++;
r0 = *pimage++;
a0 = *pimage++;
bitp = bitmap->buffer + (y * bitmap->pitch) + bitmap_x0;
p = yuv_pixels + (y0 + y) * video_width + (x1 * 2) + 1;
for (n = bitmap_width; n > 0; --n) {
if (*bitp) {
*(p + 2) = CLAMP (*(p + 2) - *bitp, 0, 255);
*(p - 2) = CLAMP (*(p - 2) - *bitp, 0, 255);
*(p - video_width) = CLAMP (*(p - video_width) - *bitp, 0, 255);
*(p + video_width) = CLAMP (*(p + video_width) - *bitp, 0, 255);
b1 = *pimage++;
g1 = *pimage++;
r1 = *pimage++;
a1 = *pimage++;
a0 += a1 + 2;
a0 /= 2;
if (a0 == 0) {
dest += 4;
continue;
}
p += 2;
bitp++;
COMP_Y (y0, r0, g0, b0);
COMP_Y (y1, r1, g1, b1);
b0 += b1 + 2;
g0 += g1 + 2;
r0 += r1 + 2;
b0 /= 2;
g0 /= 2;
r0 /= 2;
COMP_U (u, r0, g0, b0);
COMP_V (v, r0, g0, b0);
BLEND (*dest, a0, u, *dest);
dest++;
BLEND (*dest, a0, y0, *dest);
dest++;
BLEND (*dest, a0, v, *dest);
dest++;
BLEND (*dest, a0, y1, *dest);
dest++;
}
}
/* now blit text */
for (y = bitmap_y0; y < bitmap_y0 + bitmap_height; y++) {
int n;
bitp = bitmap->buffer + (y * bitmap->pitch) + bitmap_x0;
p = yuv_pixels + (y0 + y) * video_width + (x1 * 2) + 1;
for (n = bitmap_width; n > 0; --n) {
if (*bitp) {
*p = *bitp;
*(p - 1) = 0x80;
}
p += 2;
bitp++;
}
}
}
static void
gst_text_overlay_resize_bitmap (GstTextOverlay * overlay, gint width,
gint height)
{
FT_Bitmap *bitmap = &overlay->bitmap;
int pitch = (width | 3) + 1;
int size = pitch * height;
/* no need to keep reallocating; just keep the maximum size so far */
if (size <= overlay->bitmap_buffer_size) {
bitmap->rows = height;
bitmap->width = width;
bitmap->pitch = pitch;
memset (bitmap->buffer, 0, overlay->bitmap_buffer_size);
return;
}
if (!bitmap->buffer) {
/* initialize */
bitmap->pixel_mode = ft_pixel_mode_grays;
bitmap->num_grays = 256;
}
overlay->bitmap_buffer_size = size;
bitmap->buffer = g_realloc (bitmap->buffer, size);
memset (bitmap->buffer, 0, size);
bitmap->rows = height;
bitmap->width = width;
bitmap->pitch = pitch;
}
static void
gst_text_overlay_render_text (GstTextOverlay * overlay,
const gchar * text, gint textlen)
{
PangoRectangle ink_rect, logical_rect;
gchar *string;
if (!overlay->need_render) {
@ -1136,13 +1436,7 @@ gst_text_overlay_render_text (GstTextOverlay * overlay,
/* FIXME: should we check for UTF-8 here? */
GST_DEBUG ("Rendering '%s'", string);
pango_layout_set_markup (overlay->layout, string, textlen);
pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
gst_text_overlay_resize_bitmap (overlay, ink_rect.width,
ink_rect.height + ink_rect.y);
pango_ft2_render_layout (&overlay->bitmap, overlay->layout, -ink_rect.x, 0);
overlay->baseline_y = ink_rect.y;
gst_text_overlay_render_pangocairo (overlay, string, textlen);
g_free (string);
@ -1153,31 +1447,45 @@ static GstFlowReturn
gst_text_overlay_push_frame (GstTextOverlay * overlay, GstBuffer * video_frame)
{
gint xpos, ypos;
gint width, height;
GstTextOverlayVAlign valign;
GstTextOverlayHAlign halign;
video_frame = gst_buffer_make_writable (video_frame);
width = overlay->image_width;
height = overlay->image_height;
switch (overlay->halign) {
if (overlay->use_vertical_render)
halign = GST_TEXT_OVERLAY_HALIGN_RIGHT;
else
halign = overlay->halign;
switch (halign) {
case GST_TEXT_OVERLAY_HALIGN_LEFT:
xpos = overlay->xpad;
break;
case GST_TEXT_OVERLAY_HALIGN_CENTER:
xpos = (overlay->width - overlay->bitmap.width) / 2;
xpos = (overlay->width - width) / 2;
break;
case GST_TEXT_OVERLAY_HALIGN_RIGHT:
xpos = overlay->width - overlay->bitmap.width - overlay->xpad;
xpos = overlay->width - width - overlay->xpad;
break;
default:
xpos = 0;
}
xpos += overlay->deltax;
if (overlay->use_vertical_render)
valign = GST_TEXT_OVERLAY_VALIGN_TOP;
else
valign = overlay->valign;
switch (overlay->valign) {
switch (valign) {
case GST_TEXT_OVERLAY_VALIGN_BOTTOM:
ypos = overlay->height - overlay->bitmap.rows - overlay->ypad;
ypos = overlay->height - height - overlay->ypad;
break;
case GST_TEXT_OVERLAY_VALIGN_BASELINE:
ypos = overlay->height - (overlay->bitmap.rows + overlay->ypad);
ypos = overlay->height - (height + overlay->ypad);
break;
case GST_TEXT_OVERLAY_VALIGN_TOP:
ypos = overlay->ypad;
@ -1188,40 +1496,23 @@ gst_text_overlay_push_frame (GstTextOverlay * overlay, GstBuffer * video_frame)
}
ypos += overlay->deltay;
/* shaded background box */
if (overlay->want_shading) {
if (ypos < 0)
ypos = 0;
if (overlay->text_image) {
switch (overlay->format) {
case GST_MAKE_FOURCC ('I', '4', '2', '0'):
gst_text_overlay_shade_I420_y (overlay,
GST_BUFFER_DATA (video_frame), xpos, xpos + overlay->bitmap.width,
ypos, ypos + overlay->bitmap.rows);
break;
case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
gst_text_overlay_shade_UYVY_y (overlay,
GST_BUFFER_DATA (video_frame), xpos, xpos + overlay->bitmap.width,
ypos, ypos + overlay->bitmap.rows);
break;
default:
g_assert_not_reached ();
}
}
if (overlay->bitmap.buffer) {
switch (overlay->format) {
case GST_MAKE_FOURCC ('I', '4', '2', '0'):
gst_text_overlay_blit_I420 (overlay, &overlay->bitmap,
gst_text_overlay_blit_I420 (overlay,
GST_BUFFER_DATA (video_frame), xpos, ypos);
break;
case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
gst_text_overlay_blit_UYVY (overlay, &overlay->bitmap,
gst_text_overlay_blit_UYVY (overlay,
GST_BUFFER_DATA (video_frame), xpos, ypos);
break;
default:
g_assert_not_reached ();
}
}
return gst_pad_push (overlay->srcpad, video_frame);
}

View file

@ -2,7 +2,7 @@
#define __GST_TEXT_OVERLAY_H__
#include <gst/gst.h>
#include <pango/pangoft2.h>
#include <pango/pangocairo.h>
G_BEGIN_DECLS
@ -126,15 +126,20 @@ struct _GstTextOverlay {
gboolean wait_text;
PangoLayout *layout;
FT_Bitmap bitmap;
gint bitmap_buffer_size;
gdouble shadow_offset;
gdouble outline_offset;
guchar *text_image;
gint image_width;
gint image_height;
gint baseline_y;
gboolean auto_adjust_size;
gboolean need_render;
gint shading_value; /* for timeoverlay subclass */
gboolean have_pango_markup;
gboolean use_vertical_render;
};
struct _GstTextOverlayClass {

View file

@ -1,6 +1,7 @@
/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (C) <2003> David Schleef <ds@schleef.org>
* Copyright (C) <2009> Young-Ho Cha <ganadist@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@ -46,10 +47,13 @@
#include <gst/video/video.h>
#include "gsttextrender.h"
#include <string.h>
GST_DEBUG_CATEGORY_EXTERN (pango_debug);
#define GST_CAT_DEFAULT pango_debug
#define MINIMUM_OUTLINE_OFFSET 1.0
static const GstElementDetails text_render_details =
GST_ELEMENT_DETAILS ("Text renderer",
"Filter/Editor/Video",
@ -150,6 +154,9 @@ gst_text_render_line_align_get_type (void)
return text_render_line_align_type;
}
static void gst_text_render_adjust_values_with_fontdesc (GstTextRender *
render, PangoFontDescription * desc);
GST_BOILERPLATE (GstTextRender, gst_text_render, GstElement, GST_TYPE_ELEMENT);
static void gst_text_render_finalize (GObject * object);
@ -176,6 +183,7 @@ gst_text_render_class_init (GstTextRenderClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
PangoFontMap *fontmap;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
@ -186,7 +194,9 @@ gst_text_render_class_init (GstTextRenderClass * klass)
gobject_class->set_property = gst_text_render_set_property;
gobject_class->get_property = gst_text_render_get_property;
klass->pango_context = pango_ft2_get_context (72, 72);
fontmap = pango_cairo_font_map_get_default ();
klass->pango_context =
pango_cairo_font_map_create_context (PANGO_CAIRO_FONT_MAP (fontmap));
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC,
g_param_spec_string ("font-desc", "font description",
"Pango font description of font "
@ -217,47 +227,85 @@ gst_text_render_class_init (GstTextRenderClass * klass)
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
resize_bitmap (GstTextRender * render, gint width, gint height)
gst_text_render_adjust_values_with_fontdesc (GstTextRender * render,
PangoFontDescription * desc)
{
FT_Bitmap *bitmap = &render->bitmap;
gint pitch = (width | 3) + 1;
gint size = pitch * height;
gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
/* no need to keep reallocating; just keep the maximum size so far */
if (size <= render->bitmap_buffer_size) {
bitmap->rows = height;
bitmap->width = width;
bitmap->pitch = pitch;
memset (bitmap->buffer, 0, render->bitmap_buffer_size);
return;
}
if (!bitmap->buffer) {
/* initialize */
bitmap->pixel_mode = ft_pixel_mode_grays;
bitmap->num_grays = 256;
}
if (bitmap->buffer)
bitmap->buffer = g_realloc (bitmap->buffer, size);
else
bitmap->buffer = g_malloc (size);
bitmap->rows = height;
bitmap->width = width;
bitmap->pitch = pitch;
memset (bitmap->buffer, 0, size);
render->bitmap_buffer_size = size;
render->shadow_offset = (double) (font_size) / 13.0;
render->outline_offset = (double) (font_size) / 15.0;
if (render->outline_offset < MINIMUM_OUTLINE_OFFSET)
render->outline_offset = MINIMUM_OUTLINE_OFFSET;
}
static void
gst_text_render_render_text (GstTextRender * render)
gst_text_render_render_pangocairo (GstTextRender * render)
{
cairo_t *cr;
cairo_surface_t *surface;
cairo_t *cr_shadow;
cairo_surface_t *surface_shadow;
PangoRectangle ink_rect, logical_rect;
gint width, height;
pango_layout_get_pixel_extents (render->layout, &ink_rect, &logical_rect);
resize_bitmap (render, ink_rect.width, ink_rect.height + ink_rect.y);
pango_ft2_render_layout (&render->bitmap, render->layout, -ink_rect.x, 0);
render->baseline_y = ink_rect.y;
width = logical_rect.width + render->shadow_offset;
height = logical_rect.height + logical_rect.y + render->shadow_offset;
surface_shadow = cairo_image_surface_create (CAIRO_FORMAT_A8, width, height);
cr_shadow = cairo_create (surface_shadow);
/* clear shadow surface */
cairo_set_source_rgba (cr_shadow, 0.0, 0.0, 0.0, 0.0);
cairo_set_operator (cr_shadow, CAIRO_OPERATOR_CLEAR);
cairo_paint (cr_shadow);
cairo_set_operator (cr_shadow, CAIRO_OPERATOR_OVER);
cairo_set_source_rgb (cr_shadow, 0.0, 0.0, 0.0);
pango_cairo_update_layout (cr_shadow, render->layout);
/* draw shadow text */
cairo_save (cr_shadow);
cairo_set_source_rgba (cr_shadow, 0.0, 0.0, 0.0, 0.5);
cairo_translate (cr_shadow, render->shadow_offset, render->shadow_offset);
pango_cairo_show_layout (cr_shadow, render->layout);
cairo_restore (cr_shadow);
/* draw outline text */
cairo_save (cr_shadow);
cairo_set_line_width (cr_shadow, render->outline_offset);
pango_cairo_layout_path (cr_shadow, render->layout);
cairo_stroke (cr_shadow);
cairo_restore (cr_shadow);
cairo_destroy (cr_shadow);
render->text_image = g_realloc (render->text_image, 4 * width * height);
memset (render->text_image, 0, 4 * width * height);
surface = cairo_image_surface_create_for_data (render->text_image,
CAIRO_FORMAT_ARGB32, width, height, width * 4);
cr = cairo_create (surface);
/* set default color */
cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
/* draw text */
pango_cairo_update_layout (cr, render->layout);
pango_cairo_show_layout (cr, render->layout);
/* composite shadow with offset */
cairo_set_operator (cr, CAIRO_OPERATOR_DEST_OVER);
cairo_set_source_surface (cr, surface_shadow, 0.0, 0.0);
cairo_paint (cr);
cairo_destroy (cr);
cairo_surface_destroy (surface_shadow);
cairo_surface_destroy (surface);
render->image_width = width;
render->image_height = height;
}
static void
@ -306,7 +354,7 @@ gst_text_render_setcaps (GstPad * pad, GstCaps * caps)
GST_DEBUG ("Got caps %" GST_PTR_FORMAT, caps);
if (width >= render->bitmap.width && height >= render->bitmap.rows) {
if (width >= render->image_width && height >= render->image_height) {
render->width = width;
render->height = height;
ret = TRUE;
@ -325,92 +373,62 @@ gst_text_render_fixate_caps (GstPad * pad, GstCaps * caps)
GstStructure *s = gst_caps_get_structure (caps, 0);
GST_DEBUG ("Fixating caps %" GST_PTR_FORMAT, caps);
gst_structure_fixate_field_nearest_int (s, "width", render->width);
gst_structure_fixate_field_nearest_int (s, "height", render->height);
gst_structure_fixate_field_nearest_int (s, "width", render->image_width);
gst_structure_fixate_field_nearest_int (s, "height", render->image_height);
GST_DEBUG ("Fixated to %" GST_PTR_FORMAT, caps);
gst_object_unref (render);
}
static void
gst_text_renderer_bitmap_to_ayuv (GstTextRender * render, FT_Bitmap * bitmap,
guchar * pixbuf, gint x0, gint x1, gint y0, gint y1)
gst_text_renderer_image_to_ayuv (GstTextRender * render, guchar * pixbuf,
int xpos, int ypos, int stride)
{
int y; /* text bitmap coordinates */
int rowinc, bit_rowinc;
guchar *p, *bitp;
guchar v;
guchar a, r, g, b;
int width, height;
x0 = CLAMP (x0, 0, render->width);
x1 = CLAMP (x1, 0, render->width);
width = render->image_width;
height = render->image_height;
bitp = render->text_image;
y0 = CLAMP (y0, 0, render->height);
y1 = CLAMP (y1, 0, render->height);
rowinc = render->width - bitmap->width;
bit_rowinc = bitmap->pitch - bitmap->width;
bitp = bitmap->buffer;
p = pixbuf + ((x0 + (render->width * y0)) * 4);
for (y = y0; y < y1; y++) {
for (y = 0; y < height; y++) {
int n;
p = pixbuf + ypos * stride + xpos;
for (n = 0; n < width; n++) {
b = *bitp++;
g = *bitp++;
r = *bitp++;
a = *bitp++;
for (n = x0; n < x1; n++) {
v = *bitp;
if (v) {
p[0] = v;
p[1] = 255;
p[2] = 0x80;
p[3] = 0x80;
}
p += 4;
bitp++;
*p++ = a;
*p++ = CLAMP ((int) (((19595 * r) >> 16) + ((38470 * g) >> 16) +
((7471 * b) >> 16)), 0, 255);
*p++ = CLAMP ((int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) +
((32768 * b) >> 16) + 128), 0, 255);
*p++ = CLAMP ((int) (((32768 * r) >> 16) - ((27439 * g) >> 16) -
((5329 * b) >> 16) + 128), 0, 255);
}
p += rowinc * 4;
bitp += bit_rowinc;
}
}
static void
gst_text_renderer_bitmap_to_argb (GstTextRender * render, FT_Bitmap * bitmap,
guchar * pixbuf, gint x0, gint x1, gint y0, gint y1)
gst_text_renderer_image_to_argb (GstTextRender * render, guchar * pixbuf,
int xpos, int ypos, int stride)
{
int y; /* text bitmap coordinates */
int rowinc, bit_rowinc;
int i;
guchar *p, *bitp;
guchar v;
int width, height;
x0 = CLAMP (x0, 0, render->width);
x1 = CLAMP (x1, 0, render->width);
width = render->image_width;
height = render->image_height;
bitp = render->text_image;
y0 = CLAMP (y0, 0, render->height);
y1 = CLAMP (y1, 0, render->height);
rowinc = render->width - bitmap->width;
bit_rowinc = bitmap->pitch - bitmap->width;
bitp = bitmap->buffer;
p = pixbuf + ((x0 + (render->width * y0)) * 4);
for (y = y0; y < y1; y++) {
int n;
for (n = x0; n < x1; n++) {
v = *bitp;
if (v) {
p[0] = v;
p[1] = 255;
p[2] = 255;
p[3] = 255;
}
p += 4;
bitp++;
}
p += rowinc * 4;
bitp += bit_rowinc;
for (i = 0; i < height; i++) {
p = pixbuf + ypos * stride + xpos;
memcpy (p, bitp, width * 4);
bitp += width * 4;
}
}
@ -438,7 +456,7 @@ gst_text_render_chain (GstPad * pad, GstBuffer * inbuf)
/* render text */
GST_DEBUG ("rendering '%*s'", size, data);
pango_layout_set_markup (render->layout, (gchar *) data, size);
gst_text_render_render_text (render);
gst_text_render_render_pangocairo (render);
gst_text_render_check_argb (render);
@ -487,10 +505,10 @@ gst_text_render_chain (GstPad * pad, GstBuffer * inbuf)
xpos = render->xpad;
break;
case GST_TEXT_RENDER_HALIGN_CENTER:
xpos = (render->width - render->bitmap.width) / 2;
xpos = (render->width - render->image_width) / 2;
break;
case GST_TEXT_RENDER_HALIGN_RIGHT:
xpos = render->width - render->bitmap.width - render->xpad;
xpos = render->width - render->image_width - render->xpad;
break;
default:
xpos = 0;
@ -498,10 +516,10 @@ gst_text_render_chain (GstPad * pad, GstBuffer * inbuf)
switch (render->valign) {
case GST_TEXT_RENDER_VALIGN_BOTTOM:
ypos = render->height - render->bitmap.rows - render->ypad;
ypos = render->height - render->image_height - render->ypad;
break;
case GST_TEXT_RENDER_VALIGN_BASELINE:
ypos = render->height - (render->bitmap.rows + render->ypad);
ypos = render->height - (render->image_height + render->ypad);
break;
case GST_TEXT_RENDER_VALIGN_TOP:
ypos = render->ypad;
@ -511,13 +529,13 @@ gst_text_render_chain (GstPad * pad, GstBuffer * inbuf)
break;
}
if (render->bitmap.buffer) {
if (render->text_image) {
if (render->use_ARGB) {
gst_text_renderer_bitmap_to_argb (render, &render->bitmap, data, xpos,
xpos + render->bitmap.width, ypos, ypos + render->bitmap.rows);
gst_text_renderer_image_to_argb (render, data, xpos, ypos,
render->width * 4);
} else {
gst_text_renderer_bitmap_to_ayuv (render, &render->bitmap, data, xpos,
xpos + render->bitmap.width, ypos, ypos + render->bitmap.rows);
gst_text_renderer_image_to_ayuv (render, data, xpos, ypos,
render->width * 4);
}
}
@ -536,7 +554,7 @@ gst_text_render_finalize (GObject * object)
{
GstTextRender *render = GST_TEXT_RENDER (object);
g_free (render->bitmap.buffer);
g_free (render->text_image);
if (render->layout)
g_object_unref (render->layout);
@ -573,7 +591,6 @@ gst_text_render_init (GstTextRender * render, GstTextRenderClass * klass)
pango_layout_new (GST_TEXT_RENDER_GET_CLASS (render)->pango_context);
pango_layout_set_alignment (render->layout,
(PangoAlignment) render->line_align);
memset (&render->bitmap, 0, sizeof (render->bitmap));
render->halign = DEFAULT_PROP_HALIGNMENT;
render->valign = DEFAULT_PROP_VALIGNMENT;
@ -619,8 +636,9 @@ gst_text_render_set_property (GObject * object, guint prop_id,
GST_LOG ("font description set: %s", g_value_get_string (value));
GST_OBJECT_LOCK (render);
pango_layout_set_font_description (render->layout, desc);
gst_text_render_adjust_values_with_fontdesc (render, desc);
pango_font_description_free (desc);
gst_text_render_render_text (render);
gst_text_render_render_pangocairo (render);
GST_OBJECT_UNLOCK (render);
} else {
GST_WARNING ("font description parse failed: %s",

View file

@ -2,7 +2,7 @@
#define __GST_TEXT_RENDER_H__
#include <gst/gst.h>
#include <pango/pangoft2.h>
#include <pango/pangocairo.h>
G_BEGIN_DECLS
@ -75,8 +75,11 @@ struct _GstTextRender {
gint width;
gint height;
PangoLayout *layout;
FT_Bitmap bitmap;
gint bitmap_buffer_size;
gdouble shadow_offset;
gdouble outline_offset;
guchar *text_image;
gint image_width;
gint image_height;
gint baseline_y;
gboolean use_ARGB;