aboutsummaryrefslogtreecommitdiffstats
path: root/eazel-engine/src/eazel-theme-pixmaps.c
diff options
context:
space:
mode:
Diffstat (limited to 'eazel-engine/src/eazel-theme-pixmaps.c')
-rw-r--r--eazel-engine/src/eazel-theme-pixmaps.c630
1 files changed, 630 insertions, 0 deletions
diff --git a/eazel-engine/src/eazel-theme-pixmaps.c b/eazel-engine/src/eazel-theme-pixmaps.c
new file mode 100644
index 0000000..933b57a
--- /dev/null
+++ b/eazel-engine/src/eazel-theme-pixmaps.c
@@ -0,0 +1,630 @@
+/* eazel-theme-pixmaps.c -- image manipulation and caching
+
+ Copyright (C) 2000 Eazel, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ $Id: eazel-theme-pixmaps.c,v 1.5 2001/01/27 01:19:39 jsh Exp $
+
+ Authors: John Harper <jsh@eazel.com> */
+
+/* AIX requires this to be the first thing in the file. */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#ifndef __GNUC__
+# if HAVE_ALLOCA_H
+# include <alloca.h>
+# else
+# ifdef _AIX
+ #pragma alloca
+# else
+# ifndef alloca /* predefined by HP cc +Olibcalls */
+ char *alloca ();
+# endif
+# endif
+# endif
+#endif
+
+#include "eazel-theme.h"
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/* pixmap caching, borrowed from sawfish */
+
+static gulong cached_pixels, max_cached_pixels = 64 * 1024;
+static gulong hits, misses;
+
+typedef struct pixmap_cache_node_struct pixmap_cache_node;
+
+struct pixmap_cache_node_struct {
+ pixmap_cache_node *next, *pred;
+ pixmap_cache_node *newer, *older;
+ eazel_engine_image *im;
+ int width, height;
+ GdkPixmap *p1;
+ GdkBitmap *p2;
+ int ref_count;
+};
+
+static pixmap_cache_node *oldest, *newest;
+
+/* list manipulators */
+
+static void
+remove_from_age_list (pixmap_cache_node *n)
+{
+ if (n->newer != 0)
+ n->newer->older = n->older;
+ else
+ newest = n->older;
+ if (n->older != 0)
+ n->older->newer = n->newer;
+ else
+ oldest = n->newer;
+}
+
+static void
+prepend_to_age_list (pixmap_cache_node *n)
+{
+ n->newer = oldest;
+ if (n->newer != 0)
+ n->newer->older = n;
+ oldest = n;
+ n->older = 0;
+ if (newest == 0)
+ newest = n;
+}
+
+static void
+remove_from_image (pixmap_cache_node *n)
+{
+ if (n->next != 0)
+ n->next->pred = n->pred;
+ else
+ n->im->pixmap_last = n->pred;
+ if (n->pred != 0)
+ n->pred->next = n->next;
+ else
+ n->im->pixmap_first = n->next;
+}
+
+static void
+prepend_to_image (pixmap_cache_node *n)
+{
+ eazel_engine_image *im = n->im;
+ n->next = im->pixmap_first;
+ if (n->next != 0)
+ n->next->pred = n;
+ im->pixmap_first = n;
+ n->pred = 0;
+ if (im->pixmap_last == 0)
+ im->pixmap_last = n;
+}
+
+static void
+free_node (pixmap_cache_node *n, gboolean dealloc)
+{
+ if (n->p1 != 0)
+ gdk_pixmap_unref (n->p1);
+ if (n->p2 != 0)
+ gdk_bitmap_unref (n->p2);
+ if (dealloc)
+ g_free (n);
+}
+
+static void
+delete_node (pixmap_cache_node *n, gboolean dealloc)
+{
+ remove_from_image (n);
+ remove_from_age_list (n);
+ cached_pixels -= n->width * n->height;
+ free_node (n, dealloc);
+}
+
+/* public interface */
+
+static gboolean
+pixmap_cache_ref (eazel_engine_image *im, int width, int height,
+ GdkPixmap **p1, GdkBitmap **p2)
+{
+ pixmap_cache_node *n;
+ for (n = im->pixmap_first; n != 0; n = n->next)
+ {
+ if (n->width == width && n->height == height)
+ {
+ remove_from_image (n);
+ prepend_to_image (n);
+ remove_from_age_list (n);
+ prepend_to_age_list (n);
+ n->ref_count++;
+ *p1 = n->p1;
+ *p2 = n->p2;
+ hits++;
+ return TRUE;
+ }
+ }
+ misses++;
+ return FALSE;
+}
+
+static void
+pixmap_cache_unref (eazel_engine_image *im, GdkPixmap *p1, GdkBitmap *p2)
+{
+ pixmap_cache_node *n;
+ for (n = im->pixmap_first; n != 0; n = n->next)
+ {
+ if (n->p1 == p1 && n->p2 == p2)
+ {
+ n->ref_count--;
+#ifdef DISABLE_CACHE
+ if (n->ref_count == 0)
+ delete_node (n, TRUE);
+#endif
+ return;
+ }
+ }
+ fprintf (stderr, "warning: unref'ing unknown image in pixmap-cache\n");
+}
+
+static void
+pixmap_cache_set (eazel_engine_image *im, int width, int height,
+ GdkPixmap *p1, GdkBitmap *p2)
+{
+ int pixel_count = width * height;
+ pixmap_cache_node *n = 0;
+
+ while (pixel_count + cached_pixels > max_cached_pixels)
+ {
+ /* remove oldest node */
+ pixmap_cache_node *this = oldest;
+ while (this != 0 && this->ref_count > 0)
+ this = this->newer;
+ if (this == 0)
+ break;
+ delete_node (this, n != 0);
+ if (n == 0)
+ n = this;
+ }
+
+ if (n == 0)
+ n = g_new0 (pixmap_cache_node, 1);
+
+ n->im = im;
+ n->width = width;
+ n->height = height;
+ n->p1 = p1;
+ n->p2 = p2;
+ n->ref_count = 1;
+
+ prepend_to_image (n);
+ prepend_to_age_list (n);
+ cached_pixels += pixel_count;
+}
+
+static void
+pixmap_cache_flush_image (eazel_engine_image *im)
+{
+ pixmap_cache_node *n, *next;
+ for (n = im->pixmap_first; n != 0; n = next)
+ {
+ next = n->next;
+ remove_from_age_list (n);
+ free_node (n, TRUE);
+ }
+ im->pixmap_first = im->pixmap_last = 0;
+}
+
+
+/* image filtering */
+
+typedef struct {
+ guchar red, green, blue;
+ guchar alpha;
+} pixbuf_pixel;
+
+/* Returns either a new pixbuf, or PIXBUF with its ref-count incremented */
+static GdkPixbuf *
+map_pixbuf (GdkPixbuf *pixbuf, void (*fun)(pixbuf_pixel *, void *), void *data)
+{
+ int width, height, row_stride;
+ guchar *pixels;
+ int x, y;
+
+ if (gdk_pixbuf_get_n_channels (pixbuf) == 3)
+ pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
+ else
+ gdk_pixbuf_ref (pixbuf);
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ row_stride = gdk_pixbuf_get_rowstride (pixbuf);
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+ g_assert (gdk_pixbuf_get_n_channels (pixbuf) == 4);
+
+ for (y = 0; y < height; y++)
+ {
+ guchar *row_pixels = pixels + y * row_stride;
+
+ for (x = 0; x < width; x++)
+ {
+ pixbuf_pixel pixel;
+
+ pixel.red = row_pixels[0];
+ pixel.green = row_pixels[1];
+ pixel.blue = row_pixels[2];
+ pixel.alpha = row_pixels[3];
+
+ fun (&pixel, data);
+
+ row_pixels[0] = pixel.red;
+ row_pixels[1] = pixel.green;
+ row_pixels[2] = pixel.blue;
+ row_pixels[3] = pixel.alpha;
+
+ row_pixels += 4;
+ }
+ }
+
+ return pixbuf;
+}
+
+struct recolor_closure {
+ guchar rgb_buf[256*3];
+};
+
+static void
+recolor_callback (pixbuf_pixel *pixel, void *data)
+{
+ struct recolor_closure *rc = data;
+ int base;
+
+ /* The trick is this:
+
+ Recolorable pixels must have zero red and blue channels. The
+ green channel gives a notional grey-scale value that is multiplied
+ by the values in the closure */
+
+ if (pixel->red != 0 || pixel->blue != 0)
+ return;
+
+ base = pixel->green * 3;
+
+ pixel->red = rc->rgb_buf[base+0];
+ pixel->green = rc->rgb_buf[base+1];
+ pixel->blue = rc->rgb_buf[base+2];
+}
+
+static GdkPixbuf *
+recolor_pixbuf (GdkPixbuf *pixbuf, eazel_engine_gradient *gradient)
+{
+ struct recolor_closure rc;
+
+ eazel_engine_fill_gradient_rgb_buffer (gradient, 256, rc.rgb_buf, 0, 256);
+
+ return map_pixbuf (pixbuf, recolor_callback, &rc);
+}
+
+
+/* image loading */
+
+/* XXX move to gtkrc */
+static char *image_path[] = {
+ DATADIR,
+ 0
+};
+
+static GdkPixbuf *
+load_image (const char *file)
+{
+ char **path = image_path;
+ while (*path != 0)
+ {
+ GdkPixbuf *pixbuf;
+ size_t len = strlen (*path) + strlen (file) + 2;
+ char *buf = alloca (len);
+ sprintf (buf, "%s/%s", *path, file);
+ pixbuf = gdk_pixbuf_new_from_file (buf);
+ if (pixbuf != 0)
+ return pixbuf;
+ path++;
+ }
+ /* XXX error handling here */
+ g_error ("No such image: %s", file);
+ return 0;
+}
+
+static GdkPixbuf *
+eazel_engine_image_get_pixbuf (eazel_engine_image *p)
+{
+ if (p->pixbuf == 0)
+ {
+ g_assert (p->filename != 0);
+ p->pixbuf = load_image (p->filename);
+
+ if (p->pixbuf == 0)
+ return 0;
+
+ if (p->recolor != 0)
+ {
+ GdkPixbuf *new;
+ new = recolor_pixbuf (p->pixbuf, p->recolor);
+ if (new != NULL)
+ {
+ gdk_pixbuf_unref (p->pixbuf);
+ p->pixbuf = new;
+ }
+ }
+
+ }
+ return p->pixbuf;
+}
+
+
+/* image rendering */
+
+static void
+do_scale (GdkPixbuf *src, int src_x, int src_y, int src_w, int src_h,
+ GdkPixbuf *dst, int dst_x, int dst_y, int dst_w, int dst_h)
+{
+ double scale_x, scale_y;
+ double off_x, off_y;
+
+ if (src_w <= 0 || src_h <= 0 || dst_w <= 0 || dst_h <= 0)
+ return;
+
+ scale_x = dst_w / (double) src_w;
+ scale_y = dst_h / (double) src_h;
+
+ off_x = dst_x - scale_x * src_x;
+ off_y = dst_y - scale_y * src_y;
+
+ gdk_pixbuf_scale (src, dst,
+ dst_x, dst_y,
+ dst_w, dst_h,
+ off_x, off_y,
+ scale_x, scale_y,
+ GDK_INTERP_NEAREST);
+}
+
+static void
+eazel_engine_image_render (eazel_engine_image *image, int width, int height,
+ GdkPixmap **pixmap, GdkBitmap **mask)
+{
+ GdkPixbuf *im = eazel_engine_image_get_pixbuf (image);
+ GdkPixbuf *scaled = im;
+ gboolean need_to_unref = FALSE;
+ int im_width = gdk_pixbuf_get_width (im);
+ int im_height = gdk_pixbuf_get_height (im);
+
+ g_assert (im != 0);
+ g_return_if_fail (width > 0);
+ g_return_if_fail (height > 0);
+
+ if (pixmap_cache_ref (image, width, height, pixmap, mask))
+ return;
+
+ /* XXX handle cases where combined image borders are larger
+ XXX than the destination image.. */
+
+ if (im_width != width || im_height != height)
+ {
+ /* need to scale to width by height */
+
+ int border[4];
+ border[0] = image->border[0];
+ border[1] = image->border[1];
+ border[2] = image->border[2];
+ border[3] = image->border[3];
+
+ /* truncate borders if dest image is too small */
+ if (border[0] + border[1] > width)
+ {
+ border[0] = MIN (border[0], width / 2);
+ border[1] = MIN (border[1], width / 2);
+ }
+ if (border[2] + border[3] > height
+ || image->border[2] + image->border[3] >= im_height)
+ {
+ border[2] = MIN (border[2], height / 2);
+ border[3] = MIN (border[3], height / 2);
+ }
+
+ g_assert (border[0] + border[1] <= width);
+ g_assert (border[2] + border[3] <= height);
+
+ /* create a new buffer */
+ scaled = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (im),
+ gdk_pixbuf_get_has_alpha (im),
+ gdk_pixbuf_get_bits_per_sample (im),
+ width, height);
+ need_to_unref = TRUE;
+
+ /* stretch borders to fit scaled image */
+
+ if (border[0] > 0)
+ {
+ do_scale (im, 0, image->border[2], image->border[0],
+ im_height - (image->border[2] + image->border[3]),
+ scaled, 0, border[2], border[0],
+ height - (border[2] + border[3]));
+ }
+ if (border[1] > 0)
+ {
+ do_scale (im, im_width - image->border[1], image->border[2],
+ image->border[1],
+ im_height - (image->border[2] + image->border[3]),
+ scaled, width - border[1], border[2], border[1],
+ height - (border[2] + border[3]));
+ }
+
+ if (border[2] > 0)
+ {
+ do_scale (im, image->border[0], 0,
+ im_width - (image->border[0] + image->border[1]),
+ image->border[2],
+ scaled, border[0], 0,
+ width - (border[0] + border[1]), border[2]);
+ }
+ if (border[3] > 0)
+ {
+ do_scale (im, image->border[0], im_height - image->border[3],
+ im_width - (image->border[0] + image->border[1]),
+ image->border[3],
+ scaled, border[0], height - border[3],
+ width - (border[0] + border[1]), border[3]);
+ }
+
+ /* now do corner intersections between borders */
+
+ if (border[0] > 0 && border[2] > 0)
+ {
+ do_scale (im, 0, 0, image->border[0], image->border[2],
+ scaled, 0, 0, border[0], border[2]);
+ }
+ if (border[1] > 0 && border[2] > 0)
+ {
+ do_scale (im, im_width - image->border[1], 0,
+ image->border[1], image->border[2],
+ scaled, width - border[1], 0, border[1], border[2]);
+ }
+ if (border[0] > 0 && border[3] > 0)
+ {
+ do_scale (im, 0, im_height - image->border[3],
+ image->border[0], image->border[3],
+ scaled, 0, height - border[3], border[0], border[3]);
+ }
+ if (border[1] > 0 && border[3] > 0)
+ {
+ do_scale (im, im_width - image->border[1],
+ im_height - image->border[3],
+ image->border[1], image->border[3],
+ scaled, width - border[1], height - border[3],
+ border[1], border[3]);
+ }
+
+ /* scale the inner parts of the image */
+ if (border[0] + border[1] < width
+ || border[2] + border[3] < height)
+ {
+ do_scale (im, image->border[0], image->border[2],
+ im_width - (image->border[0] + image->border[1]),
+ im_height - (image->border[2] + image->border[3]),
+ scaled, border[0], border[2],
+ width - (border[0] + border[1]),
+ height - (border[2] + border[3]));
+ }
+ }
+
+ gdk_pixbuf_render_pixmap_and_mask (scaled, pixmap, mask, 128);
+ if (need_to_unref)
+ gdk_pixbuf_unref (scaled);
+
+ pixmap_cache_set (image, width, height, *pixmap, *mask);
+}
+
+static void
+eazel_engine_image_free_pixmaps (eazel_engine_image *image,
+ GdkPixmap *pixmap, GdkBitmap *mask)
+{
+ pixmap_cache_unref (image, pixmap, mask);
+}
+
+
+/* stock images */
+
+void
+eazel_engine_stock_table_unref (eazel_engine_stock_table *table)
+{
+ table->ref_count--;
+
+ if (table->ref_count == 0)
+ {
+ int i;
+ for (i = 0; i < EAZEL_ENGINE_STOCK_MAX; i++)
+ {
+ if (table->images[i].pixbuf != 0)
+ gdk_pixbuf_unref (table->images[i].pixbuf);
+ pixmap_cache_flush_image (&table->images[i]);
+ }
+ g_free (table);
+ }
+}
+
+eazel_engine_stock_table *
+eazel_engine_stock_table_ref (eazel_engine_stock_table *table)
+{
+ table->ref_count++;
+ return table;
+}
+
+static inline eazel_engine_image *
+get_stock_image (eazel_engine_stock_table *table,
+ eazel_engine_stock_image type)
+{
+ g_assert (type >= 0 && type < EAZEL_ENGINE_STOCK_MAX);
+
+ return &table->images[type];
+}
+
+void
+eazel_engine_stock_pixmap_and_mask_scaled (eazel_engine_stock_table *table,
+ eazel_engine_stock_image type,
+ int width, int height,
+ GdkPixmap **image, GdkBitmap **mask)
+{
+ eazel_engine_image *img = get_stock_image (table, type);
+ eazel_engine_image_render (img, width, height, image, mask);
+}
+
+void
+eazel_engine_stock_pixmap_and_mask (eazel_engine_stock_table *table,
+ eazel_engine_stock_image type,
+ GdkPixmap **image, GdkBitmap **mask)
+{
+ eazel_engine_image *img = get_stock_image (table, type);
+ GdkPixbuf *pixbuf = eazel_engine_image_get_pixbuf (img);
+
+ eazel_engine_image_render (img, gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ image, mask);
+}
+
+void
+eazel_engine_stock_free_pixmaps (eazel_engine_stock_table *table,
+ eazel_engine_stock_image type,
+ GdkPixmap *image, GdkPixmap *mask)
+{
+ eazel_engine_image *img = get_stock_image (table, type);
+ eazel_engine_image_free_pixmaps (img, image, mask);
+}
+
+void
+eazel_engine_stock_get_size (eazel_engine_stock_table *table,
+ eazel_engine_stock_image type,
+ int *width, int *height)
+{
+ eazel_engine_image *img = get_stock_image (table, type);
+ GdkPixbuf *pixbuf = eazel_engine_image_get_pixbuf (img);
+
+ if (width != 0)
+ *width = gdk_pixbuf_get_width (pixbuf);
+ if (height != 0)
+ *height = gdk_pixbuf_get_height (pixbuf);
+}