/* * Color.c -- Color management module. * * Authors : Patrick Lecoanet. * Creation date : Thu Dec 16 15:41:53 1999 * * $Id$ */ /* * 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 #include #include "Types.h" #include "Image.h" #include "Color.h" #include "Geo.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 -- * *---------------------------------------------------------------------- */ XColor * ZnGetGradientColor(ZnGradient *grad, ZnReal position, unsigned short *alpha) { int index, min, max; XColor *shade=NULL; if ((grad->num_colors == 1) || (position <= 0.0)) { if (alpha) { *alpha = grad->colors[0].alpha; } return grad->colors[0].rgb; } if (position >= 100.0) { if (alpha) { *alpha = grad->colors[grad->num_colors-1].alpha; } shade = grad->colors[grad->num_colors-1].rgb; } else { min = 0; max = grad->num_colors-1; index = (max + min) / 2; while (max - min != 1) { /*printf("color index %d, min: %d, max: %d\n", index, min, max);*/ if (grad->colors[index].position < position) { min = index; } else { max = index; } index = (max + min) / 2; } shade = grad->colors[index].rgb; if (alpha) { *alpha = grad->colors[index].alpha; } } return shade; } /* *-------------------------------------------------------------- * * ZnGradientFlat -- * * Returns true if the gradient is defined by a single * color. * *-------------------------------------------------------------- */ ZnBool ZnGradientFlat(ZnGradient *grad) { return (grad->num_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; grad = ZnGetGradient(interp, tkwin, grad_descr); if (!grad) { Tcl_AppendResult(interp, "gradient specification \"", grad_descr, "\", is invalid", NULL); return False; } /* * 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; } hash = Tcl_CreateHashEntry(&gradient_table, Tk_GetUid(name), &new); if (!new) { 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)); } } /* *-------------------------------------------------------------- * * 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 * args := angle if type = axial; angle in (0..360) * args := x y if type = (radial| path); x and * y being real valued. * * 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; } 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; char type; char const *scan_ptr, *next_ptr, *str_ptr; int angle, position, control; double x=0, y=0; char *color_ptr, *end, segment[SEGMENT_SIZE]; ZnGradientColor *first, *last; XColor color; int new, red_range, green_range, blue_range; /* 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); hash = Tcl_CreateHashEntry(&gradient_table, desc, &new); if (!new) { grad = (ZnGradient *) Tcl_GetHashValue(hash); grad->ref_count++; } else { /* * No satisfactory gradient exists yet. Initialize a new one. */ type = ZN_AXIAL_GRADIENT; angle = 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; angle = strtol(scan_ptr, &end, 10); nspace = strspn(end, " \t"); if ((end+nspace) != next_ptr) { grad_err3: Tcl_AppendResult(interp, "invalid gradient parameter \"", desc, "\",", NULL); goto grad_err1; } } 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; } x = strtod(scan_ptr, &end); if (end == scan_ptr) { goto grad_err3; } scan_ptr = end; y = strtod(scan_ptr, &end); nspace = strspn(end, " \t"); if ((end+nspace) != next_ptr) { 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; grad->num_colors = num_colors; grad->type = type; if (type == ZN_AXIAL_GRADIENT) { grad->g.angle = angle; } else { grad->g.p.x = x; grad->g.p.y = y; } grad->hash = hash; Tcl_SetHashValue(hash, grad); for (i = 0; i < num_colors; i++) { grad->colors[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[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[i].position = 0; grad->colors[i].control = 50; position = strtol(scan_ptr, &end, 10); if (end != scan_ptr) { grad->colors[i].position = position; scan_ptr = end; /* * Try to parse the control point */ control = strtol(scan_ptr, &end, 10); if (end != scan_ptr) { grad->colors[i].control = 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[i].rgb = Tk_GetColor(interp, tkwin, Tk_GetUid(segment)); if (grad->colors[i].rgb == NULL) { Tcl_AppendResult(interp, "incorrect color value in gradient \"", desc, "\",", NULL); goto grad_err2; } if (color_ptr) { color_ptr++; grad->colors[i].alpha = atoi(color_ptr); } if (i == 0) { grad->colors[i].position = 0; } else if (i == num_colors - 1) { grad->colors[i].position = 100; } if ((i > 0) && ((grad->colors[i].position > 100) || (grad->colors[i].position < grad->colors[i-1].position))) { Tcl_AppendResult(interp, "incorrect color position in gradient \"", desc, "\",", NULL); goto grad_err2; } if (grad->colors[i].control > 100) { grad->colors[i].control = 100; } if (grad->colors[i].alpha > 100) { grad->colors[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-1; i++) { first = &grad->colors[i]; last = &grad->colors[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[grad->num_colors-1].mid_rgb = NULL; /* 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; i++) { Tk_FreeColor(grad->colors[i].rgb); if (grad->colors[i].mid_rgb) { Tk_FreeColor(grad->colors[i].mid_rgb); } } 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; }