// Copyright (C) 1999-2014
// Smithsonian Astrophysical Observatory, Cambridge, MA, USA
// For conditions of distribution and use, see copyright notice in "copyright"

#include "framergb.h"
#include "fitsimage.h"
#include "ps.h"
#include "outfile.h"
#include "outchannel.h"
#include "outsocket.h"

#include "sigbus.h"

// Frame Member Functions

FrameRGB::FrameRGB(Tcl_Interp* i, Tk_Canvas c, Tk_Item* item)
  : FrameBase(i,c,item)
{
  context = new Context[3];
  context[0].parent(this);
  context[1].parent(this);
  context[2].parent(this);

  channel = 0;
  rgbSystem = Coord::WCS;

  for (int ii=0; ii<3; ii++) {
    view[ii] = 1;
    bias[ii] = .5;
    contrast[ii] = 1.0;
    colorScale[ii] = NULL;
  }

  colorCount = 0;
  colorCells = NULL;

  currentContext = &context[channel];
  keyContext = &context[channel];
  keyContextSet =0;
}

FrameRGB::~FrameRGB()
{
  if (context)
    delete [] context;

  for (int ii=0; ii<3; ii++) {
    if (colorScale[ii])
      delete colorScale[ii];
  }

  if (colorCells)
    delete [] colorCells;
}

void FrameRGB::alignWCS()
{
  if (!wcsAlign_ || !(keyContext->fits) || !keyContext->fits->hasWCS(wcsSystem_)) {
    wcsOrientation = Coord::NORMAL;
    wcsOrientationMatrix.identity();
    wcsRotation = 0;
  }
  else
    calcAlignWCS(keyContext->fits, wcsSystem_, wcsSky_,
		 &wcsOrientation, &wcsOrientationMatrix, &wcsRotation);

  updateRGBMatrices();
}   

void FrameRGB::alignWCS(Coord::CoordSystem sys, Coord::SkyFrame sky)
{
  if (!wcsAlign_ || !(keyContext->fits) || !keyContext->fits->hasWCS(sys)) {
    wcsOrientation = Coord::NORMAL;
    wcsOrientationMatrix.identity();
    wcsRotation = 0;
  }
  else
    calcAlignWCS(keyContext->fits, sys, sky,
		 &wcsOrientation, &wcsOrientationMatrix, &wcsRotation);

  updateRGBMatrices();
}

void FrameRGB::alignWCS(FitsImage* ptr, Coord::CoordSystem sys)
{
  if (!wcsAlign_ || !(keyContext->fits) || !ptr || 
      !keyContext->fits->hasWCS(wcsSystem_)) {
    wcsOrientation = Coord::NORMAL;
    wcsOrientationMatrix.identity();
    wcsRotation = 0;
  }
  else
    calcAlignWCS(ptr, keyContext->fits, wcsSystem_, sys, wcsSky_,
		 &wcsOrientation, &wcsOrientationMatrix, &wcsRotation, &zoom_);

  updateRGBMatrices();
}

int FrameRGB::doRender()
{
  return ((context[0].fits&&view[0]) || 
	  (context[1].fits&&view[1]) || 
	  (context[2].fits&&view[2]));
}

unsigned char* FrameRGB::fillImage(int width, int height, 
				   Coord::InternalSystem sys)
{
  // img
  unsigned char* img = new unsigned char[width*height*3];
  memset(img,0,width*height*3);

  // mk
  char* mk = new char[width*height];
  memset(mk,0,width*height);

  SETSIGBUS

  // one channel at a time
  for (int kk=0; kk<3; kk++) {
    if (!view[kk] || !context[kk].fits)
      continue;

    // basics
    int length = colorScale[kk]->size() - 1;
    const unsigned char* table = colorScale[kk]->psColors();

    FitsImage* sptr = context[kk].cfits;
    int mosaic = context[kk].isMosaic();

    // variable
    double* mm = sptr->matrixToData(sys).mm();
    FitsBound* params = sptr->getDataParams(context[kk].frScale.scanMode());
    int srcw = sptr->width();

    double ll = sptr->getLowDouble();
    double hh = sptr->getHighDouble();
    double diff = hh - ll;

    // main loop
    unsigned char* dest = img;
    char* mkptr = mk;

    for (long jj=0; jj<height; jj++) {
      for (long ii=0; ii<width; ii++, dest+=3, mkptr++) {

	if (mosaic) {
	  sptr = context[kk].cfits;

	  mm = sptr->matrixToData(sys).mm();
	  params = sptr->getDataParams(context[kk].frScale.scanMode());
	  srcw = sptr->width();

	  ll = sptr->getLowDouble();
	  hh = sptr->getHighDouble();
	  diff = hh - ll;
	}

	do {
	  double xx = ii*mm[0] + jj*mm[3] + mm[6];
	  double yy = ii*mm[1] + jj*mm[4] + mm[7];

	  if (xx>=params->xmin && xx<params->xmax && 
	      yy>=params->ymin && yy<params->ymax) {
	    double value = sptr->getValueDouble(long(yy)*srcw + long(xx));

	    if (isfinite(value)) {
	      if (value <= ll)
		*(dest+kk) = *table;
	      else if (value >= hh)
		*(dest+kk) = *(table+length);
	      else
		*(dest+kk) = *(table+((int)(((value - ll)/diff * length) +.5)));
	      *mkptr =2;
	    }
	    else if (*mkptr < 2)
	      *mkptr =1;

	    break;
	  }
	  else {
	    if (mosaic) {
	      sptr = sptr->nextMosaic();

	      if (sptr) {
		mm = sptr->matrixToData(sys).mm();
		params = sptr->getDataParams(context[kk].frScale.scanMode());
		srcw = sptr->width();

		ll = sptr->getLowDouble();
		hh = sptr->getHighDouble();
		diff = hh - ll;
	      }
	    }
	  }
	}
	while (mosaic && sptr);
      }
    }
  }

  // now fill in bg
  {
    unsigned char* dest = img;
    char* mkptr = mk;
    for (int jj=0; jj<height; jj++)
      for (int ii=0; ii<width; ii++, dest+=3, mkptr++) {
	if (*mkptr == 2) // good value
	  ;
	else if (*mkptr == 1) { // nan
	  *dest = (unsigned char)nanColor->red;
	  *(dest+1) = (unsigned char)nanColor->green;
	  *(dest+2) = (unsigned char)nanColor->blue;
	}
	else { // bg
	  *dest = (unsigned char)bgColor->red;
	  *(dest+1) = (unsigned char)bgColor->green;
	  *(dest+2) = (unsigned char)bgColor->blue;
	}
      }	
  }
  CLEARSIGBUS

  // clean up
  delete [] mk;

  return img;
}

BBox FrameRGB::imageBBox(FrScale::ScanMode mode)
{
  // returns imageBBox in IMAGE coords
  //   and extends edge to edge

  updateRGBMatrices();

  BBox rr;
  int first=1;
  for (int ii=0; ii<3; ii++) {
    if (context[ii].fits) {
      FitsImage* ptr = context[ii].fits;
      while (ptr) {
	FitsBound* params = ptr->getDataParams(mode);
	Matrix mm = ptr->wcsToRef * rgb[ii] * dataToImage;

	Vector aa = Vector(params->xmin,params->ymin) * mm;
	if (first) {
	  rr = BBox(aa,aa);
	  first = 0;
	}
	else
	  rr.bound(aa);

	rr.bound(Vector(params->xmax,params->ymin) * mm);
	rr.bound(Vector(params->xmax,params->ymax) * mm);
	rr.bound(Vector(params->xmin,params->ymax) * mm);

	ptr = ptr->nextMosaic();
      }
    }
  }

  return rr;
}

void FrameRGB::loadRGBCube(MemType which, const char* fn, FitsImage* img)
{
  if (!img || !img->isValid() || !(img->isImage() || img->isPost()) || (img->depth() != 3))
    goto error;

  context[0].fits = img;

  if (img->isPost())
    which = POST;

  switch (which) {
  case ALLOC:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImageFitsNextAlloc(this, fn, context[0].fits->fitsFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImageFitsNextAlloc(this, fn, context[1].fits->fitsFile(),3);
    break;
  case ALLOCGZ:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImageFitsNextAllocGZ(this, fn, context[0].fits->fitsFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImageFitsNextAllocGZ(this, fn, context[1].fits->fitsFile(),3);
    break;
  case CHANNEL:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImageFitsNextChannel(this, fn, context[0].fits->fitsFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImageFitsNextChannel(this, fn, context[1].fits->fitsFile(),3);
    break;
  case MMAP:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImageFitsNextMMap(this, fn, context[0].fits->fitsFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImageFitsNextMMap(this, fn, context[1].fits->fitsFile(),3);
    break;
  case SMMAP:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImageFitsNextSMMap(this, fn, context[0].fits->fitsFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImageFitsNextSMMap(this, fn, context[1].fits->fitsFile(),3);
    break;
  case MMAPINCR:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImageFitsNextMMapIncr(this, fn,context[0].fits->fitsFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImageFitsNextMMapIncr(this, fn,context[1].fits->fitsFile(),3);
    break;
  case SHARE:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImageFitsNextShare(this, fn, context[0].fits->fitsFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImageFitsNextShare(this, fn, context[1].fits->fitsFile(),3);
    break;
  case SSHARE:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImageFitsNextSShare(this, fn, context[0].fits->fitsFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImageFitsNextSShare(this, fn, context[1].fits->fitsFile(),3);
    break;
  case SOCKET:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImageFitsNextSocket(this, fn, context[0].fits->fitsFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImageFitsNextSocket(this, fn, context[1].fits->fitsFile(),3);
    break;
  case SOCKETGZ:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImageFitsNextSocketGZ(this, fn,context[0].fits->fitsFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImageFitsNextSocketGZ(this, fn,context[1].fits->fitsFile(),3);
    break;
  case VAR:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImageFitsNextVar(this, fn, context[0].fits->fitsFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImageFitsNextVar(this, fn, context[1].fits->fitsFile(),3);
    break;
  case POST:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImageFitsNextPost(this, img,context[0].fits->baseFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImageFitsNextPost(this, img,context[1].fits->baseFile(),3);
    break;
  case PHOTO:
    if (context[0].fits && context[0].fits->isValid())
      context[1].fits = new FitsImagePhotoCubeNext(this, fn, context[0].fits->baseFile(),2);
    if (context[1].fits && context[1].fits->isValid())
      context[2].fits = new FitsImagePhotoCubeNext(this, fn, context[1].fits->baseFile(),3);
    break;
  }

  // is everything ok?
  if (context[0].fits && context[0].fits->isValid() &&
      (context[0].fits->isImage() || context[0].fits->isPost()) &&
      context[1].fits && context[1].fits->isValid() &&
      (context[1].fits->isImage() || context[1].fits->isPost()) &&
      context[2].fits && context[2].fits->isValid() &&
      (context[2].fits->isImage() || context[2].fits->isPost())) {

    loadRGBFinish();
    return;
  }

 error:
  context[0].unload();
  context[1].unload();
  context[2].unload();

  reset();
  updateColorScale();
    
  Tcl_AppendResult(interp, "Unable to load rgb cube file", NULL);
  result = TCL_ERROR;
  return;
}

void FrameRGB::loadRGBImage(MemType which, const char* fn, FitsImage* img)
{
  FitsImage* r = img;
  FitsImage* g = NULL;
  FitsImage* b = NULL;

  if (!img || !img->isValid() || !(img->isImage() || img->isPost()))
    goto error;

  switch (which) {
  case ALLOC:
    if (r && r->isValid())
      g = new FitsImageMosaicNextAlloc(this, fn, r->fitsFile(), 
				       FitsFile::NOFLUSH,1);
    if (g && g->isValid())
      b = new FitsImageMosaicNextAlloc(this, fn, g->fitsFile(), 
				       FitsFile::NOFLUSH,1);
    break;
  case ALLOCGZ:
    if (r && r->isValid())
      g = new FitsImageMosaicNextAllocGZ(this,fn,r->fitsFile(), 
					 FitsFile::NOFLUSH,1);
    if (g && g->isValid())
      b = new FitsImageMosaicNextAllocGZ(this,fn,g->fitsFile(), 
					 FitsFile::NOFLUSH,1);
    break;
  case CHANNEL:
    if (r && r->isValid())
      g = new FitsImageMosaicNextChannel(this,fn,r->fitsFile(), 
					 FitsFile::NOFLUSH,1);
    if (g && g->isValid())
      b = new FitsImageMosaicNextChannel(this,fn,g->fitsFile(), 
					 FitsFile::NOFLUSH,1);
    break;
  case MMAP:
    if (r && r->isValid())
      g = new FitsImageMosaicNextMMap(this, fn, r->fitsFile(),1);
    if (g && g->isValid())
      b = new FitsImageMosaicNextMMap(this, fn, g->fitsFile(),1);
    break;
  case MMAPINCR:
    if (r && r->isValid())
      g = new FitsImageMosaicNextMMapIncr(this, fn, r->fitsFile(),1);
    if (g && g->isValid())
      b = new FitsImageMosaicNextMMapIncr(this, fn, g->fitsFile(),1);
    break;
  case SHARE:
    if (r && r->isValid())
      g = new FitsImageMosaicNextShare(this,fn, r->fitsFile(),1);
    if (g && g->isValid())
      b = new FitsImageMosaicNextShare(this,fn, g->fitsFile(),1);
    break;
  case SOCKET:
    if (r && r->isValid())
      g = new FitsImageMosaicNextSocket(this,fn,r->fitsFile(), 
					FitsFile::FLUSH,1);
    if (g && g->isValid())
      b = new FitsImageMosaicNextSocket(this,fn,g->fitsFile(), 
					FitsFile::FLUSH,1);
    break;
  case SOCKETGZ:
    if (r && r->isValid())
      g = new FitsImageMosaicNextSocketGZ(this,fn,r->fitsFile(), 
					  FitsFile::FLUSH,1);
    if (g && g->isValid())
      b = new FitsImageMosaicNextSocketGZ(this,fn,g->fitsFile(), 
					  FitsFile::FLUSH,1);
    break;
  case VAR:
    if (r && r->isValid())
      g = new FitsImageMosaicNextVar(this, fn, r->fitsFile(),1);
    if (g && g->isValid())
      b = new FitsImageMosaicNextVar(this, fn, g->fitsFile(),1);
    break;
  }

  // ok, figure out which is which channel
  context[0].fits = context[1].fits = context[2].fits = NULL;

  {
    const char* ext = r->fitsFile()->extname();
    if (ext) {
      if (!strncmp(ext,"RED",3))
	context[0].fits = r;
      else if (!strncmp(ext,"GREEN",3))
	context[1].fits = r;
      else if (!strncmp(ext,"BLUE",3))
	context[2].fits = r;
      else
	context[0].fits = r;
    }
    else
      context[0].fits = r;
  }

  {
    const char* ext = g->fitsFile()->extname();
    if (ext) {
      if (!strncmp(ext,"RED",3))
	context[0].fits = g;
      else if (!strncmp(ext,"GREEN",3))
	context[1].fits = g;
      else if (!strncmp(ext,"BLUE",3))
	context[2].fits = g;
      else
	context[1].fits = g;
    }
    else
      context[1].fits = g;
  }

  {
    const char* ext = b->fitsFile()->extname();
    if (ext) {
      if (!strncmp(ext,"RED",3))
	context[0].fits = b;
      else if (!strncmp(ext,"GREEN",3))
	context[1].fits = b;
      else if (!strncmp(ext,"BLUE",3))
	context[2].fits = b;
      else
	context[2].fits = b;
    }
    else
      context[2].fits = b;
  }

  // is everything ok?
  if (context[0].fits && context[0].fits->isValid() && 
      (context[0].fits->isImage() || context[0].fits->isPost()) &&
      context[1].fits && context[1].fits->isValid() && 
      (context[1].fits->isImage() || context[1].fits->isPost()) &&
      context[2].fits && context[2].fits->isValid() && 
      (context[2].fits->isImage() || context[2].fits->isPost())) {

    loadRGBFinish();
    return;
  }

 error:
  context[0].unload();
  context[1].unload();
  context[2].unload();

  reset();
  updateColorScale();
    
  Tcl_AppendResult(interp, "Unable to load rgb image file", NULL);
  result = TCL_ERROR;
  return;
}

void FrameRGB::loadRGBFinish()
{
  for (int ii=0; ii<3; ii++) {
    context[ii].loadInit(NOMOSAIC,Coord::WCS);
    context[ii].loadFinish();
  }

  channel = 0;
  currentContext = &context[channel];
  keyContext = &context[channel];
  keyContextSet =1;

  alignWCS();
  if (!preservePan) {
    centerImage();
    // cursor is in REF, crosshair in REF
    crosshair = cursor;
  }
  updateColorScale();
  update(MATRIX);
}

void FrameRGB::pushMatrices()
{
  for (int ii=0; ii<3; ii++) {
    FitsImage* ptr = context[ii].fits;
    while (ptr) {
      FitsImage* sptr = ptr;
      while (sptr) {
	sptr->updateMatrices(rgb[ii], refToWidget, widgetToCanvas);
	sptr = sptr->nextSlice();
      }
      ptr = ptr->nextMosaic();
    }
  }
}

void FrameRGB::pushMagnifierMatrices()
{
  for (int ii=0; ii<3; ii++) {
    FitsImage* ptr = context[ii].fits;
    while (ptr) {
      FitsImage* sptr = ptr;
      while (sptr) {
	sptr->updateMagnifierMatrices(refToMagnifier);
	sptr = sptr->nextSlice();
      }
      ptr = ptr->nextMosaic();
    }
  }
}

void FrameRGB::pushPannerMatrices()
{
  for (int ii=0; ii<3; ii++) {
    FitsImage* ptr = context[ii].fits;
    while (ptr) {
      FitsImage* sptr = ptr;
      while (sptr) {
	sptr->updatePannerMatrices(refToPanner);
	sptr = sptr->nextSlice();
      }
      ptr = ptr->nextMosaic();
    }
  }
}

void FrameRGB::pushPSMatrices(float scale, int width, int height)
{
  Matrix mx = psMatrix(scale, width, height);
  for (int kk=0; kk<3; kk++)
    if (context[kk].fits) {
      FitsImage* ptr = context[kk].cfits;
      while (ptr) {
	ptr->updatePS(mx);
	ptr = ptr->nextMosaic();
      }
    }
}

void FrameRGB::reset()
{
  for (int ii=0; ii<3; ii++) {
    bias[ii] = 0.5;
    contrast[ii] = 1.0;
    context[ii].frScale.resetScanMode();
    context[ii].updateClip();
  }

  Base::reset();
}

void FrameRGB::rgbAlignWCS(int ii)
{
  if (keyContext->fits  && keyContext->fits->hasWCS(rgbSystem))
    rgb[ii] = calcAlignWCS(keyContext->fits, context[ii].fits, rgbSystem, rgbSystem, Coord::FK5);

  if (DebugRGB)
    cerr << "rgbAlignWCS " << rgb[ii] << endl;
}

void FrameRGB::saveFitsRGBImageFileCmd(const char* fn)
{
  if (keyContext->fits) {
    OutFitsFile str(fn);
    saveFitsRGBImage(str);
  }
}

void FrameRGB::saveFitsRGBImageChannelCmd(const char* ch)
{
  if (keyContext->fits) {
    OutFitsChannel str(interp, ch);
    saveFitsRGBImage(str);
  }
}

void FrameRGB::saveFitsRGBImageSocketCmd(int ss)
{
  if (keyContext->fits) {
    OutFitsSocket str(ss);
    saveFitsRGBImage(str);
  }
}

void FrameRGB::saveFitsRGBCubeFileCmd(const char* fn)
{
  if (keyContext->fits) {
    OutFitsFile str(fn);
    saveFitsRGBCube(str);
  }
}

void FrameRGB::saveFitsRGBCubeChannelCmd(const char* ch)
{
  if (keyContext->fits) {
    OutFitsChannel str(interp, ch);
    saveFitsRGBCube(str);
  }
}

void FrameRGB::saveFitsRGBCubeSocketCmd(int ss)
{
  if (keyContext->fits) {
    OutFitsSocket str(ss);
    saveFitsRGBCube(str);
  }
}

void FrameRGB::saveArrayRGBCubeFileCmd(const char* fn, FitsFile::ArchType endian)
{
  if (keyContext->fits) {
    OutFitsFile str(fn);
    saveArrayRGBCube(str, endian);
  }
}

void FrameRGB::saveArrayRGBCubeChannelCmd(const char* ch, FitsFile::ArchType endian)
{
  if (keyContext->fits) {
    OutFitsChannel str(interp, ch);
    saveArrayRGBCube(str, endian);
  }
}

void FrameRGB::saveArrayRGBCubeSocketCmd(int ss, FitsFile::ArchType endian)
{
  if (keyContext->fits) {
    OutFitsSocket str(ss);
    saveArrayRGBCube(str, endian);
  }
}

void FrameRGB::savePhotoCmd(const char* ph)
{
  // need to determine size from key context
  FitsImage* fits = keyContext->fits;
  if (!fits)
    return;

  // check size
  FitsBound* params = fits->getDataParams(context->frScale.scanMode());
  for (int kk=0; kk<3; kk++) {
    if (!view[kk] || !context[kk].fits)
      continue;

    FitsImage* ptr = context[kk].fits;
    FitsBound* pptr = ptr->getDataParams(context[kk].frScale.scanMode());
    if (params->xmin != pptr->xmin || params->xmax != pptr->xmax ||
	params->ymin != pptr->ymin || params->ymax != pptr->ymax) {
      internalError("All channels need to be same size.");
      return;
    }
  }

  // width,height
  int width = params->xmax - params->xmin;
  int height = params->ymax - params->ymin;

  // photo
  if (*ph == '\0') {
    Tcl_AppendResult(interp, "bad image name ", NULL);
    return;
  }
  Tk_PhotoHandle photo = Tk_FindPhoto(interp, ph);
  if (!photo) {
    Tcl_AppendResult(interp, "bad image handle ", NULL);
    return;
  }
  if (Tk_PhotoSetSize(interp, photo, width, height) != TCL_OK) {
    Tcl_AppendResult(interp, "bad photo set size ", NULL);
    return;
  }    
  Tk_PhotoBlank(photo);
  Tk_PhotoImageBlock block;
  if (!Tk_PhotoGetImage(photo,&block)) {
    Tcl_AppendResult(interp, "bad image block ", NULL);
    return;
  }

  if (block.pixelSize<4) {
    Tcl_AppendResult(interp, "bad pixel size ", NULL);
    return;
  }

  // clear, set alpha channel
  unsigned char* dest = block.pixelPtr;
  for (long jj=0; jj<height; jj++) {
    for (long ii=0; ii<width; ii++, dest+=block.pixelSize) {
      *(dest+block.offset[0]) = 0; // red
      *(dest+block.offset[1]) = 0; // green
      *(dest+block.offset[2]) = 0; // blue
      *(dest+block.offset[3]) = 255; // alpha
    }
  }

  // main loop
  SETSIGBUS

  // one channel at a time
  for (int kk=0; kk<3; kk++) {
    if (!view[kk] || !context[kk].fits)
      continue;

    // basics
    int length = colorScale[kk]->size() - 1;
    const unsigned char* table = colorScale[kk]->psColors();

    // variable
    FitsImage* fits = context[kk].cfits;
    double ll = fits->getLowDouble();
    double hh = fits->getHighDouble();
    double diff = hh - ll;

    unsigned char* dest = block.pixelPtr;
    for (long jj=params->ymax-1; jj>=params->ymin; jj--) {
      for (long ii=params->xmin; ii<params->xmax; ii++, dest+=block.pixelSize) {
	double value = fits->getValueDouble(Vector(ii,jj));

	if (isfinite(value)) {
	  if (value <= ll)
	    *(dest+block.offset[kk]) = table[0];
	  else if (value >= hh)
	    *(dest+block.offset[kk]) = table[length];
	  else
	    *(dest+block.offset[kk]) = table[(int)(((value - ll)/diff * length) + .5)];
	}
      }
    }
  }
  CLEARSIGBUS

  if (Tk_PhotoPutBlock(interp, photo, &block, 0, 0, width, height, 
			TK_PHOTO_COMPOSITE_SET) != TCL_OK) {
    Tcl_AppendResult(interp, "bad put block ", NULL);
    return;
  }
}

void FrameRGB::setBinCursor()
{
  for (int ii=0; ii<3; ii++)
    if (context[ii].fits)
      context[ii].fits->setBinCursor(cursor);
}

void FrameRGB::updateColorCells(unsigned char* cells, int cnt)
{
  if (DebugRGB) 
    cerr << "updateColorCells" << endl;

  // the colorbar widget will pass us a pointer to the indexCells

  colorCount = cnt;

  // copy the rgb vales to the colorCells array (for postscript printing)

  if (colorCells)
    delete [] colorCells;
  colorCells = new unsigned char[cnt*3];
  if (!colorCells) {
    internalError("Unable to Alloc colorCells");
    return;
  }

  memcpy(colorCells, cells, cnt*3);
}

void FrameRGB::updateColorScale()
{
  // we need colors before we can construct a scale
  if (!colorCells)
    return;

  if (DebugRGB) 
    cerr << "updateColorScale" << endl;

  for (int ii=0; ii<3; ii++) {
    if (colorScale[ii])
      delete colorScale[ii];

    switch (context[ii].frScale.colorScaleType()) {
    case FrScale::LINEARSCALE:
      colorScale[ii] = 
	new LinearScaleRGB(ii, colorCount, colorCells, colorCount);
      break;
    case FrScale::LOGSCALE:
      colorScale[ii] =
	new LogScaleRGB(ii, SCALESIZE, colorCells, colorCount, 
			context[ii].frScale.expo());
      break;
    case FrScale::POWSCALE:
      colorScale[ii] =
	new PowScaleRGB(ii, SCALESIZE, colorCells, colorCount, 
			context[ii].frScale.expo());
      break;
    case FrScale::SQRTSCALE:
      colorScale[ii] =
	new SqrtScaleRGB(ii, SCALESIZE, colorCells, colorCount);
      break;
    case FrScale::SQUAREDSCALE:
      colorScale[ii] =
	new SquaredScaleRGB(ii, SCALESIZE, colorCells, colorCount);
      break;
    case FrScale::ASINHSCALE:
      colorScale[ii] =
	new AsinhScaleRGB(ii, SCALESIZE, colorCells, colorCount);
      break;
    case FrScale::SINHSCALE:
      colorScale[ii] =
	new SinhScaleRGB(ii, SCALESIZE, colorCells, colorCount);
      break;
    case FrScale::HISTEQUSCALE:
      colorScale[ii] = 
	new HistEquScaleRGB(ii, SCALESIZE, colorCells, colorCount, 
			    context[ii].histequ(), HISTEQUSIZE);
      break;
    }
  }
}


void FrameRGB::updateRGBMatrices()
{
  // image,pysical,amplifier,detector are ok, check for wcs
  if (rgbSystem >= Coord::WCS) {
    for (int ii=0; ii<3; ii++) {
      if (context[ii].fits && !context[ii].fits->hasWCS(rgbSystem)) {
	// ok, don't have requested coordinate system
	// down grade to image
	rgbSystem = Coord::IMAGE;
	break;
      }
    }
  }

  // rgb align
  for (int ii=0; ii<3; ii++) {
    rgb[ii].identity();

    if (context[ii].fits && keyContext->fits) {
      switch (rgbSystem) {
      case Coord::IMAGE:
	// nothing to do here
	break;
      case Coord::PHYSICAL:
	if (context[ii].fits != keyContext->fits) 
	  rgb[ii] = 
	    context[ii].fits->imageToPhysical *
	    keyContext->fits->physicalToImage;
	break;
      case Coord::AMPLIFIER:
	if (context[ii].fits != keyContext->fits) 
	  rgb[ii] = context[ii].fits->imageToAmplifier *
	    keyContext->fits->amplifierToImage;
	break;
      case Coord::DETECTOR:
	if (context[ii].fits != keyContext->fits) 
	  rgb[ii] = context[ii].fits->imageToDetector * 
	    keyContext->fits->detectorToImage;
	break;
      default:
	rgbAlignWCS(ii);
	break;
      }
    }

    if (DebugRGB) 
      cerr << "rgb[" << ii << "] " << rgb[ii] << endl;
  }
}

void FrameRGB::unloadAllFits()
{
  if (DebugPerf)
    cerr << "Frame::RGBunloadAllFits" << endl;

  for (int ii=0; ii<3; ii++) {
    rgb[ii].identity();
    context[ii].unload();

    // always (for HISTEQU and LOG)
    updateColorScale();
  }

  channel =0;
  currentContext = &context[channel];
  keyContext = &context[channel];
  keyContextSet =0;

  FrameBase::unloadFits();
}

void FrameRGB::unloadFits()
{
  if (DebugPerf)
    cerr << "FrameRGB::unloadFits" << endl;

  rgb[channel].identity();
  context[channel].unload();

  // always (for HISTEQU and LOG)
  updateColorScale();
}

// Commands

void FrameRGB::getColorbarCmd()
{
  ostringstream str;

  str << "rgb " << setiosflags(ios::fixed);
  for (int ii=0; ii<3; ii++)
    str << bias[ii] << ' ';
  for (int ii=0; ii<3; ii++)
    str << contrast[ii] << ' ';
  str << invert << ' ' << ends;

  Tcl_AppendResult(interp, str.str().c_str(), NULL);
}

void FrameRGB::getInfoCmd(const Vector& vv, Coord::InternalSystem ref, char* var)
{
  FrameBase::getInfoCmd(vv, ref, var);
  if (!currentContext->cfits)
    return;

  char* array[3] = {"value,red","value,green","value,blue"};

  SETSIGBUS
  for (int ii=0; ii<3; ii++) {

    // make sure we have an image
    FitsImage* sptr = context[ii].cfits;
    if (!sptr)
      continue;

    int mosaic = context[ii].isMosaic();
    FitsBound* params = sptr->getDataParams(context[ii].frScale.scanMode());

    do {
      Vector3d rr = mapToRef3d(vv,ref);
      Vector img = Vector(rr) * sptr->refToData;

      if (img[0]>=params->xmin && img[0]<params->xmax && 
	  img[1]>=params->ymin && img[1]<params->ymax) {

	Tcl_SetVar2(interp,var,array[ii],(char*)sptr->getValue(img),0);
	break;
      }
      else {
	if (mosaic) {
	  sptr = sptr->nextMosaic();
	  if (sptr)
	    params = sptr->getDataParams(context[ii].frScale.scanMode());
	}
      }
    }
    while (mosaic && sptr);
  }
  CLEARSIGBUS
}

void FrameRGB::getRGBChannelCmd()
{
  switch (channel) {
  case 0:
    Tcl_AppendResult(interp, "red", NULL);
    return;
  case 1:
    Tcl_AppendResult(interp, "green", NULL);
    return;
  case 2:
    Tcl_AppendResult(interp, "blue", NULL);
    return;
  }
}

void FrameRGB::getRGBSystemCmd()
{
  printCoordSystem(rgbSystem);
}

void FrameRGB::getRGBViewCmd()
{
  for (int ii=0; ii<3; ii++)
    Tcl_AppendElement(interp, view[ii] ? "1" : "0");
}

void FrameRGB::getTypeCmd()
{
  Tcl_AppendResult(interp, "rgb", NULL);
}

void FrameRGB::loadPhotoCmd(const char* ph, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImagePhotoCube(this, interp, ph, fn, 1);
  loadRGBCube(ALLOC,fn,img);
}

void FrameRGB::loadRGBCubeAllocCmd(const char* ch, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageFitsAlloc(this, ch, fn, FitsFile::NOFLUSH, 1);
  loadRGBCube(ALLOC,fn,img);
}

void FrameRGB::loadRGBCubeAllocGZCmd(const char* ch, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageFitsAllocGZ(this, ch, fn, FitsFile::NOFLUSH, 1);
  loadRGBCube(ALLOCGZ,fn,img);
}

void FrameRGB::loadRGBCubeChannelCmd(const char* ch, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageFitsChannel(this, interp, ch, fn, 
					    FitsFile::NOFLUSH, 1);
  loadRGBCube(CHANNEL,fn,img);
}

void FrameRGB::loadRGBCubeMMapCmd(const char* fn, LoadMethod lm)
{
  unloadAllFits();
  FitsImage* img = new FitsImageFitsMMap(this, fn, 1);
  setScanModeIncr(lm);
  loadRGBCube(MMAP,fn,img);
}

void FrameRGB::loadRGBCubeSMMapCmd(const char* hdr, const char* fn, 
				   LoadMethod lm)
{
  unloadAllFits();
  FitsImage* img = new FitsImageFitsSMMap(this, hdr, fn, 1);
  setScanModeIncr(lm);
  loadRGBCube(SMMAP,fn,img);
}

void FrameRGB::loadRGBCubeMMapIncrCmd(const char* fn, LoadMethod lm)
{
  unloadAllFits();
  FitsImage* img = new FitsImageFitsMMapIncr(this, fn, 1);
  setScanModeIncr(lm);
  loadRGBCube(MMAPINCR,fn,img);
}

void FrameRGB::loadRGBCubeShareCmd(ShmType type, int id, const char* fn,
				   LoadMethod lm)
{
  unloadAllFits();
  FitsImage* img = new FitsImageFitsShare(this, type, id, fn, 1);
  setScanModeIncr(lm);
  loadRGBCube(SHARE,fn,img);
}

void FrameRGB::loadRGBCubeSShareCmd(ShmType type, int hdr, int id,
				    const char* fn, LoadMethod lm)
{
  unloadAllFits();
  FitsImage* img = new FitsImageFitsSShare(this, type, hdr, id, fn, 1);
  setScanModeIncr(lm);
  loadRGBCube(SSHARE,fn,img);
}

void FrameRGB::loadRGBCubeSocketCmd(int s, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageFitsSocket(this, s, fn, FitsFile::FLUSH, 1);
  loadRGBCube(SOCKET,fn,img);
}

void FrameRGB::loadRGBCubeSocketGZCmd(int s, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageFitsSocketGZ(this, s, fn, FitsFile::FLUSH, 1);
  loadRGBCube(SOCKETGZ,fn,img);
}

void FrameRGB::loadRGBCubeVarCmd(const char* ch, const char* fn,
				     LoadMethod lm)
{
  unloadAllFits();
  FitsImage* img = new FitsImageFitsVar(this, interp, ch, fn, 1);
  setScanModeIncr(lm);
  loadRGBCube(VAR,fn,img);
}

void FrameRGB::loadRGBImageAllocCmd(const char* ch, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageMosaicAlloc(this, ch, fn, FitsFile::NOFLUSH, 1);
  loadRGBImage(ALLOC,fn,img);
}

void FrameRGB::loadRGBImageAllocGZCmd(const char* ch, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageMosaicAllocGZ(this, ch, fn, FitsFile::NOFLUSH, 1);
  loadRGBImage(ALLOCGZ,fn,img);
}

void FrameRGB::loadRGBImageChannelCmd(const char* ch, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageMosaicChannel(this, interp, ch, fn, 
					      FitsFile::NOFLUSH, 1);
  loadRGBImage(CHANNEL,fn,img);
}

void FrameRGB::loadRGBImageMMapCmd(const char* fn, LoadMethod lm)
{
  unloadAllFits();
  FitsImage* img = new FitsImageMosaicMMap(this, fn, 1);
  setScanModeIncr(lm);
  loadRGBImage(MMAP,fn,img);
}

void FrameRGB::loadRGBImageMMapIncrCmd(const char* fn, LoadMethod lm)
{
  unloadAllFits();
  FitsImage* img = new FitsImageMosaicMMapIncr(this, fn, 1);
  setScanModeIncr(lm);
  loadRGBImage(MMAPINCR,fn,img);
}

void FrameRGB::loadRGBImageShareCmd(ShmType type, int id, const char* fn,
				    LoadMethod lm)
{
  unloadAllFits();
  FitsImage* img = new FitsImageMosaicShare(this, type, id, fn, 1);
  setScanModeIncr(lm);
  loadRGBImage(SHARE,fn,img);
}

void FrameRGB::loadRGBImageSocketCmd(int s, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageMosaicSocket(this, s, fn, FitsFile::FLUSH, 1);
  loadRGBImage(SOCKET,fn,img);
}

void FrameRGB::loadRGBImageSocketGZCmd(int s, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageMosaicSocketGZ(this, s, fn, FitsFile::FLUSH, 1);
  loadRGBImage(SOCKETGZ,fn,img);
}

void FrameRGB::loadRGBImageVarCmd(const char* ch, const char* fn,
				      LoadMethod lm)
{
  unloadAllFits();
  FitsImage* img = new FitsImageMosaicVar(this, interp, ch, fn, 1);
  setScanModeIncr(lm);
  loadRGBImage(VAR,fn,img);
}

void FrameRGB::loadArrayRGBCubeAllocCmd(const char* ch, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageArrAlloc(this, ch, fn, FitsFile::NOFLUSH, 1);
  loadRGBCube(ALLOC,fn,img);
}

void FrameRGB::loadArrayRGBCubeAllocGZCmd(const char* ch, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageArrAllocGZ(this, ch, fn, FitsFile::NOFLUSH, 1);
  loadRGBCube(ALLOCGZ,fn,img);
}

void FrameRGB::loadArrayRGBCubeChannelCmd(const char* ch, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageArrChannel(this, interp, ch, fn, 
					   FitsFile::NOFLUSH, 1);
  loadRGBCube(CHANNEL,fn,img);
}

void FrameRGB::loadArrayRGBCubeMMapCmd(const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageArrMMap(this, fn, 1);
  loadRGBCube(MMAP,fn,img);
}

void FrameRGB::loadArrayRGBCubeMMapIncrCmd(const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageArrMMapIncr(this, fn, 1);
  loadRGBCube(MMAPINCR,fn,img);
}

void FrameRGB::loadArrayRGBCubeShareCmd(ShmType type, int id, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageArrShare(this, type, id, fn, 1);
  loadRGBCube(SHARE,fn,img);
}

void FrameRGB::loadArrayRGBCubeSocketCmd(int s, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageArrSocket(this, s, fn, FitsFile::FLUSH, 1);
  loadRGBCube(SOCKET,fn,img);
}

void FrameRGB::loadArrayRGBCubeSocketGZCmd(int s, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageArrSocketGZ(this, s, fn, FitsFile::FLUSH, 1);
  loadRGBCube(SOCKETGZ,fn,img);
}

void FrameRGB::loadArrayRGBCubeVarCmd(const char* ch, const char* fn)
{
  unloadAllFits();
  FitsImage* img = new FitsImageArrVar(this, interp, ch, fn, 1);
  loadRGBCube(VAR,fn,img);
}

void FrameRGB::setRGBChannelCmd(const char* c)
{
  if (!strncmp(c,"red",3))
    channel = 0;
  else if (!strncmp(c,"gre",3))
    channel = 1;
  else if (!strncmp(c,"blu",3))
    channel = 2;
  else
    channel = 0;

  currentContext = &context[channel];

  // execute any update callbacks
  updateCBMarkers();

 // always update
  update(BASE);
}

void FrameRGB::setRGBSystemCmd(Coord::CoordSystem sys)
{
  rgbSystem = sys;

  // save current matrix
  Matrix old[3];
  for (int ii=0; ii<3; ii++)
    old[ii] = rgb[ii];

  alignWCS();

  // fix any contours
  for(int ii=0; ii<3; ii++)
    if (context[ii].contour) {
      Matrix mm = old[ii].invert() * rgb[ii];
      context[ii].contour->updateCoords(mm);
    }

  update(MATRIX);
}

void FrameRGB::setRGBViewCmd(int r, int g, int b)
{
  view[0] = r ? 1 : 0;
  view[1] = g ? 1 : 0;
  view[2] = b ? 1 : 0;

  update(BASE); // always update
}
