/* GTK+ Blueprint Engine
 * Copyright (C) 2003 Sun Microsystems, inc
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Written by Erwann Chenede <erwann.chenede@sun.com> 
 * based on code by Owen Taylor <otaylor@redhat.com>
 * and Carsten Haitzler <raster@rasterman.com>
 */

#include <string.h>
#include <stdio.h>

#include "blueprint.h"
#include <gdk-pixbuf/gdk-pixbuf.h>

static GCache *pixbuf_cache = NULL;
static GSList *scaled_pb_cache;

blueprint_clear_pb_cache ()
{
  if (scaled_pb_cache)
    {
      GSList *tmp;
      for (tmp = scaled_pb_cache; tmp; tmp = tmp->next)
	{
	  GSList *c_tmp;
	  ThemeBlueprint *theme_pb = (ThemeBlueprint *) tmp->data;
	  if (theme_pb && theme_pb->scaled)
	    {
	      for (c_tmp = theme_pb->scaled; c_tmp ; c_tmp = c_tmp->next)
		{
		  CachedPixbuf *cached = (CachedPixbuf *) c_tmp->data;
		  if (cached)
		    {
		      if (GDK_IS_PIXBUF (cached->pb))
			gdk_pixbuf_unref (cached->pb);
		      g_free (cached);
		    }
		}
	      g_slist_free (theme_pb->scaled);
	    }
	}
      if (scaled_pb_cache)
	{
	  g_slist_free (scaled_pb_cache);
	  scaled_pb_cache = NULL;
	}
    }
}

static GdkPixbuf *
bilinear_gradient (GdkPixbuf    *src,
		   gint          src_x,
		   gint          src_y,
		   gint          width,
		   gint          height)
{
  guint n_channels = gdk_pixbuf_get_n_channels (src);
  guint src_rowstride = gdk_pixbuf_get_rowstride (src);
  guchar *src_pixels = gdk_pixbuf_get_pixels (src);
  guchar *p1, *p2, *p3, *p4;
  guint dest_rowstride;
  guchar *dest_pixels;
  GdkPixbuf *result;
  int i, j, k;

  p1 = src_pixels + (src_y - 1) * src_rowstride + (src_x - 1) * n_channels;
  p2 = p1 + n_channels;
  p3 = src_pixels + src_y * src_rowstride + (src_x - 1) * n_channels;
  p4 = p3 + n_channels;

  result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
			   width, height);
  dest_rowstride = gdk_pixbuf_get_rowstride (result);
  dest_pixels = gdk_pixbuf_get_pixels (result);

  for (i = 0; i < height; i++)
    {
      guchar *p = dest_pixels + dest_rowstride *i;
      guint v[4];
      gint dv[4];

      for (k = 0; k < n_channels; k++)
	{
	  guint start = ((height - i) * p1[k] + (1 + i) * p3[k]) / (height + 1);
	  guint end = ((height -  i) * p2[k] + (1 + i) * p4[k]) / (height + 1);

	  dv[k] = (((gint)end - (gint)start) << 16) / (width + 1);
	  v[k] = (start << 16) + dv[k] + 0x8000;
	}

      for (j = width; j; j--)
	{
	  for (k = 0; k < n_channels; k++)
	    {
	      *(p++) = v[k] >> 16;
	      v[k] += dv[k];
	    }
	}
    }

  return result;
}

static GdkPixbuf *
horizontal_gradient (GdkPixbuf    *src,
		     gint          src_x,
		     gint          src_y,
		     gint          width,
		     gint          height)
{
  guint n_channels = gdk_pixbuf_get_n_channels (src);
  guint src_rowstride = gdk_pixbuf_get_rowstride (src);
  guchar *src_pixels = gdk_pixbuf_get_pixels (src);
  guint dest_rowstride;
  guchar *dest_pixels;
  GdkPixbuf *result;
  int i, j, k;

  result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
			   width, height);
  dest_rowstride = gdk_pixbuf_get_rowstride (result);
  dest_pixels = gdk_pixbuf_get_pixels (result);

  for (i = 0; i < height; i++)
    {
      guchar *p = dest_pixels + dest_rowstride *i;
      guchar *p1 = src_pixels + (src_y + i) * src_rowstride + (src_x - 1) * n_channels;
      guchar *p2 = p1 + n_channels;

      guint v[4];
      gint dv[4];

      for (k = 0; k < n_channels; k++)
	{
	  dv[k] = (((gint)p2[k] - (gint)p1[k]) << 16) / (width + 1);
	  v[k] = (p1[k] << 16) + dv[k] + 0x8000;
	}
      
      for (j = width; j; j--)
	{
	  for (k = 0; k < n_channels; k++)
	    {
	      *(p++) = v[k] >> 16;
	      v[k] += dv[k];
	    }
	}
    }

  return result;
}

static GdkPixbuf *
vertical_gradient (GdkPixbuf    *src,
		   gint          src_x,
		   gint          src_y,
		   gint          width,
		   gint          height)
{
  guint n_channels = gdk_pixbuf_get_n_channels (src);
  guint src_rowstride = gdk_pixbuf_get_rowstride (src);
  guchar *src_pixels = gdk_pixbuf_get_pixels (src);
  guchar *top_pixels, *bottom_pixels;
  guint dest_rowstride;
  guchar *dest_pixels;
  GdkPixbuf *result;
  int i, j;

  top_pixels = src_pixels + (src_y - 1) * src_rowstride + (src_x) * n_channels;
  bottom_pixels = top_pixels + src_rowstride;

  result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
			   width, height);
  dest_rowstride = gdk_pixbuf_get_rowstride (result);
  dest_pixels = gdk_pixbuf_get_pixels (result);

  for (i = 0; i < height; i++)
    {
      guchar *p = dest_pixels + dest_rowstride *i;
      guchar *p1 = top_pixels;
      guchar *p2 = bottom_pixels;

      for (j = width * n_channels; j; j--)
	*(p++) = ((height - i) * *(p1++) + (1 + i) * *(p2++)) / (height + 1);
    }

  return result;
}

static GdkPixbuf *
replicate_single (GdkPixbuf    *src,
		  gint          src_x,
		  gint          src_y,
		  gint          width,
		  gint          height)
{
  guint n_channels = gdk_pixbuf_get_n_channels (src);
  guchar *pixels = (gdk_pixbuf_get_pixels (src) +
		    src_y * gdk_pixbuf_get_rowstride (src) +
		    src_x * n_channels);
  guchar r = *(pixels++);
  guchar g = *(pixels++);
  guchar b = *(pixels++);
  guint dest_rowstride;
  guchar *dest_pixels;
  guchar a = 0;
  GdkPixbuf *result;
  int i, j;

  if (n_channels == 4)
    a = *(pixels++);

  result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
			   width, height);
  dest_rowstride = gdk_pixbuf_get_rowstride (result);
  dest_pixels = gdk_pixbuf_get_pixels (result);
  
  for (i = 0; i < height; i++)
    {
      guchar *p = dest_pixels + dest_rowstride *i;

      for (j = 0; j < width; j++)
	{
	  *(p++) = r;
	  *(p++) = g;
	  *(p++) = b;

	  if (n_channels == 4)
	    *(p++) = a;
	}
    }

  return result;
}

static GdkPixbuf *
replicate_rows (GdkPixbuf    *src,
		gint          src_x,
		gint          src_y,
		gint          width,
		gint          height)
{
  guint n_channels = gdk_pixbuf_get_n_channels (src);
  guint src_rowstride = gdk_pixbuf_get_rowstride (src);
  guchar *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x * n_channels);
  guchar *dest_pixels;
  GdkPixbuf *result;
  guint dest_rowstride;
  int i;

  result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
			   width, height);
  dest_rowstride = gdk_pixbuf_get_rowstride (result);
  dest_pixels = gdk_pixbuf_get_pixels (result);

  for (i = 0; i < height; i++)
    memcpy (dest_pixels + dest_rowstride * i, pixels, n_channels * width);

  return result;
}

static GdkPixbuf *
replicate_cols (GdkPixbuf    *src,
		gint          src_x,
		gint          src_y,
		gint          width,
		gint          height)
{
  guint n_channels = gdk_pixbuf_get_n_channels (src);
  guint src_rowstride = gdk_pixbuf_get_rowstride (src);
  guchar *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x * n_channels);
  guchar *dest_pixels;
  GdkPixbuf *result;
  guint dest_rowstride;
  int i, j;

  result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
			   width, height);
  dest_rowstride = gdk_pixbuf_get_rowstride (result);
  dest_pixels = gdk_pixbuf_get_pixels (result);

  for (i = 0; i < height; i++)
    {
      guchar *p = dest_pixels + dest_rowstride * i;
      guchar *q = pixels + src_rowstride * i;

      guchar r = *(q++);
      guchar g = *(q++);
      guchar b = *(q++);
      guchar a = 0;
      
      if (n_channels == 4)
	a = *(q++);

      for (j = 0; j < width; j++)
	{
	  *(p++) = r;
	  *(p++) = g;
	  *(p++) = b;

	  if (n_channels == 4)
	    *(p++) = a;
	}
    }

  return result;
}

/* Scale the rectangle (src_x, src_y, src_width, src_height)
 * onto the rectangle (dest_x, dest_y, dest_width, dest_height)
 * of the destination, clip by clip_rect and render into a pixbuf
 */

static void
blueprint_render (GdkPixbuf    *src,
		  guint         hints,
		  GdkPixbuf    *scaled,
		  GdkBitmap    *mask,
		  GdkRectangle *clip_rect,
		  gint          src_x,
		  gint          src_y,
		  gint          src_width,
		  gint          src_height,
		  gint          dest_x,
		  gint          dest_y,
		  gint          dest_width,
		  gint          dest_height)
{
  GdkPixbuf *tmp_pixbuf;
  GdkRectangle rect;
  int x_offset, y_offset;
  gboolean has_alpha = gdk_pixbuf_get_has_alpha (src);
  gint src_rowstride = gdk_pixbuf_get_rowstride (src);
  gint src_n_channels = gdk_pixbuf_get_n_channels (src);

  if (dest_width <= 0 || dest_height <= 0)
    return;

  rect.x = dest_x;
  rect.y = dest_y;
  rect.width = dest_width;
  rect.height = dest_height;

  if (hints & THEME_MISSING)
    return;

  if (dest_width == src_width && dest_height == src_height)
    {
      tmp_pixbuf = g_object_ref (src);

      x_offset = src_x + rect.x - dest_x;
      y_offset = src_y + rect.y - dest_y;
    }
  else if (src_width == 0 && src_height == 0)
    {
      tmp_pixbuf = bilinear_gradient (src, src_x, src_y, dest_width, dest_height);      
      
      x_offset = rect.x - dest_x;
      y_offset = rect.y - dest_y;
    }
  else if (src_width == 0 && dest_height == src_height)
    {
      tmp_pixbuf = horizontal_gradient (src, src_x, src_y, dest_width, dest_height);      
      
      x_offset = rect.x - dest_x;
      y_offset = rect.y - dest_y;
    }
  else if (src_height == 0 && dest_width == src_width)
    {
      tmp_pixbuf = vertical_gradient (src, src_x, src_y, dest_width, dest_height);
      
      x_offset = rect.x - dest_x;
      y_offset = rect.y - dest_y;
    }
  else if ((hints & THEME_CONSTANT_COLS) && (hints & THEME_CONSTANT_ROWS))
    {
      tmp_pixbuf = replicate_single (src, src_x, src_y, dest_width, dest_height);

      x_offset = rect.x - dest_x;
      y_offset = rect.y - dest_y;
    }
  else if (dest_width == src_width && (hints & THEME_CONSTANT_COLS))
    {
      tmp_pixbuf = replicate_rows (src, src_x, src_y, dest_width, dest_height);

      x_offset = rect.x - dest_x;
      y_offset = rect.y - dest_y;
    }
  else if (dest_height == src_height && (hints & THEME_CONSTANT_ROWS))
    {
      tmp_pixbuf = replicate_cols (src, src_x, src_y, dest_width, dest_height);

      x_offset = rect.x - dest_x;
      y_offset = rect.y - dest_y;
    }
  else 
    {
      double x_scale = (double)dest_width / src_width;
      double y_scale = (double)dest_height / src_height;
      guchar *pixels;
      GdkPixbuf *partial_src;
      
      pixels = (gdk_pixbuf_get_pixels (src)
		+ src_y * src_rowstride
		+ src_x * src_n_channels);

      partial_src = gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB,
					      has_alpha,
					      8, src_width, src_height,
					      src_rowstride,
					      NULL, NULL);
						  
      tmp_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
				   has_alpha, 8,
				   rect.width, rect.height);

      gdk_pixbuf_scale (partial_src, tmp_pixbuf,
			0, 0, rect.width, rect.height,
			dest_x - rect.x, dest_y - rect.y, 
			x_scale, y_scale,
			GDK_INTERP_BILINEAR);

      gdk_pixbuf_unref (partial_src);

      x_offset = 0;
      y_offset = 0;
    }

  if (mask)
    {
      gdk_pixbuf_render_threshold_alpha (tmp_pixbuf, mask,
					 x_offset, y_offset,
					 rect.x, rect.y,
					 rect.width, rect.height,
					 128);
    }

  if (rect.x >= 0 && rect.x + rect.width <= gdk_pixbuf_get_width (scaled) &&
      rect.y >= 0 && rect.y + rect.height <= gdk_pixbuf_get_height (scaled))	
    {
      gdk_pixbuf_copy_area (tmp_pixbuf,
			    x_offset, y_offset,
			    rect.width, rect.height,
			    scaled,
			    rect.x,
			    rect.y);
    }
  gdk_pixbuf_unref (tmp_pixbuf);
}

ThemeBlueprint *
theme_blueprint_new (void)
{
  ThemeBlueprint *result = g_new0 (ThemeBlueprint, 1);
  result->filename = NULL;
  result->pixbuf = NULL;

  result->stretch = TRUE;
  result->border_left = 0;
  result->border_right = 0;
  result->border_bottom = 0;
  result->border_top = 0;

  return result;
}

void
theme_blueprint_destroy (ThemeBlueprint *theme_pb)
{
  theme_blueprint_set_filename (theme_pb, NULL);
  g_free (theme_pb);
}

void         
theme_blueprint_set_filename (ThemeBlueprint *theme_pb,
			   const char  *filename)
{
  if (theme_pb->pixbuf)
    {
      g_cache_remove (pixbuf_cache, theme_pb->pixbuf);
      theme_pb->pixbuf = NULL;
    }

  if (theme_pb->filename)
    g_free (theme_pb->filename);

  if (filename)
    theme_pb->filename = g_strdup (filename);
  else
    theme_pb->filename = NULL;
}

static guint
compute_hint (GdkPixbuf *pixbuf,
	      gint       x0,
	      gint       x1,
	      gint       y0,
	      gint       y1)
{
  int i, j;
  int hints = THEME_CONSTANT_ROWS | THEME_CONSTANT_COLS | THEME_MISSING;
  int n_channels = gdk_pixbuf_get_n_channels (pixbuf);
  
  guchar *data = gdk_pixbuf_get_pixels (pixbuf);
  int rowstride = gdk_pixbuf_get_rowstride (pixbuf);

  if (x0 == x1 || y0 == y1)
    return 0;

  for (i = y0; i < y1; i++)
    {
      guchar *p = data + i * rowstride + x0 * n_channels;
      guchar r = p[0];
      guchar g = p[1];
      guchar b = p[2];
      guchar a = 0;
      
      if (n_channels == 4)
	a = p[3];

      for (j = x0; j < x1 ; j++)
	{
	  if (n_channels != 4 || p[3] != 0)
	    {
	      hints &= ~THEME_MISSING;
	      if (!(hints & THEME_CONSTANT_ROWS))
		goto cols;
	    }
	  
	  if (r != *(p++) ||
	      g != *(p++) ||
	      b != *(p++) ||
	      (n_channels != 4 && a != *(p++)))
	    {
	      hints &= ~THEME_CONSTANT_ROWS;
	      if (!(hints & THEME_MISSING))
		goto cols;
	    }
	}
    }

 cols:
  for (i = y0 + 1; i < y1; i++)
    {
      guchar *base = data + y0 * rowstride + x0 * n_channels;
      guchar *p = data + i * rowstride + x0 * n_channels;

      if (memcmp (p, base, n_channels * (x1 - x0)) != 0)
	{
	  hints &= ~THEME_CONSTANT_COLS;
	  return hints;
	}
    }

  return hints;
}

static void
theme_blueprint_compute_hints (ThemeBlueprint *theme_pb)
{
  int i, j;
  gint width = gdk_pixbuf_get_width (theme_pb->pixbuf);
  gint height = gdk_pixbuf_get_height (theme_pb->pixbuf);

  if (theme_pb->border_left + theme_pb->border_right > width ||
      theme_pb->border_top + theme_pb->border_bottom > height)
    {
      g_warning ("Invalid borders specified for theme blueprint:\n"
		 "        %s,\n"
		 "borders don't fit within the image", theme_pb->filename);
      if (theme_pb->border_left + theme_pb->border_right > width)
	{
	  theme_pb->border_left = width / 2;
	  theme_pb->border_right = (width + 1) / 2;
	}
      if (theme_pb->border_bottom + theme_pb->border_top > height)
	{
	  theme_pb->border_top = height / 2;
	  theme_pb->border_bottom = (height + 1) / 2;
	}
    }
  
  for (i = 0; i < 3; i++)
    {
      gint y0, y1;

      switch (i)
	{
	case 0:
	  y0 = 0;
	  y1 = theme_pb->border_top;
	  break;
	case 1:
	  y0 = theme_pb->border_top;
	  y1 = height - theme_pb->border_bottom;
	  break;
	default:
	  y0 = height - theme_pb->border_bottom;
	  y1 = height;
	  break;
	}
      
      for (j = 0; j < 3; j++)
	{
	  gint x0, x1;
	  
	  switch (j)
	    {
	    case 0:
	      x0 = 0;
	      x1 = theme_pb->border_left;
	      break;
	    case 1:
	      x0 = theme_pb->border_left;
	      x1 = width - theme_pb->border_right;
	      break;
	    default:
	      x0 = width - theme_pb->border_right;
	      x1 = width;
	      break;
	    }

	  theme_pb->hints[i][j] = compute_hint (theme_pb->pixbuf, x0, x1, y0, y1);
	}
    }
  
}

void
theme_blueprint_set_border (ThemeBlueprint *theme_pb,
			 gint         left,
			 gint         right,
			 gint         top,
			 gint         bottom)
{
  theme_pb->border_left = left;
  theme_pb->border_right = right;
  theme_pb->border_top = top;
  theme_pb->border_bottom = bottom;

  if (theme_pb->pixbuf)
    theme_blueprint_compute_hints (theme_pb);
}

void
theme_blueprint_set_stretch (ThemeBlueprint *theme_pb,
			  gboolean     stretch)
{
  theme_pb->stretch = stretch;

  if (theme_pb->pixbuf)
    theme_blueprint_compute_hints (theme_pb);
}

typedef struct {
  gchar *filename;
  gboolean recolorable;
  GdkColor color;
  gboolean use_as_bkg_mask;
} ThemeKey;
    
GdkPixbuf *
blueprint_cache_value_new (ThemeKey *key)
{
  GError *err = NULL;
    
  GdkPixbuf *result = gdk_pixbuf_new_from_file (key->filename, &err);
		     
  if (!result)
    {
      g_warning ("Blueprint theme: Cannot load pixmap file %s: %s\n",
		 key->filename, err->message);
      g_error_free (err);
    }

  return result;
}

void
blueprint_cache_key_destroy (ThemeKey *key)
{
  if (key->filename)
    g_free (key->filename);

  g_free (key);
}

ThemeKey *
blueprint_cache_key_dup (ThemeKey *key)
{

  ThemeKey *new_key = g_new0 (ThemeKey, 1);

  new_key->filename = g_strdup (key->filename);
 
  new_key->recolorable = key->recolorable;
  
  new_key->color.red = key->color.red;
  new_key->color.green = key->color.green;
  new_key->color.blue = key->color.blue;
  new_key->color.pixel = key->color.pixel;
  
  return new_key;
}

  
gboolean 
key_equal (ThemeKey *key1,
	   ThemeKey *key2)
{
  if (key1->filename != NULL &&  key2->filename != NULL)
    {
      if ((strcmp (key1->filename, key2->filename) == 0) &&
	  key1->use_as_bkg_mask == key2->use_as_bkg_mask &&
	  key1->recolorable == key2->recolorable &&
	  gdk_color_equal (&key1->color, &key2->color))
	return TRUE;
    }
  return FALSE;
}

static void 
copy_color (GdkColor *src, GdkColor *dest)
{
  dest->red = src->red;
  dest->green = src->green;
  dest->blue = src->blue;
  dest->pixel = src->pixel;
}
  
/*return true if the color is different */
static gboolean 
get_parent_color (GtkWidget *widget, GdkColor* src, GdkColor *new) 
{
  GtkWidget *parent = widget->parent;
  gboolean different = FALSE;
  int recurse = 0;
  
  while (parent)
    {
      if (!gdk_color_equal (src, 
			    &parent->style->bg [GTK_STATE_NORMAL]))
	{
	  copy_color (&parent->style->bg [GTK_STATE_NORMAL], 
		      new);
	  parent = NULL;
	  different = TRUE;
	}
      else
	parent = parent->parent;
      if (recurse > 4)
	parent = NULL;
      recurse++;
    }
  return different;
}

GdkPixbuf *
theme_blueprint_get_pixbuf (GtkWidget *widget, ThemeBlueprint *theme_pb)
{
  if (!theme_pb->pixbuf)
    {
      ThemeKey key;
      memset ((void*)&key, 0, sizeof(ThemeKey));
      
      if (!pixbuf_cache)
	pixbuf_cache = g_cache_new ((GCacheNewFunc)blueprint_cache_value_new,
				    (GCacheDestroyFunc)gdk_pixbuf_unref,
				    (GCacheDupFunc)blueprint_cache_key_dup,
				    (GCacheDestroyFunc)blueprint_cache_key_destroy,
				    g_str_hash,
				    g_direct_hash, (GEqualFunc)key_equal);
      
      key.filename = theme_pb->filename;
      key.recolorable = theme_pb->recolorable;
  
      copy_color (&theme_pb->colorize_color, &key.color);
     
      theme_pb->pixbuf = g_cache_insert (pixbuf_cache, &key);
      
      /* should be done in the cache function */
      if (theme_pb->use_as_bkg_mask)
	{
	  GdkColor tmp;
	  
	  /* Get parent widget background */
	  if (widget && get_parent_color (widget, &theme_pb->colorize_color, &tmp))
	    {
		copy_color (&tmp,&theme_pb->colorize_color);
	    }
	  /* mask with the default background color
	   * used for textfield, combo, spiner */
	  blueprint_set_mask (theme_pb->pixbuf,
			      &theme_pb->colorize_color);
	} /* use as bkg mask make the recoloration pointless */
      else if (theme_pb->recolorable)
	{
	  blueprint_colorize (theme_pb->pixbuf,
			      &theme_pb->colorize_color,
			      theme_pb->colorize_color.pixel,
			      TRUE);
	}
	  
      if (theme_pb->stretch)
	theme_blueprint_compute_hints (theme_pb);
    }
  else
    { /* Druid special case */
      if (theme_pb->use_as_bkg_mask)
	{
	  if (get_ancestor_of_type (widget, "GnomeDruidPageStandard"))
	    {
	      GtkWidget *event_box = get_ancestor_of_type (widget, "GtkEventBox");

	      /* dirty hacks for evolution startup-wizard */
	      if (strcmp (gtk_widget_get_name (gtk_widget_get_toplevel (widget)), 
			  "startup-wizard") == 0) 
		{
		  /* the Druid page timezone-page is a normal druid */
		  if (strcmp (gtk_widget_get_name (get_ancestor_of_type (widget, "GnomeDruidPageStandard")),
			  "timezone-page") != 0)
		    return theme_pb->pixbuf;
		}
	      
	      if (event_box)
		{
		  if (!gdk_color_equal (&theme_pb->colorize_color, 
					&event_box->style->bg [GTK_STATE_NORMAL]))
		    {
		      blueprint_set_mask (theme_pb->pixbuf,
					  &event_box->style->bg [GTK_STATE_NORMAL]);
		      copy_color (&event_box->style->bg [GTK_STATE_NORMAL], 
				  &theme_pb->colorize_color);
		    }
		}
	    }
	  else
	    {
	      gboolean recolorize = FALSE;
	      if (widget)
		{
		  GdkColor tmp;
		  recolorize = get_parent_color (widget, &theme_pb->colorize_color, &tmp);
		  if (recolorize)
		    {
		      copy_color (&tmp, &theme_pb->colorize_color);
		      blueprint_set_mask (theme_pb->pixbuf,
					  &theme_pb->colorize_color);
		    }
		}
	    }
	}
    }
    
  return theme_pb->pixbuf;
}

static GdkPixbuf *
get_cached_scaled_pixbuf (ThemeBlueprint *theme_pb,
			  gint width,
			  gint height)
{
  GSList *tmp;

  for (tmp = theme_pb->scaled; tmp ; tmp = tmp->next)
    {
      CachedPixbuf *cached = (CachedPixbuf *) tmp->data; 

      if (cached->pb && (gdk_pixbuf_get_width (cached->pb) == width &&
	  gdk_pixbuf_get_height (cached->pb) == height))
	{
	  cached->num_called++;
	  return cached->pb;
	}
    }
  return NULL;
}

static gint
sort_cached_pixbuf (CachedPixbuf *a, 
		    CachedPixbuf *b)
{
  if (a->num_called == b->num_called)
    return 0;
  if (a->num_called > b->num_called)
    return 1;
  else
    return -1;
}

static gboolean
cache_scaled_pixbuf  (ThemeBlueprint *theme_pb, 
		      GdkPixbuf *scaled)
{
#define MAX_PIXBUF_CACHED 3
#define PIXBUF_CACHED_TO_REMOVE 1
  gboolean cache_it = FALSE;

  if (theme_pb->scaled == NULL)
    {
      cache_it = TRUE;
      scaled_pb_cache = g_slist_prepend (scaled_pb_cache, theme_pb);
    }

  if (g_slist_length (theme_pb->scaled) < MAX_PIXBUF_CACHED)
    cache_it = TRUE;
  else
    { 
      int num_to_remove = PIXBUF_CACHED_TO_REMOVE;
      
      theme_pb->scaled = g_slist_sort (theme_pb->scaled, 
				       (GCompareFunc)sort_cached_pixbuf);

      while (num_to_remove >= 0)
	{
	  GSList *elem_to_remove = g_slist_last (theme_pb->scaled);
	  CachedPixbuf *to_remove = (CachedPixbuf*) elem_to_remove->data; 
	  
	  gdk_pixbuf_unref (to_remove->pb);
	  g_free (to_remove);

	  theme_pb->scaled = g_slist_delete_link (theme_pb->scaled, 
						  elem_to_remove);
	  num_to_remove--;
	}
      
      cache_it = TRUE;
    }
  
  if (cache_it)
    {
      CachedPixbuf *cached = g_new0 (CachedPixbuf, 1);
      cached->pb = scaled;
      
      theme_pb->scaled = g_slist_prepend (theme_pb->scaled, cached);
      return TRUE;
    }
  return FALSE;
}

void
theme_blueprint_render (ThemeBlueprint  *theme_pb,
			GdkWindow    *window,
			GtkWidget    *widget,
			GdkBitmap    *mask,
			GdkRectangle *clip_rect,
			guint         component_mask,
			gboolean      center,
			gint          x,
			gint          y,
			gint          width,
			gint          height)
{
  GdkPixbuf *pixbuf = theme_blueprint_get_pixbuf (widget, theme_pb);
  gint src_x[4], src_y[4], dest_x[4], dest_y[4];
  gint pixbuf_width = gdk_pixbuf_get_width (pixbuf);
  gint pixbuf_height = gdk_pixbuf_get_height (pixbuf);
  GdkGC *scaled_tmp_gc = NULL;

  if (!pixbuf)
    return;

/*  if (width == 0 || height == 0)
    return;*/

  if (clip_rect)
    {
      scaled_tmp_gc = gdk_gc_new (window);
      gdk_gc_set_clip_rectangle (scaled_tmp_gc, clip_rect);
    }


  if (theme_pb->stretch)
    {
      gboolean cached = TRUE;
      GdkPixbuf *scaled = get_cached_scaled_pixbuf (theme_pb, width, height);
      
      if (!scaled)
	{
	  scaled = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (theme_pb->pixbuf),
				     gdk_pixbuf_get_has_alpha (theme_pb->pixbuf),
				     gdk_pixbuf_get_bits_per_sample (theme_pb->pixbuf),
				     width, 
				     height);
	  
	  gdk_pixbuf_fill (scaled, 0x00000000);

	  cached = FALSE;

	  src_x[0] = 0;
	  src_x[1] = theme_pb->border_left;    
	  src_x[2] = pixbuf_width - theme_pb->border_right;      
	  src_x[3] = pixbuf_width;
	  
	  src_y[0] = 0;
	  src_y[1] = theme_pb->border_top;      
	  src_y[2] = pixbuf_height - theme_pb->border_bottom;	  
	  src_y[3] = pixbuf_height;
	  
	  dest_x[0] = 0;
	  dest_x[1] = theme_pb->border_left;
	  dest_x[2] = width - theme_pb->border_right;
	  dest_x[3] = width;
	  
	  dest_y[0] = 0;
	  dest_y[1] = theme_pb->border_top;
	  dest_y[2] = height - theme_pb->border_bottom;
	  dest_y[3] = height;
	  
	  
	  if (component_mask & COMPONENT_ALL)
	    component_mask = (COMPONENT_ALL - 1) & ~component_mask;
	  
#define RENDER_COMPONENT(X1,X2,Y1,Y2)					         \
        blueprint_render (pixbuf, theme_pb->hints[Y1][X1],			 \
			  scaled, mask, clip_rect,				 \
	 	          src_x[X1], src_y[Y1],				         \
			  src_x[X2] - src_x[X1], src_y[Y2] - src_y[Y1],	         \
			  dest_x[X1], dest_y[Y1],				 \
			  dest_x[X2] - dest_x[X1], dest_y[Y2] - dest_y[Y1]);
	
	if (component_mask & COMPONENT_NORTH_WEST)	
	  RENDER_COMPONENT (0, 1, 0, 1);  
	
	if (component_mask & COMPONENT_NORTH)
	  RENDER_COMPONENT (1, 2, 0, 1);
	
	if (component_mask & COMPONENT_NORTH_EAST)
	  RENDER_COMPONENT (2, 3, 0, 1);
	
	if (component_mask & COMPONENT_WEST)
	  RENDER_COMPONENT (0, 1, 1, 2);
	
	if (component_mask & COMPONENT_CENTER)
	  RENDER_COMPONENT (1, 2, 1, 2);
	
	if (component_mask & COMPONENT_EAST)
	  RENDER_COMPONENT (2, 3, 1, 2);
	
	if (component_mask & COMPONENT_SOUTH_WEST)
	  RENDER_COMPONENT (0, 1, 2, 3);
	
	if (component_mask & COMPONENT_SOUTH)
	  RENDER_COMPONENT (1, 2, 2, 3);
	
	if (component_mask & COMPONENT_SOUTH_EAST)
	  RENDER_COMPONENT (2, 3, 2, 3);
	}
	
	gdk_draw_pixbuf (window, scaled_tmp_gc, scaled,
			 0, 0,
			 x, y,
			 width, height,
			 GDK_RGB_DITHER_NORMAL,
			 0, 0);

	if (!cached)
	  {
	    if (!cache_scaled_pixbuf  (theme_pb, scaled))
	      g_object_unref (scaled);
	  }
    }
  else
    {
      if (center)
	{
	  x += (width - pixbuf_width) / 2;
	  y += (height - pixbuf_height) / 2;
	  
	  gdk_draw_pixbuf (window, scaled_tmp_gc, pixbuf,
			   0, 0,
			   x, y,
			   pixbuf_width, pixbuf_height,
			   GDK_RGB_DITHER_NORMAL,
			   0, 0);
	}
      else
	{
	  GdkPixmap *tmp_pixmap;
	  GdkGC *tmp_gc;
	  GdkGCValues gc_values;

	  tmp_pixmap = gdk_pixmap_new (window,
				       pixbuf_width,
				       pixbuf_height,
				       -1);
	  tmp_gc = gdk_gc_new (tmp_pixmap);
	  gdk_pixbuf_render_to_drawable (pixbuf, tmp_pixmap, tmp_gc,
					 0, 0, 
					 0, 0,
					 pixbuf_width, pixbuf_height,
					 GDK_RGB_DITHER_NORMAL,
					 0, 0);
	  gdk_gc_unref (tmp_gc);

	  gc_values.fill = GDK_TILED;
	  gc_values.tile = tmp_pixmap;
	  tmp_gc = gdk_gc_new_with_values (window,
					   &gc_values, GDK_GC_FILL | GDK_GC_TILE);
	  if (clip_rect)
	    gdk_draw_rectangle (window, tmp_gc, TRUE,
				clip_rect->x, clip_rect->y, clip_rect->width, clip_rect->height);
	  else
	    gdk_draw_rectangle (window, tmp_gc, TRUE, x, y, width, height);
	  
	  gdk_gc_unref (tmp_gc);
	  gdk_pixmap_unref (tmp_pixmap);
	}
    }


  if (scaled_tmp_gc)
    gdk_gc_unref (scaled_tmp_gc);
}

static gint
blueprint_hls_value (gdouble n1,
		     gdouble n2,
		     gdouble hue)
{
  gdouble value;

  if (hue > 255)
    hue -= 255;
  else if (hue < 0)
    hue += 255;
  if (hue < 42.5)
    value = n1 + (n2 - n1) * (hue / 42.5);
  else if (hue < 127.5)
    value = n2;
  else if (hue < 170)
    value = n1 + (n2 - n1) * ((170 - hue) / 42.5);
  else
    value = n1;

  return (gint) (value * 255);
}

static void
blueprint_hls_to_rgb (gint *hue,
		      gint *lightness,
		      gint *saturation)
{
  gdouble h, l, s;
  gdouble m1, m2;

  h = *hue;
  l = *lightness;
  s = *saturation;

  if (s == 0)
    {
      /*  achromatic case  */
      *hue        = l;
      *lightness  = l;
      *saturation = l;
    }
  else
    {
      if (l < 128)
	m2 = (l * (255 + s)) / 65025.0;
      else
	m2 = (l + s - (l * s) / 255.0) / 255.0;

      m1 = (l / 127.5) - m2;

      /*  chromatic case  */
      *hue        = blueprint_hls_value (m1, m2, h + 85);
      *lightness  = blueprint_hls_value (m1, m2, h);
      *saturation = blueprint_hls_value (m1, m2, h - 85);
    }
}

static void
blueprint_rgb_to_hls (gint *red,
		      gint *green,
		      gint *blue)
{
  gint    r, g, b;
  gdouble h, l, s;
  gint    min, max;
  gint    delta;

  r = *red;
  g = *green;
  b = *blue;

  if (r > g)
    {
      max = MAX (r, b);
      min = MIN (g, b);
    }
  else
    {
      max = MAX (g, b);
      min = MIN (r, b);
    }

  l = (max + min) / 2.0;

  if (max == min)
    {
      s = 0.0;
      h = 0.0;
    }
  else
    {
      delta = (max - min);

      if (l < 128)
	s = 255 * (gdouble) delta / (gdouble) (max + min);
      else
	s = 255 * (gdouble) delta / (gdouble) (511 - max - min);

      if (r == max)
	h = (g - b) / (gdouble) delta;
      else if (g == max)
	h = 2 + (b - r) / (gdouble) delta;
      else
	h = 4 + (r - g) / (gdouble) delta;

      h = h * 42.5;

      if (h < 0)
	h += 255;
      else if (h > 255)
	h -= 255;
    }

  *red   = h;
  *green = l;
  *blue  = s;
}

void
blueprint_colorize (GdkPixbuf *pixbuf,
		    GdkColor  *color,
		    int	       alpha,
		    gboolean   use_alpha)
{
  guchar *pixels;
  gint r, g, b, a, hue, lum, sat;
  gint r_src, g_src, b_src, a_src;
  guchar *p;
  gint w, h;
  int width = gdk_pixbuf_get_width (pixbuf);
  int height = gdk_pixbuf_get_height (pixbuf);
  int n_channels = gdk_pixbuf_get_n_channels (pixbuf);
  int rowstride = gdk_pixbuf_get_rowstride (pixbuf);

  g_return_if_fail (GDK_IS_PIXBUF (pixbuf));

  if (width == 0 || height == 0)
          return;

  pixels = gdk_pixbuf_get_pixels(pixbuf);

  r = hue = color->red;
  g = lum = color->green;
  b = sat = color->blue;
  a = alpha;

  blueprint_rgb_to_hls (&hue, &lum, &sat);

  h = height;
  
  while (h--) {
    w = width;
    p = pixels;
    
    while (w--) 
      {
	r_src = p[0];      
	g_src = p[1];
	b_src = p[2];
	
	blueprint_rgb_to_hls (&r_src, &g_src, &b_src);
	
	r_src = hue;
	b_src = sat;
	
	blueprint_hls_to_rgb (&r_src, &g_src, &b_src); 
	
	p[0] = r_src;
	p[1] = g_src;
	p[2] = b_src;
	
	switch (n_channels)
	  {
	  case 3:
	    p +=3;
	    break;
	  case 4:
	    a_src = p[3];
	    
	    if (use_alpha)
	      p[3] = MIN (p[3], alpha);
	    p += 4;
	    break;
	  default:
	    break;
	  }
      }
    pixels += rowstride;
  }
}

void
blueprint_set_mask (GdkPixbuf *pixbuf,
		    GdkColor  *color)
{
        guchar *pixels;
        guchar *p;
        guint w, h;
	int width = gdk_pixbuf_get_width (pixbuf);
	int height = gdk_pixbuf_get_height (pixbuf);
	int n_channels = gdk_pixbuf_get_n_channels (pixbuf);
	int rowstride = gdk_pixbuf_get_rowstride (pixbuf);

        g_return_if_fail (GDK_IS_PIXBUF (pixbuf));

        if (width == 0 || height == 0)
                return;

        pixels = gdk_pixbuf_get_pixels(pixbuf);
        
	h = height;
        
        while (h--) {
                w = width;
                p = pixels;

                switch (n_channels) {
                case 3:
			g_warning ("set Mask Error no alpha channel");
                        break;
                case 4:
                        while (w--) {
			  
			  p[0] = color->red;
			  p[1] = color->green;
			  p[2] = color->blue;
			  /* p[3] don't touch alpha */
	  
			  p += 4;
                        }
                        break;
                default:
                        break;
                }
                pixels += rowstride;
        }
}


