view src/pixbuf_util.c @ 1743:d8e9d0cc640e

Allow to switch to fullscreen mode using LIRC. Imagine the following situation (which happened to me several times) : you want to see photos from your bed or your sofa so you launch geeqie and go to the right directory, then you take your remote control and sit comfortably far from your keyboard and mouse. And when you want to begin to watch photos, you realize you forgot to enable full screen! You have to stand up and to go until your computer and come back, whereas you could have done it with your remote control. Patch by Bernard Massot.
author zas_
date Tue, 05 Jan 2010 17:49:50 +0000
parents dc406812db14
children
line wrap: on
line source

/*
 * Geeqie
 * (C) 2004 John Ellis
 * Copyright (C) 2008 - 2009 The Geeqie Team
 *
 * Author: John Ellis
 *
 * This software is released under the GNU General Public License (GNU GPL).
 * Please read the included file COPYING for more information.
 * This software comes with no warranty of any kind, use at your own risk!
 */


#include "main.h"
#include "pixbuf_util.h"
#include "exif.h"
#include "ui_fileops.h"

#include "icons/icons_inline.h"

#include <math.h>


/*
 *-----------------------------------------------------------------------------
 * png save
 *-----------------------------------------------------------------------------
 */

gboolean pixbuf_to_file_as_png(GdkPixbuf *pixbuf, const gchar *filename)
{
	GError *error = NULL;
	gboolean ret;

	if (!pixbuf || !filename) return FALSE;

	ret = gdk_pixbuf_save(pixbuf, filename, "png", &error,
			      "tEXt::Software", GQ_APPNAME " " VERSION, NULL);

	if (error)
		{
		log_printf("Error saving png file: %s\n", error->message);
		g_error_free(error);
		}

	return ret;
}

/*
 *-----------------------------------------------------------------------------
 * jpeg save
 *-----------------------------------------------------------------------------
 */

gboolean pixbuf_to_file_as_jpg(GdkPixbuf *pixbuf, const gchar *filename, gint quality)
{
	GError *error = NULL;
	gchar *qbuf;
	gboolean ret;

	if (!pixbuf || !filename) return FALSE;

	if (quality == -1) quality = 75;
	if (quality < 1 || quality > 100)
		{
		log_printf("Jpeg not saved, invalid quality %d\n", quality);
		return FALSE;
		}

	qbuf = g_strdup_printf("%d", quality);
	ret = gdk_pixbuf_save(pixbuf, filename, "jpeg", &error, "quality", qbuf, NULL);
	g_free(qbuf);

	if (error)
		{
		log_printf("Error saving jpeg to %s\n%s\n", filename, error->message);
		g_error_free(error);
		}

	return ret;
}

/*
 *-----------------------------------------------------------------------------
 * pixbuf from inline
 *-----------------------------------------------------------------------------
 */

typedef struct _PixbufInline PixbufInline;
struct _PixbufInline
{
	const gchar *key;
	const guint8 *data;
};

static PixbufInline inline_pixbuf_data[] = {
	{ PIXBUF_INLINE_FOLDER_CLOSED,	folder_closed },
	{ PIXBUF_INLINE_FOLDER_LOCKED,	folder_locked },
	{ PIXBUF_INLINE_FOLDER_OPEN,	folder_open },
	{ PIXBUF_INLINE_FOLDER_UP,	folder_up },
	{ PIXBUF_INLINE_SCROLLER,	icon_scroller },
	{ PIXBUF_INLINE_BROKEN,		icon_broken },
	{ PIXBUF_INLINE_ICON,		gqview_icon },
	{ PIXBUF_INLINE_LOGO,		geeqie_logo },
	{ PIXBUF_INLINE_ICON_FLOAT,	icon_float },
	{ PIXBUF_INLINE_ICON_THUMB,	icon_thumb },
	{ PIXBUF_INLINE_ICON_BOOK,	icon_book },
	{ PIXBUF_INLINE_ICON_CONFIG,	icon_config },
	{ PIXBUF_INLINE_ICON_TOOLS,	icon_tools },
	{ PIXBUF_INLINE_ICON_VIEW,	icon_view },
	{ NULL, NULL }
};

GdkPixbuf *pixbuf_inline(const gchar *key)
{
	gint i;

	if (!key) return NULL;

	i = 0;
	while (inline_pixbuf_data[i].key)
		{
		if (strcmp(inline_pixbuf_data[i].key, key) == 0)
			{
			return gdk_pixbuf_new_from_inline(-1, inline_pixbuf_data[i].data, FALSE, NULL);
			}
		i++;
		}

	log_printf("warning: inline pixbuf key \"%s\" not found.\n", key);

	return NULL;
}

static void register_stock_icon(const gchar *key, GdkPixbuf *pixbuf)
{
	static GtkIconFactory *icon_factory = NULL;
	GtkIconSet *icon_set;
	
	if (!icon_factory)
		{
		icon_factory = gtk_icon_factory_new();
		gtk_icon_factory_add_default(icon_factory);
		}
	
	icon_set = gtk_icon_set_new_from_pixbuf(pixbuf);
	gtk_icon_factory_add(icon_factory, key, icon_set);
}


void pixbuf_inline_register_stock_icons(void)
{
	gint i;

	i = 0;
	while (inline_pixbuf_data[i].key)
		{
		register_stock_icon(inline_pixbuf_data[i].key, pixbuf_inline(inline_pixbuf_data[i].key));
		i++;
		}
}

gboolean register_theme_icon_as_stock(const gchar *key, const gchar *icon)
{
	GtkIconTheme *icon_theme;
	GdkPixbuf *pixbuf;
	GError *error = NULL;

	icon_theme = gtk_icon_theme_get_default();
	
	if (gtk_icon_theme_has_icon(icon_theme, key)) return FALSE;

	pixbuf = gtk_icon_theme_load_icon(icon_theme,
                           icon, /* icon name */
                           64, /* size */
                           0,  /* flags */
                           &error);
	if (!pixbuf) 
		{
		if (error)
			{
			DEBUG_1("Couldn't load icon %s: %s", icon, error->message);
			g_error_free(error);
			error = NULL;
			}
			
		if (strchr(icon, '.'))
			{
			/* try again without extension */
			gchar *icon2 = remove_extension_from_path(icon);
			pixbuf = gtk_icon_theme_load_icon(icon_theme,
		                           icon2, /* icon name */
		                           64, /* size */
		                           0,  /* flags */
		                           &error);
			if (error)
				{
				DEBUG_1("Couldn't load icon %s: %s", icon2, error->message);
				g_error_free(error);
				}
			g_free(icon2);
			}
		}

	if (!pixbuf) return FALSE;
	
	register_stock_icon(key, pixbuf);
	return TRUE;
}

gboolean pixbuf_scale_aspect(gint req_w, gint req_h,
			     gint old_w, gint old_h,
			     gint *new_w, gint *new_h)
{
	if (((gdouble)req_w / old_w) < ((gdouble)req_h / old_h))
		{
		*new_w = req_w;
		*new_h = (gdouble)*new_w / old_w * old_h;
		if (*new_h < 1) *new_h = 1;
		}
	else
		{
		*new_h = req_h;
		*new_w = (gdouble)*new_h / old_h * old_w;
		if (*new_w < 1) *new_w = 1;
		}

	return (*new_w != old_w || *new_h != old_h);
}

GdkPixbuf *pixbuf_fallback(FileData *fd, gint requested_width, gint requested_height)
{
	GdkPixbuf *pixbuf = pixbuf_inline(PIXBUF_INLINE_BROKEN); /* FIXME use different images according to FORMAT_CLASS */
	
	if (requested_width && requested_height)
		{
		gint w = gdk_pixbuf_get_width(pixbuf);
		gint h = gdk_pixbuf_get_height(pixbuf);

		if (w > requested_width || h > requested_height)
			{
			gint nw, nh;

			if (pixbuf_scale_aspect(requested_width, requested_height,
							  w, h, &nw, &nh))
				{
				GdkPixbuf *tmp;

				tmp = pixbuf;
				pixbuf = gdk_pixbuf_scale_simple(tmp, nw, nh, GDK_INTERP_TILES);
				g_object_unref(G_OBJECT(tmp));
				}
			}
		}
	return pixbuf;
}


/*
 *-----------------------------------------------------------------------------
 * misc utils
 *-----------------------------------------------------------------------------
 */

gboolean util_clip_region(gint x, gint y, gint w, gint h,
			  gint clip_x, gint clip_y, gint clip_w, gint clip_h,
			  gint *rx, gint *ry, gint *rw, gint *rh)
{
	if (clip_x + clip_w <= x ||
	    clip_x >= x + w ||
	    clip_y + clip_h <= y ||
	    clip_y >= y + h)
		{
		return FALSE;
		}

	*rx = MAX(x, clip_x);
	*rw = MIN((x + w), (clip_x + clip_w)) - *rx;

	*ry = MAX(y, clip_y);
	*rh = MIN((y + h), (clip_y + clip_h)) - *ry;

	return TRUE;
}

/*
 *-----------------------------------------------------------------------------
 * pixbuf rotation
 *-----------------------------------------------------------------------------
 */

static void pixbuf_copy_block_rotate(guchar *src, gint src_row_stride, gint x, gint y,
				     guchar *dest, gint dest_row_stride, gint w, gint h,
				     gint bytes_per_pixel, gboolean counter_clockwise)
{
	gint i, j;
	guchar *sp;
	guchar *dp;

	for (i = 0; i < h; i++)
		{
		sp = src + ((i + y) * src_row_stride) + (x * bytes_per_pixel);
		for (j = 0; j < w; j++)
			{
			if (counter_clockwise)
				{
				dp = dest + ((w - j - 1) * dest_row_stride) + (i * bytes_per_pixel);
				}
			else
				{
				dp = dest + (j * dest_row_stride) + ((h - i - 1) * bytes_per_pixel);
				}
			*(dp++) = *(sp++);	/* r */
			*(dp++) = *(sp++);	/* g */
			*(dp++) = *(sp++);	/* b */
			if (bytes_per_pixel == 4) *(dp) = *(sp++);	/* a */
			}
		}

}

static void pixbuf_copy_block(guchar *src, gint src_row_stride, gint w, gint h,
			      guchar *dest, gint dest_row_stride, gint x, gint y, gint bytes_per_pixel)
{
	gint i;
	guchar *sp;
	guchar *dp;

	for (i = 0; i < h; i++)
		{
		sp = src + (i * src_row_stride);
		dp = dest + ((y + i) * dest_row_stride) + (x * bytes_per_pixel);
		memcpy(dp, sp, w * bytes_per_pixel);
		}
}

#define ROTATE_BUFFER_WIDTH 48
#define ROTATE_BUFFER_HEIGHT 48

/*
 * Returns a copy of pixbuf src rotated 90 degrees clockwise or 90 counterclockwise
 *
 */
GdkPixbuf *pixbuf_copy_rotate_90(GdkPixbuf *src, gboolean counter_clockwise)
{
	GdkPixbuf *dest;
	gboolean has_alpha;
	gint sw, sh, srs;
	gint dw, dh, drs;
	guchar *s_pix;
	guchar *d_pix;
#if 0
	guchar *sp;
	guchar *dp;
#endif
	gint i, j;
	gint a;
	GdkPixbuf *buffer;
	guchar *b_pix;
	gint brs;
	gint w, h;

	if (!src) return NULL;

	sw = gdk_pixbuf_get_width(src);
	sh = gdk_pixbuf_get_height(src);
	has_alpha = gdk_pixbuf_get_has_alpha(src);
	srs = gdk_pixbuf_get_rowstride(src);
	s_pix = gdk_pixbuf_get_pixels(src);

	dw = sh;
	dh = sw;
	dest = gdk_pixbuf_new(GDK_COLORSPACE_RGB, has_alpha, 8, dw, dh);
	drs = gdk_pixbuf_get_rowstride(dest);
	d_pix = gdk_pixbuf_get_pixels(dest);

	a = (has_alpha ? 4 : 3);

	buffer = gdk_pixbuf_new(GDK_COLORSPACE_RGB, has_alpha, 8,
				ROTATE_BUFFER_WIDTH, ROTATE_BUFFER_HEIGHT);
	b_pix = gdk_pixbuf_get_pixels(buffer);
	brs = gdk_pixbuf_get_rowstride(buffer);

	for (i = 0; i < sh; i+= ROTATE_BUFFER_WIDTH)
		{
		w = MIN(ROTATE_BUFFER_WIDTH, (sh - i));
		for (j = 0; j < sw; j += ROTATE_BUFFER_HEIGHT)
			{
			gint x, y;

			h = MIN(ROTATE_BUFFER_HEIGHT, (sw - j));
			pixbuf_copy_block_rotate(s_pix, srs, j, i,
						 b_pix, brs, h, w,
						 a, counter_clockwise);

			if (counter_clockwise)
				{
				x = i;
				y = sw - h - j;
				}
			else
				{
				x = sh - w - i;
				y = j;
				}
			pixbuf_copy_block(b_pix, brs, w, h,
					  d_pix, drs, x, y, a);
			}
		}

	g_object_unref(buffer);

#if 0
	/* this is the simple version of rotation (roughly 2-4x slower) */

	for (i = 0; i < sh; i++)
		{
		sp = s_pix + (i * srs);
		for (j = 0; j < sw; j++)
			{
			if (counter_clockwise)
				{
				dp = d_pix + ((dh - j - 1) * drs) + (i * a);
				}
			else
				{
				dp = d_pix + (j * drs) + ((dw - i - 1) * a);
				}

			*(dp++) = *(sp++);	/* r */
			*(dp++) = *(sp++);	/* g */
			*(dp++) = *(sp++);	/* b */
			if (has_alpha) *(dp) = *(sp++);	/* a */
			}
		}
#endif

	return dest;
}

/*
 * Returns a copy of pixbuf mirrored and or flipped.
 * TO do a 180 degree rotations set both mirror and flipped TRUE
 * if mirror and flip are FALSE, result is a simple copy.
 */
GdkPixbuf *pixbuf_copy_mirror(GdkPixbuf *src, gboolean mirror, gboolean flip)
{
	GdkPixbuf *dest;
	gboolean has_alpha;
	gint w, h, srs;
	gint drs;
	guchar *s_pix;
	guchar *d_pix;
	guchar *sp;
	guchar *dp;
	gint i, j;
	gint a;

	if (!src) return NULL;

	w = gdk_pixbuf_get_width(src);
	h = gdk_pixbuf_get_height(src);
	has_alpha = gdk_pixbuf_get_has_alpha(src);
	srs = gdk_pixbuf_get_rowstride(src);
	s_pix = gdk_pixbuf_get_pixels(src);

	dest = gdk_pixbuf_new(GDK_COLORSPACE_RGB, has_alpha, 8, w, h);
	drs = gdk_pixbuf_get_rowstride(dest);
	d_pix = gdk_pixbuf_get_pixels(dest);

	a = has_alpha ? 4 : 3;

	for (i = 0; i < h; i++)
		{
		sp = s_pix + (i * srs);
		if (flip)
			{
			dp = d_pix + ((h - i - 1) * drs);
			}
		else
			{
			dp = d_pix + (i * drs);
			}
		if (mirror)
			{
			dp += (w - 1) * a;
			for (j = 0; j < w; j++)
				{
				*(dp++) = *(sp++);	/* r */
				*(dp++) = *(sp++);	/* g */
				*(dp++) = *(sp++);	/* b */
				if (has_alpha) *(dp) = *(sp++);	/* a */
				dp -= (a + 3);
				}
			}
		else
			{
			for (j = 0; j < w; j++)
				{
				*(dp++) = *(sp++);	/* r */
				*(dp++) = *(sp++);	/* g */
				*(dp++) = *(sp++);	/* b */
				if (has_alpha) *(dp++) = *(sp++);	/* a */
				}
			}
		}

	return dest;
}

GdkPixbuf *pixbuf_apply_orientation(GdkPixbuf *pixbuf, gint orientation)
{
	GdkPixbuf *dest;
	GdkPixbuf *tmp = NULL;
	
	switch (orientation)
		{
		case EXIF_ORIENTATION_TOP_LEFT:
			dest = gdk_pixbuf_copy(pixbuf);
			break;
		case EXIF_ORIENTATION_TOP_RIGHT:
			/* mirrored */
			dest = pixbuf_copy_mirror(pixbuf, TRUE, FALSE);
			break;
		case EXIF_ORIENTATION_BOTTOM_RIGHT:
			/* upside down */
			dest = pixbuf_copy_mirror(pixbuf, TRUE, TRUE);
			break;
		case EXIF_ORIENTATION_BOTTOM_LEFT:
			/* flipped */
			dest = pixbuf_copy_mirror(pixbuf, FALSE, TRUE);
			break;
		case EXIF_ORIENTATION_LEFT_TOP:
			tmp = pixbuf_copy_mirror(pixbuf, FALSE, TRUE);
			dest = pixbuf_copy_rotate_90(tmp, FALSE);
			break;
		case EXIF_ORIENTATION_RIGHT_TOP:
			/* rotated -90 (270) */
			dest = pixbuf_copy_rotate_90(pixbuf, FALSE);
			break;
		case EXIF_ORIENTATION_RIGHT_BOTTOM:
			tmp = pixbuf_copy_mirror(pixbuf, FALSE, TRUE);
			dest = pixbuf_copy_rotate_90(tmp, TRUE);
			break;
		case EXIF_ORIENTATION_LEFT_BOTTOM:
			/* rotated 90 */
			dest = pixbuf_copy_rotate_90(pixbuf, TRUE);
			break;
		default:
			dest = gdk_pixbuf_copy(pixbuf);
			break;
		}
	if (tmp) g_object_unref(tmp);
	return dest;

}


/*
 *-----------------------------------------------------------------------------
 * pixbuf drawing (rectangles)
 *-----------------------------------------------------------------------------
 */

/*
 * Fills region of pixbuf at x,y over w,h
 * with colors red (r), green (g), blue (b)
 * applying alpha (a), use a=255 for solid.
 */
void pixbuf_draw_rect_fill(GdkPixbuf *pb,
			   gint x, gint y, gint w, gint h,
			   gint r, gint g, gint b, gint a)
{
	gboolean has_alpha;
	gint pw, ph, prs;
	guchar *p_pix;
	guchar *pp;
	gint i, j;

	if (!pb) return;

	pw = gdk_pixbuf_get_width(pb);
	ph = gdk_pixbuf_get_height(pb);

	if (x < 0 || x + w > pw) return;
	if (y < 0 || y + h > ph) return;

	has_alpha = gdk_pixbuf_get_has_alpha(pb);
	prs = gdk_pixbuf_get_rowstride(pb);
	p_pix = gdk_pixbuf_get_pixels(pb);

	for (i = 0; i < h; i++)
		{
		pp = p_pix + (y + i) * prs + (x * (has_alpha ? 4 : 3));
		for (j = 0; j < w; j++)
			{
			*pp = (r * a + *pp * (256-a)) >> 8;
			pp++;
			*pp = (g * a + *pp * (256-a)) >> 8;
			pp++;
			*pp = (b * a + *pp * (256-a)) >> 8;
			pp++;
			if (has_alpha) pp++;
			}
		}
}

void pixbuf_draw_rect(GdkPixbuf *pb,
		      gint x, gint y, gint w, gint h,
		      gint r, gint g, gint b, gint a,
		      gint left, gint right, gint top, gint bottom)
{
	pixbuf_draw_rect_fill(pb, x + left, y, w - left - right, top,
			      r, g, b ,a);
	pixbuf_draw_rect_fill(pb, x + w - right, y, right, h,
			      r, g, b ,a);
	pixbuf_draw_rect_fill(pb, x + left, y + h - bottom, w - left - right, bottom,
			      r, g, b ,a);
	pixbuf_draw_rect_fill(pb, x, y, left, h,
			      r, g, b ,a);
}

void pixbuf_set_rect_fill(GdkPixbuf *pb,
			  gint x, gint y, gint w, gint h,
			  gint r, gint g, gint b, gint a)
{
	gboolean has_alpha;
	gint pw, ph, prs;
	guchar *p_pix;
	guchar *pp;
	gint i, j;

	if (!pb) return;

	pw = gdk_pixbuf_get_width(pb);
	ph = gdk_pixbuf_get_height(pb);

	if (x < 0 || x + w > pw) return;
	if (y < 0 || y + h > ph) return;

	has_alpha = gdk_pixbuf_get_has_alpha(pb);
	prs = gdk_pixbuf_get_rowstride(pb);
	p_pix = gdk_pixbuf_get_pixels(pb);

	for (i = 0; i < h; i++)
		{
		pp = p_pix + (y + i) * prs + (x * (has_alpha ? 4 : 3));
		for (j = 0; j < w; j++)
			{
			*pp = r; pp++;
			*pp = g; pp++;
			*pp = b; pp++;
			if (has_alpha) { *pp = a; pp++; }
			}
		}
}

void pixbuf_set_rect(GdkPixbuf *pb,
		     gint x, gint y, gint w, gint h,
		     gint r, gint g, gint b, gint a,
		     gint left, gint right, gint top, gint bottom)
{
	pixbuf_set_rect_fill(pb, x + left, y, w - left - right, top,
			     r, g, b ,a);
	pixbuf_set_rect_fill(pb, x + w - right, y, right, h,
			     r, g, b ,a);
	pixbuf_set_rect_fill(pb, x + left, y + h - bottom, w - left - right, bottom,
			     r, g, b ,a);
	pixbuf_set_rect_fill(pb, x, y, left, h,
			     r, g, b ,a);
}

void pixbuf_pixel_set(GdkPixbuf *pb, gint x, gint y, gint r, gint g, gint b, gint a)
{
	guchar *buf;
	gboolean has_alpha;
	gint rowstride;
	guchar *p;

	if (x < 0 || x >= gdk_pixbuf_get_width(pb) ||
	    y < 0 || y >= gdk_pixbuf_get_height(pb)) return;

	buf = gdk_pixbuf_get_pixels(pb);
	has_alpha = gdk_pixbuf_get_has_alpha(pb);
	rowstride = gdk_pixbuf_get_rowstride(pb);

	p = buf + (y * rowstride) + (x * (has_alpha ? 4 : 3));
	*p = r; p++;
	*p = g; p++;
	*p = b; p++;
	if (has_alpha) *p = a;
}


/*
 *-----------------------------------------------------------------------------
 * pixbuf text rendering
 *-----------------------------------------------------------------------------
 */

static void pixbuf_copy_font(GdkPixbuf *src, gint sx, gint sy,
			     GdkPixbuf *dest, gint dx, gint dy,
			     gint w, gint h,
			     guint8 r, guint8 g, guint8 b, guint8 a)
{
	gint sw, sh, srs;
	gboolean s_alpha;
	gint s_step;
	guchar *s_pix;
	gint dw, dh, drs;
	gboolean d_alpha;
	gint d_step;
	guchar *d_pix;

	guchar *sp;
	guchar *dp;
	gint i, j;

	if (!src || !dest) return;

	sw = gdk_pixbuf_get_width(src);
	sh = gdk_pixbuf_get_height(src);

	if (sx < 0 || sx + w > sw) return;
	if (sy < 0 || sy + h > sh) return;

	dw = gdk_pixbuf_get_width(dest);
	dh = gdk_pixbuf_get_height(dest);

	if (dx < 0 || dx + w > dw) return;
	if (dy < 0 || dy + h > dh) return;

	s_alpha = gdk_pixbuf_get_has_alpha(src);
	d_alpha = gdk_pixbuf_get_has_alpha(dest);
	srs = gdk_pixbuf_get_rowstride(src);
	drs = gdk_pixbuf_get_rowstride(dest);
	s_pix = gdk_pixbuf_get_pixels(src);
	d_pix = gdk_pixbuf_get_pixels(dest);

	s_step = (s_alpha) ? 4 : 3;
	d_step = (d_alpha) ? 4 : 3;

	for (i = 0; i < h; i++)
		{
		sp = s_pix + (sy + i) * srs + sx * s_step;
		dp = d_pix + (dy + i) * drs + dx * d_step;
		for (j = 0; j < w; j++)
			{
			if (*sp)
				{
				guint8 asub;

				asub = a * sp[0] / 255;
				*dp = (r * asub + *dp * (256-asub)) >> 8;
				dp++;
				asub = a * sp[1] / 255;
				*dp = (g * asub + *dp * (256-asub)) >> 8;
				dp++;
				asub = a * sp[2] / 255;
				*dp = (b * asub + *dp * (256-asub)) >> 8;
				dp++;

				if (d_alpha)
					{
					*dp = MAX(*dp, a * ((sp[0] + sp[1] + sp[2]) / 3) / 255);
					dp++;
					}
				}
			else
				{
				dp += d_step;
				}

			sp += s_step;
			}
		}
}

void pixbuf_draw_layout(GdkPixbuf *pixbuf, PangoLayout *layout, GtkWidget *widget,
			gint x, gint y,
			guint8 r, guint8 g, guint8 b, guint8 a)
{
	GdkPixmap *pixmap;
	GdkPixbuf *buffer;
	gint w, h;
	GdkGC *gc;
	gint sx, sy;
	gint dw, dh;

	if (!widget || !widget->window) return;

	pango_layout_get_pixel_size(layout, &w, &h);
	if (w < 1 || h < 1) return;

	pixmap = gdk_pixmap_new(widget->window, w, h, -1);

	gc = gdk_gc_new(widget->window);
	gdk_gc_copy(gc, widget->style->black_gc);
	gdk_draw_rectangle(pixmap, gc, TRUE, 0, 0, w, h);
	gdk_gc_copy(gc, widget->style->white_gc);
	gdk_draw_layout(pixmap, gc, 0, 0, layout);
	g_object_unref(gc);

	buffer = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, w, h);
	gdk_pixbuf_get_from_drawable(buffer, pixmap,
				     gdk_drawable_get_colormap(widget->window),
				     0, 0, 0, 0, w, h);
	g_object_unref(pixmap);

	sx = 0;
	sy = 0;
	dw = gdk_pixbuf_get_width(pixbuf);
	dh = gdk_pixbuf_get_height(pixbuf);

	if (x < 0)
		{
		w += x;
		sx = -x;
		x = 0;
		}

	if (y < 0)
		{
		h += y;
		sy = -y;
		y = 0;
		}

	if (x + w > dw)	w = dw - x;
	if (y + h > dh) h = dh - y;

	pixbuf_copy_font(buffer, sx, sy,
			 pixbuf, x, y, w, h,
			 r, g, b, a);

	g_object_unref(buffer);
}

/*
 *-----------------------------------------------------------------------------
 * pixbuf drawing (triangle)
 *-----------------------------------------------------------------------------
 */

void util_clip_triangle(gint x1, gint y1, gint x2, gint y2, gint x3, gint y3,
			gint *rx, gint *ry, gint *rw, gint *rh)
{
	gint tx, ty, tw, th;

	tx = MIN(x1, x2);
	tx = MIN(tx, x3);
	ty = MIN(y1, y2);
	ty = MIN(ty, y3);
	tw = MAX(abs(x1 - x2), abs(x2 - x3));
	tw = MAX(tw, abs(x3 - x1));
	th = MAX(abs(y1 - y2), abs(y2 - y3));
	th = MAX(th, abs(y3 - y1));

	*rx = tx;
	*ry = ty;
	*rw = tw;
	*rh = th;
}

void pixbuf_draw_triangle(GdkPixbuf *pb,
			  gint clip_x, gint clip_y, gint clip_w, gint clip_h,
			  gint x1, gint y1, gint x2, gint y2, gint x3, gint y3,
			  guint8 r, guint8 g, guint8 b, guint8 a)
{
	gboolean has_alpha;
	gint pw, ph, prs;
	gint rx, ry, rw, rh;
	gint tx, ty, tw, th;
	gint fx1, fy1;
	gint fx2, fy2;
	gint fw, fh;
	guchar *p_pix;
	guchar *pp;
	gint p_step;
	gdouble slope1, slope2;
	gint slope1_x, slope1_y;
	gint y;
	gint t;
	gboolean middle = FALSE;

	if (!pb) return;

	pw = gdk_pixbuf_get_width(pb);
	ph = gdk_pixbuf_get_height(pb);

	if (!util_clip_region(0, 0, pw, ph,
			      clip_x, clip_y, clip_w, clip_h,
			      &rx, &ry, &rw, &rh)) return;

	util_clip_triangle(x1, y1, x2, y2, x3, y3,
			   &tx, &ty, &tw, &th);

	if (!util_clip_region(rx, ry, rw, rh,
			      tx, ty, tw, th,
			      &fx1, &fy1, &fw, &fh)) return;
	fx2 = fx1 + fw;
	fy2 = fy1 + fh;

	has_alpha = gdk_pixbuf_get_has_alpha(pb);
	prs = gdk_pixbuf_get_rowstride(pb);
	p_pix = gdk_pixbuf_get_pixels(pb);

	p_step = (has_alpha) ? 4 : 3;

	if (y1 > y2)
		{
		t = x1; x1 = x2; x2 = t;
		t = y1; y1 = y2; y2 = t;
		}
	if (y2 > y3)
		{
		t = x2; x2 = x3; x3 = t;
		t = y2; y2 = y3; y3 = t;
		}
	if (y1 > y2)
		{
		t = x1; x1 = x2; x2 = t;
		t = y1; y1 = y2; y2 = t;
		}

	slope1 = (gdouble)(y2 - y1);
	if (slope1) slope1 = (gdouble)(x2 - x1) / slope1;
	slope1_x = x1;
	slope1_y = y1;
	slope2 = (gdouble)(y3 - y1);
	if (slope2) slope2 = (gdouble)(x3 - x1) / slope2;

	for (y = fy1; y < fy2; y++)
		{
		gint xa, xb;

		if (!middle && y > y2)
			{
			slope1 = (gdouble)(y3 - y2);
			if (slope1) slope1 = (gdouble)(x3 - x2) / slope1;
			slope1_x = x2;
			slope1_y = y2;

			middle = TRUE;
			}

		xa = slope1_x + ((gdouble)slope1 * (y - slope1_y) + 0.5);
		xb = x1 + ((gdouble)slope2 * (y - y1) + 0.5);

		if (xa > xb)
			{
			t = xa; xa = xb; xb = t;
			}

		xa = CLAMP(xa, fx1, fx2);
		xb = CLAMP(xb, fx1, fx2);

		pp = p_pix + y * prs + xa * p_step;

		while (xa < xb)
			{
			*pp = (r * a + *pp * (256-a)) >> 8;
			pp++;
			*pp = (g * a + *pp * (256-a)) >> 8;
			pp++;
			*pp = (b * a + *pp * (256-a)) >> 8;
			pp++;
			if (has_alpha) pp++;

			xa++;
			}
		}
}

/*
 *-----------------------------------------------------------------------------
 * pixbuf drawing (line)
 *-----------------------------------------------------------------------------
 */

static gboolean util_clip_line(gdouble clip_x, gdouble clip_y, gdouble clip_w, gdouble clip_h,
			       gdouble x1, gdouble y1, gdouble x2, gdouble y2,
			       gdouble *rx1, gdouble *ry1, gdouble *rx2, gdouble *ry2)
{
	gboolean flip = FALSE;
	gdouble d;

	if (x1 > x2)
		{
		gdouble t;

		t = x1; x1 = x2; x2 = t;
		t = y1; y1 = y2; y2 = t;
		flip = TRUE;
		}

	if (x2 < clip_x || x1 > clip_x + clip_w) return FALSE;

	if (y1 < y2)
		{
		if (y2 < clip_y || y1 > clip_y + clip_h) return FALSE;
		}
	else
		{
		if (y1 < clip_y || y2 > clip_y + clip_h) return FALSE;
		}

#if 0
	if (x1 >= clip_x && x2 <= clip_x + clip_w)
		{
		if (y1 < y2)
			{
			if (y1 >= clip_y && y2 <= clip_y + clip_h) return TRUE;
			}
		else
			{
			if (y2 >= clip_y && y1 <= clip_y + clip_h) return TRUE;
			}
		}
#endif

	d = x2 - x1;
	if (d > 0.0)
		{
		gdouble slope;

		slope = (y2 - y1) / d;
		if (x1 < clip_x)
			{
			y1 = y1 + slope * (clip_x - x1);
			x1 = clip_x;
			}
		if (x2 > clip_x + clip_w)
			{
			y2 = y2 + slope * (clip_x + clip_w - x2);
			x2 = clip_x + clip_w;
			}
		}

	if (y1 < y2)
		{
		if (y2 < clip_y || y1 > clip_y + clip_h) return FALSE;
		}
	else
		{
		gdouble t;

		if (y1 < clip_y || y2 > clip_y + clip_h) return FALSE;

		t = x1; x1 = x2; x2 = t;
		t = y1; y1 = y2; y2 = t;
		flip = !flip;
		}

	d = y2 - y1;
	if (d > 0.0)
		{
		gdouble slope;

		slope = (x2 - x1) / d;
		if (y1 < clip_y)
			{
			x1 = x1 + slope * (clip_y - y1);
			y1 = clip_y;
			}
		if (y2 > clip_y + clip_h)
			{
			x2 = x2 + slope * (clip_y + clip_h - y2);
			y2 = clip_y + clip_h;
			}
		}

	if (flip)
		{
		*rx1 = x2;
		*ry1 = y2;
		*rx2 = x1;
		*ry2 = y1;
		}
	else
		{
		*rx1 = x1;
		*ry1 = y1;
		*rx2 = x2;
		*ry2 = y2;
		}

	return TRUE;
}

void pixbuf_draw_line(GdkPixbuf *pb,
		      gint clip_x, gint clip_y, gint clip_w, gint clip_h,
		      gint x1, gint y1, gint x2, gint y2,
		      guint8 r, guint8 g, guint8 b, guint8 a)
{
	gboolean has_alpha;
	gint pw, ph, prs;
	gint rx, ry, rw, rh;
	gdouble rx1, ry1, rx2, ry2;
	guchar *p_pix;
	guchar *pp;
	gint p_step;
	gdouble slope;
	gdouble x, y;
	gint px, py;
	gint cx1, cy1, cx2, cy2;

	if (!pb) return;

	pw = gdk_pixbuf_get_width(pb);
	ph = gdk_pixbuf_get_height(pb);

	if (!util_clip_region(0, 0, pw, ph,
			      clip_x, clip_y, clip_w, clip_h,
			      &rx, &ry, &rw, &rh)) return;
	if (!util_clip_line((gdouble)rx, (gdouble)ry, (gdouble)rw, (gdouble)rh,
			    (gdouble)x1, (gdouble)y1, (gdouble)x2, (gdouble)y2,
			    &rx1, &ry1, &rx2, &ry2)) return;

	cx1 = rx;
	cy1 = ry;
	cx2 = rx + rw;
	cy2 = ry + rh;

	has_alpha = gdk_pixbuf_get_has_alpha(pb);
	prs = gdk_pixbuf_get_rowstride(pb);
	p_pix = gdk_pixbuf_get_pixels(pb);

	p_step = (has_alpha) ? 4 : 3;

	if (fabs(rx2 - rx1) > fabs(ry2 - ry1))
		{
		if (rx1 > rx2)
			{
			gdouble t;
			t = rx1; rx1 = rx2; rx2 = t;
			t = ry1; ry1 = ry2; ry2 = t;
			}

		slope = rx2 - rx1;
		if (slope != 0.0) slope = (ry2 - ry1) / slope;
		for (x = rx1; x < rx2; x += 1.0)
			{
			px = (gint)(x + 0.5);
			py = (gint)(ry1 + (x - rx1) * slope + 0.5);

			if (px >=  cx1 && px < cx2 && py >= cy1 && py < cy2)
				{
				pp = p_pix + py * prs + px * p_step;
				*pp = (r * a + *pp * (256-a)) >> 8;
				pp++;
				*pp = (g * a + *pp * (256-a)) >> 8;
				pp++;
				*pp = (b * a + *pp * (256-a)) >> 8;
				}
			}
		}
	else
		{
		if (ry1 > ry2)
			{
			gdouble t;
			t = rx1; rx1 = rx2; rx2 = t;
			t = ry1; ry1 = ry2; ry2 = t;
			}

		slope = ry2 - ry1;
		if (slope != 0.0) slope = (rx2 - rx1) / slope;
		for (y = ry1; y < ry2; y += 1.0)
			{
			px = (gint)(rx1 + (y - ry1) * slope + 0.5);
			py = (gint)(y + 0.5);

			if (px >=  cx1 && px < cx2 && py >= cy1 && py < cy2)
				{
				pp = p_pix + py * prs + px * p_step;
				*pp = (r * a + *pp * (256-a)) >> 8;
				pp++;
				*pp = (g * a + *pp * (256-a)) >> 8;
				pp++;
				*pp = (b * a + *pp * (256-a)) >> 8;
				}
			}
		}
}

/*
 *-----------------------------------------------------------------------------
 * pixbuf drawing (fades and shadows)
 *-----------------------------------------------------------------------------
 */

static void pixbuf_draw_fade_linear(guchar *p_pix, gint prs, gboolean has_alpha,
				    gint s, gboolean vertical, gint border,
				    gint x1, gint y1, gint x2, gint y2,
				    guint8 r, guint8 g, guint8 b, guint8 a)
{
	guchar *pp;
	gint p_step;
	guint8 n = a;
	gint i, j;

	p_step = (has_alpha) ? 4 : 3;
	for (j = y1; j < y2; j++)
		{
		pp = p_pix + j * prs + x1 * p_step;
		if (!vertical) n = a - a * abs(j - s) / border;
		for (i = x1; i < x2; i++)
			{
			if (vertical) n = a - a * abs(i - s) / border;
			*pp = (r * n + *pp * (256-n)) >> 8;
			pp++;
			*pp = (g * n + *pp * (256-n)) >> 8;
			pp++;
			*pp = (b * n + *pp * (256-n)) >> 8;
			pp++;
			if (has_alpha) pp++;
			}
		}
}

static void pixbuf_draw_fade_radius(guchar *p_pix, gint prs, gboolean has_alpha,
				    gint sx, gint sy, gint border,
				    gint x1, gint y1, gint x2, gint y2,
				    guint8 r, guint8 g, guint8 b, guint8 a)
{
	guchar *pp;
	gint p_step;
	gint i, j;

	p_step = (has_alpha) ? 4 : 3;
	for (j = y1; j < y2; j++)
		{
		pp = p_pix + j * prs + x1 * p_step;
		for (i = x1; i < x2; i++)
			{
			guint8 n;
			gint r;

			r = MIN(border, (gint)sqrt((i-sx)*(i-sx) + (j-sy)*(j-sy)));
			n = a - a * r / border;
			*pp = (r * n + *pp * (256-n)) >> 8;
			pp++;
			*pp = (g * n + *pp * (256-n)) >> 8;
			pp++;
			*pp = (b * n + *pp * (256-n)) >> 8;
			pp++;
			if (has_alpha) pp++;
			}
		}
}

void pixbuf_draw_shadow(GdkPixbuf *pb,
			gint clip_x, gint clip_y, gint clip_w, gint clip_h,
			gint x, gint y, gint w, gint h, gint border,
			guint8 r, guint8 g, guint8 b, guint8 a)
{
	gint has_alpha;
	gint pw, ph, prs;
	gint rx, ry, rw, rh;
	gint fx, fy, fw, fh;
	guchar *p_pix;

	if (!pb) return;

	pw = gdk_pixbuf_get_width(pb);
	ph = gdk_pixbuf_get_height(pb);

	if (!util_clip_region(0, 0, pw, ph,
			      clip_x, clip_y, clip_w, clip_h,
			      &rx, &ry, &rw, &rh)) return;

	has_alpha = gdk_pixbuf_get_has_alpha(pb);
	prs = gdk_pixbuf_get_rowstride(pb);
	p_pix = gdk_pixbuf_get_pixels(pb);

	if (util_clip_region(x + border, y + border, w - border * 2, h - border * 2,
			     rx, ry, rw, rh,
			     &fx, &fy, &fw, &fh))
		{
		pixbuf_draw_rect_fill(pb, fx, fy, fw, fh, r, g, b, a);
		}

	if (border < 1) return;

	if (util_clip_region(x, y + border, border, h - border * 2,
			     rx, ry, rw, rh,
			     &fx, &fy, &fw, &fh))
		{
		pixbuf_draw_fade_linear(p_pix, prs, has_alpha,
					x + border, TRUE, border,
					fx, fy, fx + fw, fy + fh,
					r, g, b, a);
		}
	if (util_clip_region(x + w - border, y + border, border, h - border * 2,
			     rx, ry, rw, rh,
			     &fx, &fy, &fw, &fh))
		{
		pixbuf_draw_fade_linear(p_pix, prs, has_alpha,
					x + w - border, TRUE, border,
					fx, fy, fx + fw, fy + fh,
					r, g, b, a);
		}
	if (util_clip_region(x + border, y, w - border * 2, border,
			     rx, ry, rw, rh,
			     &fx, &fy, &fw, &fh))
		{
		pixbuf_draw_fade_linear(p_pix, prs, has_alpha,
					y + border, FALSE, border,
					fx, fy, fx + fw, fy + fh,
					r, g, b, a);
		}
	if (util_clip_region(x + border, y + h - border, w - border * 2, border,
			     rx, ry, rw, rh,
			     &fx, &fy, &fw, &fh))
		{
		pixbuf_draw_fade_linear(p_pix, prs, has_alpha,
					y + h - border, FALSE, border,
					fx, fy, fx + fw, fy + fh,
					r, g, b, a);
		}
	if (util_clip_region(x, y, border, border,
			     rx, ry, rw, rh,
			     &fx, &fy, &fw, &fh))
		{
		pixbuf_draw_fade_radius(p_pix, prs, has_alpha,
					x + border, y + border, border,
					fx, fy, fx + fw, fy + fh,
					r, g, b, a);
		}
	if (util_clip_region(x + w - border, y, border, border,
			     rx, ry, rw, rh,
			     &fx, &fy, &fw, &fh))
		{
		pixbuf_draw_fade_radius(p_pix, prs, has_alpha,
					x + w - border, y + border, border,
					fx, fy, fx + fw, fy + fh,
					r, g, b, a);
		}
	if (util_clip_region(x, y + h - border, border, border,
			     rx, ry, rw, rh,
			     &fx, &fy, &fw, &fh))
		{
		pixbuf_draw_fade_radius(p_pix, prs, has_alpha,
					x + border, y + h - border, border,
					fx, fy, fx + fw, fy + fh,
					r, g, b, a);
		}
	if (util_clip_region(x + w - border, y + h - border, border, border,
			     rx, ry, rw, rh,
			     &fx, &fy, &fw, &fh))
		{
		pixbuf_draw_fade_radius(p_pix, prs, has_alpha,
					x + w - border, y + h - border, border,
					fx, fy, fx + fw, fy + fh,
					r, g, b, a);
		}
}


/*
 *-----------------------------------------------------------------------------
 * pixbuf color alterations
 *-----------------------------------------------------------------------------
 */

void pixbuf_desaturate_rect(GdkPixbuf *pb,
			    gint x, gint y, gint w, gint h)
{
	gboolean has_alpha;
	gint pw, ph, prs;
	guchar *p_pix;
	guchar *pp;
	gint i, j;

	if (!pb) return;

	pw = gdk_pixbuf_get_width(pb);
	ph = gdk_pixbuf_get_height(pb);

	if (x < 0 || x + w > pw) return;
	if (y < 0 || y + h > ph) return;

	has_alpha = gdk_pixbuf_get_has_alpha(pb);
	prs = gdk_pixbuf_get_rowstride(pb);
	p_pix = gdk_pixbuf_get_pixels(pb);

	for (i = 0; i < h; i++)
		{
		pp = p_pix + (y + i) * prs + (x * (has_alpha ? 4 : 3));
		for (j = 0; j < w; j++)
			{
			guint8 grey;

			grey = (pp[0] + pp[1] + pp[2]) / 3;
			*pp = grey;
			pp++;
			*pp = grey;
			pp++;
			*pp = grey;
			pp++;
			if (has_alpha) pp++;
			}
		}
}
/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */