/*
* Color.c -- Color management module.
*
* Authors : Patrick Lecoanet.
* Creation date : Thu Dec 16 15:41:53 1999
*
* $Id: Color.c,v 1.37 2005/12/02 13:35:39 lecoanet Exp $
*/
/*
* Copyright (c) 1999 - 2005 CENA, Patrick Lecoanet --
*
* See the file "Copyright" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
*/
/*
* 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 +
(unsigned short) ((gc2->rgb->red - gc1->mid_rgb->red) * rel_pos / 100.0);
color->green = gc1->mid_rgb->green +
(unsigned short) ((gc2->rgb->green - gc1->mid_rgb->green) * rel_pos / 100.0);
color->blue = gc1->mid_rgb->blue +
(unsigned short) ((gc2->rgb->blue - gc1->mid_rgb->blue) * rel_pos / 100.0);
*alpha = gc1->mid_alpha +
(unsigned short) ((gc2->alpha - gc1->mid_alpha) * rel_pos / 100.0);
}
else {
rel_pos = rel_pos * 100.0 / gc1->control;
color->red = gc1->rgb->red +
(unsigned short) ((gc1->mid_rgb->red - gc1->rgb->red) * rel_pos / 100.0);
color->green = gc1->rgb->green +
(unsigned short) ((gc1->mid_rgb->green - gc1->rgb->green) * rel_pos / 100.0);
color->blue = gc1->rgb->blue +
(unsigned short) ((gc1->mid_rgb->blue - gc1->rgb->blue) * rel_pos / 100.0);
*alpha = gc1->alpha +
(unsigned short) ((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)
{
ZnReal pos1, pos2, ipos, interp_rel_pos, tmp;
XColor rgb;
//printf("interp_pos: %d, min_pos: %d, span: %d\n ", interp_pos, min_pos, span);
pos1 = ((ZnReal)gc1->position-(ZnReal)min_pos)/(ZnReal)span;
pos2 = ((ZnReal)gc2->position-(ZnReal)min_pos)/(ZnReal)span;
ipos = ((ZnReal)interp_pos-(ZnReal)min_pos)/(ZnReal)span;
interp_rel_pos = (ipos-pos1)*100/(pos2-pos1);
//printf("pos1: %g, pos2: %g, interp_rel_pos: %g\n", pos1, pos2, interp_rel_pos);
if (interp_rel_pos < gc1->control) {
tmp = interp_rel_pos * 100.0 / gc1->control;
//printf("rgb : %d, mid rgb : %d\n\n", gc1->rgb, gc1->mid_rgb);
rgb.red = (unsigned short) (gc1->rgb->red + (gc1->mid_rgb->red - gc1->rgb->red) * tmp / 100.0);
rgb.green = (unsigned short) (gc1->rgb->green + (gc1->mid_rgb->green - gc1->rgb->green) * tmp / 100.0);
rgb.blue = (unsigned short) (gc1->rgb->blue + (gc1->mid_rgb->blue - gc1->rgb->blue) * tmp / 100.0);
gc_interp->alpha = (unsigned char) (gc1->alpha + (gc1->mid_alpha - gc1->alpha) * tmp / 100.0);
}
else if (interp_rel_pos > gc1->control) {
tmp = (interp_rel_pos - gc1->control) * 100.0 / (100.0 - gc1->control);
rgb.red = (unsigned short) (gc1->mid_rgb->red + (gc2->rgb->red - gc1->mid_rgb->red)*tmp / 100.0);
rgb.green = (unsigned short) (gc1->mid_rgb->green + (gc2->rgb->green - gc1->mid_rgb->green)*tmp / 100.0);
rgb.blue = (unsigned short) (gc1->mid_rgb->blue + (gc2->rgb->blue - gc1->mid_rgb->blue)*tmp / 100.0);
gc_interp->alpha = (unsigned char) (gc1->mid_alpha + (gc2->alpha - gc1->mid_alpha)*tmp / 100.0);
}
else {
rgb = *gc1->mid_rgb;
gc_interp->alpha = gc1->mid_alpha;
}
gc_interp->rgb = Tk_GetColorByValue(tkwin, &rgb);
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 - (int) 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->alpha + (gc2->alpha - gc_interp->alpha)/2;
gc_interp->control = 50;
}
}
else {
/*
* Interested in the segment from color 1 (color adjusted) to
* the interpolated color.
*/
gc_interp->position = 100;
gc_interp->mid_rgb = NULL;
gc_interp->mid_alpha = 100;
if (interp_rel_pos <= gc1->control) {
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->alpha + (gc_interp->alpha - gc1->alpha)/2;
gc_adjust->control = 50;
}
}
//printf("out of InterpolateGradientColor\n");
}
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;
//printf("In ReduceGradient %d\n", grad->num_colors_in);
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;
}
//printf("start_in_new: %g, end_in_new: %g\n", start_in_new, end_in_new);
if ((start_in_new > 100.0) || (end_in_new < 0.0)) {
grad->num_actual_colors = 1;
grad->actual_colors = ZnMalloc(sizeof(ZnGradientColor));
grad->actual_colors[0].position = 0;
grad->actual_colors[0].mid_rgb = NULL;
if (end_in_new < 0.0) {
grad->actual_colors[0].alpha = grad->colors_in[grad->num_colors_in-1].alpha;
grad->actual_colors[0].rgb = Tk_GetColorByValue(tkwin, grad->colors_in[grad->num_colors_in-1].rgb);
}
else {
grad->actual_colors[0].alpha = grad->colors_in[0].alpha;
grad->actual_colors[0].rgb = Tk_GetColorByValue(tkwin, grad->colors_in[0].rgb);
}
return;
}
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) {
/*
* The gradient starts outside the bbox,
* Find the color at the bbox edge. First
* find the correct gradient segment and then
* interpolate to get the color.
*/
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) {
/*
* The gradient ends outside the bbox,
* Find the color at the bbox edge. First
* find the correct gradient segment and then
* interpolate to get the color.
*/
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));
//printf("allocating %d colors\n", grad->num_actual_colors);
j = 0;
if (interpolate_first) {
//printf("Interpolate first color, index: %d\n", first_color);
InterpolateGradientColor(tkwin,
&grad->colors_in[first_color-1],
&grad->colors_in[first_color],
&grad->actual_colors[0],
NULL,
minx100, minx100, span100);
j++;
}
else if ((first_color == 0) && (grad->type != ZN_RADIAL_GRADIENT)) {
grad->actual_colors[0] = grad->colors_in[0];
grad->actual_colors[0].rgb = Tk_GetColorByValue(tkwin, grad->colors_in[0].rgb);
if (grad->colors_in[0].mid_rgb) {
grad->actual_colors[0].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("j: %d, first color: %d, last color: %d, num colors: %d\n",
// j, 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) {
//printf("Interpolate last color: %d, j :%d\n", last_color, j);
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;
//printf("i: %d, j: %d\n", 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);
}
/*printf("adding a color at end\n");*/
}
grad->actual_colors[grad->num_actual_colors-1].position = 100;
//printf("Out of ReduceGradient\n");
}
/*
*--------------------------------------------------------------
*
* 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;
}