
/* Copyright (C) 1992  AHPCRC, Univeristy of Minnesota
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program in a file named 'Copying'; if not, write to
 * the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139.
 */

/* Author:
 *	Ken Chin-Purcell (ken@ahpcrc.umn.edu)
 *	Army High Performance Computing Research Center (AHPCRC)
 *	Univeristy of Minnesota
 *
 * $Header: /usr/people/ken/gvl/icol/RCS/cbar.c,v 2.1 92/10/19 16:59:27 ken Exp $
 *
 * $Log:	cbar.c,v $
 * Revision 2.1  92/10/19  16:59:27  ken
 * *** empty log message ***
 * 
 * Revision 1.1  92/10/19  16:58:09  ken
 * Initial revision
 * 
 */

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <math.h>

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

#include "util.h"
#include "xtutil.h"
#include "rgb.h"
#include "cbar.h"

#define MARGIN		3
#define BMARGIN		4
#define BARSPACE	2
#define MAXBAR		60
#define BRUSHSIZE	8


static void Crgb2hsv(Color *color)
{
    rgb2hsv( color->channel[0],  color->channel[1],  color->channel[2], 
	    &color->channel[0], &color->channel[1], &color->channel[2]);
}


static void Chsv2rgb(Color *color)
{
    hsv2rgb( color->channel[0],  color->channel[1],  color->channel[2],
	    &color->channel[0], &color->channel[1], &color->channel[2]);
}


static void Crgb2yuv(Color *color)
{
    rgb2yuv( color->channel[0],  color->channel[1],  color->channel[2], 
	    &color->channel[0], &color->channel[1], &color->channel[2]);
}


static void Cyuv2rgb(Color *color)
{
    yuv2rgb( color->channel[0],  color->channel[1],  color->channel[2],
	    &color->channel[0], &color->channel[1], &color->channel[2]);
}


static void ColorSet(Color *color, float r, float g, float b, int knot)
{
    color->channel[0] = r;
    color->channel[1] = g;
    color->channel[2] = b;
    color->knot   = knot;
}


void GetRGB(ColorBar *cbar, int i, float *r, float *g, float *b)
{
    float	*channel = cbar->color[i].channel;

    switch (cbar->rgbmode) {
      case RGB_MODE:
	*r = channel[0];
	*g = channel[1];
	*b = channel[2];
	break;
      case HSV_MODE:
	hsv2rgb(channel[0], channel[1], channel[2], r, g, b);
	break;
      case YUV_MODE:
      case ALPHA_MODE:
	yuv2rgb(channel[0], channel[1], channel[2], r, g, b);
	break;
    }
}


void PutRGB(ColorBar *cbar, int i, float r, float g, float b)
{
    float	*channel = cbar->color[i].channel;

    if (r < 0) r = 0;
    if (r > 1) r = 1;
    if (g < 0) g = 0;
    if (g > 1) g = 1;
    if (b < 0) b = 0;
    if (b > 1) b = 1;

    switch (cbar->rgbmode) {
      case RGB_MODE:
	channel[0] = r;
	channel[1] = g;
	channel[2] = b;
	break;
      case HSV_MODE:
	rgb2hsv(r, g, b, channel, channel + 1, channel + 2);
	break;
      case YUV_MODE:
      case ALPHA_MODE:
	rgb2yuv(r, g, b, channel, channel + 1, channel + 2);
	break;
    }
    if (cbar->color[i].knot  &&  i > 0  &&  i < cbar->ncolor-1) {
	if (cbar->focus == i)
	    ColorBarUnsetFocus(cbar);
	cbar->color[i].knot = False;
    }
}


#include "square.x"
#include "circle.x"
#include "diamond.x"

static void MakeBrushIcon(ColorBar *cbar, Icon *icon, char *bits, 
			  unsigned int w, unsigned int h, unsigned int depth)
{
    Pixel	fg = cbar->fg[3] ^ cbar->bg;
    Pixel	bg = 0;
   
    icon->pixmap = 
	XCreatePixmapFromBitmapData(cbar->display, XtWindow(cbar->chart), 
				    bits, w, h, fg, bg, depth);
    icon->width  = w;
    icon->height = h;
}

static int sq_lift[] = {
    8, 8, 8, 8,   8, 8, 8, 8,   8, 8, 8, 8,   8, 8, 8, 8,
};
static int ci_lift[] = {
    2, 4, 6, 6,   7, 7, 8, 8,   8, 8, 7, 7,   6, 6, 4, 2,
};
static int di_lift[] = {
    1, 2, 3, 4,   5, 6, 7, 8,   8, 7, 6, 5,   4, 3, 2, 1,
};

static void MakeBrush(ColorBar *cbar)
{
    Brush	*brush = &cbar->brush;
    int		depth;

    GetValue(XtNdepth, &depth, cbar->chart);

    MakeBrushIcon(cbar, brush->icon + 0, 
		  square_bits, square_width, square_height, depth);
    MakeBrushIcon(cbar, brush->icon + 1, 
		  circle_bits, circle_width, circle_height, depth);
    MakeBrushIcon(cbar, brush->icon + 2, 
		  diamond_bits, diamond_width, diamond_height, depth);

    brush->lift = CallocType(int *, 3);
    MemCheck(brush->lift);
    brush->lift[0] = sq_lift;
    brush->lift[1] = ci_lift;
    brush->lift[2] = di_lift;
    brush->channel = -1;
    brush->kind = 0;
}


static char *cbarColorName[NFGCOLOR] = {
    "red", "green", "blue", "black", "white"
    };

void ColorBarResizeButtons(ColorBar *cbar)
{
    int		x, y, width, height;
    int		rows, cols;
    int		knotbump;
    Dimension	wdim, hdim;
    
    int		i;

    GetValue(XtNwidth, &wdim, (Widget) NULL);
    GetValue(XtNheight, &hdim, cbar->button);

    width  = wdim - 2*BMARGIN;
    height = hdim - 2*BMARGIN;

    cbar->cellSize = 0;
    cbar->knotSize = 0;
    
    if (width <= 0  ||  height <= 0)
	return;

    cols = 1 + (sqrt((double) cbar->ncolor) * 
		sqrt((double) width / (double) height));
    cols = MAX(cols, 1);
    cols = MIN(cols, cbar->ncolor);
    rows = cbar->ncolor / cols;
    while (rows * cols < cbar->ncolor)
	rows++;

    cbar->cellSize = MIN(width / cols, height / rows);
    cbar->knotSize = MAX(3, cbar->cellSize / 4);
    if (cbar->knotSize % 2  ==  cbar->cellSize % 2)
	cbar->knotSize++;

    x = (wdim - cbar->cellSize * cols) / 2;
    y = (hdim - cbar->cellSize * rows) / 2;
    knotbump = 1 + (cbar->cellSize - cbar->knotSize - 1) / 2;

    for (i = 0; i < cbar->ncolor; i++) {
	cbar->color[i].cx = x + cbar->cellSize * (i % cols);
	cbar->color[i].cy = y + cbar->cellSize * (i / cols);
	cbar->color[i].bx = cbar->color[i].cx + knotbump;
	cbar->color[i].by = cbar->color[i].cy + knotbump;
    }
}


static void SetLineWidth(ColorBar *cbar, int width)
{
    XSetLineAttributes(cbar->display, cbar->gc, width,
		       LineSolid, CapButt, JoinMiter);
}


void ColorBarResizeChart(ColorBar *cbar)
{
    Dimension	wdim, hdim;
    int		depth, lwidth;
    Rectangle	*gr = &cbar->graphRect;
    Rectangle	*br = &cbar->barRect;
    int		i;
  
    GetValue(XtNwidth, &wdim, (Widget) NULL);
    GetValue(XtNdepth, &depth, (Widget) NULL);
    GetValue(XtNheight, &hdim, cbar->chart);

    br->x      = MARGIN;
    br->y      = hdim/2 + BARSPACE;
    br->width  = wdim - 2*MARGIN + 2;
    br->height = hdim - MARGIN - br->y;

    if (br->height > MAXBAR) {
	br->y += br->height - MAXBAR;	
	br->height = MAXBAR;
    }

    gr->x      = MARGIN;
    gr->y      = MARGIN;
    gr->width  = wdim - 2*MARGIN + 1;
    gr->height = br->y - BARSPACE - 1 - gr->y;

    lwidth     = 1 + br->width / cbar->ncolor;

    if (cbar->barPixmap)
	XFreePixmap(cbar->display, cbar->barPixmap);
    cbar->barPixmap = XCreatePixmap(cbar->display, XtWindow(cbar->chart), 
				    br->width - lwidth, br->height, depth);
	
    SetLineWidth(cbar, lwidth == 1 ? 0 : lwidth);

    for (i = 0; i < cbar->ncolor; i++) {
	int	x = (i * br->width) / cbar->ncolor;
	XSetForeground(cbar->display, cbar->gc, cbar->color[i].xcolor->pixel);
	XDrawLine(cbar->display, cbar->barPixmap, cbar->gc, 
		  x, 0, x, br->height);
    }

    br->width -= lwidth;
}


void ColorBarSetWidget(ColorBar *cbar, Widget chart, Widget button)
{
    XGCValues	gcv;

    cbar->chart = chart;
    cbar->button = button;

    GetValue(XtNbackground, &gcv.background, (Widget) chart);
    cbar->bg = gcv.background;
    cbar->gc = XCreateGC(cbar->display, XtWindow(cbar->chart), 
			 GCBackground, &gcv);
    
    MakeBrush(cbar);
    ColorBarResizeChart(cbar);
    ColorBarResizeButtons(cbar);
}


void ColorBarStore(ColorBar *cbar, Colormap cmap)
{
    /* Time critical routine.
     */
    int		i;
    float	r, g, b;
    float	cscale = 0xffff;

    for (i = 0; i < cbar->ncolor; i++) {
	GetRGB(cbar, i, &r, &g, &b);
	cbar->color[i].xcolor->red   = r * cscale;
	cbar->color[i].xcolor->green = g * cscale;
	cbar->color[i].xcolor->blue  = b * cscale;
    }
    
    XStoreColors(cbar->display, cmap, cbar->xcolor, cbar->nxcolor);
}


void ColorBarStomp(ColorBar *cbar, Colormap cmap)
{
    /* Time critical routine.
     */
    int		i;
    float	r, g, b;
    float	cscale = 0xffff;

    for (i = 0; i < cbar->ncolor; i++) {
	GetRGB(cbar, i, &r, &g, &b);
	cbar->color[i].xstomp->red   = r * cscale;
	cbar->color[i].xstomp->green = g * cscale;
	cbar->color[i].xstomp->blue  = b * cscale;
    }
    
    XStoreColors(cbar->display, cmap, cbar->xstomp, cbar->nxstomp);
}


void ColorBarDrawBar(ColorBar *cbar)
{
    Rectangle	*br = &cbar->barRect;

    if (br->width <= 0  ||  br->height <= 0)
	return;

    XCopyArea(cbar->display, cbar->barPixmap, XtWindow(cbar->chart), cbar->gc, 
	      0, 0, br->width, br->height, br->x, br->y);
}


static void DrawKnots(ColorBar *cbar)
{
    Rectangle	*gr = &cbar->graphRect;
    int		x, y;
    int		i;
    
    for (i = 0; i < cbar->ncolor; i++) {
	if (cbar->color[i].knot) {	
	    if (cbar->rgbmode == ALPHA_MODE)
		y = gr->height * (1 - cbar->color[i].channel[0]);
	    else
		y = gr->height * (1 - max3(cbar->color[i].channel[0], 
					   cbar->color[i].channel[1],
					   cbar->color[i].channel[2]));
	    x = gr->x + (i * gr->width) / cbar->ncolor;
	    if (i == cbar->focus)
		XSetForeground(cbar->display, cbar->gc, cbar->fg[3]);
	    else
		XSetForeground(cbar->display, cbar->gc, cbar->fg[4]);
	    XDrawLine(cbar->display, XtWindow(cbar->chart), cbar->gc, 
		      x, gr->y + y + 1, x, gr->y + gr->height + 2);
	}
    }
}


static void DrawGraph(ColorBar *cbar, int xormode)
{
    /* Time critical routine
     */
    int		x = cbar->graphRect.x;
    float	y = cbar->graphRect.y + cbar->graphRect.height + 0.5;
    int		width  = cbar->graphRect.width;
    float	height = -cbar->graphRect.height;
    int		ncolor = cbar->ncolor;
    Color	*col = cbar->color;

    static XPoint	*point = NULL;
    static int		*draw = NULL;
    
    if (!point) {
	point = CallocType(XPoint, cbar->ncolor);
	MemCheck(point);
	draw = CallocType(int, cbar->ncolor);
	MemCheck(draw);
    }
    
    if (xormode) {
	int	skip = MAX(1, ncolor / cbar->quickSeg);
	int	ch = cbar->changeChannel;
	int	i, j, end;

	for (i = 0; i < ncolor; i += skip) {
	    draw[i] = 1;
	    end = MIN(i + skip, ncolor - 1);
	    for (j = i + 1; j < end; j++)
		draw[j] = (col[j].channel[ch] > col[i].channel[ch])  != 
		    (col[j].channel[ch] < col[end].channel[ch]);
	}
	draw[end] = 1;

	j = 0;
	for (i = 0; i < ncolor; i++)
	    if (draw[i]) {
		point[j].x = x + (i * width) / ncolor;
		point[j].y = y + height * col[i].channel[ch];
		++j;
	    }
	
	if (cbar->rgbmode == ALPHA_MODE)
	    XSetForeground(cbar->display, cbar->gc, cbar->fg[3] ^ cbar->bg);
	else
	    XSetForeground(cbar->display, cbar->gc, cbar->fg[ch] ^ cbar->bg);
	XDrawLines(cbar->display, XtWindow(cbar->chart), cbar->gc, 
		   point, j, CoordModeOrigin);

    } else {
	int	i, k;

	for (i = 0; i < ncolor; i++)
	    point[i].x = x + (i * width) / ncolor;
	
	for (k = 0; k < 3; k++) {
	    if (k == cbar->changeChannel)
		continue;
	    if (cbar->rgbmode == ALPHA_MODE  &&  k > 0)
		continue;

	    for (i = 0; i < cbar->ncolor; i++)
		point[i].y = y + height * col[i].channel[k];
	    
	    if (cbar->rgbmode == ALPHA_MODE)
		XSetForeground(cbar->display, cbar->gc, cbar->fg[3]);
	    else
		XSetForeground(cbar->display, cbar->gc, cbar->fg[k]);
	    XDrawLines(cbar->display, XtWindow(cbar->chart), cbar->gc, 
		       point, cbar->ncolor, CoordModeOrigin);
	}
    }
}


static void DrawBrush(ColorBar *cbar)
{
    Rectangle	*gr = &cbar->graphRect;
    Icon	*icon = cbar->brush.icon + cbar->brush.kind;
    int		x, y;
    
    x = gr->x + (cbar->brush.index * gr->width) / cbar->ncolor - BRUSHSIZE;
    y = gr->y + (int) (gr->height * (1 - cbar->brush.value)) - BRUSHSIZE;
    if (!cbar->brush.above)
	++y;
    
    XCopyArea(cbar->display, icon->pixmap, XtWindow(cbar->chart), cbar->gc, 
	      0, 0, icon->width, icon->height, x, y);
}


void ColorBarClearGraph(ColorBar *cbar)
{
    Rectangle	*gr = &cbar->graphRect;

    XClearArea(cbar->display, XtWindow(cbar->chart), 
	       gr->x, gr->y, gr->width, gr->height + 3, False);
}


void ColorBarDrawGraph(ColorBar *cbar, int xormode)
{
    Rectangle	*gr = &cbar->graphRect;
    
    if (gr->width <= 0  ||  gr->height <= 0)
	return;
    
    SetLineWidth(cbar, 0);

    if (!xormode) {
	if (cbar->changeChannel >= 0)
	    ColorBarClearGraph(cbar);
	DrawKnots(cbar);
	DrawGraph(cbar, False);
    }

    if (xormode || cbar->changeChannel >= 0) {
	XSetFunction(cbar->display, cbar->gc, GXxor);
	DrawGraph(cbar, True);
	if (cbar->brush.channel >= 0)
	    DrawBrush(cbar);
	XSetFunction(cbar->display, cbar->gc, GXcopy);
    }
}


void ColorBarDrawChart(ColorBar *cbar)
{
    ColorBarDrawGraph(cbar, False);
    ColorBarDrawBar(cbar);
}


void ColorBarDrawButtons(ColorBar *cbar)
{
    Window	win = XtWindow(cbar->button);
    Color	*color;
    int		i;

    if (cbar->cellSize <= 0)
	return;

    SetLineWidth(cbar, 0);

    for (i = 0; i < cbar->ncolor; i++) {
	color = cbar->color + i;

	XSetForeground(cbar->display, cbar->gc, color->xcolor->pixel);
	XFillRectangle(cbar->display, win, cbar->gc, color->cx, color->cy, 
		       cbar->cellSize, cbar->cellSize);
	XSetForeground(cbar->display, cbar->gc, cbar->fg[3]);
	XDrawRectangle(cbar->display, win, cbar->gc, color->cx, color->cy, 
		       cbar->cellSize, cbar->cellSize);

	if (color->knot) {
	    float	r, g, b;

	    XFillRectangle(cbar->display, win, cbar->gc, color->bx, color->by, 
			   cbar->knotSize, cbar->knotSize);
	    GetRGB(cbar, i, &r, &g, &b);
	    if (r + 2*g + b < 1.2) {
		XSetForeground(cbar->display, cbar->gc, cbar->fg[4]);
		XDrawRectangle(cbar->display, win, cbar->gc, 
			       color->bx - 1, color->by - 1, 
			       cbar->knotSize + 1, cbar->knotSize + 1);
	    }
	}
    }

    color = cbar->color + cbar->focus;

    SetLineWidth(cbar, 2);
    XSetForeground(cbar->display, cbar->gc, cbar->fg[3]);
    XDrawRectangle(cbar->display, win, cbar->gc, color->cx - 3, color->cy - 3, 
		   cbar->cellSize + 7, cbar->cellSize + 7);

    SetLineWidth(cbar, 3);
    XSetForeground(cbar->display, cbar->gc, cbar->fg[4]);
    XDrawRectangle(cbar->display, win, cbar->gc, color->cx - 1, color->cy - 1, 
		   cbar->cellSize + 2, cbar->cellSize + 2);
}


int ColorBarFindButton(ColorBar *cbar, int x, int y)
{
    int		i;
    Color	*color;

    for (i = 0; i < cbar->ncolor; i++) {
	color = cbar->color + i;
	if (x >= color->cx  &&  x < color->cx + cbar->cellSize  &&
	    y >= color->cy  &&  y < color->cy + cbar->cellSize)
	    return i;
    }
    return -1;
}


int ColorBarFindChart(ColorBar *cbar, int x)
{
    int		i;

    i = ((x - cbar->graphRect.x) * cbar->ncolor) / cbar->graphRect.width;
    if (i < 0)
	i = 0;
    if (i >= cbar->ncolor)
	i = cbar->ncolor - 1;
    return i;
}


void ColorBarSetFocus(ColorBar *cbar, int focus)
{
    XClearArea(cbar->display, XtWindow(cbar->button), 
	       cbar->color[cbar->focus].cx - 5, cbar->color[cbar->focus].cy - 5, 
	       cbar->cellSize + 10, cbar->cellSize + 10, False);
    cbar->focus = MIN(cbar->ncolor - 1, focus);
    cbar->color[cbar->focus].knot = True;
}


void ColorBarUnsetFocus(ColorBar *cbar)
{
    int		i;

    if (cbar->focus == 0  || cbar->focus == cbar->ncolor - 1)
	return;
    
    cbar->color[cbar->focus].knot = False;
    XClearArea(cbar->display, XtWindow(cbar->button), 
	       cbar->color[cbar->focus].cx - 5, cbar->color[cbar->focus].cy - 5, 
	       cbar->cellSize + 10, cbar->cellSize + 10, False);

    for (i = cbar->focus; i < cbar->ncolor; i++)
	if (cbar->color[i].knot) {
	    cbar->focus = i;
	    break;
	}
}


void ColorBarPlaceKnots(ColorBar *cbar, int slopediff)
{
    int		i, j, k;
    float	a, b, c, d, e;
    float	sd = slopediff / 2560.0;
    
    j = cbar->ncolor - 2;
    for (i = 1; i <= j; i++) {
	cbar->color[i].knot = 0;
	for (k = 0; k < 3; k++) {
	    b = cbar->color[i-1].channel[k];
	    c = cbar->color[i  ].channel[k];
	    d = cbar->color[i+1].channel[k];
	    if (i > 1 && i < j) {
		a = cbar->color[i-2].channel[k];
		e = cbar->color[i+2].channel[k];
		if (fabs(a + c - 2*b) < sd &&
		    fabs(c + e - 2*d) < sd &&
		    fabs(a + e - 2*c) > 2*sd)
		    cbar->color[i].knot = True;
	    }
	    if (fabs(b + d - 2*c) > 4*sd)
		cbar->color[i].knot = True;
	}
    }
    if (!cbar->color[cbar->focus].knot)
	ColorBarUnsetFocus(cbar);
}


void ColorBarSet(ColorBar *cbar, int ic, float value)
{
    int		iprev = cbar->focus;
    int		inext = cbar->focus;
    float	endval;
    int		i;
    
    while (--iprev >= 0)
	if (cbar->color[iprev].knot)
	    break;
    while (++inext < cbar->ncolor)
	if (cbar->color[inext].knot)
	    break;

    endval = value - cbar->color[cbar->focus].channel[ic];

    for (i = iprev + 1; i < cbar->focus; i++) {
	float	fe = (float) (i - iprev) / (float) (cbar->focus - iprev);
	
	cbar->color[i].channel[ic] += fe * endval;
	if (cbar->color[i].channel[ic] > 1) 
	    cbar->color[i].channel[ic] = 1;
	if (cbar->color[i].channel[ic] < 0) 
	    cbar->color[i].channel[ic] = 0;
    }

    for (i = cbar->focus; i < inext; i++) {
	float	fb = 1 - (float) (i - cbar->focus) / (float) (inext - cbar->focus);
	
	cbar->color[i].channel[ic] += fb * endval;
	if (cbar->color[i].channel[ic] > 1) 
	    cbar->color[i].channel[ic] = 1;
	if (cbar->color[i].channel[ic] < 0) 
	    cbar->color[i].channel[ic] = 0;
    }
}


void ColorBarSnap(ColorBar *cbar, int channel)
{
    int		iprev = cbar->focus;
    int		inext = cbar->focus;
    float	*begval, *endval;
    int		i, k;

    /* Limit color space in yuv mode
     */
    if (cbar->rgbmode == YUV_MODE) {
	Cyuv2rgb(cbar->color + cbar->focus);
	Crgb2yuv(cbar->color + cbar->focus);
    }

    while (--iprev >= 0)
	if (cbar->color[iprev].knot)
	    break;
    while (++inext < cbar->ncolor)
	if (cbar->color[inext].knot)
	    break;
    
    begval = cbar->color[iprev].channel;
    endval = cbar->color[cbar->focus].channel;

    for (i = iprev + 1; i < cbar->focus; i++) {
	float	fe = (float) (i - iprev) / (float) (cbar->focus - iprev);
	float	fb = 1 - fe;

	if (channel < 0)
	    for (k = 0; k < 3; k++)
		cbar->color[i].channel[k] = fb * begval[k] + fe * endval[k];
	else
	    cbar->color[i].channel[channel] = 
		fb * begval[channel] + fe * endval[channel];
    }

    begval = cbar->color[cbar->focus].channel;
    endval = cbar->color[inext].channel;

    for (i = cbar->focus + 1; i < inext; i++) {
	float	fe = (float) (i - cbar->focus) / (float) (inext - cbar->focus);
	float	fb = 1 - fe;
	
	if (channel < 0)
	    for (k = 0; k < 3; k++)
		cbar->color[i].channel[k] = fb * begval[k] + fe * endval[k];
	else
	    cbar->color[i].channel[channel] = 
		fb * begval[channel] + fe * endval[channel];
    }
}


void ColorBarSnapAll(ColorBar *cbar)
{
    int		iprev = 0;
    int		inext = 0;
    float	*begval, *endval;
    int		i, k;

    /* Limit color space in yuv mode
     */
    if (cbar->rgbmode == YUV_MODE) {
	Cyuv2rgb(cbar->color + inext);
	Crgb2yuv(cbar->color + inext);
    }
    
    while (iprev < cbar->ncolor - 1) {
	while (++inext < cbar->ncolor)
	    if (cbar->color[inext].knot)
		break;
	
	if (cbar->rgbmode == YUV_MODE) {
	    Cyuv2rgb(cbar->color + inext);
	    Crgb2yuv(cbar->color + inext);
	}
	
	begval = cbar->color[iprev].channel;
	endval = cbar->color[inext].channel;
	
	for (i = iprev + 1; i < inext; i++) {
	    float	fe = (float) (i - iprev) / (float) (inext - iprev);
	    float	fb = 1 - fe;
	    
	    for (k = 0; k < 3; k++)
		cbar->color[i].channel[k] = fb * begval[k] + fe * endval[k];
	}

	iprev = inext;
    }
}


void ColorBarHSV(ColorBar *cbar)
{
    int		i;
    
    switch (cbar->rgbmode) {
      case RGB_MODE:
	for (i = 0; i < cbar->ncolor; i++)
	    Crgb2hsv(cbar->color + i);
	break;
      case HSV_MODE:
	break;
      case YUV_MODE:
      case ALPHA_MODE:
	for (i = 0; i < cbar->ncolor; i++) {
	    Cyuv2rgb(cbar->color + i);
	    Crgb2hsv(cbar->color + i);
	}
	break;
    }

    cbar->rgbmode = HSV_MODE;
}


void ColorBarRGB(ColorBar *cbar)
{
    int		i;
    
    switch (cbar->rgbmode) {
      case RGB_MODE:
	break;
      case HSV_MODE:
	for (i = 0; i < cbar->ncolor; i++)
	    Chsv2rgb(cbar->color + i);
	break;
      case YUV_MODE:
      case ALPHA_MODE:
	for (i = 0; i < cbar->ncolor; i++)
	    Cyuv2rgb(cbar->color + i);
	break;
    }

    cbar->rgbmode = RGB_MODE;
}


void ColorBarYUV(ColorBar *cbar)
{
    int		i;
    
    switch (cbar->rgbmode) {
      case RGB_MODE:
	for (i = 0; i < cbar->ncolor; i++)
	    Crgb2yuv(cbar->color + i);
	break;
      case HSV_MODE:
	for (i = 0; i < cbar->ncolor; i++) {
	    Chsv2rgb(cbar->color + i);
	    Crgb2yuv(cbar->color + i);
	}
	break;
      case YUV_MODE:
      case ALPHA_MODE:
	break;
    }

    cbar->rgbmode = YUV_MODE;
}


void ColorBarAlpha(ColorBar *cbar)
{
    int		i;
    
    switch (cbar->rgbmode) {
      case RGB_MODE:
	for (i = 0; i < cbar->ncolor; i++)
	    Crgb2yuv(cbar->color + i);
	break;
      case HSV_MODE:
	for (i = 0; i < cbar->ncolor; i++) {
	    Chsv2rgb(cbar->color + i);
	    Crgb2yuv(cbar->color + i);
	}
	break;
      case YUV_MODE:
	break;
      case ALPHA_MODE:
	break;
    }

    for (i = 0; i < cbar->ncolor; i++)
	cbar->color[i].channel[1] = cbar->color[i].channel[2] = 0.5;

    cbar->rgbmode = ALPHA_MODE;
}


static void BrushBump(ColorBar *cbar)
{
    int		width = cbar->graphRect.width;
    float	bsize = (float) (BRUSHSIZE*cbar->ncolor) / (float) width;
    int		bmin = floor(cbar->brush.index - bsize);
    int		bmax = ceil(cbar->brush.index + bsize);
    float	bfact = 1 / (float) cbar->graphRect.height;
    float	limit;
    float	*v;
    int		i, j;
    
    bmin = MAX(bmin, 0);
    bmin = MIN(bmin, cbar->ncolor);
    bmax = MAX(bmax, 0);
    bmax = MIN(bmax, cbar->ncolor);
    
    for (i = bmin; i < bmax; i++) {
	v = cbar->color[i].channel + cbar->brush.channel;
	j = BRUSHSIZE + (i - cbar->brush.index) * width / cbar->ncolor;
	j = MAX(j, 0);
	j = MIN(j, 15);
	limit = bfact * cbar->brush.lift[cbar->brush.kind][j];

	if (cbar->brush.above) {
	    limit = cbar->brush.value - limit;
	    if (*v > limit)
		*v = limit;
	} else {
	    limit = cbar->brush.value + limit;
	    if (*v < limit)
		*v = limit;
	}
	if (*v < 0)
	    *v = 0;
	if (*v > 1)
	    *v = 1;
    }
}


void ColorBarSetBrush(ColorBar *cbar, int brush, int x, int y)
{
    Rectangle	*gr = &cbar->graphRect;
    int		bi;

    switch (brush) {
      case Button1:
	cbar->brush.channel = 0;
	break;
      case Button2:
	cbar->brush.channel = 1;
	break;
      case Button3:
	cbar->brush.channel = 2;
	break;
      default:
	cbar->brush.channel = -1;
	break;
    }

    cbar->changeChannel = cbar->brush.channel;
    if (cbar->brush.channel < 0)
	return;
    
    cbar->brush.index = ((x - gr->x) * cbar->ncolor) / gr->width;
    cbar->brush.value = 1 - (float) (y - gr->y) / (float) gr->height;

    bi = MAX(cbar->brush.index, 0);
    bi = MIN(bi, cbar->ncolor - 1);
    cbar->brush.above = cbar->brush.value > 
	cbar->color[bi].channel[cbar->brush.channel];

    BrushBump(cbar);
}


void ColorBarMoveBrush(ColorBar *cbar, int x, int y)
{
    Rectangle	*gr = &cbar->graphRect;
    
    if (cbar->brush.channel < 0)
	return;
    
    cbar->brush.index = ((x - gr->x) * cbar->ncolor) / gr->width;
    cbar->brush.value = 1 - (float) (y - gr->y) / (float) gr->height;

    BrushBump(cbar);
}


static SaveStack *NewSaveStack(int size, int ncolor)
{
    SaveStack	*stack = MallocType(SaveStack);
    int		i;
    
    MemCheck(stack);

    stack->nsave = MAX(1, size);
    stack->save = CallocType(SaveBar, stack->nsave);
    MemCheck(stack->save);
    
    for (i = 0; i < stack->nsave; i++) {
	stack->save[i].color = CallocType(SaveColor, ncolor);
	MemCheck(stack->save[i].color);
    }

    stack->base = 0;
    stack->size = 0;
    
    return stack;
}


static SaveBar *PushSave(SaveStack *stack)
{
    if (stack->size < stack->nsave)
	++stack->size;
    else
	stack->base = (stack->base + 1) % stack->nsave;
    
    return stack->save + ((stack->base + stack->size - 1) % stack->nsave);
}


static SaveBar *PopSave(SaveStack *stack)
{
    if (stack->size > 0) {
	--stack->size;
	return stack->save + ((stack->base + stack->size) % stack->nsave);
    } else
	return NULL;
}


void SaveBarWrite(SaveBar *save, ColorBar *cbar)
{
    int		i;

    /* Save colors, knots, color mode, and current focus
     */
    save->focus = cbar->focus;
    save->rgbmode = cbar->rgbmode;

    for (i = 0; i < cbar->ncolor; i++) {
	save->color[i].channel[0] = cbar->color[i].channel[0];
	save->color[i].channel[1] = cbar->color[i].channel[1];
	save->color[i].channel[2] = cbar->color[i].channel[2];
	save->color[i].knot = cbar->color[i].knot;
    }
}


void SaveBarRead(SaveBar *save, ColorBar *cbar)
{
    int		i;

    if (!save)
	return;

    /* Restore colors, knots, color mode, and focus
     */
    for (i = 0; i < cbar->ncolor; i++) {
	cbar->color[i].channel[0] = save->color[i].channel[0];
	cbar->color[i].channel[1] = save->color[i].channel[1];
	cbar->color[i].channel[2] = save->color[i].channel[2];
	cbar->color[i].knot = save->color[i].knot;
    }

    cbar->focus = save->focus;
    cbar->rgbmode = save->rgbmode;
}


void ColorBarSave(ColorBar *cbar, int undo)
{
    SaveBarWrite(PushSave(undo ? cbar->undo : cbar->redo), cbar);
}


void ColorBarRestore(ColorBar *cbar, int undo)
{
    int		lastFocus = cbar->focus;
    int		nextFocus;

    SaveBarRead(PopSave(undo ? cbar->undo : cbar->redo), cbar);
    if (lastFocus != cbar->focus) {
	nextFocus = cbar->focus;
	cbar->focus = lastFocus;
	ColorBarSetFocus(cbar, nextFocus);
    }
}


ColorBar *ColorBarNew(Widget w, Colormap cmap, int mapsize, int ncolor, 
		      int nsave, int quickSeg, int stompbase)
{
    ColorBar	*cbar = MallocType(ColorBar);
    int		i;
    XColor	xcExact, xcScreen;
    Pixel	*holdPix;

    MemCheck(cbar);
    ZeroType(cbar, ColorBar);

    cbar->ncolor = ncolor;
    cbar->color = CallocType(Color, cbar->ncolor);
    MemCheck(cbar->color);

    cbar->nxcolor = ncolor;
    cbar->xcolor = CallocType(XColor, cbar->nxcolor);
    MemCheck(cbar->xcolor);

    cbar->nxstomp = MIN(ncolor, mapsize);
    cbar->xstomp = CallocType(XColor, cbar->nxstomp);
    MemCheck(cbar->xstomp);

    cbar->rgbmode = RGB_MODE;
    cbar->display = XtDisplay(w);

    for (i = 0; i < NFGCOLOR; i++) {
	(void) XAllocNamedColor(cbar->display, cmap, cbarColorName[i],
				&xcScreen, &xcExact);
	cbar->fg[i] = xcScreen.pixel;
    }
  
    for (i = 1; i < cbar->ncolor - 1; i++) {
	float	c = (float) i / (float) (cbar->ncolor - 1);
	ColorSet(cbar->color + i, c, c, c, False);
    }

    ColorSet(cbar->color, 0.0, 0.0, 0.0, True);
    ColorSet(cbar->color + cbar->ncolor - 1, 1.0, 1.0, 1.0, True);
    
    cbar->focus = 0;
    
    holdPix = CallocType(Pixel, ncolor);
    MemCheck(holdPix);
    
    for ( ; cbar->nxcolor; --cbar->nxcolor)
	if (XAllocColorCells(cbar->display, cmap, 0, NULL, 0, 
			     holdPix, cbar->nxcolor))
	    break;
    
    if (cbar->nxcolor <= 2)
	XtAppError(XtWidgetToApplicationContext(w), 
		   "Need at least 2 free color cells");
    
    for (i = 0; i < cbar->nxcolor; i++) {
	cbar->xcolor[i].pixel = holdPix[i];
	cbar->xcolor[i].flags = DoRed | DoGreen | DoBlue;
    }

    for (i = 0; i < cbar->ncolor; i++) {
	cbar->color[i].xcolor = cbar->xcolor + (i*cbar->nxcolor)/cbar->ncolor;
	if (i < cbar->nxstomp) {
	    cbar->xstomp[i].pixel = i + stompbase;
	    cbar->xstomp[i].flags = DoRed | DoGreen | DoBlue;
	    cbar->color[i].xstomp = cbar->xstomp + i;
	} else
	    cbar->color[i].xstomp = NULL;
    }

    cbar->changeChannel = -1;
    cbar->brush.channel = -1;

    cbar->undo = NewSaveStack(nsave, ncolor);
    cbar->redo = NewSaveStack(nsave, ncolor);

    cbar->quickSeg = quickSeg;

    return cbar;
}


