/*
 * image.cc - Defines how images are handled.
 * Copyright (C) 1997-1999 by Brad Hughes, bhughes@tcac.net
 *
 * 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 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; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include "sapphire.hh"

#include <malloc.h>

#ifdef GradientHack
#  include <math.h>
#  include <float.h>
#endif

// *************************************************************************
// Graphics engine class code
// *************************************************************************

BImage::BImage(BImageControl *c, unsigned int w, unsigned int h) {
  control = c;

  width = ((signed) w > 0) ? w : 1;
  height = ((signed) h > 0) ? h : 1;

  red = new unsigned char[width * height];
  green = new unsigned char[width * height];
  blue = new unsigned char[width * height];

  cpc = control->colorsPerChannel();
  cpccpc = cpc * cpc;
  
  control->getMaskTables(&tr, &tg, &tb, &roff, &goff, &boff);
  
  if (control->v()->c_class != TrueColor)
    control->getColorTable(&colors, &ncolors);
}


BImage::~BImage(void) {
  if (red) delete [] red;
  if (green) delete [] green;
  if (blue) delete [] blue;
}


Pixmap BImage::render(unsigned long texture, const BColor &c1,
                      const BColor &c2)
{
  if (texture & BImage_Solid)
    return render_solid(texture, c1);
  else if (texture & BImage_Gradient)
    return render_gradient(texture, c1, c2);

  return None;
}


Pixmap BImage::render_solid(unsigned long texture, const BColor &color) {
  int inverted = 0;
  
  if ((texture & BImage_Sunken) && (! (texture & BImage_Invert))) inverted = 1;
  else if (texture & BImage_Invert) inverted = 1;
  
  background(color);
  
  if (texture & BImage_Bevel1) bevel1(True, (color.red == color.green &&
                                             color.green == color.blue &&
                                             color.blue == 0));
  else if (texture & BImage_Bevel2) bevel2(True, (color.red == color.green &&
                                                  color.green == color.blue &&
                                                  color.blue == 0));
  
  if (inverted) invert();
  
  return renderPixmap();
}


Pixmap BImage::render_gradient(unsigned long texture, const BColor &color1,
                               const BColor &color2) {
  int inverted = 0;
  
  if (texture & BImage_Sunken) {
    from = color2;
    to = color1;

    if (! (texture & BImage_Invert)) inverted = 1;
  } else {
    from = color1;
    to = color2;

    if (texture & BImage_Invert) inverted = 1;
  }

  if (texture & BImage_Diagonal) dgradient();
  else if (texture & BImage_Horizontal) hgradient();
  else if (texture & BImage_Vertical) vgradient();

  if (texture & BImage_Bevel1) bevel1(False);
  else if (texture & BImage_Bevel2) bevel2(False);
  
  if (inverted) invert();

  return renderPixmap();
}


XImage *BImage::renderXImage(void) {
  XImage *image = XCreateImage(control->d(), control->v(), control->depth(),
                               ZPixmap, 0, 0, width, height, 32, 0);
  if (! image) {
    fprintf(stderr, "BImage::renderXImage: error creating XImage\n");
    return 0;
  }

  unsigned char *d = (unsigned char *) malloc(image->bytes_per_line * height);
  if (! d) {
    fprintf(stderr,
	    "BImage::renderXImage: error allocating memory for image\n");
    XDestroyImage(image);
    return 0;
  }
  
  register unsigned int x, y, r = 0, g = 0, b = 0, i, off;
  
  unsigned char *idata = d, *pd = d, *pr, *pg, *pb;
  unsigned long pixel;

  if ((! tr) || (! tg) || (! tb)) {
    fprintf(stderr, "BImage::renderXImage: error getting color mask tables\n");
    XDestroyImage(image);
    return 0;
  }
  
  if (control->v()->c_class != TrueColor)
    if ((! colors) || (! ncolors)) {
      fprintf(stderr,
	      "BImage::renderXImage: error getting pseudo color tables\n");
      XDestroyImage(image);
      return 0;
    }

  if (control->dither()) {
    short er, eg, eb, *OR, *og, *ob, *nor, *nog, *nob, *por, *pog, *pob;
    unsigned short *ort, *ogt, *obt;
    
    control->getDitherBuffers(width + 2, &OR, &og, &ob, &nor, &nog, &nob,
			      &ort, &ogt, &obt);
    if ((! OR) || (! og) || (! ob) || (! nor) || (! nog) || (! nob) ||
	(! ort) || (! ogt) || (! obt)) {
      fprintf(stderr,
	      "BImage::renderXImage: error getting dither information\n");
      XDestroyImage(image);
      return 0;
    }
    
    x = width;

    por = OR;
    pog = og;
    pob = ob;

    pr = red;
    pg = green;
    pb = blue;

    while (x--) {
      *(por++) = *(pr++);
      *(pog++) = *(pg++);
      *(pob++) = *(pb++);
    }
    
    *por = *pog = *pob = 0;
    
    for (y = 0, off = 0; y < height; y++) {
      if (y < (height - 1)) {
	for (x = 0, i = off + width; x < width; x++, i++) {
	  *(nor + x) = *(red + i);
	  *(nog + x) = *(green + i);
	  *(nob + x) = *(blue + i);
	}
	
	i--;
	*(nor + x) = *(red + i);
	*(nog + x) = *(green + i);
	*(nob + x) = *(blue + i);
      }
      
      for (x = 0; x < width; x++) {
	if (*(OR + x) > 255) *(OR + x) = 255;
	else if (*(OR + x) < 0) *(OR + x) = 0;
	if (*(og + x) > 255) *(og + x) = 255;
	else if (*(og + x) < 0) *(og + x) = 0;
	if (*(ob + x) > 255) *(ob + x) = 255;
	else if (*(ob + x) < 0) *(ob + x) = 0;
	
	r = *(tr + *(OR + x));
	g = *(tg + *(og + x));
	b = *(tb + *(ob + x));
	
	switch (control->v()->c_class) {
	case StaticColor:
	case PseudoColor:
	  pixel = (r * cpccpc) + (g * cpc) + b;
	  *idata++ = colors[pixel].pixel;
	  
	  break;
	  
	case TrueColor:
	  pixel = (r << roff) | (g << goff) | (b << boff);
	  
	  switch (image->byte_order) {
	  case MSBFirst:
	    {
	      switch (image->bits_per_pixel) {
	      case 32: *(idata++) = (pixel >> 24);
	      case 24: *(idata++) = (pixel >> 16);
	      case 16: *(idata++) = (pixel >> 8);
	      default: *(idata++) = (pixel);
	      }

	      break;
	    }

	  case LSBFirst:
	    {
	      *(idata++) = pixel;
	      
	      switch (image->bits_per_pixel) {
	      case 32:
		*(idata++) = (pixel >> 8);
		*(idata++) = (pixel >> 16);
		*(idata++) = (pixel >> 24);
		break;

	      case 24:
		*(idata++) = (pixel >> 8);
		*(idata++) = (pixel >> 16);
		break;
		
	      case 16:
		*(idata++) = (pixel >> 8);
		break;
	      }

	      break;
	    }
	  }
	  
          break;
	  
	default:
	  fprintf(stderr, "BImage::renderXImage: unsupported visual\n");
	  image->data = (char *) d;
	  XDestroyImage(image);
	  return 0;
	}

	er = *(OR + x) - *(ort + *(OR + x));
	eg = *(og + x) - *(ogt + *(og + x));
	eb = *(ob + x) - *(obt + *(ob + x));
	
	*(OR + x + 1) += er;
	*(og + x + 1) += eg;
	*(ob + x + 1) += eb;
	
	*(nor + x) += er;
	*(nog + x) += eg;
	*(nob + x) += eb;
	
	*(nor + x + 1) -= (er >> 1) + (er >> 2);
	*(nog + x + 1) -= (eg >> 1) + (eg >> 2);
	*(nob + x + 1) -= (eb >> 1) + (eb >> 2);
      }
      
      off += image->width;
      idata = (pd += image->bytes_per_line);
      
      por = OR; OR = nor; nor = por;
      pog = og; og = nog; nog = pog;
      pob = ob; ob = nob; nob = pob;
    }
  } else {
    for (y = 0, off = 0; y < height; y++) {
      for (x = 0; x < width; x++, off++) {
	r = *(tr + *(red + off));
	g = *(tg + *(green + off));
	b = *(tb + *(blue + off));
	
	switch (control->v()->c_class) {
	case StaticColor:
	case PseudoColor:
	  pixel = (r * cpccpc) + (g * cpc) + b;
	  *idata++ = colors[pixel].pixel;
	  break;
	  
	case TrueColor:
	  pixel = (r << roff) | (g << goff) | (b << boff);
	  
	  switch (image->byte_order) {
	  case MSBFirst:
	    {
	      switch (image->bits_per_pixel) {
	      case 32: *(idata++) = (pixel >> 24);
	      case 24: *(idata++) = (pixel >> 16);
	      case 16: *(idata++) = (pixel >> 8);
	      default: *(idata++) = (pixel);
	      }
	      
	      break;
	    }
	    
	  case LSBFirst:
	    {
	      *(idata++) = pixel;
	      
	      switch (image->bits_per_pixel) {
	      case 32:
		*(idata++) = (pixel >> 8);
		*(idata++) = (pixel >> 16);
		*(idata++) = (pixel >> 24);
		break;
		
	      case 24:
		*(idata++) = (pixel >> 8);
		*(idata++) = (pixel >> 16);
		break;
		
	      case 16:
		*(idata++) = (pixel >> 8);
		break;
	      }
	      
	      break;
	    }
	  }
	  
	  break;
	  
	default:
	  fprintf(stderr, "BImage::renderXImage: unsupported visual\n");
	  image->data = (char *) d;
	  XDestroyImage(image);
	  return 0; 
	}
      }

      idata = (pd += image->bytes_per_line);
    }
  }
  
  image->data = (char *) d;
  return image;
}


Pixmap BImage::renderPixmap(void) {
  Pixmap pixmap = XCreatePixmap(control->d(), control->drawable(), width,
                                height, control->depth());
  if (pixmap == None) {
    fprintf(stderr, "BImage::renderPixmap: error creating pixmap\n");
    return None;
  }

  XImage *image = renderXImage();
  if (! image) {
    XFreePixmap(control->d(), pixmap);
    return None;
  }
  
  XPutImage(control->d(), pixmap,
	    DefaultGC(control->d(), control->screen()),
            image, 0, 0, 0, 0, width, height);
  XDestroyImage(image);
  return pixmap;
}


void BImage::background(const BColor &c) {
  unsigned int i, wh = width * height;

  bg = c;
  
  for (i = 0; i < wh; i++) {
    *(red + i) = c.red;
    *(green + i) = c.green;
    *(blue + i) = c.blue;
  }
}


void BImage::bevel1(Bool solid, Bool solidblack) {
  if (width > 2 && height > 2) {
    unsigned char *pr = red, *pg = green, *pb = blue;
    
    register unsigned char r, g, b, rr ,gg ,bb;
    register unsigned int w = width, h = height - 1, wh = w * h;
    
    if (solid) {
      if (solidblack) {
	r = g = b = 0xc0;
	rr = gg = bb = 0x60;
      } else {
	r = bg.red + (bg.red >> 1);
	if (r < bg.red) r = ~0;
	g = bg.green + (bg.green >> 1);
	if (g < bg.green) g = ~0;
	b = bg.blue + (bg.blue >> 1);
	if (b < bg.blue) b = ~0;
	
	rr = (bg.red >> 2) + (bg.red >> 1);
	if (rr > bg.red) rr = 0;
	gg = (bg.green >> 2) + (bg.green >> 1);
	if (gg > bg.green) gg = 0;
	bb = (bg.blue >> 2) + (bg.blue >> 1);
	if (bb > bg.blue) bb = 0;
      }      

      while (--w) {
	*pr = r;
	*pg = g;
	*pb = b;
	*((pr++) + wh) = rr;
	*((pg++) + wh) = gg;
	*((pb++) + wh) = bb;
      }

      *pr = r;
      *pg = g;
      *pb = b;
      *(pr + wh) = rr;
      *(pg + wh) = gg;
      *(pb + wh) = bb;
      
      pr = red + width;
      pg = green + width;
      pb = blue + width;
      
      while (--h) {
	*pr = r;
	*pg = g;
	*pb = b;
	
	pr += width - 1;
	pg += width - 1;
	pb += width - 1;
	
	*(pr++) = rr;
	*(pg++) = gg;
	*(pb++) = bb;
      }
      
      *pr = r;
      *pg = g;
      *pb = b;
      
      pr += width - 1;
      pg += width - 1;
      pb += width - 1;
      
      *pr = rr;
      *pg = gg;
      *pb = bb;
    } else {
      while (--w) {
	r = *pr;
	rr = r + (r >> 1);
	if (rr < r) rr = ~0;
	g = *pg;
	gg = g + (g >> 1);
	if (gg < g) gg = ~0;
	b = *pb;
	bb = b + (b >> 1);
	if (bb < b) bb = ~0;

	*pr = rr;
	*pg = gg;
	*pb = bb;

	r = *(pr + wh);
	rr = (r >> 2) + (r >> 1) ;
	if (rr > r) rr = 0;
	g = *(pg + wh);
	gg = (g >> 2) + (g >> 1);
	if (gg > g) gg = 0;
	b = *(pb + wh);
	bb = (b >> 2) + (b >> 1);
	if (bb > b) bb = 0;

	*((pr++) + wh) = rr;
	*((pg++) + wh) = gg;
	*((pb++) + wh) = bb;
      }

      r = *pr;
      rr = r + (r >> 1);
      if (rr < r) rr = ~0;
      g = *pg;
      gg = g + (g >> 1); 
      if (gg < g) gg = ~0;
      b = *pb;
      bb = b + (b >> 1);
      if (bb < b) bb = ~0;

      *pr = rr;
      *pg = gg;
      *pb = bb;

      r = *(pr + wh);
      rr = (r >> 2) + (r >> 1);
      if (rr > r) rr = 0;
      g = *(pg + wh);
      gg = (g >> 2) + (g >> 1);
      if (gg > g) gg = 0;
      b = *(pb + wh);
      bb = (b >> 2) + (b >> 1);
      if (bb > b) bb = 0;
      
      *(pr + wh) = rr;
      *(pg + wh) = gg;
      *(pb + wh) = bb;
      
      pr = red + width;
      pg = green + width;
      pb = blue + width;
      
      while (--h) {
	r = *pr;
	rr = r + (r >> 1);
	if (rr < r) rr = ~0;
	g = *pg;
	gg = g + (g >> 1);
	if (gg < g) gg = ~0;
	b = *pb;
	bb = b + (b >> 1);
	if (bb < b) bb = ~0;
	
	*pr = rr;
	*pg = gg;
	*pb = bb;
	
	pr += width - 1;
	pg += width - 1;
	pb += width - 1;
	
	r = *pr;
	rr = (r >> 2) + (r >> 1);
	if (rr > r) rr = 0;
	g = *pg;
	gg = (g >> 2) + (g >> 1);
	if (gg > g) gg = 0;
	b = *pb;
	bb = (b >> 2) + (b >> 1);
	if (bb > b) bb = 0;

	*(pr++) = rr;
	*(pg++) = gg;
	*(pb++) = bb;
      }
      
      r = *pr;
      rr = r + (r >> 1);
      if (rr < r) rr = ~0;
      g = *pg;
      gg = g + (g >> 1);
      if (gg < g) gg = ~0;
      b = *pb;
      bb = b + (b >> 1);
      if (bb < b) bb = ~0;

      *pr = rr;
      *pg = gg;
      *pb = bb;
      
      pr += width - 1;
      pg += width - 1;
      pb += width - 1;
      
      r = *pr;
      rr = (r >> 2) + (r >> 1);
      if (rr > r) rr = 0;
      g = *pg;
      gg = (g >> 2) + (g >> 1);
      if (gg > g) gg = 0;
      b = *pb;
      bb = (b >> 2) + (b >> 1);
      if (bb > b) bb = 0;
      
      *pr = rr;
      *pg = gg;
      *pb = bb;
    }
  }
}


void BImage::bevel2(Bool solid, Bool solidblack) {
  if (width > 4 && height > 4) {
    unsigned char r, g, b, rr ,gg ,bb, *pr = red + width + 1,
      *pg = green + width + 1, *pb = blue + width + 1;
    unsigned int w = width - 2, h = height - 1, wh = width * (height - 3);
    
    if (solid) {
      if (solidblack) {
	r = g = b = 0xc0;
	rr = gg = bb = 0x60;
      } else {
	r = bg.red + (bg.red >> 1);
	if (r < bg.red) r = ~0;
	g = bg.green + (bg.green >> 1);
	if (g < bg.green) g = ~0;
	b = bg.blue + (bg.blue >> 1);
	if (b < bg.blue) b = ~0;
	
	rr = (bg.red >> 2) + (bg.red >> 1);
	if (rr > bg.red) rr = 0;
	gg = (bg.green >> 2) + (bg.green >> 1);
	if (gg > bg.green) gg = 0;
	bb = (bg.blue >> 2) + (bg.blue >> 1);
	if (bb > bg.blue) bb = 0;
      }      

      while (--w) {
	*pr = r;
	*pg = g;
	*pb = b;
	*((pr++) + wh) = rr;
	*((pg++) + wh) = gg;
	*((pb++) + wh) = bb;
      }
      
      pr = red + width;
      pg = green + width;
      pb = blue + width;
      
      while (--h) {
	*(++pr) = r;
	*(++pg) = g;
	*(++pb) = b;
	
	pr += width - 3;
	pg += width - 3;
	pb += width - 3;
	
	*(pr++) = rr;
	*(pg++) = gg;
	*(pb++) = bb;

        pr++; pg++; pb++;
      }
    } else {
      while (--w) {
	r = *pr;
	rr = r + (r >> 1);
	if (rr < r) rr = ~0;
	g = *pg;
	gg = g + (g >> 1);
	if (gg < g) gg = ~0;
	b = *pb;
	bb = b + (b >> 1);
	if (bb < b) bb = ~0;

	*pr = rr;
	*pg = gg;
	*pb = bb;

	r = *(pr + wh);
	rr = (r >> 2) + (r >> 1);
	if (rr > r) rr = 0;
	g = *(pg + wh);
	gg = (g >> 2) + (g >> 1);
	if (gg > g) gg = 0;
	b = *(pb + wh);
	bb = (b >> 2) + (b >> 1);
	if (bb > b) bb = 0;

	*((pr++) + wh) = rr;
	*((pg++) + wh) = gg;
	*((pb++) + wh) = bb;
      }
      
      pr = red + width;
      pg = green + width;
      pb = blue + width;
      
      while (--h) {
	r = *pr;
	rr = r + (r >> 1);
	if (rr < r) rr = ~0;
	g = *pg;
	gg = g + (g >> 1);
	if (gg < g) gg = ~0;
	b = *pb;
	bb = b + (b >> 1);
	if (bb < b) bb = ~0;
	
	*(++pr) = rr;
	*(++pg) = gg;
	*(++pb) = bb;
	
	pr += width - 3;
	pg += width - 3;
	pb += width - 3;
	
	r = *pr;
	rr = (r >> 2) + (r >> 1);
	if (rr > r) rr = 0;
	g = *pg;
	gg = (g >> 2) + (g >> 1);
	if (gg > g) gg = 0;
	b = *pb;
	bb = (b >> 2) + (b >> 1);
	if (bb > b) bb = 0;

	*(pr++) = rr;
	*(pg++) = gg;
	*(pb++) = bb;

        pr++; pg++; pb++;
      }
    }
  }
}


void BImage::invert(void) {
  register unsigned int i, j, wh = (width * height) - 1;
  unsigned char tmp;

  for (i = 0, j = wh; j > i; j--, i++) {
    tmp = *(red + j);
    *(red + j) = *(red + i);
    *(red + i) = tmp;
    
    tmp = *(green + j);
    *(green + j) = *(green + i);
    *(green + i) = tmp;
    
    tmp = *(blue + j);
    *(blue + j) = *(blue + i);
    *(blue + i) = tmp;
  }
}


void BImage::dgradient(void) {
  float fr = (float) from.red,
    fg = (float) from.green,
    fb = (float) from.blue,
    tr = (float) to.red,
    tg = (float) to.green,
    tb = (float) to.blue,
    w = (float) (width * 2),
    h = (float) (height * 2),
    yr = 0.0,
    yg = 0.0,
    yb = 0.0,
    xr, xg, xb, drx, dgx, dbx, dry, dgy, dby;
  unsigned char *pr = red, *pg = green, *pb = blue;
  
  register unsigned int x, y;
  
  drx = dry = (tr - fr);
  dgx = dgy = (tg - fg);
  dbx = dby = (tb - fb);

  drx /= w;
  dgx /= w;
  dbx /= w;

  dry /= h;
  dgy /= h;
  dby /= h;

  for (y = 0; y < height; y++) {
    xr = fr + yr;
    xg = fg + yg;
    xb = fb + yb;
    
    for (x = 0; x < width; x++) {   
      *(pr++) = (unsigned char) (xr);
      *(pg++) = (unsigned char) (xg);
      *(pb++) = (unsigned char) (xb);
      
      xr += drx;
      xg += dgx;
      xb += dbx;
    }
    
    yr += dry;
    yg += dgy;
    yb += dby;
  }
}


void BImage::hgradient(void) {
  float fr, fg, fb, tr, tg, tb, drx, dgx, dbx, xr, xg, xb, w = (float) width;
  unsigned char *pr, *pg, *pb, *rr, *gg, *bb;

  register unsigned int x, y;

  xr = fr = (float) from.red;
  xg = fg = (float) from.green;
  xb = fb = (float) from.blue;

  tr = (float) to.red;
  tg = (float) to.green;
  tb = (float) to.blue;

  drx = (tr - fr) / w;
  dgx = (tg - fg) / w;
  dbx = (tb - fb) / w;

  pr = red;
  pg = green;
  pb = blue;

  // this renders one line of the hgradient
  for (x = 0; x < width; x++) {
    *(pr++) = (unsigned char) (xr);
    *(pg++) = (unsigned char) (xg);
    *(pb++) = (unsigned char) (xb);
    
    xr += drx;
    xg += dgx;
    xb += dbx;
  }

  // and this copies to the rest of the image
  for (y = 1; y < height; y++) {
    rr = red;
    gg = green;
    bb = blue;
    
    for (x = 0; x < width; x++) {
      *(pr++) = *(rr++);
      *(pg++) = *(gg++);
      *(pb++) = *(bb++);
    }
  }
}


void BImage::vgradient(void) {
  float fr, fg, fb, tr, tg, tb, dry, dgy, dby, yr, yg, yb,
    h = (float) height;
  unsigned char *pr = red, *pg = green, *pb = blue;
  
  register unsigned char r, g, b;
  register unsigned int x, y;
  
  yr = fr = (float) from.red;
  yg = fg = (float) from.green;
  yb = fb = (float) from.blue;

  tr = (float) to.red;
  tg = (float) to.green;
  tb = (float) to.blue;

  dry = (tr - fr) / h;
  dgy = (tg - fg) / h;
  dby = (tb - fb) / h;

  for (y = 0; y < height; y++) {
    yr += dry;
    yg += dgy;
    yb += dby;
      
    r = (unsigned char) (yr);
    g = (unsigned char) (yg);
    b = (unsigned char) (yb);
    
    for (x = 0; x < width; x++) {
      *(pr++) = r;
      *(pg++) = g;
      *(pb++) = b;
    }
  }
}


// *************************************************************************
// Image control class code
// *************************************************************************

BImageControl::BImageControl() {
  display = wm->getDisplay();
  screen_depth = wm->getDepth();
  window = wm->getRootWindow();
  screen_number = wm->getScreen();

  colors = 0;
  colors_per_channel = ncolors = 0;
  
  int count;
  XPixmapFormatValues *pmv = XListPixmapFormats(display, &count);
  root_colormap = DefaultColormap(display, screen_number);
  
  if (pmv) {
    bits_per_pixel = 0;
    for (int i = 0; i < count; i++)
      if (pmv[i].depth == screen_depth) {
	bits_per_pixel = pmv[i].bits_per_pixel;
	break;
      }
  }
  
  if (bits_per_pixel == 0) bits_per_pixel = screen_depth;
  if (pmv) XFree(pmv);

  red_offset = green_offset = blue_offset = 0;

  switch (wm->getVisual()->c_class) {
  case TrueColor:
    {
      int i;

      // compute tables for red channel
      unsigned long mask = wm->getVisual()->red_mask, emask = mask;
      while (! (mask & 1)) { red_offset++; mask >>= 1; }
      for (i = 0; i < 256; i++) {
	rmask_table[i] = (i * mask + 0x7f) / 0xff;
	rerr_table[i] = ((rmask_table[i] << 8) / (emask >> red_offset));
      }
      
      // compute tables for green channel
      emask = mask = wm->getVisual()->green_mask;
      while (! (mask & 1)) { green_offset++; mask >>= 1; }
      for (i = 0; i < 256; i++) {
	gmask_table[i] = (i * mask + 0x7f) / 0xff;
	gerr_table[i] = ((gmask_table[i] << 8) / (emask >> green_offset));
      }
      
      // compute tables for blue channel
      emask = mask = wm->getVisual()->blue_mask;
      while (! (mask & 1)) { blue_offset++; mask >>= 1; }
      for (i = 0; i < 256; i++) {
	bmask_table[i] = (i * mask + 0x7f) / 0xff;
	berr_table[i] = ((bmask_table[i] << 8) / (emask >> blue_offset));
      }
    }
    
    break;
    
  case PseudoColor:
  case StaticColor:
    {
      colors_per_channel = wm->getColorsPerChannel();
      ncolors = colors_per_channel * colors_per_channel * colors_per_channel;
      
      if (ncolors > (1 << screen_depth)) {
	colors_per_channel = (1 << screen_depth) / 3;
	ncolors = colors_per_channel * colors_per_channel * colors_per_channel;
      }
      
      if (colors_per_channel < 2 || ncolors > (1 << screen_depth)) {
	fprintf(stderr,
		"BImageControl::BImageControl: invalid colormap size %d "
		"(%d/%d/%d)", ncolors, colors_per_channel, colors_per_channel,
		colors_per_channel);
	exit(1);
      }
      
      colors = new XColor[ncolors];
      if (! colors) {
	fprintf(stderr, "BImageControl::BImageControl: error allocating "
		"colormap\n");
	exit(1);
      }

      int i = 0, ii, p, r, g, b;
      unsigned long mask = colors_per_channel - 1;
      for (i = 0; i < 256; i++) {
	rmask_table[i] = gmask_table[i] = bmask_table[i] =
	  (i * mask + 0x7f) / 0xff;
	rerr_table[i] = gerr_table[i] = berr_table[i] =
	  ((rmask_table[i] << 8) / (mask));
      }
      
      for (int r = 0, i = 0; r < colors_per_channel; r++)
	for (int g = 0; g < colors_per_channel; g++)
	  for (int b = 0; b < colors_per_channel; b++, i++) {
	    colors[i].red = (r * 0xffff) / (colors_per_channel - 1);
	    colors[i].green = (g * 0xffff) / (colors_per_channel - 1);
	    colors[i].blue = (b * 0xffff) / (colors_per_channel - 1);;
	    colors[i].flags = DoRed|DoGreen|DoBlue;
	  }
      
      //blackbox->syncGrabServer();
      XGrabServer(wm->getDisplay());
      XSync(wm->getDisplay(), False);
      
      for (i = 0; i < ncolors; i++)
	if (! XAllocColor(display, root_colormap, &colors[i])) {
	  fprintf(stderr, "couldn't alloc color %i %i %i\n", colors[i].red,
		  colors[i].green, colors[i].blue);
	  colors[i].flags = 0;
	} else
	  colors[i].flags = DoRed|DoGreen|DoBlue;
      
      //blackbox->ungrabServer();
      XUngrabServer(wm->getDisplay());
      
      XColor icolors[256];
      int incolors = (((1 << screen_depth) > 256) ? 256 : (1 << screen_depth));
      
      for (i = 0; i < incolors; i++)
	icolors[i].pixel = i;
      
      XQueryColors(display, root_colormap, icolors, incolors);
      for (i = 0; i < ncolors; i++) {
	if (! colors[i].flags) {
	  unsigned long chk = 0xffffffff, pixel, close = 0;
	  
	  p = 2;
	  while (p--) {
	    for (ii = 0; ii < incolors; ii++) {
	      r = (colors[i].red - icolors[i].red) >> 8;
	      g = (colors[i].green - icolors[i].green) >> 8;
	      b = (colors[i].blue - icolors[i].blue) >> 8;
	      pixel = (r * r) + (g * g) + (b * b);
	      
	      if (pixel < chk) {
		chk = pixel;
		close = ii;
	      }
	      
	      colors[i].red = icolors[close].red;
	      colors[i].green = icolors[close].green;
	      colors[i].blue = icolors[close].blue;
	      
	      if (XAllocColor(display, root_colormap, &colors[i])) {
		colors[i].flags = DoRed|DoGreen|DoBlue;
		break;
	      }
	    }
	  }
	}
      }

      break;
    }
    
  case GrayScale:
  case StaticGray:
    fprintf(stderr, "BImageControl::BImageControl: GrayScale/StaticGray "
                    "visual unsupported\n");
    break;
    
  default:
    fprintf(stderr, "BImageControl::BImageControl: unsupported visual %d\n",
	    wm->getVisual()->c_class);
    exit(1);
  }
  
  cache = new LinkedList<Cache>;
  
  dither_buf_width = 0;
  red_err = green_err = blue_err = next_red_err = next_green_err =
    next_blue_err = 0;
}


BImageControl::~BImageControl(void) {
  if (red_err) delete [] red_err;
  if (green_err) delete [] green_err;
  if (blue_err) delete [] blue_err;
  if (next_red_err) delete [] next_red_err;
  if (next_green_err) delete [] next_green_err;
  if (next_blue_err) delete [] next_blue_err;

  if (colors) {
    unsigned long *pixels = new unsigned long [ncolors];

    int i;
    for (i = 0; i < ncolors; i++)
      *(pixels + i) = (*(colors + i)).pixel;

    XFreeColors(display, DefaultColormap(display, screen_number),
		pixels, ncolors, 0);

    delete [] colors;
  }

  if (cache->count()) {
    //printf("BImageContol::~BImageControl: pixmap cache - "
	//    "releasing %d pixmaps\n", n);
    
    //for (i = 0; i < n; i++) {
    //  Cache *tmp = cache->first();
    //  XFreePixmap(display, tmp->pixmap);
    //  cache->remove(tmp);
    //  delete tmp;
    //}
    

    	LinkedListIterator<Cache> it(cache);
	for (; it.current(); it++) {
	  XFreePixmap(display, it.current()->pixmap);
	}
 
 	cache->removeAll();
    
  }
  
  delete cache;
}


Bool BImageControl::dither(void) {
  return wm->getImageDither();
}


Visual *BImageControl::v(void) {
  return wm->getVisual();
}


// *************************************************************************
// pixmap cache control
// *************************************************************************

Pixmap BImageControl::searchCache(
				  //char* name,
				  unsigned int width, 
				  unsigned int height,
				  unsigned long texture, 
				  const BColor &c1,
				  const BColor &c2) {
  if (cache->count()) {
    LinkedListIterator<Cache> it(cache);

    for (; it.current(); it++) {
      if (
	  //(strcmp(it.current()->name, name)==0) &&
      	  (it.current()->width == width) &&
	  (it.current()->height == height) &&
	  (it.current()->texture == texture) &&
	  (it.current()->pixel1 == c1.pixel))
	if (texture & BImage_Gradient) {
	  if (it.current()->pixel2 == c2.pixel) {
	    it.current()->count++;
	    
	    return it.current()->pixmap;
	  }
	} else {
	  it.current()->count++;
	  	  
	  return it.current()->pixmap;
	  
	}
    }
  }

  return None;
}

Pixmap BImageControl::renderImage(/*char* name,*/ unsigned int width, unsigned int height,
			     unsigned long texture, const BColor &c1,
			     const BColor &c2) {
  Pixmap pixmap = searchCache(/*name,*/ width, height, texture, c1, c2);
  if (pixmap) return pixmap;

  BImage image(this, width, height);
  pixmap = image.render(texture, c1, c2);
  
  if (pixmap) {
    Cache *tmp = new Cache;
	
    //tmp->name = name;
    tmp->pixmap = pixmap;
    tmp->width = width;
    tmp->height = height;
    tmp->count = 1;
    tmp->texture = texture;
    tmp->pixel1 = c1.pixel;

    if (texture & BImage_Gradient)
      tmp->pixel2 = c2.pixel;
    else
      tmp->pixel2 = 0l;

    cache->insert(tmp);

    return pixmap;
  }
  
  return None;
}

void BImageControl::removeAllImages() {

	LinkedListIterator<Cache> it(cache);
	for (; it.current(); it++) XFreePixmap(display, it.current()->pixmap);
 
	cache->removeAll();
}

void BImageControl::removeImage(Pixmap pixmap) {
  if (pixmap) {
    LinkedListIterator<Cache> it(cache);

    for (; it.current(); it++) {
      if (it.current()->pixmap == pixmap) {
	Cache *tmp = it.current();
	tmp->count--;
	  
	if (! tmp->count) {	  
	  XFreePixmap(display, tmp->pixmap);
	  cache->remove(tmp);
	  delete tmp;
	}
	
	return;
      }
    }
  }
}


unsigned long BImageControl::getColor(const char *colorname,
				      unsigned char *r, unsigned char *g,
				      unsigned char *b)
{
  XColor color;
  XWindowAttributes attributes;
  
  XGetWindowAttributes(display, window, &attributes);
  color.pixel = 0;
  if (!XParseColor(display, attributes.colormap, colorname, &color)) {
    fprintf(stderr, "BImageControl::getColor: color parse error: \"%s\"\n",
	    colorname);
  } else if (!XAllocColor(display, attributes.colormap, &color)) {
    fprintf(stderr, "BImageControl::getColor: color alloc error: \"%s\"\n",
	    colorname);
  }
  
  if (color.red == 65535) *r = 0xff;
  else *r = (unsigned char) (color.red / 0xff);
  if (color.green == 65535) *g = 0xff;
  else *g = (unsigned char) (color.green / 0xff);
  if (color.blue == 65535) *b = 0xff;
  else *b = (unsigned char) (color.blue / 0xff);
 
  return color.pixel;
}


unsigned long BImageControl::getColor(const char *colorname) {
  XColor color;
  XWindowAttributes attributes;
  
  XGetWindowAttributes(display, window, &attributes);
  color.pixel = 0;
  if (!XParseColor(display, attributes.colormap, colorname, &color)) {
    fprintf(stderr, "BImageControl::getColor: color parse error: \"%s\"\n",
	    colorname);
  } else if (!XAllocColor(display, attributes.colormap, &color)) {
    fprintf(stderr, "BImageControl::getColor: color alloc error: \"%s\"\n",
	    colorname);
  }

  return color.pixel;
}


void BImageControl::getMaskTables(unsigned short **rmt, unsigned short **gmt,
				  unsigned short **bmt,
				  int *roff, int *goff, int *boff) {
  if (rmt) *rmt = rmask_table;
  if (gmt) *gmt = gmask_table;
  if (bmt) *bmt = bmask_table;

  if (roff) *roff = red_offset;
  if (goff) *goff = green_offset;
  if (boff) *boff = blue_offset;
}


void BImageControl::getColorTable(XColor **c, int *n) {
  if (c) *c = colors;
  if (n) *n = ncolors;
}


void BImageControl::getDitherBuffers(unsigned int w,
				     short **r, short **g, short **b,
				     short **nr, short **ng, short **nb,
				     unsigned short **ret,
				     unsigned short **get,
				     unsigned short **bet) {
  if (w > dither_buf_width) {
    if (red_err) delete [] red_err;
    if (green_err) delete [] green_err;
    if (blue_err) delete [] blue_err;
    if (next_red_err) delete [] next_red_err;
    if (next_green_err) delete [] next_green_err;
    if (next_blue_err) delete [] next_blue_err;
    
    dither_buf_width = w;
    
    red_err = new short[dither_buf_width];
    green_err = new short[dither_buf_width];
    blue_err = new short[dither_buf_width];
    next_red_err = new short[dither_buf_width];
    next_green_err = new short[dither_buf_width];
    next_blue_err = new short[dither_buf_width];
  }

  *r = red_err;
  *g = green_err;
  *b = blue_err;
  *nr = next_red_err;
  *ng = next_green_err;
  *nb = next_blue_err;
  *ret = rerr_table;
  *get = gerr_table;
  *bet = berr_table;
}


void BImageControl::installRootColormap(void) {
  //blackbox->syncGrabServer();
  
  XGrabServer(wm->getDisplay());
  XSync(wm->getDisplay(), False);
  
  Bool install = True;
  int i = 0, ncmap = 0;
  Colormap *cmaps = XListInstalledColormaps(display, window, &ncmap);
  
  if (cmaps) {
    for (i = 0; i < ncmap; i++)
      if (*(cmaps + i) == root_colormap)
	install = False;
    
    if (install)
      XInstallColormap(display, root_colormap);
    
    XFree(cmaps);
  }
  
  //blackbox->ungrabServer();
  XUngrabServer(wm->getDisplay());
}
