/*
 * Copyright (c) 1999 Ethan Fischer <allanon@crystaltokyo.com>
 * Copyright (c) 1999 Sasha Vasko <sashav@sprintmail.com> 
 * Copyright (c) 1996 Alfredo K. Kojima (kojima@inf.ufrgs.br)
 * 
 * This module 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.
 * 
 */

#include "../configure.h"

#include <stdlib.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/Intrinsic.h>

#include "../include/aftersteplib.h"
#include "../include/afterstep.h"
#include "../include/parse.h"
#include "../include/style.h"
#include "../include/misc.h"
#include "../include/screen.h"
#include "../include/hashtable.h"
#include "../include/ascolor.h"
#include "../include/stepgfx.h"
#include "../include/XImage_utils.h"

static int table_initialized = 0;
static HashTable ColorTable;
static int Bits;

static int
get_shifts (unsigned long mask)
{
  register int i = 1;

  while (mask >> i)
    i++;

  return i - 1;			/* can't be negative */
}

static int
get_bits (unsigned long mask)
{
  register int i;

  for (i = 0; mask; mask >>= 1)
    if (mask & 1)
      i++;

  return i;			/* can't be negative */
}

static Colormap ascolor_colormap = None;
static Visual *ascolor_visual = NULL;
static int ascolor_rshift, ascolor_gshift, ascolor_bshift;
static int ascolor_rbits, ascolor_gbits, ascolor_bbits;
int ascolor_true_depth = 0;	/* depth for true color - 0 otherwise */
int BGR_mode = 0;

unsigned long
ascolor_to_pixel_default (ASCOLOR ascol)
{
  XColor color;
  unsigned long key, r, g, b;

  color.red = ASCOLOR_RED16 (ascol);
  color.green = ASCOLOR_GREEN16 (ascol);
  color.blue = ASCOLOR_BLUE16 (ascol);
  r = color.red >> Bits;
  g = color.green >> Bits;
  b = color.blue >> Bits;
  key = r * r + g * g + b * b;

  if (!table_iget (&ColorTable, key, &(color.pixel)))
    {
      /* color not in cache, have to allocate this new color */
      if (XAllocColor (dpy, ascolor_colormap, &color) == 0)
	return 0;
      /* put color in cache */
      table_iput (&ColorTable, key, color.pixel);
    }
  return color.pixel;
}

unsigned long
ascolor_to_pixel_low (ASCOLOR ascol)
{
  return (((ASCOLOR_RED16 (ascol) >> ascolor_rshift) & ascolor_visual->red_mask) |
	  ((ASCOLOR_GREEN16 (ascol) >> ascolor_gshift) & ascolor_visual->green_mask) |
  ((ASCOLOR_BLUE16 (ascol) >> ascolor_bshift) & ascolor_visual->blue_mask));
}

unsigned long
ascolor_to_pixel15 (ASCOLOR ascol)
{
  return ASCOLOR2PIXEL15 (ascol);
}
unsigned long
ascolor_to_pixel16 (ASCOLOR ascol)
{
  return ASCOLOR2PIXEL16 (ascol);
}
unsigned long
ascolor_to_pixel24 (ASCOLOR ascol)
{
  if (BGR_mode)
    return ASCOLOR2PIXEL24BGR (ascol);
  return ASCOLOR2PIXEL24RGB (ascol);
}

void
set_ascolor_depth (Window root, unsigned int depth)
{
  XWindowAttributes attr;
  XGetWindowAttributes (dpy, root, &attr);
  if (depth == 0)
    depth = attr.depth;
  else if (depth == 16 && attr.depth == 15)
    depth = 15;

  if (ascolor_visual != attr.visual)
    {
      ascolor_visual = attr.visual;
      BGR_mode = attr.visual->red_mask & 0x01;
      if (ascolor_visual->class == StaticColor || ascolor_visual->class == TrueColor)
	{
	  ascolor_rshift = get_shifts (ascolor_visual->red_mask);
	  ascolor_gshift = get_shifts (ascolor_visual->green_mask);
	  ascolor_bshift = get_shifts (ascolor_visual->blue_mask);
	  ascolor_rbits = get_bits (ascolor_visual->red_mask);
	  ascolor_gbits = get_bits (ascolor_visual->green_mask);
	  ascolor_bbits = get_bits (ascolor_visual->blue_mask);
	}

      if (ascolor_visual->class != TrueColor)
	{
	  Colormap new_map;
	  if (attr.map_installed == True)
	    new_map = attr.colormap;
	  else
	    new_map = DefaultColormap (dpy, DefaultScreen (dpy));

	  if (!table_initialized)
	    {
	      table_initialized = 1;
	      table_init (&ColorTable);
	    }
	  else if (new_map != ascolor_colormap)
	    {
	      table_idestroy (&ColorTable);
	      table_init (&ColorTable);
	    }
	  ascolor_colormap = new_map;
	  Bits = 16 - ascolor_visual->bits_per_rgb;
	}
      else if (depth < 15)
	{
	  ascolor_rshift = 15 - get_shifts (ascolor_visual->red_mask);
	  ascolor_gshift = 15 - get_shifts (ascolor_visual->green_mask);
	  ascolor_bshift = 15 - get_shifts (ascolor_visual->blue_mask);
	}
    }

  if (ascolor_visual->class == TrueColor)
    {
      switch (depth)
	{
	case 15:
	  ascolor_to_pixel_func = ascolor_to_pixel15;
	  break;
	case 16:
	  ascolor_to_pixel_func = ascolor_to_pixel16;
	  break;
	case 32:
	case 24:
	  ascolor_to_pixel_func = ascolor_to_pixel24;
	  break;
	default:
	  ascolor_to_pixel_func = ascolor_to_pixel_low;
	}
      ascolor_true_depth = depth;
    }
  else
    {
      ascolor_to_pixel_func = ascolor_to_pixel_default;
      ascolor_true_depth = 0;
    }

}

unsigned long
ascolor_to_pixel_init (ASCOLOR ascol)
{
  set_ascolor_depth (Scr.Root, Scr.d_depth);
  return ascolor_to_pixel_func (ascol);
}

unsigned long (*ascolor_to_pixel_func) (ASCOLOR ascol) = ascolor_to_pixel_init;

/*
 * Allocates a color or get's it from the color cache.
 * If the display is TrueColor, make the color by ourselves.
 */
int
MyAllocColor (XColor * color)
{
  color->pixel = ASCOLOR2PIXEL (MAKE_ASCOLOR_RGB16 (color->red, color->green, color->blue));
  return 1;
}

/**************************************************************************/
/* will scatter supplied pair of colors over supplied array making
   illusion of smooth gradient 
 */
static RND32 stepgfx_rnd32_seed = GRADIENT_SEED;

ASCOLOR
MakeLight (ASCOLOR base)
{
  ASCOLOR average = ASCOLOR_AVG_COMP (base);
  ASCOLOR light = ((base >> 1) & ASCOLOR_MASK);
  int red, green, blue;

  red = ASCOLOR_RED8 (light) + average;
  green = ASCOLOR_GREEN8 (light) + average;
  blue = ASCOLOR_BLUE8 (light) + average;
  red = (red > ASCOLOR_MAXCOMP) ? ASCOLOR_MAXCOMP : red;
  green = (green > ASCOLOR_MAXCOMP) ? ASCOLOR_MAXCOMP : green;
  blue = (blue > ASCOLOR_MAXCOMP) ? ASCOLOR_MAXCOMP : blue;
  light = MAKE_ASCOLOR_RGB8 (red, green, blue);
  return light;
}

ASCOLOR
MakeDark (ASCOLOR base)
{
  /* this is 1/4 of base (can shift only one bit at a time) */
  ASCOLOR substr = ((((base >> 1) & ASCOLOR_MASK) >> 1) & ASCOLOR_MASK);
  /* getting 1/4 - 1/16 to approximate to original 20% substract defined by Kojima */
  substr -= ((((substr >> 1) & ASCOLOR_MASK) >> 1) & ASCOLOR_MASK);

  return (base - substr) & ASCOLOR_MASK;
}

void
draw_gradient_line_dither (ASCOLOR * line, int width, int npoints, XColor * color, double *offset)
{
  int i, offset_prev = offset[0] * width, x = MAX (0, offset_prev);
  for (i = 1; i < npoints; i++)
    {
      int offset_next = offset[i] * width;
      if (offset_next > offset_prev)
	{
	  int xf = MIN (offset_next, width);
	  int ratio_add = MAX_MY_RND32 / (offset_next - offset_prev);
	  int ratio = (x - offset_prev - 1) * ratio_add;
	  for (; x < xf; x++)
	    line[x] = MY_RND32 () > (ratio += ratio_add) ? color[i - 1].pixel : color[i].pixel;
	}
      offset_prev = offset_next;
    }
}

void
draw_gradient_line_true (ASCOLOR * line, int width, int npoints, XColor * color, double *offset)
{
  int i, x = 0, offset_prev = offset[0] * width;
  for (i = 1; i < npoints; i++)
    {
      int offset_next = offset[i] * width;
      if (offset_next > offset_prev)
	{
	  int xf = MIN (offset_next, width);
	  int red = ((int) color[i - 1].red) << 13;
	  int red_add = ((((int) color[i].red) << 13) - red) / (offset_next - offset_prev);
	  int green = ((int) color[i - 1].green) << 13;
	  int green_add = ((((int) color[i].green) << 13) - green) / (offset_next - offset_prev);
	  int blue = ((int) color[i - 1].blue) << 13;
	  int blue_add = ((((int) color[i].blue) << 13) - blue) / (offset_next - offset_prev);
	  red += (x - offset_prev - 1) * red_add;
	  green += (x - offset_prev - 1) * green_add;
	  blue += (x - offset_prev - 1) * blue_add;
	  for (; x < xf; x++)
	    {
	      line[x] = MAKE_ASCOLOR_RGB16 ((red += red_add) >> 13, (green += green_add) >> 13, (blue += blue_add) >> 13);
	    }
	}
      offset_prev = offset_next;
    }
}

/* returns the maximum number of true colors between a and b */
int
xcolor_manhattan_distance (XColor * a, XColor * b)
{
  return (ABS ((int) a->red - b->red) >> (16 - ascolor_rbits)) +
    (ABS ((int) a->green - b->green) >> (16 - ascolor_gbits)) +
    (ABS ((int) a->blue - b->blue) >> (16 - ascolor_bbits));
}

/* draw dithered gradient to XImage, then put XImage to drawable; this 
 * algorithm cares about maxcolors and finesse */
void
draw_gradient_dither (Display * dpy, Drawable d, int tx, int ty, int tw, int th,
		      int npoints, XColor * color, double *offset, int type,
		      int maxcolors, int finesse, int depth, GC gc)
{
  int length = (type == TEXTURE_GRADIENT_L2R) ? tw : ((type == TEXTURE_GRADIENT_T2B) ? th : 2 * tw);
  int iw, ih;
  int i, free_color = 0;
  XImage *image;
  Pixmap pixmap;

  /* calculate image width and height */
  switch (type)
    {
    case TEXTURE_GRADIENT_TL2BR:	/* top-left to bottom-right */
    case TEXTURE_GRADIENT_BL2TR:	/* bottom-left to top-right */
      iw = tw * 2;
      ih = MIN (th, finesse);
      break;
    case TEXTURE_GRADIENT_L2R:	/* left to right */
      iw = tw;
      ih = MIN (th, finesse);
      break;
    default:
    case TEXTURE_GRADIENT_T2B:	/* top to bottom */
      iw = MIN (tw, finesse);
      ih = th;
      break;
    }

  /* create an XImage to draw on */
  image = CreateXImageAndData (dpy, DefaultVisual (dpy, Scr.screen), depth,
			       ZPixmap, 0, iw, ih);
  if (image)
    set_ascolor_depth (Scr.Root, image->bits_per_pixel);
  else
    return;

  /* split up gradient if we have enough colors */
  if (maxcolors > 0 && maxcolors < 255 && maxcolors > npoints)
    {
      XColor *color2 = NEW_ARRAY (XColor, maxcolors);
      double *offset2 = NEW_ARRAY (double, maxcolors);
      free_color = 1;
      for (i = 0; i < npoints; i++)
	{
	  color2[i] = color[i];
	  offset2[i] = offset[i];
	}
      color = color2;
      offset = offset2;
      /* find the largest span and split it */
      for (; npoints < maxcolors; npoints++)
	{
	  int max = 0, n = -1;
	  for (i = 1; i < npoints; i++)
	    {
	      int colors_used = xcolor_manhattan_distance (color + i - 1, color + i);
	      int dist = (offset[i] - offset[i - 1]) * length;
	      int val = colors_used;
	      if (max < val && (dist >= 8 || (dist < val && dist < npoints - maxcolors)))
		{
		  max = val;
		  n = i;
		}
	    }
	  /* don't bother splitting really small spans */
	  if (n == -1)
	    break;
	  /* calculate the intermediate color and offset */
	  memmove (color + n + 1, color + n, sizeof (XColor) * (npoints - n));
	  color[n].red = (color[n - 1].red + color[n + 1].red) / 2;
	  color[n].green = (color[n - 1].green + color[n + 1].green) / 2;
	  color[n].blue = (color[n - 1].blue + color[n + 1].blue) / 2;
	  color[n].pixel = MAKE_ASCOLOR_RGB16 (color[n].red, color[n].green, color[n].blue);
	  memmove (offset + n + 1, offset + n, sizeof (double) * (npoints - n));
	  offset[n] = (offset[n - 1] + offset[n + 1]) / 2;
	}
    }

  /* draw the gradient */
  switch (type)
    {
    case TEXTURE_GRADIENT_TL2BR:
      {				/* top-left to bottom-right */
	ASCOLOR *buffer = NEW_ARRAY (ASCOLOR, iw);
	DECLARE_XIMAGE_CURSOR (image, curr, 0);
	int y;
	double *offsetp = NEW_ARRAY (double, npoints);
	double ratio = 0.5 / th;
	for (i = 0; i < npoints; i++)
	  offsetp[i] = offset[i];
	for (y = 0; y < ih; y++)
	  {
	    int x = MAX (offsetp[0] * iw, 0);
	    int xf = MIN (offsetp[npoints - 1] * iw, iw);
	    draw_gradient_line_dither (buffer, iw, npoints, color, offsetp);
	    SET_XIMAGE_CURSOR (image, curr, x, y);
	    for (; x < xf; x++)
	      PUT_ASCOLOR (image, buffer[x], curr, x, y);
	    for (i = 0; i < npoints; i++)
	      offsetp[i] -= ratio;
	  }
	free (offsetp);
	free (buffer);
	break;
      }
    case TEXTURE_GRADIENT_BL2TR:
      {				/* bottom-left to top-right */
	ASCOLOR *buffer = NEW_ARRAY (ASCOLOR, iw);
	DECLARE_XIMAGE_CURSOR (image, curr, 0);
	int y;
	double *offsetp = NEW_ARRAY (double, npoints);
	double ratio = 0.5 / th;
	for (i = 0; i < npoints; i++)
	  offsetp[i] = offset[i];
	for (y = 0; y < ih; y++)
	  {
	    int x = MAX (offsetp[0] * iw, 0);
	    int xf = MIN (offsetp[npoints - 1] * iw, iw);
	    draw_gradient_line_dither (buffer, iw, npoints, color, offsetp);
	    SET_XIMAGE_CURSOR (image, curr, x, y);
	    for (; x < xf; x++)
	      PUT_ASCOLOR (image, buffer[x], curr, x, y);
	    for (i = 0; i < npoints; i++)
	      offsetp[i] += ratio;
	  }
	free (offsetp);
	free (buffer);
	break;
      }
    case TEXTURE_GRADIENT_L2R:
      {				/* left to right */
	ASCOLOR *buffer = NEW_ARRAY (ASCOLOR, iw);
	DECLARE_XIMAGE_CURSOR (image, curr, 0);
	int xi = MAX (offset[0] * iw, 0);
	int xf = MIN (offset[npoints - 1] * iw, iw);
	int x, y;
	for (y = 0; y < ih; y++)
	  {
	    draw_gradient_line_dither (buffer, iw, npoints, color, offset);
	    SET_XIMAGE_CURSOR (image, curr, xi, y);
	    for (x = xi; x < xf; x++)
	      PUT_ASCOLOR (image, buffer[x], curr, x, y);
	  }
	free (buffer);
	break;
      }
    case TEXTURE_GRADIENT_T2B:
      {				/* top to bottom */
	ASCOLOR *buffer = NEW_ARRAY (ASCOLOR, ih);
	DECLARE_XIMAGE_CURSOR (image, curr, 0);
	int yi = MAX (offset[0] * ih, 0);
	int yf = MIN (offset[npoints - 1] * ih, ih);
	int x, y, next_offset = image->bytes_per_line - BYTES_PER_PIXEL;
	for (x = 0; x < iw; x++)
	  {
	    draw_gradient_line_dither (buffer, ih, npoints, color, offset);
	    SET_XIMAGE_CURSOR (image, curr, x, yi);
	    for (y = yi; y < yf; y++)
	      {
		PUT_ASCOLOR (image, buffer[y], curr, x, y);
		INCREMENT_XIMAGE_CURSOR (curr, next_offset);
	      }
	  }
	free (buffer);
	break;
      }
    }

  /* create a Pixmap to allow server-side copies via XCopyArea() */
  pixmap = XCreatePixmap (dpy, d, iw, ih, depth);

  /* copy the XImage to the Pixmap */
  XPutImage (dpy, pixmap, gc, image, 0, 0, 0, 0, iw, ih);

  /* draw the Pixmap to the Drawable */
  switch (type)
    {
    case TEXTURE_GRADIENT_TL2BR:
      {				/* top-left to bottom-right */
	int y;
	double x = 0, x_add = (double) tw * ih / th;
	for (y = 0; y < th; y += ih)
	  {
	    XCopyArea (dpy, pixmap, d, gc, x, 0, tw, MIN (ih, th - y), tx, ty + y);
	    x += x_add;
	  }
	break;
      }
    case TEXTURE_GRADIENT_BL2TR:
      {				/* bottom-left to top-right */
	int y;
	double x = tw, x_add = (double) tw * ih / th;
	for (y = 0; y < th; y += ih)
	  {
	    XCopyArea (dpy, pixmap, d, gc, x, 0, tw, MIN (ih, th - y), tx, ty + y);
	    x -= x_add;
	  }
	break;
      }
    case TEXTURE_GRADIENT_L2R:
      {				/* left to right */
	int y;
	for (y = 0; y < th; y += ih)
	  XCopyArea (dpy, pixmap, d, gc, 0, 0, tw, MIN (ih, th - y), tx, ty + y);
	break;
      }
    case TEXTURE_GRADIENT_T2B:
      {				/* top to bottom */
	int x;
	for (x = 0; x < tw; x += iw)
	  XCopyArea (dpy, pixmap, d, gc, 0, 0, MIN (iw, tw - x), th, tx + x, ty);
	break;
      }
    }

  /* free up memory */
  XFreePixmap (dpy, pixmap);
  XDestroyImage (image);
  if (free_color)
    {
      free (color);
      free (offset);
    }
}

/* draw gradient using XDrawLine(); this algorithm assumes truecolor, and 
 * ignores maxcolors and finesse */
void
draw_gradient_true (Display * dpy, Drawable d, int tx, int ty, int tw, int th,
		    int npoints, XColor * color, double *offset, int type,
		    int maxcolors, int finesse, int depth, GC gc)
{
  XRectangle clip_region;

  /* set a clip region to clip diagonal gradients */
  clip_region.x = tx;
  clip_region.y = ty;
  clip_region.width = tw;
  clip_region.height = th;
  XSetClipRectangles (dpy, gc, 0, 0, &clip_region, 1, YXSorted);

  /* draw the gradient */
  switch (type)
    {
    case TEXTURE_GRADIENT_TL2BR:
      {				/* top-left to bottom-right */
	int x = 2 * MAX (offset[0] * tw, 0);
	int xf = 2 * CLAMP (offset[npoints - 1] * tw, 0, tw);
	ASCOLOR *buffer = NEW_ARRAY (ASCOLOR, 2 * tw);
	draw_gradient_line_true (buffer, 2 * tw, npoints, color, offset);
	for (; x < xf; x++)
	  {
	    XSetForeground (dpy, gc, ASCOLOR2PIXEL (buffer[x]));
	    XDrawLine (dpy, d, gc, tx + x, ty, tx + x - tw, ty + th - 1);
	  }
	free (buffer);
	break;
      }
    case TEXTURE_GRADIENT_BL2TR:
      {				/* bottom-left to top-right */
	int x = 2 * CLAMP (offset[0] * tw, 0, tw);
	int xf = 2 * CLAMP (offset[npoints - 1] * tw, 0, tw);
	ASCOLOR *buffer = NEW_ARRAY (ASCOLOR, 2 * tw);
	draw_gradient_line_true (buffer, 2 * tw, npoints, color, offset);
	for (; x < xf; x++)
	  {
	    XSetForeground (dpy, gc, ASCOLOR2PIXEL (buffer[x]));
	    XDrawLine (dpy, d, gc, tx + x - tw, ty, tx + x, ty + th - 1);
	  }
	free (buffer);
	break;
      }
    case TEXTURE_GRADIENT_L2R:
      {				/* left to right */
	int x = CLAMP (offset[0] * tw, 0, tw);
	int xf = CLAMP (offset[npoints - 1] * tw, 0, tw);
	ASCOLOR *buffer = NEW_ARRAY (ASCOLOR, tw);
	draw_gradient_line_true (buffer, tw, npoints, color, offset);
	for (; x < xf; x++)
	  {
	    XSetForeground (dpy, gc, ASCOLOR2PIXEL (buffer[x]));
	    XDrawLine (dpy, d, gc, tx + x, ty, tx + x, ty + th - 1);
	  }
	free (buffer);
	break;
      }
    case TEXTURE_GRADIENT_T2B:
      {				/* top to bottom */
	int y = CLAMP (offset[0] * th, 0, th);
	int yf = CLAMP (offset[npoints - 1] * th, 0, th);
	ASCOLOR *buffer = NEW_ARRAY (ASCOLOR, th);
	draw_gradient_line_true (buffer, th, npoints, color, offset);
	for (; y < yf; y++)
	  {
	    XSetForeground (dpy, gc, ASCOLOR2PIXEL (buffer[y]));
	    XDrawLine (dpy, d, gc, tx, ty + y, tx + tw - 1, ty + y);
	  }
	free (buffer);
	break;
      }
    }
}

/* 

 * relief : 
 *  <0 - sunken
 *  =0 - flat
 *  >0 - rised
 * type:
 *   TEXTURE_GRADIENT_TL2BR => top-left to bottom-right
 *   TEXTURE_GRADIENT_BL2TR => bottom-left to top-right
 *   TEXTURE_GRADIENT_L2R   => left to right
 *   TEXTURE_GRADIENT_T2B   => top to bottom
 * when the gradient is dithered, only finesse lines will actually be 
 * drawn, and all other lines will be copies of them
 */

int
draw_gradient (Display * dpy, Drawable d, int tx, int ty, int tw, int th,
	       int npoints, XColor * color, double *offset, int relief,
	       int type, int maxcolors, int finesse)
{
  GC gc;
  Window root;
  int i, junk, dither = 0;
  unsigned int width, height, bw, depth;
  int length = (type == TEXTURE_GRADIENT_L2R) ? tw : ((type == TEXTURE_GRADIENT_T2B) ? th : 2 * tw);

  /* check for invalid type, or less than 2 points */
  if (npoints < 2 || type < TEXTURE_GRADIENT_TL2BR || type > TEXTURE_GRADIENT_L2R)
    return -1;

  /* limit finesse to no longer than the gradient length */
  finesse = MIN (length, finesse);

  /* drawable may have different colordepth then default */
  XGetGeometry (dpy, d, &root, &junk, &junk, &width, &height, &bw, &depth);
  set_ascolor_depth (root, depth);

  /* create a temporary drawing GC */
  gc = XCreateGC (dpy, d, 0, NULL);

  /* convert original colors */
  for (i = 0; i < npoints; i++)
    color[i].pixel = MAKE_ASCOLOR_RGB16 (color[i].red, color[i].green, color[i].blue);

  /* draw the bevel */
  if (relief)
    {
      int r = ABS (relief);
      if (tw > 2 * r && th > 2 * r)
	{
	  ASCOLOR hilite, shadow;
	  if (maxcolors < npoints + 2)
	    {
	      hilite = color[0].pixel;
	      shadow = color[npoints - 1].pixel;
	    }
	  else
	    {
	      hilite = MakeLight (color[i].pixel);
	      shadow = MakeDark (color[npoints - 1].pixel);
	      maxcolors -= 2;
	    }
	  if (relief < 0)
	    SWAP (hilite, shadow, ASCOLOR);
	  XSetForeground (dpy, gc, ASCOLOR2PIXEL (hilite));
	  for (i = 0; i < r; i++)
	    {
	      XDrawLine (dpy, d, gc, tx + i, ty + i, tx + i, ty + th - i - 1);
	      XDrawLine (dpy, d, gc, tx + i, ty + i, tx + tw - i - 1, ty + i);
	    }
	  XSetForeground (dpy, gc, ASCOLOR2PIXEL (shadow));
	  for (i = 0; i < r; i++)
	    {
	      XDrawLine (dpy, d, gc, tx + tw - i - 1, ty + th - i - 1, tx + tw - i - 1, ty + i);
	      XDrawLine (dpy, d, gc, tx + tw - i - 1, ty + th - i - 1, tx + i, ty + th - i - 1);
	    }
	  tx += r;
	  ty += r;
	  tw -= 2 * r;
	  th -= 2 * r;
	}
    }

  /* choose whether or not to dither; dither if our visual uses dynamic 
   * colors (ie, class is odd), or if bands of a single color which are 
   * wider than some minimum (we use 3) are found */
  if ((ascolor_visual->class & 1) && ascolor_visual->class != DirectColor)
    {
      dither = 1;
    }
  else
    {
      int max = 3, n = -1;
      for (i = 1; i < npoints; i++)
	{
	  int colors_used = xcolor_manhattan_distance (color + i - 1, color + i);
	  if (colors_used > 0)
	    {
	      int val = (offset[i] - offset[i - 1]) * length / colors_used;
	      if (max < val)
		{
		  max = val;
		  n = i;
		}
	    }
	}
      if (n != -1)
	{
	  dither = 1;
	  maxcolors = 254;
	}
    }

  if (dither)
    draw_gradient_dither (dpy, d, tx, ty, tw, th, npoints, color, offset, type, maxcolors, finesse, depth, gc);
  else
    draw_gradient_true (dpy, d, tx, ty, tw, th, npoints, color, offset, type, maxcolors, finesse, depth, gc);

  /* free up memory */
  XFreeGC (dpy, gc);

  return 0;
}


/************************************************************************
 * 
 * Draws text with a texture
 * 
 * d - target drawable
 * font - font to draw text
 * x,y - position of text
 * gradient - texture pixmap. size must be at least as large as text
 * text - text to draw
 * chars - chars in text
 ************************************************************************/
#ifdef I18N
void
DrawTexturedText (Display * dpy, Drawable d, XFontStruct * font, XFontSet fontset, int x, int y, Pixmap gradient, char *text, int chars)
#else
void
DrawTexturedText (Display * dpy, Drawable d, XFontStruct * font, int x, int y, Pixmap gradient, char *text, int chars)
#endif
{
  Pixmap mask;
  int w, h;
  GC gc;
  XGCValues gcv;

  /* make the mask pixmap */
  w = XTextWidth (font, text, chars);
  h = font->ascent + font->descent;
  mask = XCreatePixmap (dpy, DefaultRootWindow (dpy), w + 1, h + 1, 1);
  gcv.foreground = 0;
  gcv.function = GXcopy;
  gcv.font = font->fid;
  gc = XCreateGC (dpy, mask, GCFunction | GCForeground | GCFont, &gcv);
  XFillRectangle (dpy, mask, gc, 0, 0, w, h);
  XSetForeground (dpy, gc, 1);
#undef FONTSET
#define FONTSET fontset
  XDrawString (dpy, mask, gc, 0, font->ascent, text, chars);
  XFreeGC (dpy, gc);
  /* draw the texture */
  gcv.function = GXcopy;
  gc = XCreateGC (dpy, d, GCFunction, &gcv);
  XSetClipOrigin (dpy, gc, x, y);
  XSetClipMask (dpy, gc, mask);
  XCopyArea (dpy, gradient, d, gc, 0, 0, w, h, x, y);
  XFreeGC (dpy, gc);
  XFreePixmap (dpy, mask);
}
