/*
 * Color.c -- Color management module.
 *
 * Authors		: Patrick Lecoanet.
 * Creation date	: Thu Dec 16 15:41:53 1999
 *
 * $Id: Color.c,v 1.27 2004/03/24 15:06:44 lecoanet Exp $
 */

/*
 *  Copyright (c) 1999 CENA, Patrick Lecoanet --
 *
 * This code 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 code 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 code; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
 * Most of this file is derived from Tk color code and thus
 * also copyrighted:
 *
 * Copyright (c) 1991-1994 The Regents of the University of California.
 * Copyright (c) 1994-1995 Sun Microsystems, Inc.
 *
 */


#include <string.h>
#include <stdlib.h>

#include "Types.h"
#include "Image.h"
#include "Color.h"
#include "Geo.h"
#include "Transfo.h"


/*
 * Maximum size of a color name including the \0.
 */
#define COLOR_NAME_SIZE 32

/*
 * Maximum intensity for a color.
 */
#define MAX_INTENSITY 65535

/*
 * Hash table to map from a gradient's values (color, etc.) to a
 * gradient structure for those values.
 */
static Tcl_HashTable gradient_table;

static int initialized = 0;	/* 0 means static structures haven't been
				 * initialized yet. */


/*
 *----------------------------------------------------------------------
 *
 * ColorInit --
 *
 *	Initialize the structure used for color management.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Read the code.
 *
 *----------------------------------------------------------------------
 */
static void
ColorInit()
{
  initialized = 1;
  Tcl_InitHashTable(&gradient_table, TCL_STRING_KEYS);
}


/*
 *----------------------------------------------------------------------
 *
 * ZnGetGradientColor
 * ZnInterpGradientColor --
 *
 *----------------------------------------------------------------------
 */
XColor *
ZnGetGradientColor(ZnGradient	*grad,
		   ZnReal	position,
		   unsigned short *alpha)
{
  int	 index, min, max;
  XColor *shade=NULL;
  
  if ((grad->num_actual_colors == 1) || (position <= 0.0)) {
    if (alpha) {
      *alpha = grad->actual_colors[0].alpha;
    }
    return grad->actual_colors[0].rgb;
  }
  if (position >= 100.0) {
    if (alpha) {
      *alpha = grad->actual_colors[grad->num_actual_colors-1].alpha;
    }
    shade = grad->actual_colors[grad->num_actual_colors-1].rgb;
  }
  else {
    min = 0;
    max = grad->num_actual_colors-1;
    index = (max + min) / 2;
    while (max - min != 1) {
      /*printf("color index %d, min: %d, max: %d\n", index, min, max);*/
      if (grad->actual_colors[index].position < position) {
	min = index;
      }
      else {
	max = index;
      }
      index = (max + min) / 2;
    }
    shade = grad->actual_colors[index].rgb;
    if (alpha) {
      *alpha = grad->actual_colors[index].alpha;
    }
  }

  return shade;
}

void
ZnInterpGradientColor(ZnGradient     *grad,
		      ZnReal	     position,
		      XColor	     *color,
		      unsigned short *alpha)
{
  int index, min, max;
  ZnGradientColor *gc1, *gc2;
  ZnReal rel_pos;

  if ((grad->num_actual_colors == 1) || (position <= 0.0)) {
    *alpha = grad->actual_colors[0].alpha;
    *color = *grad->actual_colors[0].rgb;
  }
  else if (position >= 100.0) {
    *alpha = grad->actual_colors[grad->num_actual_colors-1].alpha;
    *color = *grad->actual_colors[grad->num_actual_colors-1].rgb;
  }
  else {
    min = 0;
    max = grad->num_actual_colors-1;
    index = (max + min) / 2;
    while (max - min != 1) {
      /*printf("color index %d, min: %d, max: %d\n", index, min, max);*/
      if (grad->actual_colors[index].position < position) {
	min = index;
      }
      else {
	max = index;
      }
      index = (max + min) / 2;
    }
    gc1 = &grad->actual_colors[index];
    gc2 = &grad->actual_colors[index+1];
    rel_pos = (position - gc1->position) * 100.0 / (gc2->position - gc1->position);
    
    if (rel_pos > gc1->control) {
      rel_pos = (rel_pos - gc1->control) * 100.0 / (100.0 - gc1->control);
      color->red = gc1->mid_rgb->red + 
	(gc2->rgb->red - gc1->mid_rgb->red) * rel_pos / 100.0;
      color->green = gc1->mid_rgb->green + 
	(gc2->rgb->green - gc1->mid_rgb->green) * rel_pos / 100.0;
      color->blue = gc1->mid_rgb->blue +
	(gc2->rgb->blue - gc1->mid_rgb->blue) * rel_pos / 100.0;
      *alpha = gc1->mid_alpha +
	(gc2->alpha - gc1->mid_alpha) * rel_pos / 100.0;
    }
    else {
      rel_pos = rel_pos * 100.0 / gc1->control;
      color->red = gc1->rgb->red +
	(gc1->mid_rgb->red - gc1->rgb->red) * rel_pos / 100.0;
      color->green = gc1->rgb->green +
	(gc1->mid_rgb->green - gc1->rgb->green) * rel_pos / 100.0;
      color->blue = gc1->rgb->blue +
	(gc1->mid_rgb->blue - gc1->rgb->blue) * rel_pos / 100.0;
      *alpha = gc1->alpha +
	(gc1->mid_alpha - gc1->alpha) * rel_pos / 100.0;
    }
  }
}


/*
 *--------------------------------------------------------------
 *
 * ZnGradientFlat --
 *
 *	Returns true if the gradient is defined by a single
 *	color.
 *
 *--------------------------------------------------------------
 */
ZnBool
ZnGradientFlat(ZnGradient	*grad)
{
  return (grad->num_actual_colors == 1);
}


/*
 *--------------------------------------------------------------
 *
 * ZnGetReliefGradient --
 *
 *	Create a data structure containing a range of colors
 *	used to display a 3D border. Name contains the base
 *	color for the border. This is a slight variation on
 *	the syntax of a gradient that make life easier in this
 *	simple case.
 *
 * Results:
 *	The return value is a token for a data structure
 *	describing a gradient.  This token may be passed
 *	to the drawing routines.
 *	If an error prevented the gradient from being created
 *	then NULL is returned and an error message will be
 *	left in interp.
 *
 * Side effects:
 *	Data structures, etc. are allocated.
 *	It is the caller's responsibility to eventually call
 *	ZnFreeGradient to release the resources.
 *
 *--------------------------------------------------------------
 */
ZnGradient *
ZnGetReliefGradient(Tcl_Interp	*interp,
		    Tk_Window	tkwin,
		    Tk_Uid	name,
		    unsigned short alpha)
{
  XColor *base, light_color, dark_color, color;
  char	 color_name[COLOR_NAME_SIZE];
  char	 buffer[COLOR_NAME_SIZE*(3+2*ZN_RELIEF_STEPS)];
  int	 j, tmp1, tmp2;
  int	 red_range, green_range, blue_range;
  
  base = Tk_GetColor(interp, tkwin, name);
  /*
   * Compute the border gradient.
   *
   * Always consider that we are dealing with a color display with
   * enough colors available. If the colormap is full (stressed)
   * then just pray, the susbstitution algorithm may return something
   * adequate ;-).
   *
   * The extremum colors get computed using whichever formula results
   * in the greatest change in color:
   * 1. Lighter color is half-way to white, darker color is half
   *    way to dark.
   * 2. Lighter color is 40% brighter than base, darker color
   *    is 40% darker than base.
   * The first approach works better for unsaturated colors, the
   * second for saturated ones.
   *
   * NOTE: Colors are computed with integers not color shorts which
   * may lead to overflow errors.
   */
  tmp1 = (30 * (int) base->red)/100;
  tmp2 = ((int) base->red)/2;
  dark_color.red = MIN(tmp1, tmp2);
  tmp1 = (30 * (int) base->green)/100;
  tmp2 = ((int) base->green)/2;
  dark_color.green = MIN(tmp1, tmp2);
  tmp1 = (30 * (int) base->blue)/100;
  tmp2 = ((int) base->blue)/2;
  dark_color.blue = MIN(tmp1, tmp2);
  
  tmp1 = MAX_INTENSITY;/*(170 * (int) base->red)/10;*/
  if (tmp1 > MAX_INTENSITY) {
    tmp1 = MAX_INTENSITY;
  }
  tmp2 = (MAX_INTENSITY + (int) base->red)/2;
  light_color.red = MAX(tmp1, tmp2);
  tmp1 = MAX_INTENSITY;/*(170 * (int) base->green)/10;*/
  if (tmp1 > MAX_INTENSITY) {
    tmp1 = MAX_INTENSITY;
  }
  tmp2 = (MAX_INTENSITY + (int) base->green)/2;
  light_color.green = MAX(tmp1, tmp2);
  tmp1 = MAX_INTENSITY;/*(170 * (int) base->blue)/10;*/
  if (tmp1 > MAX_INTENSITY) {
    tmp1 = MAX_INTENSITY;
  }
  tmp2 = (MAX_INTENSITY + (int) base->blue)/2;
  light_color.blue = MAX(tmp1, tmp2);

  buffer[0] = 0;
  sprintf(color_name, "#%02x%02x%02x;%d|",
 	  dark_color.red/256, dark_color.green/256, dark_color.blue/256, alpha);
  red_range = (int) base->red - (int) dark_color.red;
  green_range = (int) base->green - (int) dark_color.green;
  blue_range = (int) base->blue - (int) dark_color.blue;
  strcat(buffer, color_name);
  for (j = 1; j < ZN_RELIEF_STEPS; j++) {
    color.red =(int) dark_color.red +  red_range * j/ZN_RELIEF_STEPS;
    color.green = (int) dark_color.green + green_range * j/ZN_RELIEF_STEPS;
    color.blue = (int) dark_color.blue + blue_range * j/ZN_RELIEF_STEPS;
    sprintf(color_name, "#%02x%02x%02x;%d %d|",
	    color.red/256, color.green/256, color.blue/256, alpha, 50/ZN_RELIEF_STEPS*j);
    strcat(buffer, color_name);
  }
  sprintf(color_name, "#%02x%02x%02x;%d 50|",
	  base->red/256, base->green/256, base->blue/256, alpha);
  strcat(buffer, color_name);
  red_range = (int) light_color.red - (int) base->red;
  green_range = (int) light_color.green - (int) base->green;
  blue_range = (int) light_color.blue - (int) base->blue;
  for (j = 1; j < ZN_RELIEF_STEPS; j++) {
    color.red = (int) base->red +  red_range * j/ZN_RELIEF_STEPS;
    color.green = (int) base->green + green_range * j/ZN_RELIEF_STEPS;
    color.blue = (int) base->blue + blue_range * j/ZN_RELIEF_STEPS;
    sprintf(color_name, "#%02x%02x%02x;%d %d|",
	    color.red/256, color.green/256, color.blue/256, alpha, 50+50/ZN_RELIEF_STEPS*j);
    strcat(buffer, color_name);
  }
  sprintf(color_name, "#%02x%02x%02x;%d",
	  light_color.red/256, light_color.green/256, light_color.blue/256, alpha);
  strcat(buffer, color_name);

  /*printf("gradient relief: %s \n", buffer);*/
  return ZnGetGradient(interp, tkwin, buffer);
}


/*
 *--------------------------------------------------------------
 *
 * ZnNameGradient
 * ZnDeleteGradientName --
 *
 *	Save a gradient under a name or suppress the gradient
 *	name binding. The save function returns false if the
 *	name is already in use.
 *
 *--------------------------------------------------------------
 */
ZnBool
ZnNameGradient(Tcl_Interp	*interp,
	       Tk_Window	tkwin,
	       char		*grad_descr,
	       char		*name)
{
  Tcl_HashEntry	*hash;
  int		new;
  ZnGradient	*grad;
  XColor	color;

  /*
   * First try to find if the name interfere with a color name,
   * this must be avoided. Gradients may be described by a single
   * color name and gradient descriptions / names share the same
   * name space.
   */
  if (XParseColor(Tk_Display(tkwin), Tk_Colormap(tkwin), name, &color)) {
    Tcl_AppendResult(interp, "gradient name \"", name,
		     "\", is a color name", NULL);
    return False;
  }
  grad = ZnGetGradient(interp, tkwin, grad_descr);
  if (!grad) {
    Tcl_AppendResult(interp, "gradient specification \"", grad_descr,
		     "\", is invalid", NULL);
    return False;
  }
  hash = Tcl_CreateHashEntry(&gradient_table, Tk_GetUid(name), &new);
  if (!new) {
    ZnFreeGradient(grad);
    Tcl_AppendResult(interp, "gradient name \"", name,
		     "\", is already in use", NULL);
    return False;
  }
  else {
    Tcl_SetHashValue(hash, grad);
  }
  
  return True;
}

ZnBool
ZnGradientNameExists(char	*name)
{
  if (!initialized) {
    return False;
  }
  return Tcl_FindHashEntry(&gradient_table, Tk_GetUid(name)) != NULL;
}

void
ZnDeleteGradientName(char	*name)
{
  Tcl_HashEntry	*hash;

  if (!initialized) {
    return;
  }
  
  hash = Tcl_FindHashEntry(&gradient_table, Tk_GetUid(name));
  if (hash) {
    Tcl_DeleteHashEntry(hash);
    ZnFreeGradient((ZnGradient *) Tcl_GetHashValue(hash));
  }
}

static void
InterpolateGradientColor(Tk_Window	tkwin,
			 ZnGradientColor *gc1,	    /* First color */ 
			 ZnGradientColor *gc2,	    /* Next color */
			 ZnGradientColor *gc_interp,/* New interpolated color */
			 ZnGradientColor *gc_adjust,/* Adjusted first color.
						     * Needed if interested in
						     * the range color1 interp
						     * color. */
			 int		 interp_pos,
			 int		 min_pos,
			 int		 span)
{
  int	 pos1, pos2, interp_rel_pos, tmp;
  XColor rgb;

  pos1 = (gc1->position-min_pos)/span;
  pos2 = (gc2->position-min_pos)/span;
  interp_rel_pos = (interp_pos-pos1)*100/(pos2-pos1);
  if (interp_rel_pos < gc1->control) {
    tmp = interp_rel_pos*gc1->control;
    rgb.red = gc1->rgb->red + (gc1->mid_rgb->red - gc1->rgb->red)*tmp/10000;
    rgb.green = gc1->rgb->green + (gc1->mid_rgb->green - gc1->rgb->green)*tmp/10000;
    rgb.blue = gc1->rgb->blue + (gc1->mid_rgb->blue - gc1->rgb->blue)*tmp/10000;
  }
  else if (interp_rel_pos > gc1->control) {
    tmp = (interp_rel_pos-gc1->control)*gc1->control;
    rgb.red = gc1->rgb->red + (gc1->mid_rgb->red - gc1->rgb->red)*tmp/10000;
    rgb.green = gc1->rgb->green + (gc1->mid_rgb->green - gc1->rgb->green)*tmp/10000;
    rgb.blue = gc1->rgb->blue + (gc1->mid_rgb->blue - gc1->rgb->blue)*tmp/10000;
  }
  else {
    rgb = *gc1->mid_rgb;
    gc_interp->alpha = gc1->mid_alpha;    
  }
  rgb.red = gc1->rgb->red+(gc2->rgb->red-gc1->rgb->red)*interp_rel_pos/100;
  rgb.green = gc1->rgb->green+(gc2->rgb->green-gc1->rgb->green)*interp_rel_pos/100;
  rgb.blue = gc1->rgb->blue+(gc2->rgb->blue-gc1->rgb->blue)*interp_rel_pos/100;
  gc_interp->rgb = Tk_GetColorByValue(tkwin, &rgb);
  gc_interp->alpha = gc1->alpha+(gc2->alpha-gc1->alpha)*interp_rel_pos/100;
  if (!gc_adjust) {
    /*
     * Interested in the segment from the interpolated color
     * to color 2.
     */
    gc_interp->position = 0;
    if (interp_rel_pos < gc1->control) {
      gc_interp->control = gc1->control-interp_rel_pos;
      gc_interp->mid_rgb = Tk_GetColorByValue(tkwin, gc1->mid_rgb);
      gc_interp->mid_alpha = gc1->mid_alpha;
    }
    else {
      rgb.red = gc_interp->rgb->red+(gc2->rgb->red-gc_interp->rgb->red)/2;
      rgb.green = gc_interp->rgb->green+(gc2->rgb->green-gc_interp->rgb->green)/2;
      rgb.blue = gc_interp->rgb->blue+(gc2->rgb->blue-gc_interp->rgb->blue)/2;
      gc_interp->mid_rgb = Tk_GetColorByValue(tkwin, &rgb);
      gc_interp->mid_alpha = gc_interp->mid_alpha +
	(gc2->mid_alpha - gc_interp->mid_alpha)/2;
      gc_interp->control = 50;
    }
  }
  else {
    /*
     * Interested in the segment form color 1 (color adjusted) to
     * interpolated color.
     */
    gc_interp->position = 100;
    gc_interp->mid_rgb = NULL;
    if (interp_rel_pos > gc1->control) {
      gc_adjust->control = gc1->control+interp_rel_pos;
    }
    else {
      rgb.red = gc1->rgb->red+(gc_interp->rgb->red-gc1->rgb->red)/2;
      rgb.green = gc1->rgb->green+(gc_interp->rgb->green-gc1->rgb->green)/2;
      rgb.blue = gc1->rgb->blue+(gc_interp->rgb->blue-gc1->rgb->blue)/2;
      Tk_FreeColor(gc_adjust->mid_rgb);
      gc_adjust->mid_rgb = Tk_GetColorByValue(tkwin, &rgb);
      gc_adjust->mid_alpha = gc1->mid_alpha +
	(gc_interp->mid_alpha - gc1->mid_alpha)/2;
      gc_adjust->control = 50;
    }
  }
}


static void
ReduceGradient(Tk_Window	tkwin,
	       ZnGradient	*grad)
{
  ZnReal     dx, dy, len, angle;
  ZnTransfo  t;
  ZnPoint    pbbox[4], pgrad[4];
  ZnReal     maxx, minx, span, start_in_new, end_in_new;
  int	     minx100, maxx100, span100;
  int	     i, j, first_color, last_color;
  ZnBool     interpolate_first, interpolate_last;

  dx = grad->e.x - grad->p.x;
  dy = grad->e.y - grad->p.y;
  len = sqrt(dx*dx+dy*dy);
  angle = acos(dx/len);
  if (dy < 0) {
    angle = 2*M_PI - angle;
  }  
  grad->angle = (int) -ZnRadDeg(angle);

  if (grad->type == ZN_CONICAL_GRADIENT) {
  unchanged:
    grad->actual_colors = grad->colors_in;
    grad->num_actual_colors = grad->num_colors_in;
    return;
  }

  ZnTransfoSetIdentity(&t);
  ZnTranslate(&t, -grad->p.x, -grad->p.y, False);
  ZnRotateRad(&t, -angle);
  ZnScale(&t, 1/len, 1/len);
  pbbox[0].x = -50;
  pbbox[0].y = 50;
  pbbox[1].x = 50;
  pbbox[1].y = 50;
  pbbox[2].x = 50;
  pbbox[2].y = -50;
  pbbox[3].x = -50;
  pbbox[3].y = -50;
  ZnTransformPoints(&t, pbbox, pgrad, 4);
  maxx = minx = pgrad[0].x;
  for (i = 1; i < 4; i++) {
    if (pgrad[i].x > maxx) {
      maxx = pgrad[i].x;
    }
    if (pgrad[i].x < minx) {
      minx = pgrad[i].x;
    }
  }

  span = maxx-minx;
  if (grad->type == ZN_RADIAL_GRADIENT) {
    start_in_new = 0;
    end_in_new = 100/span;
  }
  else {
    start_in_new = -minx*100/span;
    end_in_new = (1-minx)*100/span;
  }

  /*printf("minx: %g, maxx: %g, start%%: %g, end%%: %g\n",
    minx, maxx, start_in_new, end_in_new);*/
  
  /*
   * Gradient is unchanged
   */
  if ((ABS(start_in_new) < PRECISION_LIMIT) &&
      (ABS(end_in_new-100.0) < PRECISION_LIMIT)) {
    goto unchanged;
  }
  if ((start_in_new > 100.0) || (end_in_new < 0.0)) {
    grad->num_actual_colors = 1;
    grad->actual_colors = ZnMalloc(sizeof(ZnGradientColor));
    if (end_in_new < 0.0) {
      grad->actual_colors[0].alpha = grad->colors_in[0].alpha;
      grad->actual_colors[0].rgb = Tk_GetColorByValue(tkwin,
						      grad->colors_in[0].rgb);
    }
    else {
      grad->actual_colors[0].alpha = grad->colors_in[grad->num_colors_in].alpha;
      grad->actual_colors[0].rgb = Tk_GetColorByValue(tkwin,
						      grad->colors_in[grad->num_colors_in].rgb);
    }
  }

  grad->num_actual_colors = grad->num_colors_in;
  interpolate_first = False;
  minx100 = (int) (minx*100);
  maxx100 = (int) (maxx*100);
  span100 = (int) (span*100);
  if (start_in_new < 0.0) {
    /* Find the closest color inside the active area */
    first_color = 1;
    while ((first_color < (int) grad->num_colors_in) &&
	   (grad->colors_in[first_color].position < minx100)) {
      first_color++;
      grad->num_actual_colors--;
    }
    if (grad->colors_in[first_color].position == minx100) {
      grad->num_actual_colors--;
    }
    else {
      interpolate_first = True;
      /*printf("interpolate first color\n");*/
    }
  }
  else {
    first_color = 0;
    if (grad->type != ZN_RADIAL_GRADIENT) {
      grad->num_actual_colors++;
    }
  }
  interpolate_last = False;
  if (end_in_new > 100.0) {
    /* Find the closest color inside the active area */
    last_color = grad->num_colors_in-2;
    while ((last_color >= 0) &&
	   (grad->colors_in[last_color].position > maxx100)) {
      last_color--;
      grad->num_actual_colors--;
    }
    if (grad->colors_in[last_color].position == maxx100) {
      grad->num_actual_colors--;
    }
    else {
      interpolate_last = True;
      /*printf("interpolate last color\n");*/
    }
  }
  else {
    last_color = grad->num_colors_in-1;
    grad->num_actual_colors++;
  }

  grad->actual_colors = ZnMalloc(grad->num_actual_colors*sizeof(ZnGradientColor));
  j = 0;
  if (interpolate_first) {
    InterpolateGradientColor(tkwin,
			     &grad->colors_in[first_color-1],
			     &grad->colors_in[first_color],
			     &grad->actual_colors[j],
			     NULL,
			     minx100, minx100, span100);
    j++;
  }
  else if ((first_color == 0) && (grad->type != ZN_RADIAL_GRADIENT)) {
    grad->actual_colors[j] = grad->colors_in[0];
    grad->actual_colors[j].rgb =
      Tk_GetColorByValue(tkwin, grad->colors_in[0].rgb);
    if (grad->colors_in[0].mid_rgb) {
      grad->actual_colors[j].mid_rgb =
	Tk_GetColorByValue(tkwin, grad->colors_in[0].mid_rgb);
    }
    grad->actual_colors[0].position = 0;
    grad->actual_colors[0].control = 50;
    j++;
    /*printf("adding a color at start\n");*/
  }

  /*printf("first color: %d, last color: %d, num colors: %d\n",
	 first_color, last_color, grad->num_actual_colors);*/
  for (i = first_color; i <= last_color; i++, j++) {
    grad->actual_colors[j] = grad->colors_in[i];
    grad->actual_colors[j].rgb =
      Tk_GetColorByValue(tkwin, grad->colors_in[i].rgb);
    if (grad->colors_in[i].mid_rgb) {
      grad->actual_colors[j].mid_rgb =
	Tk_GetColorByValue(tkwin, grad->colors_in[i].mid_rgb);
    }
    grad->actual_colors[j].position =
      (grad->colors_in[i].position-minx100)*100/span100;
    /*printf("i: %d, j: %d, minx: %d, span: %d, position av: %d position ap: %d\n",
      i, j, minx100, span100, grad->colors_in[i].position, grad->actual_colors[j].position);*/
  }

  if (interpolate_last) {
    InterpolateGradientColor(tkwin,
			     &grad->colors_in[last_color],
			     &grad->colors_in[last_color+1],
			     &grad->actual_colors[j],
			     &grad->actual_colors[j-1],
			     maxx100, minx100, span100);
  }
  else if (last_color == ((int) grad->num_colors_in)-1) {
    i = grad->num_colors_in-1;
    grad->actual_colors[j] = grad->colors_in[i];
    grad->actual_colors[j].rgb =
      Tk_GetColorByValue(tkwin, grad->colors_in[i].rgb);
    if (grad->colors_in[i].mid_rgb) {
      grad->actual_colors[j].mid_rgb =
	Tk_GetColorByValue(tkwin, grad->colors_in[i].mid_rgb);    
    }
    /*printf("adding a color at end\n");*/
  }
  grad->actual_colors[j].position = 100;
}


/*
 *--------------------------------------------------------------
 *
 * ZnGetGradient --
 *
 *	Create a data structure containing a range of colors
 *	used to display a gradient. 
 *
 *	The gradient should have the following syntax:
 *
 *	gradient := [graddesc|]color[|....|color]
 *	  where the | are real characters not meta-syntax.
 *
 *	graddesc := =type args
 *	  where type := axial | radial | path
 *
 *	  If type = axial
 *		args := angle (0..360) | xs ys xe ye (reals)
 *
 *        The first form define the axial gradiant by its slope.
 *        With this syntax the gradient fits the whole shape.
 *	  This is a backward compatible syntax.
 *	  The second form specifies a vector which will be used
 *	  to draw the gradient. The vector defines both the angle
 *	  and the gradient area. Parts of the shape that lie before
 *	  the vector origin are filled with the first color and
 *	  parts that lie after the vector end are filled with the
 *	  last color.
 *
 *	  If type = radial or path
 *		args := xs ys [xe ye] (reals)
 *
 *	  The vector specified by the 4 coordinates defines the
 *	  gradient area.  Parts of the shape that lie before
 *	  the vector origin are filled with the first color and
 *	  parts that lie after the vector end are filled with the
 *	  last color. The vector end may be omitted, in such case
 *	  the gradient fits exactly the whole shape to be filled,
 *	  this is backward compatible with older gradients.
 *	  
 *	color := colorvalue | colorvalue position |
 *		 colorvalue control position
 *	  where position and control are in (0..100)
 *
 *	colorvalue := (colorname | #rgb | cievalue)[;alpha]
 *	  where alpha is in (0..100)
 *
 * Results:
 *	The return value is a token for a data structure
 *	describing a gradient. This token may be passed
 *	to the drawing routines.
 *	If an error prevented the gradient from being created
 *	then NULL is returned and an error message will be
 *	left in interp.
 *
 * Side effects:
 *	Data structures, etc. are allocated.
 *	It is the caller's responsibility to eventually call
 *	ZnFreeGradient to release the resources.
 *
 *--------------------------------------------------------------
 */
ZnGradient *
ZnGetGradientByValue(ZnGradient	*grad)
{
  grad->ref_count++;
  return grad;
}


static int
ParseRealList(const char *str,
	      const char *stop,
	      ZnReal	 *list,
	      int	 max)
{
  int	 num;
  char	 *end;

  num = 0;
  while ((num < max) && (str != stop)) {
    list[num] = strtod(str, &end);
    if (end == str) {
      /* A syntax error occured, return a 0 count
       * as a hint for the caller.
       */
      return 0;
    }
    num++;
    str = end+strspn(end, " \t");
  }
  return num;
}

ZnGradient *
ZnGetGradient(Tcl_Interp	*interp,
	      Tk_Window		tkwin,
	      Tk_Uid		desc)
{
  #define SEGMENT_SIZE 64
  Tcl_HashEntry	*hash;
  ZnGradient	*grad;
  unsigned int	i, j, nspace, num_colors;
  unsigned int	size, num_coords=0;
  char		type;
  char const	*scan_ptr, *next_ptr, *str_ptr;
  ZnReal	angle, position, control;
  ZnReal	coords[4];
  char		*color_ptr, *end, segment[SEGMENT_SIZE];
  ZnGradientColor *first, *last;
  XColor	color;
  int		new, red_range, green_range, blue_range;
  ZnBool	simple;

  /*  printf("ZnGetGradient : %s\n", desc);*/
  if (!desc || !*desc) {
    return NULL;
  }
  if (!initialized) {
    ColorInit();
  }
  
  /*
   * First, check to see if there's already a gradient that will work
   * for this request.
   */  
  desc = Tk_GetUid(desc);

  /*printf("get gradient: %s\n", desc);*/
  hash = Tcl_CreateHashEntry(&gradient_table, desc, &new);
  if (!new) {
    grad = (ZnGradient *) Tcl_GetHashValue(hash);
    grad->ref_count++;
    return grad;
  }

  /*
   * No satisfactory gradient exists yet.  Initialize a new one.
   */
  type = ZN_AXIAL_GRADIENT;
  angle = 0.0;
  /*
   * Skip the trailing spaces.
   */
  while (*desc == ' ') {
    desc++;
  }
  /*
   * Count the sections in the description. It should give
   * the number of colors plus may be the gradient description.
   */
  scan_ptr = desc;
  /*
   * If the first section is the gradient description, start color
   * counts up from zero.
   */
  num_colors = (*scan_ptr == '=') ? 0 : 1;
  while ((scan_ptr = strchr(scan_ptr, '|'))) {
    num_colors++;
    scan_ptr++;
  }
  if (num_colors == 0) {
    Tcl_AppendResult(interp, "gradient should have at least one color \"",
		     desc, "\",", NULL);
  grad_err1:
    Tcl_DeleteHashEntry(hash);
    /*printf("ZnGetGradient error : %s\n", desc);*/
    return NULL;
  }
  /*
   * Then look at the gradient type.
   */
  scan_ptr = desc;
  /*
   * next_ptr can't be NULL in the following code,
   * we checked that at least one color was specified
   * after the gradient description.
   */
  next_ptr = strchr(scan_ptr, '|');
  if (*scan_ptr == '=') {
    scan_ptr++;
    if ((*scan_ptr == 'a') && (strncmp(scan_ptr, "axial", 5) == 0)) {
      scan_ptr += 5;
      num_coords = ParseRealList(scan_ptr, next_ptr, coords, 4);
      if ((num_coords != 1) && (num_coords != 4)) {
      grad_err3:
	Tcl_AppendResult(interp, "invalid gradient parameter \"",
			 desc, "\",", NULL);
	goto grad_err1;
      }
      angle = (int) coords[0];
    }
    else if ((*scan_ptr == 'c') && (strncmp(scan_ptr, "conical", 7) == 0)) {
      scan_ptr += 7;
      type = ZN_CONICAL_GRADIENT;
      num_coords = ParseRealList(scan_ptr, next_ptr, coords, 4);
      if ((num_coords < 1) && (num_coords > 4)) {
	goto grad_err3;
      }
      angle = (int) coords[0];
    }
    else if (((*scan_ptr == 'r') && (strncmp(scan_ptr, "radial", 6) == 0)) ||
	     ((*scan_ptr == 'p') && (strncmp(scan_ptr, "path", 4) == 0))) {
      if (*scan_ptr == 'r') {
	type = ZN_RADIAL_GRADIENT;
	scan_ptr += 6;
      }
      else {
	type = ZN_PATH_GRADIENT;
	scan_ptr += 4;
      }
      num_coords = ParseRealList(scan_ptr, next_ptr, coords, 4);
      if ((num_coords != 2) && (num_coords != 4)) {
	goto grad_err3;
      }
    }
    else {
      Tcl_AppendResult(interp, "invalid gradient type \"",
		       desc, "\",", NULL);
      goto grad_err1;
    }
    scan_ptr = next_ptr + 1;
    next_ptr = strchr(scan_ptr, '|');
  }
  /*
   * Create the gradient structure.
   */
  grad = (ZnGradient *) ZnMalloc(sizeof(ZnGradient) +
				 sizeof(ZnGradientColor)*(num_colors-1));
  grad->ref_count = 1;
  simple = True;
  grad->num_colors_in = num_colors;
  grad->type = type;
  grad->p.x = grad->p.y = grad->e.x = grad->e.y = 0.0;
  grad->angle = 0;

  switch (type) {
  case ZN_AXIAL_GRADIENT:
    if ((num_coords == 4) &&
	((coords[0] != coords[2]) || (coords[1] != coords[3]))) {
      grad->p.x = coords[0];
      grad->p.y = coords[1];
      simple = False;
      grad->e.x = coords[2];
      grad->e.y = coords[3];
    }
    else {
      grad->angle = (int) angle;
    }
    break;
  case ZN_CONICAL_GRADIENT:
    if ((num_coords == 4) &&
	((coords[0] != coords[2]) || (coords[1] != coords[3]))) {
      grad->p.x = coords[0];
      grad->p.y = coords[1];
      simple = False;
      grad->e.x = coords[2];
      grad->e.y = coords[3];
    }
    else if (num_coords == 2) {
      grad->p.x = coords[0];
      grad->p.y = coords[1];
    }
    else if (num_coords == 3) {
      grad->p.x = coords[0];
      grad->p.y = coords[1];
      grad->angle = (int) coords[2];
    }
    else {
      grad->angle = (int) angle;
    }
    break;
  case ZN_RADIAL_GRADIENT:
    grad->p.x = coords[0];
    grad->p.y = coords[1];
    if ((num_coords == 4) &&
	((coords[0] != coords[2]) || (coords[1] != coords[3])))  {
      simple = False;
      grad->e.x = coords[2];
      grad->e.y = coords[3];
    }
    break;
  case ZN_PATH_GRADIENT:
    grad->p.x = coords[0];
    grad->p.y = coords[1];
    break;
  }
  grad->hash = hash;
  Tcl_SetHashValue(hash, grad);
  
  for (i = 0; i < num_colors; i++) {
    grad->colors_in[i].alpha = 100;
    /*
     * Try to parse the color name.
     */
    nspace = strspn(scan_ptr, " \t");	
    scan_ptr += nspace;
    str_ptr = strpbrk(scan_ptr, " \t|");
    if (str_ptr) {
      size = str_ptr - scan_ptr;
    }
    else {
      size = strlen(scan_ptr);
    }
    if (size > (SEGMENT_SIZE-1)) {
      Tcl_AppendResult(interp, "color name too long in gradient \"",
		       desc, "\",", NULL);
    grad_err2:
      for (j = 0; j < i; j++) {
	Tk_FreeColor(grad->colors_in[j].rgb);
      }
      ZnFree(grad);
      goto grad_err1;
    }
    strncpy(segment, scan_ptr, size);
    segment[size] = 0;
    scan_ptr += size;
    /*
     * Try to parse the color position.
     */
    grad->colors_in[i].position = 0;
    grad->colors_in[i].control = 50;
    position = strtod(scan_ptr, &end);
    if (end != scan_ptr) {
      grad->colors_in[i].position = (int) position;
      scan_ptr = end;
      /*
       * Try to parse the control point
       */
      control = strtod(scan_ptr, &end);
      if (end != scan_ptr) {
	grad->colors_in[i].control = (int) control;
	scan_ptr = end;
      }
    }
    nspace = strspn(scan_ptr, " \t");
    if ((scan_ptr[nspace] != 0) && (scan_ptr+nspace != next_ptr)) {
      Tcl_AppendResult(interp, "incorrect color description in gradient \"",
		       desc, "\",", NULL);
      goto grad_err2;
    }
    
    color_ptr = strchr(segment, ';');
    if (color_ptr) {
      *color_ptr = 0;
    }
    grad->colors_in[i].rgb = Tk_GetColor(interp, tkwin, Tk_GetUid(segment));
    if (grad->colors_in[i].rgb == NULL) {
      Tcl_AppendResult(interp, "incorrect color value in gradient \"",
		       desc, "\",", NULL);
      goto grad_err2;
    }
    if (color_ptr) {
      color_ptr++;
      grad->colors_in[i].alpha = atoi(color_ptr);
    }
    if (i == 0) {
      grad->colors_in[i].position = 0;
    }
    else if (i == num_colors - 1) {
      grad->colors_in[i].position = 100;
    }
    if ((i > 0) &&
	((grad->colors_in[i].position > 100) ||
	 (grad->colors_in[i].position < grad->colors_in[i-1].position))) {
      Tcl_AppendResult(interp, "incorrect color position in gradient \"",
		       desc, "\",", NULL);
      goto grad_err2;
    }
    if (grad->colors_in[i].control > 100) {
      grad->colors_in[i].control = 100;
    }
    if (grad->colors_in[i].alpha > 100) {
      grad->colors_in[i].alpha = 100;
    }
    if (next_ptr) {
      scan_ptr = next_ptr + 1;
      next_ptr = strchr(scan_ptr, '|');
    }
  }

  /*
   * Compute the mid alpha and mid color values. These will be
   * used by the gradient rendering primitives when a control
   * is not at mid range. The last color has no mid_* values.
   */
  for (i = 0; i < grad->num_colors_in-1; i++) {
    first = &grad->colors_in[i];
    last = &grad->colors_in[i+1];
    red_range = (int) last->rgb->red - (int) first->rgb->red;
    green_range = (int) last->rgb->green - (int) first->rgb->green;
    blue_range = (int) last->rgb->blue - (int) first->rgb->blue;
    color.red =(int) first->rgb->red +  red_range/2;
    color.green = (int) first->rgb->green + green_range/2;
    color.blue = (int) first->rgb->blue + blue_range/2;
    first->mid_rgb = Tk_GetColorByValue(tkwin, &color);
    first->mid_alpha = first->alpha + (last->alpha-first->alpha)/2;
  }
  grad->colors_in[grad->num_colors_in-1].mid_rgb = NULL;

  /*
   * If the gradient is 'simple' ie. described by a single point
   * or an angle for axial gradients, the processing is finished.
   * If not, we have to reduce the gradient to a simple one by adding
   * or suppressing colors and adjusting the relative position of
   * each remaining color.
   */
  if (simple) {
    grad->num_actual_colors = grad->num_colors_in;
    grad->actual_colors = grad->colors_in;
  }
  else if (type != ZN_PATH_GRADIENT) {
    ReduceGradient(tkwin, grad);
  }

  /*printf("num in: %d, num actual: %d\n", grad->num_colors_in,grad->num_actual_colors);*/
  /* printf("ZnGetGradient end : %s\n", desc);*/
  return grad;
}


/*
 *--------------------------------------------------------------
 *
 * ZnNameOfGradient --
 *
 *	Given a gradient, return a textual string identifying
 *	the gradient.
 *
 * Results:
 *	The return value is the string that was used to create
 *	the gradient.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */
char *
ZnNameOfGradient(ZnGradient	*grad)
{
  return (char *) grad->hash->key.words;
}


/*
 *--------------------------------------------------------------
 *
 * ZnFreeGradient --
 *
 *	This procedure is called when a gradient is no longer
 *	needed.  It frees the resources associated with the
 *	gradient.  After this call, the caller should never
 *	again use the gradient.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Resources are freed.
 *
 *--------------------------------------------------------------
 */
void
ZnFreeGradient(ZnGradient	*grad)
{
  unsigned int	i;
  
  grad->ref_count--;
  if (grad->ref_count == 0) {
    Tcl_DeleteHashEntry(grad->hash);
    for (i = 0; i < grad->num_colors_in; i++) {
      Tk_FreeColor(grad->colors_in[i].rgb);
      if (grad->colors_in[i].mid_rgb) {
	Tk_FreeColor(grad->colors_in[i].mid_rgb);
      }
    }
    if (grad->actual_colors != grad->colors_in) {
      for (i = 0; i < grad->num_actual_colors; i++) {
	Tk_FreeColor(grad->actual_colors[i].rgb);
	if (grad->actual_colors[i].mid_rgb) {
	  Tk_FreeColor(grad->actual_colors[i].mid_rgb);
	}
      }
      ZnFree(grad->actual_colors);
    }
    ZnFree(grad);
  }
}


/*
 *--------------------------------------------------------------
 *
 * ZnComposeAlpha --
 *
 *	This procedure takes two alpha values in percent and
 *	returns the composite value between 0 and 65535.
 *
 *--------------------------------------------------------------
 */
int
ZnComposeAlpha(unsigned short	alpha1,
	       unsigned short	alpha2)
{
  return (alpha1*alpha2/100)*65535/100;
}
