aboutsummaryrefslogtreecommitdiff
path: root/generic
diff options
context:
space:
mode:
Diffstat (limited to 'generic')
-rw-r--r--generic/Color.c1213
1 files changed, 1213 insertions, 0 deletions
diff --git a/generic/Color.c b/generic/Color.c
new file mode 100644
index 0000000..4cbaa6c
--- /dev/null
+++ b/generic/Color.c
@@ -0,0 +1,1213 @@
+/*
+ * 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 <malloc.h>
+
+#include "Types.h"
+#include "Image.h"
+#include "Color.h"
+#include "Geo.h"
+
+
+/*
+ * If a colormap fills up, attempts to allocate new colors from that
+ * colormap will fail. When that happens, we'll just choose the
+ * closest color from those that are available in the colormap.
+ * One of the following structures will be created for each "stressed"
+ * colormap to keep track of the colors that are available in the
+ * colormap (otherwise we would have to re-query from the server on
+ * each allocation, which would be very slow). These entries are
+ * flushed after a few seconds, since other clients may release or
+ * reallocate colors over time.
+ */
+typedef struct StressedCmap {
+ Colormap colormap; /* X's token for the colormap. */
+ int num_colors; /* Number of entries currently active
+ * at *colorPtr. */
+ XColor *color; /* Pointer to malloc'ed array of all
+ * colors that seem to be available in
+ * the colormap. Some may not actually
+ * be available, e.g. because they are
+ * read-write for another client; when
+ * we find this out, we remove them
+ * from the array. */
+ struct StressedCmap *next; /* Next in list of all stressed
+ * colormaps for the display. */
+} StressedCmap;
+
+typedef struct StressedDpy {
+ Display *dpy;
+ StressedCmap *stress;
+ struct StressedDpy *next;
+} StressedDpy;
+
+static StressedDpy *stressed_display_list = NULL;
+
+
+#define COLOR_MAGIC ((unsigned int) 0x46140277)
+
+typedef struct TkColor {
+ XColor color; /* Information about this color. */
+ unsigned int magic; /* Used for quick integrity check on this
+ * structure. Must always have the
+ * value COLOR_MAGIC. */
+ Screen *screen; /* Screen where this color is valid. Used
+ * to delete it, and to find its display. */
+ Colormap colormap; /* Colormap from which this entry was
+ * allocated. */
+ Visual *visual; /* Visual associated with colormap. */
+ int ref_count; /* Number of uses of this structure. */
+ Tcl_HashTable *table; /* Hash table that indexes this structure
+ * (needed when deleting structure). */
+ Tcl_HashEntry *hash; /* Pointer to hash table entry for this
+ * structure. (for use in deleting entry). */
+} TkColor;
+
+
+/*
+ * A two-level data structure is used to manage the color database.
+ * The top level consists of one entry for each color name that is
+ * currently active, and the bottom level contains one entry for each
+ * pixel value that is still in use. The distinction between
+ * levels is necessary because the same pixel may have several
+ * different names. There are two hash tables, one used to index into
+ * each of the data structures. The name hash table is used when
+ * allocating colors, and the pixel hash table is used when freeing
+ * colors.
+ */
+
+/*
+ * Hash table for name -> TkColor mapping, and key structure used to
+ * index into that table:
+ */
+static Tcl_HashTable name_table;
+
+typedef struct {
+ Tk_Uid name; /* Name of desired color. */
+ Colormap colormap; /* Colormap from which color will be
+ * allocated. */
+ Display *display; /* Display for colormap. */
+} NameKey;
+
+
+/*
+ * Hash table for value -> TkColor mapping, and key structure used to
+ * index into that table:
+ */
+static Tcl_HashTable value_table;
+
+typedef struct {
+ int red, green, blue; /* Values for desired color. */
+ Colormap colormap; /* Colormap from which color will be
+ * allocated. */
+ Display *display; /* Display for colormap. */
+} ValueKey;
+
+
+typedef struct _ColorGradient {
+ Screen *screen; /* Screen on which the gradient will be used. */
+ Visual *visual; /* Visual for all windows and pixmaps using
+ * the gradient. */
+ int depth; /* Number of bits per pixel of drawables where
+ * the gradient will be used. */
+ Colormap colormap; /* Colormap out of which pixels are
+ * allocated. */
+ int ref_count;
+ Tcl_HashEntry *hash;
+ RadarBool realized;
+ int num_colors; /* Number of steps in the gradient. */
+ XColor *colors[1]; /* Colors of the gradient. */
+} ColorGradient;
+
+
+/*
+ * Maximum size of a color name including the \0.
+ */
+#define COLOR_NAME_SIZE 128
+
+/*
+ * Maximum intensity for a color.
+ */
+#define MAX_INTENSITY 65535
+
+/*
+ * Number of steps in a border gradient (should be odd).
+ */
+#define BORDER_STEPS (RELIEF_STEPS*2+1)
+
+/*
+ * Hash table to map from a gradient's values (color, etc.) to a
+ * gradient structure for those values.
+ */
+static Tcl_HashTable gradient_table;
+
+typedef struct {
+ Tk_Uid name; /* Gradient spec. */
+ Colormap colormap; /* Colormap used for allocating gradient
+ * colors. */
+ Screen *screen; /* Screen on which gradient will be drawn. */
+} GradientKey;
+
+
+static int initialized = 0; /* 0 means static structures haven't been
+ * initialized yet. */
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * GetStressedDisplay --
+ *
+ *
+ *----------------------------------------------------------------------
+ */
+static StressedDpy *
+GetStressedDisplay(Display *dpy)
+{
+ StressedDpy *cur;
+
+ for (cur = stressed_display_list; cur != NULL; cur = cur->next) {
+ if (cur->dpy == dpy) {
+ break;
+ }
+ }
+ if (cur == NULL) {
+ /*
+ * Not found, allocate a new one.
+ */
+ cur = (StressedDpy *) RadarMalloc(sizeof(StressedDpy));
+ cur->dpy = dpy;
+ cur->stress = NULL;
+ cur->next = stressed_display_list;
+ stressed_display_list = cur;
+ }
+
+ return cur;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DeleteStressedCmap --
+ *
+ * This procedure releases the information cached for "colormap"
+ * so that it will be refetched from the X server the next time
+ * it is needed.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The StressedCmap structure for colormap is deleted; the
+ * colormap is no longer considered to be "stressed".
+ *
+ * Note:
+ * This procedure is invoked whenever a color in a colormap is
+ * freed, and whenever a color allocation in a colormap succeeds.
+ * This guarantees that StressedCmap structures are always
+ * deleted before the corresponding Colormap is freed.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+DeleteStressedCmap(Display *display,
+ Colormap colormap)
+{
+ StressedDpy *dpy = GetStressedDisplay(display);
+ StressedCmap *prev, *stress;
+
+ for (prev = NULL, stress = dpy->stress; stress != NULL;
+ prev = stress, stress = stress->next) {
+ if (stress->colormap == colormap) {
+ if (prev == NULL) {
+ dpy->stress = stress->next;
+ }
+ else {
+ prev->next = stress->next;
+ }
+ RadarFree(stress->color);
+ RadarFree(stress);
+ return;
+ }
+ }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FindClosestColor --
+ *
+ * When Tk can't allocate a color because a colormap has filled
+ * up, this procedure is called to find and allocate the closest
+ * available color in the colormap.
+ *
+ * Results:
+ * There is no return value, but *actualColorPtr is filled in
+ * with information about the closest available color in tkwin's
+ * colormap. This color has been allocated via X, so it must
+ * be released by the caller when the caller is done with it.
+ *
+ * Side effects:
+ * A color is allocated.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+FindClosestColor(Tk_Window tkwin, /* Window where color will
+ * be used. */
+ XColor *desired_color, /* RGB values of color that was
+ * wanted (but unavailable). */
+ XColor *actual_color) /* Structure to fill in with
+ * RGB and pixel for closest
+ * available color. */
+{
+ StressedDpy *dpy = GetStressedDisplay(Tk_Display(tkwin));
+ StressedCmap *stress;
+ double tmp, distance, closest_dist;
+ int i, closest, num_found;
+ XColor *color;
+ Colormap colormap = Tk_Colormap(tkwin);
+ XVisualInfo template, *vis_info;
+
+ /*
+ * Find the StressedCmap structure for this colormap, or create
+ * a new one if needed.
+ */
+ for (stress = dpy->stress; ; stress = stress->next) {
+ if (stress == NULL) {
+ stress = (StressedCmap *) RadarMalloc(sizeof(StressedCmap));
+ stress->colormap = colormap;
+ template.visualid = XVisualIDFromVisual(Tk_Visual(tkwin));
+ vis_info = XGetVisualInfo(Tk_Display(tkwin), VisualIDMask,
+ &template, &num_found);
+ if (num_found < 1) {
+ RadarWarning("FindClosestColor (Radar) couldn't lookup visual");
+ abort();
+ }
+ stress->num_colors = vis_info->colormap_size;
+ XFree((char *) vis_info);
+ stress->color = (XColor *) RadarMalloc((unsigned)
+ (stress->num_colors*sizeof(XColor)));
+ for (i = 0; i < stress->num_colors; i++) {
+ stress->color[i].pixel = (unsigned long) i;
+ }
+ XQueryColors(Tk_Display(tkwin), colormap, stress->color, stress->num_colors);
+ stress->next = dpy->stress;
+ dpy->stress = stress;
+ break;
+ }
+ if (stress->colormap == colormap) {
+ break;
+ }
+ }
+
+ /*
+ * Find the color that best approximates the desired one, then
+ * try to allocate that color. If that fails, it must mean that
+ * the color was read-write (so we can't use it, since it's owner
+ * might change it) or else it was already freed. Try again,
+ * over and over again, until something succeeds.
+ */
+ while (1) {
+ if (stress->num_colors == 0) {
+ RadarWarning("FindClosestColor (Radar) ran out of colors");
+ abort();
+ }
+ closest_dist = 1e30;
+ closest = 0;
+ for (color = stress->color, i = 0; i < stress->num_colors;
+ color++, i++) {
+ /*
+ * Use Euclidean distance in RGB space, weighted by Y (of YIQ)
+ * as the objective function; this accounts for differences
+ * in the color sensitivity of the eye.
+ */
+ tmp = 0.30*(((int) desired_color->red) - (int) color->red);
+ distance = tmp*tmp;
+ tmp = 0.61*(((int) desired_color->green) - (int) color->green);
+ distance += tmp*tmp;
+ tmp = 0.11*(((int) desired_color->blue) - (int) color->blue);
+ distance += tmp*tmp;
+ if (distance < closest_dist) {
+ closest = i;
+ closest_dist = distance;
+ }
+ }
+ if (XAllocColor(Tk_Display(tkwin), colormap, &stress->color[closest]) != 0) {
+ *actual_color = stress->color[closest];
+ return;
+ }
+
+ /*
+ * Couldn't allocate the color. Remove it from the table and
+ * go back to look for the next best color.
+ */
+ stress->color[closest] = stress->color[stress->num_colors-1];
+ stress->num_colors -= 1;
+ }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * CmapStressed --
+ *
+ * Check to see whether a given colormap is known to be out
+ * of entries.
+ *
+ * Results:
+ * 1 is returned if "colormap" is stressed (i.e. it has run out
+ * of entries recently), 0 otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+#if 0
+static RadarBool
+CmapStressed(Tk_Window tkwin,
+ Colormap colormap)
+{
+ StressedDpy *dpy = GetStressedDisplay(Tk_Display(tkwin));
+ StressedCmap *stress;
+
+ for (stress = dpy->stress; stress != NULL; stress = stress->next) {
+ if (stress->colormap == colormap) {
+ return True;
+ }
+ }
+ return False;
+}
+#endif
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ColorInit --
+ *
+ * Initialize the structure used for color management.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Read the code.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+ColorInit()
+{
+ initialized = 1;
+ Tcl_InitHashTable(&name_table, sizeof(NameKey)/sizeof(int));
+ Tcl_InitHashTable(&value_table, sizeof(ValueKey)/sizeof(int));
+ Tcl_InitHashTable(&gradient_table, sizeof(GradientKey)/sizeof(int));
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RadarGetColor --
+ *
+ * Given a string name for a color, map the name to a corresponding
+ * XColor structure.
+ *
+ * Results:
+ * The return value is a pointer to an XColor structure that
+ * indicates the red, blue, and green intensities for the color
+ * given by "name", and also specifies a pixel value to use to
+ * draw in that color. If an error occurs, NULL is returned and
+ * an error message will be left in interp->result.
+ *
+ * Side effects:
+ * The color is added to an internal database with a reference count.
+ * For each call to this procedure, there should eventually be a call
+ * to RadarFreeColor so that the database is cleaned up when colors
+ * aren't in use anymore.
+ *
+ *----------------------------------------------------------------------
+ */
+XColor *
+RadarGetColor(Tcl_Interp *interp,
+ Tk_Window tkwin, /* Window in which color will be used. */
+ Tk_Uid name) /* Name of color to allocated (in form
+ * suitable for passing to XParseColor). */
+{
+ NameKey name_key;
+ Tcl_HashEntry *name_hash;
+ int new;
+ Display *dpy = Tk_Display(tkwin);
+ Colormap colormap = Tk_Colormap(tkwin);
+ XColor color, screen;
+ TkColor *tk_col;
+
+ if (!initialized) {
+ ColorInit();
+ }
+
+ /*printf("RadarGetColor color: %s\n", name);*/
+ /*
+ * First, check to see if there's already a mapping for this color
+ * name.
+ */
+ name_key.name = name;
+ name_key.colormap = Tk_Colormap(tkwin);
+ name_key.display = Tk_Display(tkwin);
+ name_hash = Tcl_CreateHashEntry(&name_table, (char *) &name_key, &new);
+ if (!new) {
+ tk_col = (TkColor *) Tcl_GetHashValue(name_hash);
+ tk_col->ref_count++;
+ /*printf("RadarGetColor cache hit for: %d %d %d\n",
+ tk_col->color.red, tk_col->color.green, tk_col->color.blue);*/
+ return &tk_col->color;
+ }
+
+ /*
+ * Map from the name to a pixel value. Call XAllocNamedColor rather than
+ * XParseColor for non-# names: this saves a server round-trip for those
+ * names.
+ */
+ if (*name != '#') {
+ if (XAllocNamedColor(dpy, colormap, name, &screen, &color) != 0) {
+ /*printf("RadarGetColor XAllocNamedColor gives: %d %d %d\n",
+ color.red, color.green, color.blue);*/
+ DeleteStressedCmap(dpy, colormap);
+ }
+ else {
+ /*
+ * Couldn't allocate the color. Try translating the name to
+ * a color value, to see whether the problem is a bad color
+ * name or a full colormap. If the colormap is full, then
+ * pick an approximation to the desired color.
+ */
+ if (XLookupColor(dpy, colormap, name, &color, &screen) == 0) {
+ col_err:
+ if (*name == '#') {
+ Tcl_AppendResult(interp, "invalid color name \"", name,
+ "\"", (char *) NULL);
+ }
+ else {
+ Tcl_AppendResult(interp, "unknown color name \"", name,
+ "\"", (char *) NULL);
+ }
+ Tcl_DeleteHashEntry(name_hash);
+ return (XColor *) NULL;
+ }
+ FindClosestColor(tkwin, &screen, &color);
+ }
+ }
+ else {
+ if (XParseColor(dpy, colormap, name, &color) == 0) {
+ goto col_err;
+ }
+ if (XAllocColor(dpy, colormap, &color) != 0) {
+ DeleteStressedCmap(dpy, colormap);
+ }
+ else {
+ FindClosestColor(tkwin, &color, &color);
+ }
+ }
+
+ tk_col = (TkColor *) RadarMalloc(sizeof(TkColor));
+ tk_col->color = color;
+
+ /*
+ * Now create a new TkColor structure and add it to nameTable.
+ */
+ tk_col->magic = COLOR_MAGIC;
+ tk_col->screen = Tk_Screen(tkwin);
+ tk_col->colormap = name_key.colormap;
+ tk_col->visual = Tk_Visual(tkwin);
+ tk_col->ref_count = 1;
+ tk_col->table = &name_table;
+ tk_col->hash = name_hash;
+ Tcl_SetHashValue(name_hash, tk_col);
+
+ /*printf("RadarGetColor created: %d %d %d\n",
+ tk_col->color.red, tk_col->color.green, tk_col->color.blue);*/
+ return &tk_col->color;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RadarGetColorByValue --
+ *
+ * Given a desired set of red-green-blue intensities for a color,
+ * locate a pixel value to use to draw that color in a given
+ * window.
+ *
+ * Results:
+ * The return value is a pointer to an XColor structure that
+ * indicates the closest red, blue, and green intensities available
+ * to those specified in colorPtr, and also specifies a pixel
+ * value to use to draw in that color.
+ *
+ * Side effects:
+ * The color is added to an internal database with a reference count.
+ * For each call to this procedure, there should eventually be a call
+ * to RadarFreeColor, so that the database is cleaned up when colors
+ * aren't in use anymore.
+ *
+ *----------------------------------------------------------------------
+ */
+XColor *
+RadarGetColorByValue(Tk_Window tkwin,
+ XColor *color)
+{
+ ValueKey value_key;
+ Tcl_HashEntry *value_hash;
+ int new;
+ TkColor *tk_col;
+ Display *dpy = Tk_Display(tkwin);
+ Colormap colormap = Tk_Colormap(tkwin);
+
+ if (!initialized) {
+ ColorInit();
+ }
+
+ /*
+ * First, check to see if there's already a mapping for this color
+ * name.
+ */
+ value_key.red = color->red;
+ value_key.green = color->green;
+ value_key.blue = color->blue;
+ value_key.colormap = Tk_Colormap(tkwin);
+ value_key.display = Tk_Display(tkwin);
+ value_hash = Tcl_CreateHashEntry(&value_table, (char *) &value_key, &new);
+ if (!new) {
+ tk_col = (TkColor *) Tcl_GetHashValue(value_hash);
+ tk_col->ref_count++;
+ return &tk_col->color;
+ }
+
+ /*
+ * The name isn't currently known. Find a pixel value
+ * to use to draw that color in a given window.
+ */
+ tk_col = (TkColor *) RadarMalloc(sizeof(TkColor));
+ tk_col->color.red = color->red;
+ tk_col->color.green = color->green;
+ tk_col->color.blue = color->blue;
+ if (XAllocColor(dpy, colormap, &tk_col->color) != 0) {
+ DeleteStressedCmap(dpy, colormap);
+ }
+ else {
+ FindClosestColor(tkwin, &tk_col->color, &tk_col->color);
+ }
+
+ tk_col->magic = COLOR_MAGIC;
+ tk_col->screen = Tk_Screen(tkwin);
+ tk_col->colormap = value_key.colormap;
+ tk_col->visual = Tk_Visual(tkwin);
+ tk_col->ref_count = 1;
+ tk_col->table = &value_table;
+ tk_col->hash = value_hash;
+ Tcl_SetHashValue(value_hash, tk_col);
+
+ return &tk_col->color;
+}
+
+
+/*
+ *--------------------------------------------------------------
+ *
+ * RadarNameOfColor --
+ *
+ * Given a color, return a textual string identifying
+ * the color.
+ *
+ * Results:
+ * If colorPtr was created by Tk_GetColor, then the return
+ * value is the "string" that was used to create it.
+ * Otherwise the return value is a string that could have
+ * been passed to Tk_GetColor to allocate that color. The
+ * storage for the returned string is only guaranteed to
+ * persist up until the next call to this procedure.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+char *
+RadarNameOfColor(XColor *color)
+{
+ register TkColor *tk_col = (TkColor *) color;
+ static char string[20];
+
+ if ((tk_col->magic == COLOR_MAGIC) && (tk_col->table == &name_table)) {
+ return ((NameKey *) tk_col->hash->key.words)->name;
+ }
+ sprintf(string, "#%04x%04x%04x", color->red, color->green, color->blue);
+ return string;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RadarFreeColor --
+ *
+ * This procedure is called to release a color allocated by
+ * RadarGetColor or RadarGetColorByValue.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The reference count associated with colorPtr is deleted, and
+ * the color is released to X if there are no remaining uses
+ * for it.
+ *
+ *----------------------------------------------------------------------
+ */
+void
+RadarFreeColor(XColor *color) /* Color to be released. Must have been
+ * allocated by RadarGetColor or
+ * RadarGetColorByValue. */
+{
+ TkColor *tk_col = (TkColor *) color;
+ Visual *visual;
+ Screen *screen = tk_col->screen;
+ Tk_ErrorHandler handler;
+
+ /*
+ * Do a quick sanity check to make sure this color was really
+ * allocated by RadarGetColor.
+ */
+ if (tk_col->magic != COLOR_MAGIC) {
+ RadarWarning("RadarFreeColor called with bogus color");
+ abort();
+ }
+
+ tk_col->ref_count--;
+ if (tk_col->ref_count == 0) {
+ /*printf("RadarFreeColor freeing %s\n", RadarNameOfColor(color));*/
+ /*
+ * Careful! Don't free black or white, since this will
+ * make some servers very unhappy. Also, there is a bug in
+ * some servers (such Sun's X11/NeWS server) where reference
+ * counting is performed incorrectly, so that if a color is
+ * allocated twice in different places and then freed twice,
+ * the second free generates an error (this bug existed as of
+ * 10/1/92). To get around this problem, ignore errors that
+ * occur during the free operation.
+ */
+ visual = tk_col->visual;
+ if ((visual->class != StaticGray) && (visual->class != StaticColor) &&
+ (tk_col->color.pixel != BlackPixelOfScreen(screen)) &&
+ (tk_col->color.pixel != WhitePixelOfScreen(screen))) {
+ handler = Tk_CreateErrorHandler(DisplayOfScreen(screen),
+ -1, -1, -1, (Tk_ErrorProc *) NULL,
+ (ClientData) NULL);
+ XFreeColors(DisplayOfScreen(screen), tk_col->colormap,
+ &tk_col->color.pixel, 1, 0L);
+ Tk_DeleteErrorHandler(handler);
+ }
+ DeleteStressedCmap(DisplayOfScreen(screen), tk_col->colormap);
+
+ Tcl_DeleteHashEntry(tk_col->hash);
+ tk_col->magic = 0;
+ RadarFree(tk_col);
+ }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RealizeColorGradient --
+ *
+ * This procedure allocate the colors still unallocated in
+ * a gradient. The first and last colors are always allocated
+ * during the gradient's creation. For 3D gradients the center
+ * color is also allocated.
+ * It's called lazily by RadarColorGradientPixel, so that the
+ * colors aren't allocated until something is actually drawn
+ * with them.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+RealizeColorGradient(ColorGradient *grad,
+ Tk_Window tkwin)
+{
+ int i, num_colors;
+ XColor *base, *first, *last, color;
+ int red_range, green_range, blue_range;
+
+ if (grad->realized) {
+ return;
+ }
+
+ num_colors = grad->num_colors;
+ first = grad->colors[0];
+ last = grad->colors[num_colors-1];
+
+ /*
+ * Fill the in between colors. For a border gradient, this
+ * is done in two steps, first compute the colors between the
+ * base and the lightest, then the colors between base and the
+ * darkest. This ensure that the gradient will go through base.
+ * For others gradients proceed from lightest to darkest.
+ */
+ if ((num_colors == BORDER_STEPS) &&
+ (grad->colors[BORDER_STEPS/2+1])) {
+ base = grad->colors[BORDER_STEPS/2+1];
+ red_range = (int) base->red - (int) first->red;
+ green_range = (int) base->green - (int) first->green;
+ blue_range = (int) base->blue - (int) first->blue;
+ for (i = BORDER_STEPS/2; i > 0; i--) {
+ color.red = (int) base->red + red_range * i / (num_colors-2);
+ color.green = (int) base->green + green_range * i / (num_colors-2);
+ color.blue = (int) base->blue + blue_range * i / (num_colors-2);
+ grad->colors[i] = RadarGetColorByValue(tkwin, &color);
+ }
+ red_range = (int) base->red - (int) last->red;
+ green_range = (int) base->green - (int) last->green;
+ blue_range = (int) base->blue - (int) last->blue;
+ for (i = BORDER_STEPS/2+2; i < num_colors-1; i++) {
+ color.red = (int) base->red + red_range * i / (num_colors-2);
+ color.green = (int) base->green + green_range * i / (num_colors-2);
+ color.blue = (int) base->blue + blue_range * i / (num_colors-2);
+ grad->colors[i] = RadarGetColorByValue(tkwin, &color);
+ }
+ }
+ else {
+ red_range = (int) last->red - (int) first->red;
+ green_range = (int) last->green - (int) first->green;
+ blue_range = (int) last->blue - (int) first->blue;
+ for (i = 1; i < num_colors-1; i++) {
+ color.red =(int) first->red + red_range * i / (num_colors-2);
+ color.green = (int) first->green + green_range * i / (num_colors-2);
+ color.blue = (int) first->blue + blue_range * i / (num_colors-2);
+ grad->colors[i] = RadarGetColorByValue(tkwin, &color);
+ }
+ }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RadarColorGradientSpan --
+ *
+ *----------------------------------------------------------------------
+ */
+int
+RadarColorGradientSpan(RadarColorGradient gradient)
+{
+ return ((ColorGradient *) gradient)->num_colors;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RadarColorGradientPixel --
+ *
+ *----------------------------------------------------------------------
+ */
+int
+RadarColorGradientPixel(RadarColorGradient gradient,
+ Tk_Window tkwin,
+ int color_index)
+{
+ ColorGradient *grad = (ColorGradient *) gradient;
+
+ if (color_index < 0) {
+ color_index += grad->num_colors;
+ }
+ else if (color_index >= grad->num_colors) {
+ color_index = grad->num_colors-1;
+ }
+ if (!grad->realized) {
+ RealizeColorGradient(grad, tkwin);
+ }
+ return RadarPixel(grad->colors[color_index]);
+}
+
+
+/*
+ *--------------------------------------------------------------
+ *
+ * RadarGetReliefGradient --
+ *
+ * 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. This function allocate
+ * the base color and the two end colors in an attempt
+ * to use only actually needed resources. The function
+ * RadarColorGradientPixel asserts that all the colors
+ * get allocated when needed.
+ * 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
+ * RadarFreeColorGradient to release the resources.
+ *
+ *--------------------------------------------------------------
+ */
+RadarColorGradient
+RadarGetReliefGradient(Tcl_Interp *interp,
+ Tk_Window tkwin,
+ Tk_Uid name)
+{
+ GradientKey key;
+ Tcl_HashEntry *hash;
+ ColorGradient *grad;
+ int i, new, tmp1, tmp2;
+ XColor *base, color;
+
+ if (!initialized) {
+ ColorInit();
+ }
+
+ /*
+ * First, check to see if there's already a gradient that will work
+ * for this request.
+ */
+ key.name = name;
+ key.colormap = Tk_Colormap(tkwin);
+ key.screen = Tk_Screen(tkwin);
+
+ hash = Tcl_CreateHashEntry(&gradient_table, (char *) &key, &new);
+ if (!new) {
+ grad = (ColorGradient *) Tcl_GetHashValue(hash);
+ grad->ref_count++;
+ }
+ else {
+ /*
+ * No satisfactory gradient exists yet. Initialize a new one.
+ */
+ base = RadarGetColor(interp, tkwin, name);
+ if (base == NULL) {
+ Tcl_AppendResult(interp, " in border gradient", NULL);
+ Tcl_DeleteHashEntry(hash);
+ return NULL;
+ }
+
+ grad = (ColorGradient *) RadarMalloc(sizeof(ColorGradient) +
+ sizeof(XColor *)*(BORDER_STEPS-1));
+ grad->screen = Tk_Screen(tkwin);
+ grad->visual = Tk_Visual(tkwin);
+ grad->depth = Tk_Depth(tkwin);
+ grad->colormap = key.colormap;
+ grad->ref_count = 1;
+ grad->realized = False;
+ grad->hash = hash;
+ grad->num_colors = BORDER_STEPS;
+ /*
+ * BORDER_STEPS should be odd, the base color
+ * takes the center position.
+ */
+ grad->colors[BORDER_STEPS/2+1] = base;
+
+ /*
+ * 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 = (60 * (int) base->red)/100;
+ tmp2 = (MAX_INTENSITY + (int) base->red)/2;
+ color.red = MIN(tmp1, tmp2);
+ tmp1 = (60 * (int) base->green)/100;
+ tmp2 = (MAX_INTENSITY + (int) base->green)/2;
+ color.green = MIN(tmp1, tmp2);
+ tmp1 = (60 * (int) base->blue)/100;
+ tmp2 = (MAX_INTENSITY + (int) base->blue)/2;
+ color.blue = MIN(tmp1, tmp2);
+ grad->colors[0] = RadarGetColorByValue(tkwin, &color);
+
+ tmp1 = (14 * (int) base->red)/10;
+ if (tmp1 > MAX_INTENSITY) {
+ tmp1 = MAX_INTENSITY;
+ }
+ tmp2 = (MAX_INTENSITY + (int) base->red)/2;
+ color.red = MAX(tmp1, tmp2);
+ tmp1 = (14 * (int) base->green)/10;
+ if (tmp1 > MAX_INTENSITY) {
+ tmp1 = MAX_INTENSITY;
+ }
+ tmp2 = (MAX_INTENSITY + (int) base->green)/2;
+ color.green = MAX(tmp1, tmp2);
+ tmp1 = (14 * (int) base->blue)/10;
+ if (tmp1 > MAX_INTENSITY) {
+ tmp1 = MAX_INTENSITY;
+ }
+ tmp2 = (MAX_INTENSITY + (int) base->blue)/2;
+ color.blue = MAX(tmp1, tmp2);
+ grad->colors[BORDER_STEPS-1] = RadarGetColorByValue(tkwin, &color);
+
+ /*
+ * Now init the in between colors to let RealizeColorGradient know
+ * what it has to do.
+ */
+ for (i = BORDER_STEPS/2+1; i > 0; i--) {
+ grad->colors[i] = NULL;
+ }
+ for (i = BORDER_STEPS/2+2; i < BORDER_STEPS-1; i++) {
+ grad->colors[i] = NULL;
+ }
+ Tcl_SetHashValue(hash, grad);
+ }
+
+ /*
+ * Delay the allocation of colors until they are actually
+ * needed for drawing.
+ */
+ return (RadarColorGradient) grad;
+}
+
+
+/*
+ *--------------------------------------------------------------
+ *
+ * RadarGetColorGradient --
+ *
+ * Create a data structure containing a range of colors
+ * used to display a gradient. Name contains the gradient
+ * description in the form:
+ * color1:color2:steps
+ *
+ * Results:
+ * The return value is a token for a data structure
+ * describing a gradient. This token may be passed
+ * to the drawing routines. This function allocate
+ * the two end colors in an attempt to use only
+ * actually needed resources. The function
+ * RadarColorGradientPixel asserts that all the colors
+ * get allocated when needed.
+ * 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
+ * RadarFreeColorGradient to release the resources.
+ *
+ *--------------------------------------------------------------
+ */
+RadarColorGradient
+RadarGetColorGradientByValue(RadarColorGradient gradient)
+{
+ ColorGradient *grad;
+
+ grad = (ColorGradient *) gradient;
+ grad->ref_count++;
+ return gradient;
+}
+
+
+RadarColorGradient
+RadarGetColorGradient(Tcl_Interp *interp,
+ Tk_Window tkwin,
+ Tk_Uid name)
+{
+ GradientKey key;
+ Tcl_HashEntry *hash;
+ ColorGradient *grad;
+ int i, new, steps, num_tok;
+ XColor *first, *last;
+ char name_first[COLOR_NAME_SIZE], name_last[COLOR_NAME_SIZE];
+
+ if (!initialized) {
+ ColorInit();
+ }
+
+ /*
+ * First, check to see if there's already a gradient that will work
+ * for this request.
+ */
+ key.name = name;
+ key.colormap = Tk_Colormap(tkwin);
+ key.screen = Tk_Screen(tkwin);
+
+ hash = Tcl_CreateHashEntry(&gradient_table, (char *) &key, &new);
+ if (!new) {
+ grad = (ColorGradient *) Tcl_GetHashValue(hash);
+ grad->ref_count++;
+ }
+ else {
+ /*
+ * No satisfactory gradient exists yet. Initialize a new one.
+ */
+ num_tok = sscanf(name, "%s:%s:%d", name_first, name_last, &steps);
+ if ((num_tok != 3) || (steps < 2)) {
+ Tcl_AppendResult(interp, "incorrect gradient format \"",
+ name, "\"", NULL);
+ grad_err:
+ Tcl_DeleteHashEntry(hash);
+ return NULL;
+ }
+ first = RadarGetColor(interp, tkwin, name_first);
+ if (first == NULL) {
+ grad_err2:
+ Tcl_AppendResult(interp, " in gradient", NULL);
+ goto grad_err;
+ }
+ last = RadarGetColor(interp, tkwin, name_last);
+ if (last == NULL) {
+ RadarFreeColor(first);
+ goto grad_err2;
+ }
+
+ grad = (ColorGradient *) RadarMalloc(sizeof(ColorGradient) +
+ sizeof(XColor *)*(steps-1));
+ grad->screen = Tk_Screen(tkwin);
+ grad->visual = Tk_Visual(tkwin);
+ grad->depth = Tk_Depth(tkwin);
+ grad->colormap = key.colormap;
+ grad->ref_count = 1;
+ grad->colors[0] = first;
+ grad->colors[steps-1] = last;
+ for (i = 0; i < steps-1; i++) {
+ grad->colors[i] = NULL;
+ }
+ grad->num_colors = steps;
+ grad->realized = False;
+ grad->hash = hash;
+ Tcl_SetHashValue(hash, grad);
+ }
+
+ /*
+ * Delay the allocation of colors until they are actually
+ * needed for drawing.
+ */
+ return (RadarColorGradient) grad;
+}
+
+
+/*
+ *--------------------------------------------------------------
+ *
+ * RadarNameOfColorGradient --
+ *
+ * Given a gradient, return a textual string identifying
+ * the gradient. This can be either a single color (for
+ * gradients allocated by RadarGetReliefGradient) or a
+ * full gradient spec: color1:color2:steps.
+ *
+ * Results:
+ * The return value is the string that was used to create
+ * the gradient.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+char *
+RadarNameOfColorGradient(RadarColorGradient gradient)
+{
+ ColorGradient *grad = (ColorGradient *) gradient;
+
+ return ((GradientKey *) grad->hash->key.words)->name;
+}
+
+
+/*
+ *--------------------------------------------------------------
+ *
+ * RadarFreeColorGradient --
+ *
+ * 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
+RadarFreeColorGradient(RadarColorGradient gradient)
+{
+ ColorGradient *grad = (ColorGradient *) gradient;
+ int i;
+
+ grad->ref_count--;
+ if (grad->ref_count == 0) {
+ for (i = 0; i < grad->num_colors; i++) {
+ if (grad->colors[i] != NULL) {
+ RadarFreeColor(grad->colors[i]);
+ }
+ }
+ Tcl_DeleteHashEntry(grad->hash);
+ RadarFree(grad);
+ }
+}