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

#include <tk.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "base.h"
#include "context.h"
#include "fitsimage.h"
#include "marker.h"
#include "ps.h"
#include "tkpostscript.h"

// Debug
int DebugAST= 0;
int DebugBin= 0;
int DebugCompress= 0;
int DebugCrop= 0;
int DebugGZ= 0;
int DebugMosaic= 0;
int DebugPerf= 0;
int DebugRGB= 0;
int DebugWCS= 0;

// Parser Stuff
#undef yyFlexLexer
#define yyFlexLexer frFlexLexer
#include <FlexLexer.h>

void* frlval;
extern int frparse(Base*, frFlexLexer*);

int frlex(void* vval, frFlexLexer* ll)
{
  frlval = vval;
  return ll ? ll->yylex() : 0;
}

void frerror(Base* fr, frFlexLexer* ll, const char* m)
{
  fr->error(m);
  const char* cmd = ll ? ll->YYText() : (const char*)NULL;
  if (cmd && cmd[0] != '\n') {
    fr->error(": ");
    fr->error(cmd);
  }
}

// Base Member Functions

Base::Base(Tcl_Interp* i, Tk_Canvas c, Tk_Item* item) 
  : Widget(i, c, item)
{
  nthreads_ = 8;

  baseXImage = NULL;
  basePixmap = 0;
  needsUpdate = NOUPDATE;
  syncUpdate = 0;

  currentContext = NULL;
  keyContext = NULL;
  keyContextSet =0;

  orientation = Coord::NORMAL;
  zoom_ = Vector(1,1);
  rotation = 0;

  rotateRotation = 0;

  preservePan = 0;

  panPM = 0;
  panGCXOR = NULL;

  rotateGCXOR = NULL;

  pannerPixmap = 0;
  pannerXImage = NULL;
  pannerWidth = 0;
  pannerHeight = 0;
  pannerName[0] = '\0';
  usePanner = 0;
  pannerGC = XCreateGC(display, Tk_WindowId(tkwin), 0, NULL);
  XSetLineAttributes(display, pannerGC, 1, LineSolid, CapButt, JoinMiter);

  magnifierPixmap = 0;
  magnifierXImage = NULL;
  magnifierWidth = 0;
  magnifierHeight = 0;
  magnifierZoom_ = 4;
  magnifierName[0] = '\0';
  useMagnifier = 0;
  useMagnifierGraphics = 1;
  useMagnifierCursor = 1;
  magnifierColorName = dupstr("white");

  wcsSystem_ = Coord::WCS;
  wcsSky_ = Coord::FK5;
  wcsSkyFormat_ = Coord::DEGREES;

  wcsAlign_ = 0;
  wcsAlt_ =0;

  xySystem_ = Coord::IMAGE;
  xySky_ = Coord::FK5;

  wcsOrientation = Coord::NORMAL;
  wcsRotation = 0;
  irafOrientation = (Coord::Orientation)-1;

  maskColorName = dupstr("red");
  maskAlpha = 1;
  maskMark = 1;

  invert = 0;

  highliteGC = NULL;
  useHighlite = 0;

  useCrosshair = 0;

  markerEpsilon = 3;
  showMarkers = 1;
  showMarkersText = 1;
  centroidAuto = 0;
  centroidIteration = 5;
  centroidRadius = 10;
  preserveMarkers = 0;

  markerGC = NULL;
  markerGCXOR = NULL;
  selectGCXOR = NULL;

  grid = NULL;
  gridGC = NULL;

  contourGC = NULL;

  bgColorName = dupstr("white");
  bgColor = getXColor("white");
  memset(bgTrueColor_,255,4);

  nanColorName = dupstr("white");
  nanColor = getXColor("white");
  memset(nanTrueColor_,255,4);

  dlist[0] = 8;
  dlist[1] = 3;

  colorbartag =NULL;

  markers = &userMarkers;
  undoMarkers = &undoUserMarkers;
  pasteMarkers = &pasteUserMarkers;
  undoMarkerType = NONE;

  editMarker = NULL;
  rotateMarker = NULL;

  compositeMarker = NULL;
  imageToData = Translate(-.5, -.5);
  dataToImage = Translate( .5,  .5);

  inverseScale = NULL;
}

Base::~Base()
{
  if (basePixmap)
    Tk_FreePixmap(display, basePixmap);

  if (baseXImage)
    XDestroyImage(baseXImage);

  if (pannerPixmap)
    Tk_FreePixmap(display, pannerPixmap);

  if (pannerXImage)
    XDestroyImage(pannerXImage);

  if (panPM)
    Tk_FreePixmap(display, panPM);

  if (panGCXOR)
    XFreeGC(display, panGCXOR);

  if (rotateGCXOR)
    XFreeGC(display, rotateGCXOR);

  if (pannerGC)
    XFreeGC(display, pannerGC);

  if (magnifierPixmap)
    Tk_FreePixmap(display, magnifierPixmap);

  if (magnifierXImage)
    XDestroyImage(magnifierXImage);

  if (highliteGC)
    XFreeGC(display, highliteGC);

  if (maskColorName)
    delete [] maskColorName;

  if (markerGC)
    XFreeGC(display, markerGC);

  if (markerGCXOR)
    XFreeGC(display, markerGCXOR);

  if (selectGCXOR)
    XFreeGC(display, selectGCXOR);

  if (grid)
    delete grid;

  if (gridGC)
    XFreeGC(display, gridGC);

  if (contourGC)
    XFreeGC(display, contourGC);

  if (bgColorName)
    delete [] bgColorName;

  if (nanColorName)
    delete [] nanColorName;

  if (colorbartag)
    delete [] colorbartag;

  if (magnifierColorName)
    delete [] magnifierColorName;

  if (inverseScale)
    delete inverseScale;
}

void Base::alignWCS()
{
  if (!wcsAlign_ || !context->cfits || !hasWCS(wcsSystem_)) {
    wcsOrientation = Coord::NORMAL;
    wcsOrientationMatrix.identity();
    wcsRotation = 0;
    return;
  }

  calcAlignWCS(context->cfits, wcsSystem_, wcsSky_,
	       &wcsOrientation, &wcsOrientationMatrix, &wcsRotation);
}

void Base::alignWCS(Coord::CoordSystem sys, Coord::SkyFrame sky)
{
  if (!wcsAlign_ || !context->cfits || !hasWCS(sys)) {
    wcsOrientation = Coord::NORMAL;
    wcsOrientationMatrix.identity();
    wcsRotation = 0;
    return;
  }

  calcAlignWCS(context->cfits, sys, sky,
	       &wcsOrientation, &wcsOrientationMatrix, &wcsRotation);
}

void Base::alignWCS(FitsImage* ptr, Coord::CoordSystem sys)
{
  if (!wcsAlign_ || !ptr || !context->cfits || !hasWCS(wcsSystem_)) {
    wcsOrientation = Coord::NORMAL;
    wcsOrientationMatrix.identity();
    wcsRotation = 0;
    return;
  }

  calcAlignWCS(ptr, context->cfits, sys, wcsSystem_, wcsSky_,
	       &wcsOrientation, &wcsOrientationMatrix, &wcsRotation, &zoom_);
}

void Base::calcAlignWCS(FitsImage* fits1, 
			Coord::CoordSystem sys1, Coord::SkyFrame sky,
			Coord::Orientation* orientation, Matrix* oo,
			double* rotation)
{
  // init
  *orientation = Coord::NORMAL;
  oo->identity();
  *rotation = 0;

  if (!fits1 || !fits1->hasWCS(sys1))
    return;

  // orientation
  *orientation = fits1->getWCSOrientation(sys1,sky);
  switch (*orientation) {
  case Coord::NORMAL:
    oo->identity();
    break;
  case Coord::XX:
    *oo = FlipX();
    break;
  }

  // rotation
  *rotation = fits1->getWCSRotation(sys1,sky);
}

void Base::calcAlignWCS(FitsImage* fits1, FitsImage* fits2, 
			Coord::CoordSystem sys1, Coord::CoordSystem sys2, 
			Coord::SkyFrame sky,
			Coord::Orientation* orientation, Matrix* oo,
			double* rotation, Vector* zoom)
{
  // init
  *orientation = Coord::NORMAL;
  oo->identity();
  *rotation = 0;

  if ((!fits1 || !fits2) || 
      (fits1 == fits2) ||
      !(fits1->hasWCS(sys1)) ||
      !(fits2->hasWCS(sys2)))
    return;

  Vector org1 = fits1->center();
  Vector orval1 = fits1->pix2wcs(org1,sys1,sky);

  // orientation
  *orientation = fits2->getWCSOrientation(sys2,sky);
  switch (*orientation) {
  case Coord::NORMAL:
    oo->identity();
    break;
  case Coord::XX:
    *oo = FlipX();
    break;
  }

  // rotation
  Vector orpix = fits2->wcs2pix(orval1, sys2, sky);
  Vector delta = fits2->getWCScdelt(sys2).abs();
  Vector npix = 
    fits2->wcs2pix(Vector(orval1[0],orval1[1]+delta[1]),sys2,sky);
  Vector north = (npix-orpix).normalize();
  Vector epix = 
    fits2->wcs2pix(Vector(orval1[0]+delta[0],orval1[1]),sys2,sky);
  Vector east = (epix-orpix).normalize();

  // sanity check
  Vector diff = (north-east).abs();
  if ((north[0]==0 && north[1]==0) ||
      (east[0]==0 && east[1]==0) ||
      (diff[0]<.01 && diff[1]<.01)) {
    *rotation = 0;
    *orientation = Coord::NORMAL;
    oo->identity();
    return;
  }

  if ((fits2->getWCS(sys2))->imflip)
    *rotation = (north.angle()-M_PI_2);
  else
    *rotation = -(north.angle()-M_PI_2);

  // zoom
  Vector cd1 = fits1->getWCScdelt(sys1);
  Vector cd2 = fits2->getWCScdelt(sys2);
  *zoom = Vector((*zoom)[0]/fabs(cd1[0]/cd2[0]), 
		 (*zoom)[1]/fabs(cd1[1]/cd2[1]));
}

Matrix Base::calcAlignWCS(FitsImage* fits1, FitsImage* fits2, 
			  Coord::CoordSystem sys1, Coord::CoordSystem sys2, 
			  Coord::SkyFrame sky)
{
  if ((!fits1 || !fits2) || 
      (fits1 == fits2) ||
      !(fits1->hasWCS(sys1)) ||
      !(fits2->hasWCS(sys2)))
    return Matrix();

  Vector org1 = fits1->center();
  Vector orval1 = fits1->pix2wcs(org1,sys1,sky);

  Vector org2 = fits2->center();
  Vector orval2 = fits2->pix2wcs(org2,sys2,sky);

  // orientation
  Coord::Orientation orientation =Coord::NORMAL;
  {
    Coord::Orientation o1 = fits1->getWCSOrientation(sys1,sky);
    Coord::Orientation o2 = fits2->getWCSOrientation(sys2,sky);

    switch (o1) {
    case Coord::NORMAL:
      {
	switch (o2) {
	case Coord::NORMAL:
	  orientation = Coord::NORMAL;
	  break;
	case Coord::XX:
	  orientation = Coord::XX;
	  break;
	}
      }
      break;
    case Coord::XX:
      {
	switch (o2) {
	case Coord::NORMAL:
	  orientation = Coord::XX;
	  break;
	case Coord::XX:
	  orientation = Coord::NORMAL;
	  break;
	}
      }
      break;
    }
  }

  // zoom
  Vector zoom =Vector(1,1);
  {
    Vector cd1 = fits1->getWCScdelt(sys1);
    Vector cd2 = fits2->getWCScdelt(sys2);
    zoom = Vector(cd2[0]/cd1[0], cd2[1]/cd1[1]).abs();
  }
  
  // rotation
  double rotation =0;
  {
    double r1 =0;
    {
      Vector orpix = fits1->wcs2pix(orval1, sys1, sky);
      Vector delta = fits1->getWCScdelt(sys1).abs();
      Vector npix = 
	fits1->wcs2pix(Vector(orval1[0],orval1[1]+delta[1]),sys1,sky);
      Vector north = (npix-orpix).normalize();
      Vector epix = 
	fits1->wcs2pix(Vector(orval1[0]+delta[0],orval1[1]),sys1,sky);
      Vector east = (epix-orpix).normalize();

      // sanity check
      Vector diff = (north-east).abs();
      if ((north[0]==0 && north[1]==0) ||
	  (east[0]==0 && east[1]==0) ||
	  (diff[0]<.01 && diff[1]<.01))
	return Matrix();

      if ((fits1->getWCS(sys1))->imflip)
	r1 = (north.angle()-M_PI_2);
      else
	r1 = -(north.angle()-M_PI_2);
    }

    double r2 =0;
    {
      Vector orpix = fits2->wcs2pix(orval1, sys2, sky);
      Vector delta = fits2->getWCScdelt(sys2).abs();
      Vector npix = 
	fits2->wcs2pix(Vector(orval1[0],orval1[1]+delta[1]),sys2,sky);
      Vector north = (npix-orpix).normalize();
      Vector epix = 
	fits2->wcs2pix(Vector(orval1[0]+delta[0],orval1[1]),sys2,sky);
      Vector east = (epix-orpix).normalize();

      // sanity check
      Vector diff = (north-east).abs();
      if ((north[0]==0 && north[1]==0) ||
	  (east[0]==0 && east[1]==0) ||
	  (diff[0]<.01 && diff[1]<.01))
	return Matrix();

      if ((fits2->getWCS(sys2))->imflip)
	r2 = north.angle()-M_PI_2;
      else
	r2 = -(north.angle()-M_PI_2);
    }

    if ((fits1->getWCS(sys1))->imflip)
      rotation = -(r1-r2);
    else
      rotation = r1-r2;
  }

  // origin
  Vector origin;
  {
    Vector orpix1 = fits1->wcs2pix(orval2,sys1,sky) * imageToData;
    Vector orpix2 = fits2->wcs2pix(orval2,sys2,sky) * imageToData;
    origin = orpix1 - orpix2;
  }

  // matrix
  {
    Matrix flip;
    switch (orientation) {
    case Coord::NORMAL:
      break;
    case Coord::XX:
      flip = FlipX();
      break;
    }

    Vector orpix2 = fits2->wcs2pix(orval2,sys2,sky) * imageToData;

    return
      Translate(-orpix2) *
      flip *
      Scale(zoom) *
      Rotate(rotation) *
      Translate(orpix2) *
      Translate(origin);
  }
}

double Base::calcZoom(Vector src, Vector dest)
{
  // we need to calculate the width and height of the rotated image
  // so we can derived a zoom factor to shrink it to fit the requested size

  Vector cc = src/2.;

  Vector ll;
  Vector lr(src[0],0);
  Vector ur(src);
  Vector ul(0,src[1]);

  Matrix mm = Translate(-cc) * Rotate(wcsRotation) * Rotate(rotation);

  BBox bb(ll*mm);
  bb.bound(lr*mm);
  bb.bound(ur*mm);
  bb.bound(ul*mm);

  Vector bs = bb.size();
  double r0 = dest[0]/bs[0];
  double r1 = dest[1]/bs[1];

  return r0>r1 ? r1:r0;
}

void Base::centerImage()
{
  if (!keyContext->fits) {
    cursor = Vector();
    return;
  }

  // This is here because we need mapToRef to be up-to-date
  updateMatrices();
  updatePannerMatrices();

  // imageCenter is in IMAGE coords
  Vector aa = imageCenter(keyContext->frScale.scanMode());
  // always center to center of pixel, even for even sized image
  Vector bb = (aa*Translate(.5,.5)).floor();
  // cursor is in REF coords
  cursor = keyContext->fits->mapToRef(bb,Coord::IMAGE,Coord::FK5);
}

void Base::coordToTclArray(FitsImage* ptr, const Vector3d& vv, 
			   Coord::CoordSystem out, Precision pp, 
			   const char* var, const char* base)
{
  Vector rr = ptr->mapFromRef(vv, out);
  doubleToTclArray(rr[0], pp, var, base, "x");
  doubleToTclArray(rr[1], pp, var, base, "y");
}

void Base::coord3ToTclArray(FitsImage* ptr, const Vector3d& vv, 
			    Coord::CoordSystem out, Precision pp, 
			    const char* var, const char* base)
{
  double ss = ptr->mapFromRef3axis(((Vector3d&)vv)[2]+.5,out,2);
  doubleToTclArray(ss, pp, var, base, "z");
}

int Base::doRender() 
{
  return context->cfits ? 1 : 0;
}

void Base::doubleToTclArray(double d, Precision p, const char* var, 
			    const char* base, const char* mod)
{
  ostringstream str;
  str << base << "," << mod << ends;

  ostringstream vstr;
  switch (p) {
  case DEFAULT:
    vstr << d << ends;
    break;
  case FIXED:
    vstr << setiosflags(ios::fixed) << setw(9) << setprecision(3) 
	<< d << ends;
    break;
  case SCIENTIFIC:
    vstr << setiosflags(ios::scientific) << setprecision(8) << d << ends;
    break;
  case INTEGER:
    vstr << (int)d << ends;
    break;
  }

  Tcl_SetVar2(interp, (char*)var, str.str().c_str(), vstr.str().c_str(), 0);
}

FitsImage* Base::findFits()
{
  return keyContext->fits;
}

FitsImage* Base::findFits(Coord::CoordSystem sys, const Vector& vv)
{
  switch (sys) {
  case Coord::IMAGE:
  case Coord::PHYSICAL:
  case Coord::DETECTOR:
  case Coord::AMPLIFIER:
    return findFits(vv);
  default:
    if (keyContext->fits && keyContext->fits->hasWCSCel(sys))
      return keyContext->fits;
    else
      return findFits(vv);
  }
}

FitsImage* Base::findFits(const Vector& vv)
{
  FitsImage* ptr = keyContext->fits;
  while (ptr) {
    Vector img = vv * ptr->refToData;
    FitsBound* params = ptr->getDataParams(currentContext->frScale.scanMode());
    if (img[0]>=params->xmin && img[0]<params->xmax && 
	img[1]>=params->ymin && img[1]<params->ymax)
      return ptr;
    ptr = ptr->nextMosaic();
  }
  return keyContext->fits;
}

FitsImage* Base::findFits(int which)
{
  FitsImage* ptr = keyContext->fits;
  for (int ii=1; ii<which; ii++)
    if (ptr)
      ptr = ptr->nextMosaic();
  return (ptr ? ptr : keyContext->fits);
}

int Base::findFits(FitsImage* p)
{
  FitsImage* ptr = keyContext->fits;
  int rr = 0;
  while (ptr) {
    rr++;
    if (ptr == p)
      return rr;
    ptr = ptr->nextMosaic();
  }
  return rr;
}

FitsImage* Base::findAllFits(int which)
{
  // modified for medatacube
  FitsImage* rr = NULL;
  FitsImage* ptr = currentContext->fits;
  while (ptr && which) {
    FitsImage* sptr = ptr;
    while (sptr && which) {
      which--;
      if (!which) {
	rr = sptr;
	break;
      }
      sptr = sptr->nextSlice();
    }
    ptr = ptr->nextMosaic();
  }

  return rr;
}

int Base::fitsCount()
{
  return currentContext->fitsCount();
}

void Base::getInfoClearName(char* var)
{
  Tcl_SetVar2(interp,var,"filename","",0);
  Tcl_SetVar2(interp,var,"object","",0);
  Tcl_SetVar2(interp,var,"min","",0);
  Tcl_SetVar2(interp,var,"max","",0);
  Tcl_SetVar2(interp,var,"low","",0);
  Tcl_SetVar2(interp,var,"high","",0);
}

void Base::getInfoClearValue(char* var)
{
  Tcl_SetVar2(interp,var,"value","",0);
  Tcl_SetVar2(interp,var,"value,red","",0);
  Tcl_SetVar2(interp,var,"value,green","",0);
  Tcl_SetVar2(interp,var,"value,blue","",0);

  Tcl_SetVar2(interp,var,"image,x","",0);
  Tcl_SetVar2(interp,var,"image,y","",0);
  Tcl_SetVar2(interp,var,"image,z","",0);
  Tcl_SetVar2(interp,var,"physical,x","",0);
  Tcl_SetVar2(interp,var,"physical,y","",0);
  Tcl_SetVar2(interp,var,"physical,z","",0);
  Tcl_SetVar2(interp,var,"amplifier,x","",0);
  Tcl_SetVar2(interp,var,"amplifier,y","",0);
  Tcl_SetVar2(interp,var,"amplifier,z","",0);
  Tcl_SetVar2(interp,var,"detector,x","",0);
  Tcl_SetVar2(interp,var,"detector,y","",0);
  Tcl_SetVar2(interp,var,"detector,z","",0);
}

void Base::getInfoClearWCS(char* var)
{
  for (int i=0; i<MULTWCS; i++) {
    char buf[64];
    char w = !i ? NULL : '`'+i;
    Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",x"),"",0);
    Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",y"),"",0);
    Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",z"),"",0);
    Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",sys"),"",0);
  }
}

void Base::getInfoWCS(char* var, const Vector3d& rr, FitsImage* ptr, 
		      FitsImage* sptr)
{
  Vector img = Vector(rr) * sptr->refToData;

  for (int i=0; i<MULTWCS; i++) {
    char buf[64];
    char w = !i ? NULL : '`'+i;
    Coord::CoordSystem www = (Coord::CoordSystem)(Coord::WCS+i);

    if (hasWCS(www)) {
      char buff[128];
      Vector uu = img * dataToImage;
      sptr->pix2wcs(uu, www, wcsSky_, wcsSkyFormat_, buff, 128);

      int argc;
      const char** argv;
      Tcl_SplitList(interp, buff, &argc, &argv);

      if (argc > 0 && argv && argv[0])
	Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",x"),argv[0],0);
      else
	Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",x"),"",0);
      if (argc > 1 && argv && argv[1])
	Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",y"),argv[1],0);
      else
	Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",y"),"",0);
      // use first slice
      coord3ToTclArray(ptr,rr,www,FIXED,var,"wcs");

      char* wcsname = (char*)sptr->getWCSName(www);
      if (wcsname)
	Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",sys"),wcsname,0);
      else if (argc > 2 && argv && argv[2])
	Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",sys"),argv[2],0);
      else
	Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",sys"),"",0);
	    
      Tcl_Free((char*)argv);
    }
    else {
      Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",x"),"",0);
      Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",y"),"",0);
      Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",z"),"",0);
      Tcl_SetVar2(interp,var,varcat(buf,"wcs",w,",sys"),"",0);
    }
  }
}

int Base::hasATMV()
{
  return currentContext->cfits && currentContext->cfits->hasATMV();
}

int Base::hasContour()
{
  return currentContext->hasContour();
}

int Base::hasContourAux()
{
  return currentContext->hasContourAux();
}

int Base::hasDTMV()
{
  return currentContext->cfits && currentContext->cfits->hasDTMV();
}

int Base::hasLTMV()
{
  return currentContext->cfits && currentContext->cfits->hasLTMV();
}

int Base::hasWCS(Coord::CoordSystem sys)
{ 
  return currentContext->cfits && currentContext->cfits->hasWCS(sys);
}

int Base::hasWCSEqu(Coord::CoordSystem sys)
{ 
  return currentContext->cfits && currentContext->cfits->hasWCSEqu(sys);
}

int Base::hasWCSCel(Coord::CoordSystem sys)
{ 
  return currentContext->cfits && currentContext->cfits->hasWCSCel(sys);
}

int Base::hasWCSx(Coord::CoordSystem sys)
{ 
  return currentContext->cfits && currentContext->cfits->hasWCSx(sys,2);
}

Vector Base::imageCenter(FrScale::ScanMode mode) 
{
  return imageBBox(mode).center();
}

Vector Base::imageSize(FrScale::ScanMode mode) 
{
  return imageBBox(mode).size();
}

BBox Base::imageBBox(FrScale::ScanMode mode)
{
  // returns imageBBox in IMAGE coords
  // params are in DATA coords and extends edge to edge
  BBox rr;
  int first=1;
  FitsImage* ptr = context->fits;
  while (ptr) {
    FitsBound* params = ptr->getDataParams(mode);
    Matrix mm = ptr->wcsToRef * 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;
}

int Base::isIIS()
{
  return 0;
}

int Base::isCube()
{
  return currentContext->isCube();
}

int Base::isBinTable()
{
  return (currentContext->fits && currentContext->fits->isBinTable()) ? 1 : 0;
}

int Base::isMosaic()
{
  return currentContext->isMosaic();
}

FitsImage* Base::isInFits(const Vector& vv, Coord::InternalSystem ref, Vector* rv)
{
  Vector ss = mapToRef(vv,ref);
  FitsImage* ptr = currentContext->fits;

  while (ptr) {
    Vector rr = ss * ptr->refToData;
    FitsBound* params = ptr->getDataParams(currentContext->frScale.scanMode());

    if (rr[0]>=params->xmin && rr[0]<params->xmax && 
	rr[1]>=params->ymin && rr[1]<params->ymax) {
      if (rv)
	*rv = rr;
      return ptr;
    }
    ptr = ptr->nextMosaic();
  }

  return NULL;
}

FitsImage* Base::isInCFits(const Vector& vv, Coord::InternalSystem ref, Vector* rv)
{
  Vector ss = mapToRef(vv,ref);
  FitsImage* ptr = currentContext->cfits;

  while (ptr) {
    Vector rr = ss * ptr->refToData;
    FitsBound* params = ptr->getDataParams(currentContext->frScale.scanMode());

    if (rr[0]>=params->xmin && rr[0]<params->xmax && 
	rr[1]>=params->ymin && rr[1]<params->ymax) {
      if (rv)
	*rv = rr;
      return ptr;
    }
    ptr = ptr->nextMosaic();
  }

  return NULL;
}

void Base::invalidPixmap()
{
  Widget::invalidPixmap();

  if (basePixmap)
    Tk_FreePixmap(display, basePixmap);
  basePixmap = 0;

  if (baseXImage)
    XDestroyImage(baseXImage);
  baseXImage = NULL;

  needsUpdate = MATRIX;
}

Coord::Orientation Base::IRAFOrientation(Coord::Orientation oo)
{
  if (irafOrientation == (Coord::Orientation)-1)
    irafOrientation = oo;
  return irafOrientation;
}

int Base::parse(istringstream& istr)
{
  result = TCL_OK;
  frFlexLexer* ll = new frFlexLexer(&istr);
  frparse(this, ll);
  delete ll;

  return result;
}

FitsHead* Base::parseWCS(istream& str)
{
  if (!currentContext->cfits)
    return NULL;

  FitsHead* head = currentContext->cfits->head();
  FitsHead* rr = new FitsHead(head->naxis(0), head->naxis(1),
			     head->naxis(2), head->bitpix());

  // this works for both terminated (\n) and non-terminated (fits) headers
  while (!str.eof()) {
    char buf[256];
    str.get(buf,81,'\n');
    if (strlen(buf) > 0) {
      string x(buf);
      istringstream sstr(x);

      char keyword[32];
      sstr >> keyword;

      if (strchr(buf,'=')) {
	char dummy;
	sstr >> dummy;
      }

      if (strchr(buf,'\'')) {
	char val[64];
	char* ss = strchr(buf,'\'')+1;
	char* ee = strrchr(buf,'\'');
	int ll = ee-ss;
	if (ll<0 || ll>63)
	  ll =0;
	strncpy(val,ss,ll);
	val[ll] = '\0';

	rr->appendString(keyword, val, "");
      }
      else {
	double val;
	sstr >> val;
	rr->appendReal(keyword, val, 15, "");
      }

      if (strlen(buf) <= 80) {
	// eat the \n
	char b;
	str.get(b);
      }
    }
    else
      break;
  }

  return rr;
}

int Base::postscriptProc(int prepass)
{
  if (!visible)
    return TCL_OK;

  if (prepass)
    return TCL_OK;

  ps();

  switch (psLevel) {
  case 1:
    switch (psColorSpace) {
    case BW:
    case GRAY:
      if (grid)
	grid->ps(GRAY);

      psContours(GRAY);

      if (showMarkers) {
	psMarkers(&catalogMarkers, GRAY);
	psMarkers(&userMarkers, GRAY);
      }

      psCrosshair(GRAY);
      psGraphics(GRAY);

      break;
    case RGB:
    case CMYK:
      if (grid)
	grid->ps(RGB);

      psContours(RGB);

      if (showMarkers) {
	psMarkers(&catalogMarkers, RGB);
	psMarkers(&userMarkers, RGB);
      }

      psCrosshair(RGB);
      psGraphics(RGB);

      break;
    }
    break;
  case 2:
  case 3:
    if (grid)
      grid->ps(psColorSpace);

    psContours(psColorSpace);

    if (showMarkers) {
      psMarkers(&catalogMarkers, psColorSpace);
      psMarkers(&userMarkers, psColorSpace);
    }

    psCrosshair(psColorSpace);
    psGraphics(psColorSpace);

    break;
  }

  return TCL_OK;
}

void Base::printCoordSystem(Coord::CoordSystem sys)
{
  Tcl_AppendResult(interp, coord.coordSystemStr(sys), NULL);
}

void Base::printDouble(double d, Precision p)
{
  ostringstream str;
  switch (p) {
  case DEFAULT:
    str << d << ends;
    break;
  case FIXED:
    str << setiosflags(ios::fixed) << setw(9) << setprecision(3) << d << ends;
    break;
  case SCIENTIFIC:
    str << setiosflags(ios::scientific) << setprecision(8) << d  << ends;
    break;
  case INTEGER:
    str << (int)d << ends;
    break;
  }
  Tcl_AppendResult(interp, str.str().c_str(), NULL);
}

void Base::printInteger(int i)
{
  ostringstream str;
  str << i << ends;
  Tcl_AppendResult(interp, str.str().c_str(), NULL);
}

void Base::printSkyFrame(Coord::SkyFrame sky)
{
  Tcl_AppendResult(interp, coord.skyFrameStr(sky), NULL);
}

void Base::printSkyFormat(Coord::SkyFormat format)
{
  Tcl_AppendResult(interp, coord.skyFormatStr(format), NULL);
}

void Base::printSkyDist(Coord::SkyDist dist)
{
  Tcl_AppendResult(interp, coord.skyDistStr(dist), NULL);
}

void Base::printVector(const Vector& v, Precision p)
{
  ostringstream str;
  switch (p) {
  case DEFAULT:
    str << setprecision(8) << v << ends;
    break;
  case FIXED:
    str << setiosflags(ios::fixed) << setw(9) << setprecision(3) << v << ends;
    break;
  case SCIENTIFIC:
    str << setiosflags(ios::scientific) << setprecision(8) << v << ends;
    break;
  case INTEGER:
    {
      Vector z = v;
      str << z.round() << ends;
      break;
    }
  }
  Tcl_AppendResult(interp, str.str().c_str(), NULL);
}

void Base::printVector(const Vector3d& v, Precision p)
{
  ostringstream str;
  switch (p) {
  case DEFAULT:
    str << setprecision(8) << v << ends;
    break;
  case FIXED:
    str << setiosflags(ios::fixed) << setw(9) << setprecision(3) << v << ends;
    break;
  case SCIENTIFIC:
    str << setiosflags(ios::scientific) << setprecision(8) << v << ends;
    break;
  case INTEGER:
    {
      Vector z = v;
      str << z.round() << ends;
      break;
    }
  }
  Tcl_AppendResult(interp, str.str().c_str(), NULL);
}

void Base::printFromRef(FitsImage* ptr, const Vector& v,
			Coord::CoordSystem sys, Coord::SkyFrame sky, 
			Coord::SkyFormat format,
			Precision p)
{
  switch (sys) {
  case Coord::IMAGE:
  case Coord::PHYSICAL:
  case Coord::DETECTOR:
  case Coord::AMPLIFIER:
    printVector(ptr->mapFromRef(v, sys, sky), p);
    return;
  default:
    if (ptr->hasWCS(sys)) {
      if (ptr->hasWCSCel(sys)) {
	switch (format) {
	case Coord::DEGREES:
	    printVector(ptr->mapFromRef(v, sys, sky), p);
	  break;
	case Coord::SEXAGESIMAL:
	  {
	    char buf[64];
	    buf[0] = '\0';
	    ptr->mapFromRef(v, sys, sky, format, buf, 64);

	    // grap only the first two items
	    char* ptr = buf;
	    while (*ptr)
	      ptr++;
	    while (*ptr != ' ' && ptr >= buf)
	      ptr--;
	    *ptr = '\0';

	    Tcl_AppendResult(interp, buf, " ", NULL);
	  }
	  break;
	}
      }
      else
	printVector(ptr->mapFromRef(v, sys), p);
    }
    else
      printVector(Vector(),p);

    return;
  }
}

void Base::ps()

{
  Vector org = psOrigin();
  
  if (currentContext->fits) {
    Tcl_AppendResult(interp, "gsave\n", NULL);

    TkPostscriptInfo* psInfo = (TkPostscriptInfo*)(((TkCanvas*)canvas)->psInfo);
    double ss = psInfo->scale * psResolution / 72.;
    int ww = options->width*ss;
    int hh = options->height*ss;

    {
      ostringstream str;
      str << org[0] << ' ' << org[1] << " translate" << endl
	  << setprecision(12)
	  << 1/ss << ' ' << 1/ss << " scale" << endl
	  << ends;
      Tcl_AppendResult(interp, str.str().c_str(), NULL);
    }

    switch (psLevel) {
    case 1: 
      {
	psHead1(ww, hh);
	NoCompressAsciiHex filter(psLevel);
	psImage(filter, psColorSpace, ww, hh, ss);
      }
      break;
    case 2:
      {
	psHead2(ww, hh, "RunLength", "ASCII85");
	RLEAscii85 filter(psLevel);
	psImage(filter, psColorSpace, ww, hh, ss);
      }
      break;
    case 3:
      {
	psHead2(ww, hh, "Flate", "ASCII85");
	GZIPAscii85 filter(psLevel);
	psImage(filter, psColorSpace, ww, hh, ss);
      }
      break;
    }

    Tcl_AppendResult(interp, "grestore\n", NULL);
  }

  // Markers & Contours & Grids clip path
  {
    ostringstream str;
    str << org[0] << ' ' << org[1] << ' ' 
    	<< options->width << ' ' << options->height
    	<< " rectclip" << endl << ends;
    Tcl_AppendResult(interp, str.str().c_str(), NULL);
  }
}

void Base::psContours(PSColorSpace cs)
{
  // render back to front
  // aux contours
  Contour* ptr=currentContext->auxcontours.tail();
  while (ptr) {
    ptr->ps(cs);
    ptr=ptr->previous();
  }

  // main contour
  if (hasContour())
    currentContext->contour->ps(cs);
}

void Base::psCrosshair(PSColorSpace mode)
{
  if (!useCrosshair)
    return;

  Vector rr = mapFromRef(crosshair, Coord::WIDGET);
  Vector aa = Vector(rr[0],1) * widgetToCanvas;
  Vector bb = Vector(rr[0],options->height) * widgetToCanvas;
  Vector cc = Vector(1,rr[1]) * widgetToCanvas;
  Vector dd = Vector(options->width,rr[1]) * widgetToCanvas;

  {
    ostringstream str;
    switch ((PSColorSpace)mode) {
    case BW:
    case GRAY:
      psColorGray(getXColor("green"), str);
      str << " setgray";
      break;
    case RGB:
      psColorRGB(getXColor("green"), str);
      str << " setrgbcolor";
      break;
    case CMYK:
      psColorCMYK(getXColor("green"), str);
      str << " setcmykcolor";
      break;
    }
    str << endl << ends;
    Tcl_AppendResult(interp, str.str().c_str(), NULL);
  }  

  {
    ostringstream str;
    str << 1 << " setlinewidth" << endl << ends;
    Tcl_AppendResult(interp, str.str().c_str(), NULL);
  }

  { 
    ostringstream str;

    str << "newpath " 
	<< aa.TkCanvasPs(canvas) << "moveto"
	<< bb.TkCanvasPs(canvas) << "lineto"
	<< " stroke" << endl
	<< "newpath " 
	<< cc.TkCanvasPs(canvas) << "moveto"
	<< dd.TkCanvasPs(canvas) << "lineto"
	<< " stroke" << endl
	<< ends;
    Tcl_AppendResult(interp, str.str().c_str(), NULL);
  }
}

void Base::psImage(Filter& filter, PSColorSpace mode, 
		   int width, int height, float scale)
{
  pushPSMatrices(scale, width, height);

  unsigned char* img = fillImage(width, height, Coord::PS);
  if (!img)
    return;
  unsigned char* ptr = img;

  for (long jj=0; jj<height; jj++) {
    ostringstream str;

    for (long ii=0; ii<width; ii++, ptr+=3) {
      unsigned char red = ptr[0];
      unsigned char green = ptr[1];
      unsigned char blue = ptr[2];

      switch (mode) {
      case BW:
      case GRAY:
	filter << RGB2Gray(red, green, blue);
	break;
      case RGB:
	filter << red << green << blue;
	break;
      case CMYK:
	{
	  unsigned char cyan, magenta, yellow, black;
	  RGB2CMYK(red, green, blue, &cyan, &magenta, &yellow, &black);
	  filter << cyan << magenta << yellow << black;
	}
	break;
      }
      str << filter;
    }

    str << ends;
    psFix(str);
    Tcl_AppendResult(interp, str.str().c_str(), NULL);
  }

  ostringstream str;
  filter.flush(str);
  psFix(str);
  Tcl_AppendResult(interp, str.str().c_str(), NULL);

  if (img)
    delete [] img;
}

Matrix Base::psMatrix(float scale, int width, int height)
{
  Matrix userToPS = 
    wcsOrientationMatrix *              // flip x/y axis about center
    orientationMatrix *                 // flip x/y axis about cursor position
    Rotate(wcsRotation) *               // rotate about center position
    Rotate(rotation) *                  // rotate about cursor position

    Scale(zoom_) *                      // scale about cursor position
    Scale(scale) *
    FlipY() *
    Translate(Vector(width,height)/2.);

   return refToUser * userToPS;
}

void Base::pushMatrices()
{
  // alway identity
  Matrix rgbToRef; 

  FitsImage* ptr = context->fits;
  while (ptr) {
    FitsImage* sptr = ptr;
    while (sptr) {
      sptr->updateMatrices(rgbToRef, refToWidget, widgetToCanvas);
      sptr = sptr->nextSlice();
    }
    ptr = ptr->nextMosaic();
  }
}

void Base::pushMagnifierMatrices()
{
  FitsImage* ptr = context->fits;
  while (ptr) {
    FitsImage* sptr = ptr;
    while (sptr) {
      sptr->updateMagnifierMatrices(refToMagnifier);
      sptr = sptr->nextSlice();
    }
    ptr = ptr->nextMosaic();
  }
}

void Base::pushPannerMatrices()
{
  FitsImage* ptr = context->fits;
  while (ptr) {
    FitsImage* sptr = ptr;
    while (sptr) {
      sptr->updatePannerMatrices(refToPanner);
      sptr = sptr->nextSlice();
    }
    ptr = ptr->nextMosaic();
  }
}

void Base::pushPSMatrices(float scale, int width, int height)
{
  Matrix mx = psMatrix(scale, width, height);

  FitsImage* ptr = context->fits;
  while (ptr) {
    FitsImage* sptr = ptr;
    while (sptr) {
      sptr->updatePS(mx);
      sptr = sptr->nextSlice();
    }
    ptr = ptr->nextMosaic();
  }
}

void Base::reset()
{
  if (!preservePan)
    centerImage();

  crosshair = cursor;
  invert = 0;

  orientation = Coord::NORMAL;
  orientationMatrix.identity();
  zoom_ = Vector(1,1);
  rotation = 0;
  
  wcsAlign_ = 0;
  wcsAlt_ =0;

  wcsOrientation = Coord::NORMAL;
  wcsOrientationMatrix.identity();
  wcsRotation = 0;
  
  unselectMarkers(&userMarkers);
  unselectMarkers(&catalogMarkers);

  update(MATRIX);
}

FrScale::ScanMode Base::scanMode()
{
  return currentContext->frScale.scanMode();  
}

void Base::resetScanMode()
{
  currentContext->frScale.resetScanMode();  
}

void Base::setScanMode(FrScale::ScanMode mode)
{
  currentContext->frScale.setScanMode(mode);  
}

// backward compatibility
void Base::setScanModeIncr(LoadMethod lm)
{
  switch (lm) {
  case LOADALL:
    // do nothing (leave the current setting), will use incr end to reset
    break;
  case INCR:
    currentContext->frScale.setScanMode(FrScale::CROPSEC);
    break;
  }
}

void Base::setSlice(int id, int ss)
{
  // IMAGE (ranges 1-n)
  currentContext->updateSlice(id, ss);

  updateMarkers(&userMarkers);
  updateMarkers(&catalogMarkers);

  // execute any update callbacks
  updateCBMarkers();

  if (id==2)
    update(BASE);
  else {
    // load the next cube
    currentContext->updateClip();
    updateColorScale();
    update(MATRIX);
  }
}

void Base::unloadAllFits()
{
  unloadFits();
}

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

  if (!preserveMarkers) {
    // delete markers
    userMarkers.deleteAll();
    undoUserMarkers.deleteAll();
    pasteUserMarkers.deleteAll();
  }

  catalogMarkers.deleteAll();
  undoCatalogMarkers.deleteAll();
  pasteCatalogMarkers.deleteAll();

  if (grid)
    delete grid;
  grid = NULL;

  irafOrientation = (Coord::Orientation)-1;

  updateColorScale();
}

void Base::update(UpdateType flag)
{
  if (DebugPerf)
    cerr << "update " << flag << endl;

  // Be careful, someone may have already set the flag at a lower level
  // therefor, only change the flag if we need more to be done

  if (flag < needsUpdate)
    needsUpdate = flag;
  redraw();
}

void Base::update(UpdateType flag, BBox bb)
{
  if (DebugPerf)
    cerr << "update " << flag << ' ' << bb << endl;

  // bb is in canvas coords

  if (flag < needsUpdate)
    needsUpdate = flag;
  redraw(bb);
}

void Base::updateBase()
{
  if (DebugPerf)
    cerr << "updateBase...";

  int& width = options->width;
  int& height = options->height;

  if (!basePixmap) {
    if (!(basePixmap = Tk_GetPixmap(display, Tk_WindowId(tkwin), 
				    width, height, depth))){
      internalError("Unable to Create Pixmap");
      return;
    }

    // Geometry has changed, redefine our marker GCs including clip regions
    updateGCs();
  }

  if (!baseXImage) {
    if (!(baseXImage = XGetImage(display, basePixmap, 0, 0, width, height, 
				 AllPlanes, ZPixmap))) {
      internalError("Unable to Create XImage");
      return;
    }

    // we have to wait until now, since the encodings depend on baseXImage
    encodeTrueColor(baseXImage->byte_order, baseXImage->bits_per_pixel);
    encodeTrueColor(bgColor, bgTrueColor_);
    encodeTrueColor(nanColor, nanTrueColor_);

    // we have a race condition. Some Truecolor ColorScales need to know the 
    // bytes per pixel, RGB masks, and byte order, from XImage struct.
    // yet, we receive colormap commands well before we render to the 
    // screen and have a valid XImage. 
    // So, we put off creating a colorscale until we are ready to render.

    if (!validColorScale())
      updateColorScale();
  }

  if (doRender())
    ximageToPixmap(basePixmap, baseXImage, Coord::WIDGET);
  else {
    XSetForeground(display, gc, getColor(bgColorName));
    XFillRectangle(display, basePixmap, gc, 0,0,options->width,options->height);
  }
  
  if (DebugPerf)
    cerr << "end" << endl;
}

void Base::updateBin(const Matrix& mx)
{
  currentContext->updateBin(this);

  if (keyContext->fits && (keyContext->fits == currentContext->fits)) {
    crosshair *= mx;
    currentContext->updateContours(mx);
    updateMarkerCoords(&userMarkers, mx);
    updateMarkerCoords(&catalogMarkers, mx);
  }

  alignWCS();
  updateColorScale();
  updateNow(MATRIX); // because we have changed zoom

  // update markers call backs
  // wait til matrices have been updated so that any dialogs will print
  // the correct coords
  updateMarkerCBs(&userMarkers);
  updateMarkerCBs(&catalogMarkers);
}

void Base::updateGCs()
{
  // widget clip region
  BBox bbWidget = BBox(0, 0, options->width, options->height);
  Vector sizeWidget = bbWidget.size();

  rectWidget[0].x = (int)bbWidget.ll[0];
  rectWidget[0].y = (int)bbWidget.ll[1];
  rectWidget[0].width = (int)sizeWidget[0];
  rectWidget[0].height = (int)sizeWidget[1];

  // window clip region
  BBox bbWindow = bbWidget * widgetToWindow;
  Vector sizeWindow = bbWindow.size();

  rectWindow[0].x = (int)bbWindow.ll[0];
  rectWindow[0].y = (int)bbWindow.ll[1];
  rectWindow[0].width = (int)sizeWindow[0];
  rectWindow[0].height = (int)sizeWindow[1];

  if (!highliteGC) {
    highliteGC = XCreateGC(display, Tk_WindowId(tkwin), 0, NULL);
    XSetLineAttributes(display, highliteGC, 2, LineSolid, CapButt, JoinMiter);
    XSetForeground(display, highliteGC, getColor("blue"));
  }
  XSetClipRectangles(display, highliteGC, 0, 0, rectWidget, 1, Unsorted);

  // panGCXOR
  if (!panGCXOR)
    panGCXOR = XCreateGC(display, Tk_WindowId(tkwin), 0, NULL);
  XSetClipRectangles(display, panGCXOR, 0, 0, rectWindow, 1, Unsorted);

  // rotateGCXOR
  if (!rotateGCXOR)
    rotateGCXOR = XCreateGC(display, Tk_WindowId(tkwin), 0, NULL);
  XSetClipRectangles(display, rotateGCXOR, 0, 0, rectWindow, 1, Unsorted);

  // markerGC
  if (!markerGC)
    markerGC = XCreateGC(display, Tk_WindowId(tkwin), 0, NULL);
  XSetClipRectangles(display, markerGC, 0, 0, rectWidget, 1, Unsorted);

  if (!markerGCXOR) {
    markerGCXOR = XCreateGC(display, Tk_WindowId(tkwin), 0, NULL);
    XSetForeground(display, markerGCXOR, getColor("white"));
  }
  XSetClipRectangles(display, markerGCXOR, 0, 0, rectWindow, 1, Unsorted);

  if (!selectGCXOR) {
    selectGCXOR = XCreateGC(display, Tk_WindowId(tkwin), 0, NULL);
    XSetForeground(display, selectGCXOR, getColor("white"));
    x11Dash(selectGCXOR,1);
  }
  XSetClipRectangles(display, selectGCXOR, 0, 0, rectWindow, 1, Unsorted);

  // gridGC
  if (!gridGC)
    gridGC = XCreateGC(display, Tk_WindowId(tkwin), 0, NULL);
  XSetClipRectangles(display, gridGC, 0, 0, rectWidget, 1, Unsorted);

  // contourGC
  if (!contourGC) {
    contourGC = XCreateGC(display, Tk_WindowId(tkwin), 0, NULL);
    XSetLineAttributes(display, contourGC, 1, LineSolid, CapButt, JoinMiter);
  }
  XSetClipRectangles(display, contourGC, 0, 0, rectWidget, 1, Unsorted);
}

void Base::updateMagnifier()
{
  updateMagnifier(magnifierCursor);
}

void Base::updateMagnifier(const Vector& vv)
{
  // vv is in CANVAS coords
  // save it, we may need it later
  magnifierCursor = vv;

  if (!(magnifierXImage && magnifierPixmap))
    return;

  if (useMagnifier) {
    updateMagnifierMatrices();

    if (doRender()) {
      ximageToPixmapMagnifier();

      if (useMagnifierGraphics) {
	// render markers
	// markers bounding box is in canvas coords
	// map the magnifier to a bounding box in canvas coords
	Matrix mm = magnifierToRef * refToCanvas;

	Vector ll = Vector(0,0) * mm;
	Vector ur = Vector(magnifierWidth,magnifierHeight) * mm;
	BBox bb = BBox(vv,vv);
	bb.bound(ll);
	bb.bound(ur);

	if (showMarkers) {
	  x11MagnifierMarkers(&userMarkers, bb);
	  x11MagnifierMarkers(&catalogMarkers, bb);
	}

	// render crosshair
	if (useCrosshair)
	  x11Crosshair(magnifierPixmap, Coord::MAGNIFIER, 
			  magnifierWidth, magnifierHeight);

	// render contours
	x11Contours(magnifierPixmap, Coord::MAGNIFIER, 
		    magnifierWidth, magnifierHeight);
      }

      // render cursor
      if (useMagnifierCursor)
	x11MagnifierCursor(vv);
    }
    else {
      XSetForeground(display, gc, getColor(bgColorName));
      XFillRectangle(display, magnifierPixmap, gc, 0, 0, 
		   magnifierXImage->width, magnifierXImage->height);
    }

    // notify the magnifier widget
    ostringstream str;
    str << magnifierName << " update " << (void*)magnifierPixmap << ends;
    Tcl_Eval(interp, str.str().c_str());
  }
}

void Base::updateMatrices()
{
  // refToUser
  refToUser = Translate(-cursor) * FlipY(); // flip y axis for X Windows
  userToRef = refToUser.invert();

  // userToWidget
  userToWidget =
    wcsOrientationMatrix *              // flip x/y axis about center
    orientationMatrix *                 // flip x/y axis about cursor position
    Rotate(wcsRotation) *               // rotate about center position
    Rotate(rotation) *                  // rotate about cursor position

    Scale(zoom_) *                      // scale about cursor position
    // must be int to align with screen pixels
    Translate((int)(options->width/2.), (int)(options->height/2.));
  widgetToUser = userToWidget.invert();

  // widgetToCanvas
  widgetToCanvas = Translate(originX, originY);
  canvasToWidget = widgetToCanvas.invert();

  // canvasToWindow
  short xx, yy;
  Tk_CanvasWindowCoords(canvas, 0, 0, &xx, &yy);
  canvasToWindow = Translate(xx, yy);
  windowToCanvas = canvasToWindow.invert();

  // These are derived Transformation Matrices
  refToWidget = refToUser * userToWidget;
  widgetToRef = refToWidget.invert();

  refToCanvas = refToWidget * widgetToCanvas;
  canvasToRef = refToCanvas.invert();

  refToWindow = refToCanvas * canvasToWindow;
  windowToRef = refToWindow.invert();

  userToCanvas = userToWidget * widgetToCanvas;
  canvasToUser = userToCanvas.invert();

  widgetToWindow = widgetToCanvas * canvasToWindow;
  windowToWidget = widgetToWindow.invert();

    // Markers
  updateMarkers(&userMarkers);
  updateMarkers(&catalogMarkers);

  pushMatrices();
}

void Base::updateMagnifierMatrices()
{
  // magnifierCursor is in CANVAS
  Vector ww = magnifierCursor*canvasToRef;

  // refToUser
  Matrix refToUser;
  refToUser = Translate(-ww) * FlipY();

  // userToMagnifier
  userToMagnifier = 
    wcsOrientationMatrix *              // flip x/y axis about center
    orientationMatrix *                 // flip x/y axis about cursor position
    Rotate(wcsRotation) *               // rotate about center position
    Rotate(rotation) *                  // rotate about cursor position

    Scale(zoom_) *                      // scale about cursor position
    Scale(magnifierZoom_) *             // scale
    // must be int to align with screen pixels
    Translate((int)(magnifierWidth/2.), (int)(magnifierHeight/2.));
  magnifierToUser = userToMagnifier.invert();

  refToMagnifier = refToUser * userToMagnifier;
  magnifierToRef = refToMagnifier.invert();

  magnifierToWidget = magnifierToUser * userToWidget;
  widgetToMagnifier = magnifierToWidget.invert();

  pushMagnifierMatrices();
}

void Base::updatePannerMatrices()
{
  Vector center = imageCenter(FrScale::IMGSEC) * imageToData;

  // refToUser
  Matrix refToUser;
  refToUser = Translate(-center) * FlipY(); // flip y axis for X Windows
  userToRef = refToUser.invert();

  // userToPanner
  userToPanner =
    wcsOrientationMatrix *              // flip x/y axis about center
    orientationMatrix *                 // flip x/y axis about cursor position
    Rotate(wcsRotation) *               // rotate about center position
    Rotate(rotation) *                  // rotate about cursor position

    Scale(calcZoomPanner()) *
    // must be int to align with screen pixels
    Translate((int)(pannerWidth/2.), (int)(pannerHeight/2.));
  pannerToUser = userToPanner.invert();

  refToPanner = refToUser * userToPanner;
  pannerToRef = refToPanner.invert();

  pannerToWidget = pannerToRef * refToWidget;
  widgetToPanner = pannerToWidget.invert();

  pushPannerMatrices();
}

void Base::updateNow(UpdateType flag)
{
  if (DebugPerf)
    cerr << "updateNow " << flag << endl;

  if (flag < needsUpdate)
    needsUpdate = flag;
  redrawNow();
}

void Base::updateNow(UpdateType flag, BBox bb)
{
  if (DebugPerf)
    cerr << "updateNow BB " << flag << ' ' << bb << endl;

  // bb is in canvas coords
  if (flag < needsUpdate)
    needsUpdate = flag;
  redrawNow(bb);
}

void Base::updatePanner()
{
  if (usePanner) {
    if (doRender())
      ximageToPixmap(pannerPixmap, pannerXImage, Coord::PANNER);
    else {
      XSetForeground(display, pannerGC, getColor(bgColorName));
      XFillRectangle(display, pannerPixmap, pannerGC, 0, 0,
		     pannerWidth, pannerHeight);
    }  
  }
}

int Base::updatePixmap(const BBox& bb)
{
  // bbox is in canvas coords

  // Note: lack of breaks-- on purpose. 
  // If Matrices are update, both Base and Pixmap
  // also need to be updated. Same for Base-- ie, pixmap is also updated.
  switch (needsUpdate) {
  case MATRIX:
    updateMatrices();
    updatePannerMatrices();
  case BASE:
    updateBase();
    updatePanner();
  case PIXMAP:
    updatePM(bb);
    break;
  }

  needsUpdate = NOUPDATE;
  return TCL_OK;
}

void Base::updatePM(const BBox& bbox)
{
  // bbox is in Canvas Coords

  if (DebugPerf)
    cerr << "updatePM...";

  int& width = options->width;
  int& height = options->height;

  if (!pixmap) {
    if (!(pixmap = Tk_GetPixmap(display, Tk_WindowId(tkwin), 
				width, height, depth))) {
      internalError("Unable to Create Pixmap");
      return;
    }
  }

  if (!bbox.isEmpty()) {
    BBox bb = bbox * canvasToWidget;
    int x0 = (int)bb.ll[0] > 0 ? (int)bb.ll[0] : 0;
    int y0 = (int)bb.ll[1] > 0 ? (int)bb.ll[1] : 0;
    int x1 = (int)bb.ur[0] < width ? (int)bb.ur[0] : width;
    int y1 = (int)bb.ur[1] < height ? (int)bb.ur[1] : height;
    int sx = x1-x0;
    int sy = y1-y0;

    if (DebugPerf)
      cerr << ' ' << x0 << ' ' << y0 << ' ' << x1 << ' ' << y1 << ' ';

    XCopyArea(display, basePixmap, pixmap, gc, x0, y0, sx, sy, x0, y0);
  }

  // grid
  if (grid)
    grid->x11();

  // contours
  x11Contours(pixmap, Coord::WIDGET, options->width, options->height);

  // markers
  if (showMarkers) {
    x11Markers(&catalogMarkers, bbox);
    x11Markers(&userMarkers, bbox);
  }

  // crosshair
  x11Crosshair(pixmap, Coord::WIDGET, options->width, options->height);

  // highlite bbox
  x11Graphics();

  if (DebugPerf)
    cerr << "end" << endl;
}

char* Base::varcat(char* buf, char* base, char id, char* mod)
{
  int ll = strlen(base);
  strcpy(buf,base);
  buf[ll++] = id;
  buf[ll++] = NULL;
  strcat(buf,mod);
  return buf;
}

void Base::x11Contours(Pixmap pm, Coord::InternalSystem sys, int width, int height)
{
  // render from back to front
  // aux contours
  Contour* ptr=currentContext->auxcontours.tail();
  while (ptr) {
    ptr->render(pm, sys, width, height);
    ptr=ptr->previous();
  }

  // main contour
  if (hasContour())
    currentContext->contour->render(pm, sys, width, height);
}

void Base::x11Crosshair(Pixmap pm, Coord::InternalSystem sys, int width, int height)
{
  if (useCrosshair) {
    Vector rr = mapFromRef(crosshair,sys);

    XSetForeground(display, gc, getColor("green"));
    if (rr[0]>=0 && rr[0]<width)
      XDrawLine(display, pm, gc, rr[0], 1, rr[0], height);

    if (rr[1]>=0 && rr[1]<height)
      XDrawLine(display, pm, gc, 1, rr[1], width, rr[1]);
  }
}

void Base::x11Dash(GC lgc, int which)
{
  if (which) {
    char dl[2];
#ifdef _GWIN32
    dl[0] = dlist[0]/2;
    dl[1] = dlist[1]/2;
#else
    dl[0] = dlist[0];
    dl[1] = dlist[1];
#endif
    XSetLineAttributes(display, lgc, 1, LineOnOffDash, CapButt,JoinMiter);
    XSetDashes(display, lgc, 0, dl, 2);
  }
  else 
    XSetLineAttributes(display, lgc, 1, LineSolid, CapButt, JoinMiter);
}

void Base::x11Graphics()
{
  if (useHighlite)
    XDrawRectangle(display, pixmap, highliteGC, 1, 1, 
		   options->width-2, options->height-2);
}

void Base::ximageToPixmap(Pixmap pmap, XImage* xmap, Coord::InternalSystem sys)
{
  buildXImage(xmap, sys);
  XPutImage(display, pmap, gc, xmap, 0, 0, 0, 0, xmap->width, xmap->height);
}

void Base::ximageToPixmapMagnifier()
{
  ximageToPixmap (magnifierPixmap, magnifierXImage, Coord::MAGNIFIER);
}

#ifdef _MACOSX
void Base::macosx()
{
  // clip rect
  XRectangle* rr = rectWidget;
  Vector v1 = Vector(rr->x, rr->y) * widgetToCanvas;
  Vector v2 = Vector(rr->x+rr->width, rr->y+rr->height) * widgetToCanvas;
  macosxClip(v1,v2-v1);

  if (currentContext->fits) {
    // scale
    double scale = 150 / 72.;
    int width = options->width*scale;
    int height = options->height*scale;

    // image
    macosxImage(scale, width, height, v1, v2-v1);
  }
}

void Base::macosxContours()
{
  //   render back to front
  //   aux contours
  Contour* ptr=currentContext->auxcontours.tail();
  while (ptr) {
    ptr->macosx();
    ptr=ptr->previous();
  }

  // main contour
  if (hasContour())
    currentContext->contour->macosx();
}

void Base::macosxCrosshair()
{
  if (!useCrosshair)
    return;

  Vector rr = mapFromRef(crosshair,Coord::WIDGET);
  Vector aa = Vector(rr[0],1) * widgetToCanvas;
  Vector bb = Vector(rr[0],options->height) * widgetToCanvas;
  Vector cc = Vector(1,rr[1]) * widgetToCanvas;
  Vector dd = Vector(options->width,rr[1]) * widgetToCanvas;

  macosxColor(getXColor("green"));
  macosxWidth(1);
  macosxDrawLine(aa,bb);
  macosxDrawLine(cc,dd);
}

void Base::macosxImage(float scale, int width, int height, 
		       const Vector& v, const Vector& s)
{
  // we need a colorScale before we can render
  if (!validColorScale())
    return;

  pushPSMatrices(scale, width, height);

  // source
  unsigned char* src = fillImage(width, height, Coord::PS);
  if (!src)
    return;

  // destination
  unsigned char* dst = new unsigned char[width*height*4];
  if (!dst)
    return;

  unsigned char* sptr = src;
  unsigned char* dptr = dst;
  for (int ii=0; ii<width*height; ii++) {
    *dptr++ = *sptr++;
    *dptr++ = *sptr++;
    *dptr++ = *sptr++;
    *dptr++ = 0;
  }

  if (src)
    delete [] src;

  macosxBitmapCreate(dst, width, height, v, s);

  if (dst)
    delete [] dst;
}

void Base::macosxPrintCmd()
{
  if (!visible)
    return;

  // init
  macosxBegin();

  // image
  macosx();

  // grid
  if (grid)
    grid->macosx();

  // contours
  macosxContours();

  // markers
  if (showMarkers) {
    macosxMarkers(&catalogMarkers);
    macosxMarkers(&userMarkers);
  }

  macosxCrosshair();
  macosxGraphics();

  // cleanup
  macosxEnd();
}
#endif

#ifdef _GWIN32
void Base::win32()
{
  // clip rect
  XRectangle* rr = rectWidget;
  Vector v1 = Vector(rr->x, rr->y) * widgetToCanvas;
  Vector v2 = Vector(rr->x+rr->width, rr->y+rr->height) * widgetToCanvas;
  win32Clip(v1,v2-v1);

  if (currentContext->fits) {
    // scale
    double scale = 1.0;
    int width = options->width*scale;
    int height = options->height*scale;
    
    // image
    win32Image(scale, width, height, v1, v2-v1);
  }
}

void Base::win32Contours()
{
  //   render back to front
  //   aux contours
  Contour* ptr=currentContext->auxcontours.tail();
  while (ptr) {
    ptr->win32();
    ptr=ptr->previous();
  }

  // main contour
  if (hasContour())
    currentContext->contour->win32();
}

void Base::win32Crosshair()
{
  if (!useCrosshair)
    return;

  Vector rr = mapFromRef(crosshair,Coord::WIDGET);
  Vector aa = Vector(rr[0],1) * widgetToCanvas;
  Vector bb = Vector(rr[0],options->height) * widgetToCanvas;
  Vector cc = Vector(1,rr[1]) * widgetToCanvas;
  Vector dd = Vector(options->width,rr[1]) * widgetToCanvas;

  win32Color(getXColor("green"));
  win32Width(1);
  win32Dash(NULL,0);
  win32DrawLine(aa,bb);
  win32DrawLine(cc,dd);
}

void Base::win32Image(float scale, int width, int height, 
		      const Vector& v, const Vector& s)
{
  // we need a colorScale before we can render
  if (!validColorScale())
    return;

  pushPSMatrices(scale, width, height);

  // source
  unsigned char* src = fillImage(width, height, Coord::PS);
  if (!src)
    return;

  // destination (width must be aligned on 4-byte DWORD boundary)
  int jjwidth=(((width+3)/4)*4);

  // extra alignment padding which we have to skip over for each row
  int jjpad=(jjwidth-width)*3;

  unsigned char* dst = new unsigned char[jjwidth*height*3];
  if (!dst)
    return;
  memset(dst, '\0', jjwidth*height*3);

  unsigned char* sptr = src;
  unsigned char* dptr = dst;
  unsigned char red, green, blue;
  for (int jj=0; jj<height; jj++) {
    for (int ii=0; ii<width; ii++) {
      red   = *sptr++;
      green = *sptr++;
      blue  = *sptr++;
      *dptr++ = blue;
      *dptr++ = green;
      *dptr++ = red;
    }
    dptr += jjpad;
  }

  if (src)
    delete [] src;

  win32BitmapCreate(dst, jjwidth, height, v, s);

  if (dst)
    delete [] dst;
}

void Base::win32PrintCmd()
{
  if (!visible)
    return;

  // init
  win32Begin();

  // image
  win32();

  // grid
  if (grid)
    grid->win32();

  // contours
  win32Contours();

  // markers
  if (showMarkers) {
    win32Markers(&userMarkers);
    win32Markers(&catalogMarkers);
  }

  win32Crosshair();
  win32Graphics();

  // cleanup
  win32End();
}
#endif
