Help Requested for Multi-Resolution Image Viewer Plugin

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

Help Requested for Multi-Resolution Image Viewer Plugin

Shawn Mikula-2
I would like to have an ImageJ plugin for viewing multiresolution images
(such images tend to be too large to load completely into memory; hence the
need for breaking them into multiresolution pieces).   I currently have the
Java source for interactive viewing of multiresolution images, which
compiles fine, but have been unable to compile this code as an ImageJ
plugin, which would be ideal since that would permit the application of all
of ImageJ's analysis routines on multiresolution images, which otherwise
would be difficult to work with.  I'm not certain, but I think part of the
problem with compiling the plugin may be due to the fact that the source
requires Java Advanced Imaging.  If anyone can help me out here, it would
be much appreciated.  Or if you want to code up the plugin yourself, please
do.  Here's the source (composed of two files), which has a hard-coded
multiresolution URL that is loaded by default (only for demonstration
purposes), but which the user should specify themselves.  The code (and
associated icon images for interactive navigation through the
multiresolution image) may be downloaded from
http://brainmaps.org/TiledViewer.tgz


to compile:
javac TiledViewerPanel.java

to execute:
java TiledViewerPanel  (note that you may have to include JAI classpaths)

Thanks.
Shawn



FILE TiledViewer.java
=============================================
/*
 * TiledViewer.java
 *
 * Created Oct 26, 2005 08:02
 *
 * display component for the annotator that uses tiled images
 *
 * Note: this thing uses ints instead of longs for maintaining positions,
 *       so image size may have to be restricted accordingly
 *       (i.e., max image size of 2^31 pixels x 2^31 pixels, or about
 *       14 exaBytes).
 *
 * Zoomify labels its images 'level-column-row.jpg'. Level, column, and
 * row are all zero-based.
 *
 *
 * Starting conditions:
 *   imageUL.x     = 0
 *   imageUL.y     = 0
 *   imageBuffUL.x = 0
 *   imageBuffUL.y = 0
 *   viewportUL.x  = 0
 *   viewportUL.y  = 0
 *   currentX      = 0
 *   currentY      = 0
 *   tileNumUL.x   = 0
 *   tileNumUL.y   = 0
 *   =================
 *   imageLR.x     = imageWidthInPixels  -1
 *   imageLR.y     = imageHeightInPixels -1
 *   imageBuffLR.x = imageBuffUL.x + (imgBufWidthInTiles *
tileSizeInPixels)  -1
 *   imageBuffLR.y = imageBuffUL.y + (imgBufHeightInTiles *
tileSizeInPixels) -1
 *   viewportLR.x  = viewportUL.x + getWidth()  -1
 *   viewportLR.y  = viewportUL.y + getHeight() -1
 *   currentX      = 0
 *   currentY      = 0
 *   tileNumLR.x   = tileNumUL.x + imgBufWidthInTiles  -1
 *   tileNumLR.y   = tileNumUL.y + imgBufHeightInTiles -1
 *  
 *
 */




import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;
import java.util.Date;
import java.util.ArrayList;
import javax.media.jai.*;
import javax.media.jai.operator.ScaleDescriptor;
import javax.swing.*;
import javax.swing.border.*;
import com.sun.image.codec.jpeg.*;



public final class TiledViewer
   extends JPanel
   implements MouseListener,
              MouseMotionListener,
              ComponentListener {

   private static final boolean DEBUG = true;
   
   private static final int TILES_PER_TILE_GROUP    = 256;
   private static final int INITIAL_DISPLAY_HEIGHT  = 256; // pixels
   private static final int INITIAL_DISPLAY_WIDTH   = 256; // pixels
   private static final int IMG_BUF_WIDTH_IN_TILES  =   6;
   private static final int IMG_BUF_HEIGHT_IN_TILES =   6;
   private static final String ICON_DIR             = "Icons/";
   
   public int tileSizeInPixels; // read from server file 'ImageProperties.xml'
   public int numLevels;               // calc'd from server file  "
   // levelNumToDisplay corresponds directly to  zoomify's level
   public int levelNumToDisplay;   // 0 <= levelNumToDisplay < numLevels
   
   private BufferedImage imageBuffer;
   private BufferedImage[][] tileBuffers; // tileBuffer[col][row]
   private int           imgBufHeightInTiles;
   private int           imgBufWidthInTiles;
   private Graphics2D    imageBuffG2;
   private String        baseName;
   
   // currentX and currentY are used in paintComponent() to determine ...
   // ... where to draw the image on the JPanel
   private int currentX;       // of UL corner in JPanel coordinates
   private int currentY;       // of UL corner in JPanel coordinates
   private int fullImageWidthInPixels;  // from server file         "
   private int fullImageHeightInPixels; // from server file         "
   private int numTiles;                // from server file         "
   private int imageWidthInPixels;  // of the (possibly) reduced image we show
   private int imageHeightInPixels; // of the (possibly) reduced image we show
   
   private int x0;            // x-coord at start of drag: set in mousePressed
   private int y0;            // y-coord at start of drag: set in mousePressed
   
   // imageUL and imageLR (in pixels) are constant for a given image and level
   private Point imageUL;     // UL of image: always 0,0
   private Point imageLR;     // LR of image: always width-1, height-1
   
   // imageBuffUL and imageBuffLR (in pixels) change with buffer fetches
   private Point imageBuffUL; // UL of imageBuffer in IMAGE coordinates
   private Point imageBuffLR; // LR of imageBuffer in IMAGE coordinates
   
   // viewportUL and viewportLR change as the user scrolls the viewport AND ...
   // ... as buffer fetches occur (measured in pixels) and is relative ...
   // ... to the JPanel: viewportUL.x = - currentX, and ...
   //                ... viewportUL.y = - currentY
   private Point viewportUL;  // UL of displayed image--the viewport
   private Point viewportLR;  // LR of displayed image--the viewport
   
   // imgViewportUL and imgViewportLR are the bounds of the viewport ...
   // ... expressed in image coordinates
   private Point imgViewportUL;
   private Point imgViewportLR;
   
   // tileNumberUL and tileNumberLR change as buffer fetches occur
   private Point tileNumberUL; // x = col num, y = row num of UL tile in buffer
   private Point tileNumberLR; // x = col num, y = row num of LR tile in buffer
   
   private int viewportWidth;
   private int viewportHeight;

   //
   private int   rowNumLastTileOfImage;
   private int   colNumLastTileOfImage;
   
   // number of pixels in the last tile of last col/row respectively at the
   // resolution that this image is being shown
   private int pixelsInLastCol;
   private int pixelsInLastRow;
   
   private boolean isSizeSet;  // true if viewport (i.e., JPanel) size is set
   private boolean imageDisplayed; // true if buffer-fetching has been done
   private boolean isDraggingLeft;
   private boolean isDraggingRight;
   private boolean isDraggingUp;
   private boolean isDraggingDown;
   private boolean mustRefresh;
   
   private boolean isScaleFactorSet;
   private double  xScaleFactor;
   private double  yScaleFactor;
   private double  xOffset;
   private double  yOffset;
   private double  scaledX;
   private double  scaledY;
   
   private Cursor normalCursor;
   private Cursor waitCursor;
   private Cursor moveCursor;
   private Cursor crosshairCursor;
   
   private TierData[] tiers; // array index is Zoomify tier number
   private int lastTileGroupIndex;
   private TiledViewerPanel tiledViewerPanel;
   

   // (imageDrawn == true) => the buffer has been materialized and drawn
   private boolean imageDrawn;
   // (backBufDrawn == true) => image drawn into offscreen buffer
   private boolean backBufDrawn;
   // (annotationsDrawn == true) => all annots drawn into offscreen buffer
   private boolean annotationsDrawn;
   // (bitBltDone == true) => offscreen buffer drawn onto onscreen buffer
   private boolean bitBltDone;


   /* following are called by methods of the same name in TiledAnnotPanel */
   public int getReductionFactor() {
      return tiers[levelNumToDisplay].reductionFactor;
   } // getReductionFactor


   
   public TiledViewer(TiledViewerPanel pTVP) {
      super();
      tiledViewerPanel = pTVP;
      setOpaque(true);
      setBackground(Color.WHITE);
      baseName            = null;
      imageUL             = new Point();
      imageLR             = new Point();
      imageBuffUL         = new Point();
      imageBuffLR         = new Point();
      viewportUL          = new Point();
      viewportLR          = new Point();
      imgViewportUL       = new Point();
      imgViewportLR       = new Point();
      tileNumberUL        = new Point();
      tileNumberLR        = new Point();
      addMouseListener(this);
      addMouseMotionListener(this);
      addComponentListener(this);
      isSizeSet = false;
      imageDisplayed = false;
      mustRefresh = true;
      isScaleFactorSet = false;
      normalCursor = Cursor.getDefaultCursor();
      waitCursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
      moveCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
      crosshairCursor = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
      initComponents();
   } // constructor
   

   /*
    * displayNewImage: used to display an image that is NOT currently
    *                  displayed.
    *
    */
   public void displayNewImage(String pImageUrl, int pLevel) {
      double _pctFullSize;
      PlanarImage _pi;
      BufferedImage _bi;
      URL _url;
     
      baseName = pImageUrl;
      initImageProperties(pLevel);
      initImageBuffers(pLevel);
      if (tiledViewerPanel != null) {
         _pctFullSize = 1.0 /(double)tiers[pLevel].reductionFactor;
         tiledViewerPanel.showPercentFullSize(_pctFullSize);
      }
      loadImageBuffers(0, 0, pLevel);
      imageBuffUL.x = 0;
      imageBuffUL.y = 0;
      imageBuffLR.x = tileSizeInPixels * imgBufWidthInTiles  - 1;
      imageBuffLR.y = tileSizeInPixels * imgBufHeightInTiles - 1;      
      if (tiledViewerPanel != null) {
         try {
            _url = new URL(pImageUrl +"/TileGroup0/0-0-0.jpg");
            _pi = JAI.create("URL", _url);
            _bi = _pi.getAsBufferedImage();
            tiledViewerPanel.changeImage(_bi, imageWidthInPixels,
                    imageHeightInPixels,new Point(0,0),
                    new Point(imageWidthInPixels, imageHeightInPixels));
         } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
         }
      }
      imageDisplayed = true;
      mustRefresh = true;
      regenerateDisplay();
   } // displayNewImage


   private final int constrain(int pVal, int pMax) {
      int _retVal;
     
      if (pVal < 0) {
         _retVal = 0;
      } else if (pVal > pMax) {
         _retVal = pMax;
      } else {
         _retVal = pVal;
      }
      return _retVal;
   } // constrain


   /*
    * calcNewBuffUL: pUL => UL in reduced IMAGE coordinates
    *                pNewLevel => the new level to display
    *
    *       Returns: retVal.x = col tileNumber of suitable image buffer UL
    *                retVal.y = row tileNumber of suitable image buffer UL
    */
   private final Point calcNewBuffUL(Point pUL, int pNewLevel) {
      Point _retVal;
      int _xTileNum;
      int _yTileNum;

      _xTileNum = constrain(pUL.x / tileSizeInPixels,
                            (tiers[pNewLevel].numCols -
                             tiers[pNewLevel].imgBufWidthInTiles));
      _yTileNum = constrain(pUL.y / tileSizeInPixels,
                            (tiers[pNewLevel].numRows -
                             tiers[pNewLevel].imgBufHeightInTiles));
      _retVal = new Point(_xTileNum, _yTileNum);
      return _retVal;
   } // calcNewBuffUL


   /******************************
    * displayNewLevel: use to display a DIFFERENT LEVEL of an image
    *                  that is *ALREADY* shown. The location of the
    *                  image at the center of the window will be the
    *                  same before and after this method call.
    *
    */
   public void displayNewLevel(int pNewLevel) {
      double _pctFullSize;
      Point  _bufTileUL;
      double _scaleFactor;
      Point  _currentCenter;
      Point  _desiredCenter;
      Point  _imgViewportUL;
      int    _currLevel;

      _currLevel = levelNumToDisplay;
      // get scale factor
      if (pNewLevel > _currLevel) {
         _scaleFactor = (double) (1 << (pNewLevel - _currLevel));
      } else if (pNewLevel < _currLevel) {
         _scaleFactor = 1.0D / (double) (1 << (_currLevel - pNewLevel));
      } else {
         return; // same level: nothing to do
      }

      // get center of current viewport in reduced image coords
      _currentCenter = new Point();
      _currentCenter.x = (imgViewportUL.x + imgViewportLR.x)/2;
      _currentCenter.y = (imgViewportUL.y + imgViewportLR.y)/2;

      // get center of new viewport in reduced image coords
      _desiredCenter = new Point();
      _desiredCenter.x = (int) (_currentCenter.x * _scaleFactor);
      _desiredCenter.y = (int) (_currentCenter.y * _scaleFactor);

      // get UL of new viewport in reduced image coords
      _imgViewportUL = new Point();
      _imgViewportUL.x = _desiredCenter.x - viewportWidth/2;
      _imgViewportUL.y = _desiredCenter.y - viewportHeight/2;

      // check for and set any new imageBuffer parameters
      if ((tiers[_currLevel].imgBufHeightInTiles
           !=  tiers[pNewLevel].imgBufHeightInTiles)   ||
          (tiers[_currLevel].imgBufWidthInTiles
           !=  tiers[pNewLevel].imgBufWidthInTiles)) {
         initImageBuffers(pNewLevel);
      }

      // get new tileNumberUL suitable for the new viewport center
      _bufTileUL = calcNewBuffUL(_imgViewportUL, pNewLevel);

      // get new viewport UL in JPanel coords
      imageBuffUL.x = _bufTileUL.x * tileSizeInPixels;
      imageBuffUL.y = _bufTileUL.y * tileSizeInPixels;
      viewportUL.x = _imgViewportUL.x - imageBuffUL.x;
      viewportUL.y = _imgViewportUL.y - imageBuffUL.y;

      // set state variables based on new level
      currentX = -viewportUL.x;
      currentY = -viewportUL.y;
      imgBufHeightInTiles   = tiers[pNewLevel].imgBufHeightInTiles;
      imgBufWidthInTiles    = tiers[pNewLevel].imgBufWidthInTiles;
      imageWidthInPixels    = tiers[pNewLevel].imageWidthInPixels;
      imageHeightInPixels   = tiers[pNewLevel].imageHeightInPixels;
      rowNumLastTileOfImage = tiers[pNewLevel].rowNumLastTileOfImage;
      colNumLastTileOfImage = tiers[pNewLevel].colNumLastTileOfImage;
      tileNumberUL = _bufTileUL;
      tileNumberLR.x = tileNumberUL.x + imgBufWidthInTiles  - 1;
      tileNumberLR.y = tileNumberUL.y + imgBufHeightInTiles - 1;
      imageUL.x = 0;
      imageLR.y = 0;
      imageLR.x = imageWidthInPixels  - 1;
      imageLR.y = imageHeightInPixels - 1;
      imageBuffLR.x = imageBuffUL.x + (imgBufWidthInTiles *
tileSizeInPixels)-1;
      imageBuffLR.y = imageBuffUL.y + (imgBufHeightInTiles*
tileSizeInPixels)-1;
      viewportLR.x   = viewportUL.x   + viewportWidth  - 1;
      viewportLR.y   = viewportUL.y   + viewportHeight - 1;
      imgViewportUL   = _imgViewportUL;
      imgViewportLR.x = imgViewportUL.x + viewportWidth  - 1;
      imgViewportLR.y = imgViewportUL.y + viewportHeight - 1;
      levelNumToDisplay = pNewLevel;

      // fetch new tiles for the buffer
      loadImageBuffers(tileNumberUL.x, tileNumberUL.y, levelNumToDisplay);

      // call repaint()
      if (tiledViewerPanel != null) {
         _pctFullSize = 1.0 /(double)tiers[pNewLevel].reductionFactor;
         tiledViewerPanel.showPercentFullSize(_pctFullSize);
         tiledViewerPanel.setNewZoom(imgViewportUL, imgViewportLR,
                                    imageWidthInPixels);
      }
      imageDisplayed = true;
      mustRefresh    = true;
      regenerateDisplay();
      repaint();
   } // displayNewLevel
 

   public void setScaleFactors(double pXScaleFactor, double pYScaleFactor,
                               double pXOffset, double pYOffset) {
      xScaleFactor = pXScaleFactor;
      yScaleFactor = pYScaleFactor;
      xOffset = pXOffset;
      yOffset = pYOffset;
      isScaleFactorSet = true;
   } // setScaleFactors
   
   public final double getScaledX() {
      return scaledX;
   } // getScaledX
   
   public final double getScaledY() {
      return scaledY;
   } // getScaledY
   

   private void initComponents() {
      // max size is entire image buffer
      setMaximumSize(new Dimension(tileSizeInPixels * imgBufWidthInTiles,
                                   tileSizeInPixels * imgBufHeightInTiles));
      // don't set preferredSize, let UI determine it
      // min size is size of a single tile
      setMinimumSize(new Dimension (tileSizeInPixels, tileSizeInPixels));
   } // initComponents


   private boolean loadImageBuffers(int pCol, int pRow, int pLevel) {
      boolean _retVal;
      int _r;
      int _c;
     
      if (baseName == null) {
         System.err.println("basename = null");
         _retVal = false;
      } else {
         _retVal = true;
         _r = pRow;
         for (int _row = 0; _row < tiers[pLevel].imgBufHeightInTiles; _row++) {
            _c = pCol;
            for (int _col = 0; _col < tiers[pLevel].imgBufWidthInTiles;_col++) {
               tileBuffers[_col][_row] = getImageTile(_c, _r, pLevel);
               if (tileBuffers[_col][_row] == null) {
                  _retVal = false;
                  System.out.println("loadImageBuffers() failed: col="+_col+
                                     ", row="+_row+", pLevel="+pLevel);
               }
               _c++;
            }
            _r++;
         }
      }
      return _retVal;
   } // loadImageBuffers
   
   private final boolean atLastColumn() {
      return tileNumberLR.x == colNumLastTileOfImage;
   } // atLastColumn

   private final boolean atLastRow() {
      return tileNumberLR.y == rowNumLastTileOfImage;
   } // atLastRow


   private BufferedImage getImageTile(int pCol, int pRow, int pLevel) {
      BufferedImage _retVal;
      PlanarImage _pi;
      String _tileUrlString;
      URL _url;

      _retVal = null;
      _tileUrlString = getFlashFileName(pCol, pRow, pLevel);
      try {
         _url = new URL(_tileUrlString);    // 0 mS always
         _pi = JAI.create("URL", _url);     // 1600 ms 1st time, 1 mS after
         _retVal = _pi.getAsBufferedImage();// 2166 ms 1st time, then variable
      } catch (Exception e) {
         e.printStackTrace();
      }
      return _retVal;
   } // getImageTile


   private String getFlashFileName(int pCol, int pRow, int pLevel) {
      int    _tileNum;
      int    _tileGroupNum;
      String _retVal;

      _tileNum = pCol + (pRow * tiers[pLevel].numCols);
      if (_tileNum <= tiers[pLevel].tilesInStartGroup - 1) {
         _tileGroupNum = tiers[pLevel].startingTileGroup;
      } else if (_tileNum >
                 (tiers[pLevel].numTiles -  tiers[pLevel].tilesInEndGroup)) {
         _tileGroupNum = tiers[pLevel].endingTileGroup;
      } else {
         _tileNum = _tileNum - tiers[pLevel].tilesInStartGroup;
         _tileGroupNum = tiers[pLevel].startingTileGroup;
         _tileGroupNum += 1 + _tileNum / TILES_PER_TILE_GROUP;
      }
      _retVal = baseName + "/TileGroup" +_tileGroupNum+ "/"
         +pLevel+ "-" +pCol+ "-" +pRow+ ".jpg";
      return _retVal;
   } // getFlashFileName


   private int decodeXMLProp(StringBuffer pPropBuff, String pProp) {
      int _numPos;
      int _quotePos;

      _numPos = pProp.length()+ 2 + pPropBuff.indexOf(pProp);
      _quotePos = pPropBuff.indexOf("\"", _numPos);
      return Integer.parseInt(pPropBuff.substring(_numPos, _quotePos));
   } // decodeXMLProp


   /*
     * read the Flash ImageProperties.xml file and set the following
     * instance vars:
     *    fullImageWidthInPixels, fullImageHeightInPixels,
     *    numTiles, tileSizeInPixels, numLevels, imageWidthInPixels,
     *    imageHeightInPixels, imgBufWidthInTiles, imgBufHeightInTiles,
     *    imageBuffUL, imageBuffLR
     */
   private void initImageProperties(int pLevel) {
      URL _url;
      InputStream _is;
      StringBuffer _sb;
      String _propStr;
      double _largestDim;
      int _numRows;
      int _numCols;
      int _reductionFactor;
      ArrayList _tiers;
      TierData  _tierData;

      _sb = new StringBuffer(130);
      try {
         _url = new URL(baseName+"/ImageProperties.xml");
         //System.err.println(_url);
         _is = (InputStream) _url.getContent();
         while (_is.available() > 0) {
            _sb.append((char) _is.read());
         }
         _is.close();
         _propStr = _sb.toString();
         //System.err.println(_propStr);
      } catch (FileNotFoundException fnfe) {
         System.err.println("This probably isn't a Flash image.");
         return;
      } catch (IOException e) {
         e.printStackTrace();
         JOptionPane.showMessageDialog(null,
                                       "Connection refused: "+baseName,
                                       "Connection refused",
                                       JOptionPane.ERROR_MESSAGE);
         return;
      }
      fullImageWidthInPixels  = decodeXMLProp(_sb, "WIDTH");
      fullImageHeightInPixels = decodeXMLProp(_sb, "HEIGHT");
      numTiles                = decodeXMLProp(_sb, "NUMTILES");
      tileSizeInPixels        = decodeXMLProp(_sb, "TILESIZE");
      lastTileGroupIndex      = numTiles / TILES_PER_TILE_GROUP;
      if ((numTiles % TILES_PER_TILE_GROUP) == 0) {
         lastTileGroupIndex--;
      }
      _largestDim = Math.max(fullImageWidthInPixels, fullImageHeightInPixels);
      _tiers = new ArrayList();
      numLevels = 1;
      while (((int) _largestDim) > tileSizeInPixels) {
         numLevels++;
         _largestDim /= 2;
      }
      _reductionFactor = 1;
      for (int i = numLevels-1; i >= 0; i--) {
         _tierData = new TierData();
         _tierData.reductionFactor = _reductionFactor;
         _tierData.imageWidthInPixels =
            fullImageWidthInPixels / _reductionFactor;
         _tierData.imageHeightInPixels =
            fullImageHeightInPixels / _reductionFactor;
         _tierData.numRows  = (int) Math.ceil(((double)
                _tierData.imageHeightInPixels) / tileSizeInPixels);
         _tierData.numCols  = (int) Math.ceil(((double)
                _tierData.imageWidthInPixels) / tileSizeInPixels);
         _tierData.numTiles = _tierData.numRows * _tierData.numCols;
         _tierData.rowNumLastTileOfImage = _tierData.numRows - 1;
         _tierData.colNumLastTileOfImage = _tierData.numCols - 1;
         _tierData.pixelsInLastCol
            = _tierData.imageWidthInPixels % tileSizeInPixels;
         if (_tierData.pixelsInLastCol == 0) {
            _tierData.pixelsInLastCol = 256;
         }
         _tierData.pixelsInLastRow
            = _tierData.imageHeightInPixels % tileSizeInPixels;
         if (_tierData.pixelsInLastRow == 0) {
            _tierData.pixelsInLastRow = 256;
         }
         _tierData.imgBufWidthInTiles
            = (int) Math.min(_tierData.numCols, IMG_BUF_WIDTH_IN_TILES);
         _tierData.imgBufHeightInTiles
            = (int) Math.min(_tierData.numRows, IMG_BUF_HEIGHT_IN_TILES);
         _tiers.add(0, _tierData);
         _reductionFactor = _reductionFactor << 1;
      }
      tiers = new TierData[numLevels];
      tiers = (TierData[]) _tiers.toArray(tiers);
      initTiers();
      initLevel(pLevel);
   } // initImageProperties


   private void initTiers() {
      int _startingTileGroup;
      int _tileTotal;
      int _startGroupTileCount;
      int _remTiles;

      // assign starting and ending group numbers to tiers
      _startingTileGroup = 0;
      _tileTotal = 0;
      for (int i = 0; i < numLevels; i++) {
         tiers[i].startingTileGroup = _startingTileGroup;
         _tileTotal += tiers[i].numTiles;
         tiers[i].endingTileGroup = _tileTotal / TILES_PER_TILE_GROUP;
         _startingTileGroup = tiers[i].endingTileGroup;
      }
      // assign start and end tile counts to tile groups
      _startGroupTileCount = 0;
      for (int i = 0; i < numLevels; i++) {
         if ((_startGroupTileCount + tiers[i].numTiles)
             < TILES_PER_TILE_GROUP) {
            tiers[i].tilesInStartGroup = tiers[i].numTiles;
            tiers[i].tilesInEndGroup   = tiers[i].numTiles;
            _startGroupTileCount += tiers[i].numTiles;
         } else if ((_startGroupTileCount + tiers[i].numTiles) ==
                    TILES_PER_TILE_GROUP) {
            tiers[i].tilesInStartGroup = tiers[i].numTiles;
            tiers[i].tilesInEndGroup   = tiers[i].numTiles;
            _startGroupTileCount = 0;
         } else { // (_startGroupTileCount + tiers[i].numTiles) > 256
            tiers[i].tilesInStartGroup
               = TILES_PER_TILE_GROUP - _startGroupTileCount;
            _remTiles = tiers[i].numTiles - tiers[i].tilesInStartGroup;
            tiers[i].tilesInEndGroup = _remTiles % TILES_PER_TILE_GROUP;
            if (tiers[i].tilesInEndGroup == 0) { // exactly fits
               tiers[i].tilesInEndGroup = TILES_PER_TILE_GROUP;
               _startGroupTileCount = 0;
            } else {
               _startGroupTileCount = tiers[i].tilesInEndGroup;
            }
         }
      }
   } // initTiers

   /*
    * sets UL corner of everything to 0,0 and LR corner correspondingly
    */
   private void initLevel(int pLevel) {
      levelNumToDisplay = pLevel;
      imageWidthInPixels    = tiers[pLevel].imageWidthInPixels;
      imageHeightInPixels   = tiers[pLevel].imageHeightInPixels;
      imgBufWidthInTiles    = tiers[pLevel].imgBufWidthInTiles;
      imgBufHeightInTiles   = tiers[pLevel].imgBufHeightInTiles;
      rowNumLastTileOfImage = tiers[pLevel].rowNumLastTileOfImage;
      colNumLastTileOfImage = tiers[pLevel].colNumLastTileOfImage;
      tileNumberUL.x = 0;
      tileNumberUL.y = 0;
      tileNumberLR.x = imgBufWidthInTiles  - 1;
      tileNumberLR.y = imgBufHeightInTiles - 1;
      imageUL.x = 0;
      imageUL.y = 0;
      imageLR.x = imageWidthInPixels - 1;
      imageLR.y = imageHeightInPixels - 1;
      imageBuffUL.x = 0;
      imageBuffUL.y = 0;
      imageBuffLR.x = (imgBufWidthInTiles * tileSizeInPixels)  - 1;
      imageBuffLR.y = (imgBufHeightInTiles * tileSizeInPixels) - 1;
      viewportUL.x = 0;
      viewportUL.y = 0;
      viewportLR.x = getWidth()  - 1;
      viewportLR.y = getHeight() - 1;
      imgViewportUL.x = viewportUL.x;
      imgViewportUL.y = viewportUL.y;
      imgViewportLR.x = viewportLR.x;
      imgViewportLR.y = viewportLR.y;
   } // initLevel


 
   private void initImageBuffers(int pLevel) {
      imageBuffer = new BufferedImage(
              tileSizeInPixels * tiers[pLevel].imgBufWidthInTiles,
              tileSizeInPixels * tiers[pLevel].imgBufHeightInTiles,
              BufferedImage.TYPE_3BYTE_BGR);
      tileBuffers = new BufferedImage[tiers[pLevel].imgBufWidthInTiles]
         [tiers[pLevel].imgBufHeightInTiles];
   } // initImageBuffers


   public final void regenerateDisplay() {
      imageDrawn       = false;      
      backBufDrawn     = false;
      annotationsDrawn = false;
      bitBltDone       = false;
      repaint();
   } // regenerateDisplay


   // this is the original, prior to adding drawing extensions
   public void paintComponent(Graphics pG) {
      Graphics2D _g2;

      super.paintComponent(pG);
      if (!imageDisplayed) {
         return;
      }
      _g2 = (Graphics2D) pG;
      // get graphics context for our offscreen imageBuffer
      imageBuffG2 = (Graphics2D) imageBuffer.getGraphics();
      if (mustRefresh) {
         for (int _col = 0; _col < imgBufWidthInTiles; _col++) {
            for (int _row = 0; _row < imgBufHeightInTiles; _row++) {
               // draw the tiles into the main buffer
               imageBuffG2.drawImage(tileBuffers[_col][_row],
                                     _col * tileSizeInPixels,
                                     _row * tileSizeInPixels,
                                     tileBuffers[_col][_row].getWidth(),
                                     tileBuffers[_col][_row].getHeight(),
                                     Color.white,
                                     this);
            }
         }
         mustRefresh = false;
      }
      // draw the main buffer onto the JPanel
      _g2.drawImage(imageBuffer, currentX, currentY, this);
      viewportWidth = getWidth();
      viewportHeight = getHeight();
   } // paintComponent




   

   // from MouseListener
   public void mouseClicked(MouseEvent e) {
      if (e.getClickCount() > 1) { // re-center image at mouse click
         recenterImageAt(e.getX(), e.getY());
      }
   } // mouseClicked

   
   
   private final int getFullSizedX(int pPanelX) {
      return tiers[levelNumToDisplay].reductionFactor *
         (imgViewportUL.x + pPanelX);
   } // getFullSizedX

   private final int getFullSizedY(int pPanelY) {
      return tiers[levelNumToDisplay].reductionFactor *
         (imgViewportUL.y + pPanelY);
   } // getFullSizedY

   private final int getJPanelX(int pFullSizedX) {
      return (pFullSizedX / tiers[levelNumToDisplay].reductionFactor)
         - imageBuffUL.x;
   } // getJPanelX

   private final int getJPanelY(int pFullSizedY) {
      return (pFullSizedY / tiers[levelNumToDisplay].reductionFactor)
         - imageBuffUL.y;
   } // getJPanelY

   private final int getJPanelWorH(int pFullSizedParam) {
      return (pFullSizedParam / tiers[levelNumToDisplay].reductionFactor);
   } // getJPanelWorH

   // adjust viewport and imageBuffer so that UL = (pX,pY)
   // in absolute IMAGE pixel coordinates
   public void moveULCornerAbsoluteTo(int pX, int pY) {
      Point _uL; // potential new UL tile
      Point _lR; // potential new LR tile

      _uL = new Point();
      _uL.x = pX < 0 ? 0 : (pX / tileSizeInPixels);
      _uL.y = pY < 0 ? 0 : (pY / tileSizeInPixels);
      // do we need to adjust buffer contents?
      if (! _uL.equals(tileNumberUL)) {
         // yes: get new buffer data
         // calc new column num for tileNumberUL
         _lR = new Point(_uL.x + imgBufWidthInTiles - 1,
                         _uL.y + imgBufHeightInTiles - 1);
         //System.out.println("_lR: "+_lR);
         if (_lR.x > colNumLastTileOfImage) {
            // must clip desired buffer position to stay on image
            _lR.x = colNumLastTileOfImage;                    
            _uL.x = _lR.x - (imgBufWidthInTiles - 1);          
            tileNumberUL.x = _uL.x;                            
            imageBuffUL.x = tileNumberUL.x * tileSizeInPixels;
            imgViewportUL.x = pX;                              
            viewportUL.x = pX - imageBuffUL.x;                
            currentX = -viewportUL.x;                          
         } else { // just move the buffer
            //System.out.println("// just move the buffer");
            tileNumberUL.x = _uL.x;                            
            imageBuffUL.x = tileNumberUL.x * tileSizeInPixels;
            imgViewportUL.x = pX;                              
            viewportUL.x = pX - imageBuffUL.x;                
            currentX = -viewportUL.x;                          
         }  
         // then, calc new row num for tileNumberUL  
         if (_lR.y > rowNumLastTileOfImage) {  
            _lR.y = rowNumLastTileOfImage;                    
            _uL.y = _lR.y - (imgBufHeightInTiles - 1);        
            tileNumberUL.y = _uL.y;                            
            imageBuffUL.y = tileNumberUL.y * tileSizeInPixels;
            imgViewportUL.y = pY;                              
            viewportUL.y = pY - imageBuffUL.y;                
            currentY = -viewportUL.y;                          
         } else {
            //System.out.println("else part");
            tileNumberUL.y = _uL.y;                            
            imageBuffUL.y = tileNumberUL.y * tileSizeInPixels;
            imgViewportUL.y = pY;                              
            viewportUL.y = pY - imageBuffUL.y;                
            currentY = -viewportUL.y;                          
         }  
         tileNumberLR = _lR;                                    
         imageBuffLR.x = (tileNumberLR.x + 1) * tileSizeInPixels - 1;    
         imageBuffLR.y = (tileNumberLR.y + 1) * tileSizeInPixels - 1;    
         loadImageBuffers(_uL.x, _uL.y, levelNumToDisplay);
      } else { // no: don't change buffer, just move the viewport
         viewportUL.x += (pX - imgViewportUL.x);
         currentX = -viewportUL.x;
         imgViewportUL.x = pX;
         viewportUL.y += (pY - imgViewportUL.y);
         currentY = -viewportUL.y;
         imgViewportUL.y = pY;
      } // all adjustments have been made at this point
      viewportLR.x = viewportUL.x + getWidth() - 1;
      viewportLR.y = viewportUL.y + getHeight() - 1;
      imgViewportLR.x = imgViewportUL.x + getWidth() - 1;
      imgViewportLR.y = imgViewportUL.y + getHeight() - 1;
      if (tiledViewerPanel != null) {
         tiledViewerPanel.drawViewport(imgViewportUL, imgViewportLR);
      }
      mustRefresh = true;
      regenerateDisplay();
      //repaint();
   } // moveULCornerAbsoluteTo


   // adjust viewport and imageBuffer so that UL = (pX,pY)
   // relative to its CURRENT position
   // (pX > 0) => move the viewport pX pixels to the RIGHT
   // (py > 0) => move the viewport pY pixels DOWN
   public void moveULCornerRelative(int pX, int pY) {
      moveULCornerAbsoluteTo(imgViewportUL.x+pX, imgViewportUL.y+pY);
   } // moveULCornerRelative


   // adjust the image so that its center is in the middle of the viewport
   public void recenterImage() {
      Point _desiredCenter;
      Point _vpCenter;
      Point _imgViewportUL;
      Point _bufTileUL;

      //System.out.println("entering recenterImage()");

      // get desired center (i.e., center of *image*) in reduced image coords
      _desiredCenter = new Point(imageWidthInPixels/2, imageHeightInPixels/2);
     
      // get center of viewport
      _vpCenter = new Point(viewportWidth/2, viewportHeight/2);

      // get UL of viewport in reduced image coords
      _imgViewportUL = new Point();
      _imgViewportUL.x = _desiredCenter.x - viewportWidth/2;
      _imgViewportUL.y = _desiredCenter.y - viewportHeight/2;

      // get new tileNumberUL suitable for new viewport center
      _bufTileUL = calcNewBuffUL(_imgViewportUL, levelNumToDisplay);

      // get new viewport UL in JPanel coords
      imageBuffUL.x = _bufTileUL.x * tileSizeInPixels;
      imageBuffUL.y = _bufTileUL.y * tileSizeInPixels;
      viewportUL.x = _imgViewportUL.x - imageBuffUL.x;
      viewportUL.y = _imgViewportUL.y - imageBuffUL.y;
     
      // set state variables based on new center
      currentX = -viewportUL.x;
      currentY = -viewportUL.y;
      tileNumberUL = _bufTileUL;
      tileNumberLR.x = tileNumberUL.x + imgBufWidthInTiles  -1;
      tileNumberLR.y = tileNumberUL.y + imgBufHeightInTiles -1;
      imageBuffLR.x = imageBuffUL.x + (imgBufWidthInTiles *
tileSizeInPixels)-1;
      imageBuffLR.y = imageBuffUL.y + (imgBufHeightInTiles*
tileSizeInPixels)-1;
      viewportLR.x = viewportUL.x + viewportWidth  -1;
      viewportLR.y = viewportUL.y + viewportHeight -1;
      imgViewportUL = _imgViewportUL;
      imgViewportLR.x = imgViewportUL.x + viewportWidth  -1;
      imgViewportLR.y = imgViewportUL.y + viewportHeight -1;

      // fetch new tiles for the buffer
      loadImageBuffers(tileNumberUL.x, tileNumberUL.y, levelNumToDisplay);

      // call repaint()
      if (tiledViewerPanel != null) {
         tiledViewerPanel.drawViewport(imgViewportUL, imgViewportLR);
      }
      imageDisplayed = true;
      mustRefresh = true;
      regenerateDisplay();
      //repaint();
   } // recenterImage


   //
   // pX and pY are the coords of the point to be centered on the viewport
   // in JPanel coords
   //
   public void recenterImageAt(int pX, int pY) {
      int _centerX; // center of the viewport in the X direction
      int _centerY; // center of the viewport in the Y direction
      int _xlatPx;  // the amount to translate in the X and Y directions
      int _xlatPy;  // from the UL corner of viewport to the mouse click
      int _deltaX;  // the X and Y amounts used to adjust currentX and
      int _deltaY;  // currentY prior to drawing

      //System.out.println("entering recenterImageAt(" +pX+ "," +pY+ ")");
      //printEverything();
      // get coords for center of the viewport
      _centerX = (viewportUL.x + viewportLR.x) / 2; //JPanel coords
      _centerY = (viewportUL.y + viewportLR.y) / 2;
      // artificially set the original mouse location to be the
      // center of the viewport
      x0 = _centerX;  //JPanel coords
      y0 = _centerY;
      // get distance from viewportUL to location of mouse click
      _xlatPx = pX + viewportUL.x;
      _xlatPy = pY + viewportUL.y;
      // get amount to shift
      _deltaX = _centerX - _xlatPx;
      _deltaY = _centerY - _xlatPy;
      // and then shift
      currentX += _deltaX;
      currentY += _deltaY;
      // check for needed buffer updates
      boundsCheck(pX, pY);
      // update our control vars
      viewportUL.x = -currentX;  //JPanel coords
      viewportUL.y = -currentY;
      viewportLR.x = viewportUL.x + getWidth() - 1;  //JPanel coords
      viewportLR.y = viewportUL.y + getHeight() - 1;
      imgViewportUL.x -= _deltaX; // image coords
      imgViewportUL.y -= _deltaY;
      imgViewportLR.x = imgViewportUL.x + getWidth() - 1; // image coords
      imgViewportLR.y = imgViewportUL.y + getHeight() - 1;
      if (tiledViewerPanel != null) {
         tiledViewerPanel.drawViewport(imgViewportUL, imgViewportLR);
      }
      //printEverything();
      // show the new results
      regenerateDisplay();
      //repaint();
   } // recenterImageAt


   public void mousePressed(MouseEvent e) {
      this.setCursor(moveCursor);
      x0 = e.getX();
      y0 = e.getY();
   } // mousePressed
   

   public void mouseReleased(MouseEvent e) {
      this.setCursor(normalCursor);
   } //mouseReleased


   public void mouseEntered(MouseEvent e) {} // mouseEntered
   public void mouseExited(MouseEvent e) {} // mouseExited
   // from MouseMotionListener




    /**************************
     *  boundsCheck: the entry into the row/col buffer fetch
     *               mechanism. It will transparently handle such fetches; its
     *               return value is meant to indicate whether or not to scroll
     *               the image. When the scrolling would go off the image
     *               boundaries, boundsCheck returns false.
     *       pX, pY: the x,y coordinates of the mouse while dragging
     */
   private boolean boundsCheck(int pX, int pY) {
      int _deltaX;
      int _deltaY;
       
      // determine where proposed drag will take us, but don't perform drag
      _deltaX = pX - x0;
      _deltaY = pY - y0;
      if (_deltaX > 0) {
         isDraggingLeft = false;
         isDraggingRight = true;
      } else if (_deltaX < 0) {
         isDraggingLeft = true;
         isDraggingRight = false;
      } else { // purely vertical
         isDraggingLeft = false;
         isDraggingRight = false;
      }
      if (_deltaY < 0) {
         isDraggingUp = true;
         isDraggingDown = false;
      } else if (_deltaY > 0) {
         isDraggingUp = false;
         isDraggingDown = true;
      } else { // purely horizontal
         isDraggingUp = false;
         isDraggingDown = false;
      }
      // now check for new left or right column ...
      if (isDraggingRight && (imgViewportUL.x - _deltaX) < imageBuffUL.x) {
         this.setCursor(waitCursor);
         fetchBufferLeftColumn();
         this.setCursor(moveCursor);
      } else if (isDraggingLeft &&
                 (imgViewportLR.x - _deltaX) > imageBuffLR.x) {
         this.setCursor(waitCursor);
         fetchBufferRightColumn();
         this.setCursor(moveCursor);
      }
      // ... now for new top or bottom row
      if (isDraggingDown && (imgViewportUL.y - _deltaY) < imageBuffUL.y) {
         this.setCursor(waitCursor);
         fetchBufferTopRow();
         this.setCursor(moveCursor);
      } else if (isDraggingUp &&
                 (imgViewportLR.y - _deltaY) > imageBuffLR.y) {
         this.setCursor(waitCursor);
         fetchBufferBottomRow();
         this.setCursor(moveCursor);
      }
      return true;
   } // boundsCheck

   
   private void fetchBufferLeftColumn() {
      int _shiftDistance;

      // don't try to fetch left of image
      if (tileNumberUL.x == 0) {
         return;
      }
      //System.out.println("New left");
      // move buffers over
      for (int _row = 0; _row < imgBufHeightInTiles; _row++) {
         for (int _col = imgBufWidthInTiles-1; _col > 0; _col--) {
            tileBuffers[_col][_row] = tileBuffers[_col-1][_row];
         }
      }
      // get new column 0
      for (int _row = 0; _row < imgBufHeightInTiles; _row++) {
         tileBuffers[0][_row] = getImageTile(tileNumberUL.x-1,
                                             _row + tileNumberUL.y,
                                             levelNumToDisplay);
      }
      // reset the relevant coordinates
      tileNumberUL.x--;
      tileNumberLR.x--;
      _shiftDistance = /*atLastColumn()?pixelsInLastCol :*/ tileSizeInPixels;
      imageBuffUL.x -= _shiftDistance;
      imageBuffLR.x -= _shiftDistance;
      currentX -= _shiftDistance;
      mustRefresh = true;
      regenerateDisplay();
   } // fetchBufferLeftColumn


   private void fetchBufferRightColumn() {
      int _shiftDistance;

      // don't try to fetch past end of image
      if (atLastColumn()) {
         return;
      }
      //System.out.println("New right");
      // for each row r, move tileBuffer[col][r] to tileBuffer[col-1][r]
      for (int _row = 0; _row < imgBufHeightInTiles; _row++) {
         for (int _col = 0; _col < imgBufWidthInTiles-1; _col++) {
            tileBuffers[_col][_row] = tileBuffers[_col+1][_row];
         }
      }
      // for last col (tileNumberLR.x), fetch new tiles
      for (int _row = 0; _row < imgBufHeightInTiles; _row++) {
         tileBuffers[imgBufWidthInTiles-1][_row]
            = getImageTile(tileNumberLR.x+1,
                           _row + tileNumberUL.y,
                           levelNumToDisplay);
      }
      // reset the relevant coordinates
      tileNumberUL.x++;
      tileNumberLR.x++;
      _shiftDistance = /*atLastColumn()?pixelsInLastCol :*/ tileSizeInPixels;
      imageBuffUL.x += _shiftDistance;
      imageBuffLR.x += _shiftDistance;
      currentX += _shiftDistance;
      mustRefresh = true;
      regenerateDisplay();
   } // fetchBufferRightColumn


   private void fetchBufferTopRow() {
      int _shiftDistance;

      // don't try to fetch above top of image
      if (tileNumberUL.y == 0) {
         return;
      }
      //System.out.println("New top");
      // move buffers down
      for (int _col = 0; _col < imgBufWidthInTiles; _col++) {
         for (int _row = imgBufHeightInTiles-1; _row > 0; _row--) {
            tileBuffers[_col][_row] = tileBuffers[_col][_row-1];
         }
      }
      // get new row 0
      for (int _col = 0; _col < imgBufWidthInTiles; _col++) {
         tileBuffers[_col][0]
            = getImageTile(_col + tileNumberUL.x,
                           tileNumberUL.y-1,
                           levelNumToDisplay);
      }
      // reset the relevant coordinates
      tileNumberUL.y--;
      tileNumberLR.y--;
      _shiftDistance = /*atLastRow() ? pixelsInLastRow :*/ tileSizeInPixels;
      imageBuffUL.y -= _shiftDistance;
      imageBuffLR.y -= _shiftDistance;
      currentY -= _shiftDistance;
      mustRefresh = true;
      regenerateDisplay();
   } // fetchBufferTopRow

   
   private void fetchBufferBottomRow() {
      int _shiftDistance;

      // don't try to fetch past bottom of image
      if (atLastRow()) {
         return;
      }
      //System.out.println("New bottom");
      // move tiles up one row
      for (int _col = 0; _col < imgBufWidthInTiles; _col++) {
         for (int _row = 0; _row < imgBufHeightInTiles-1; _row++) {
            tileBuffers[_col][_row] = tileBuffers[_col][_row+1];
         }
      }
      // for last row (tileNumberLR.y), fetch new tiles
      for (int _col = 0; _col < imgBufWidthInTiles; _col++) {
         tileBuffers[_col][imgBufHeightInTiles-1]
            = getImageTile(_col + tileNumberUL.x,
                           tileNumberLR.y+1,
                           levelNumToDisplay);
      }
      // reset the relevant coordinates
      tileNumberUL.y++;
      tileNumberLR.y++;
      _shiftDistance = /*atLastRow() ? pixelsInLastRow :*/ tileSizeInPixels;
      imageBuffUL.y += _shiftDistance;
      imageBuffLR.y += _shiftDistance;
      currentY += _shiftDistance;
      mustRefresh = true;
      regenerateDisplay();
   } // fetchBufferBottomRow


   public void mouseDragged(MouseEvent e) {
      scrollViewport(e);
   } // mouseDragged

   
   private void scrollViewport(MouseEvent e) {
      int _x1;
      int _y1;
      int _deltaX;
      int _deltaY;

      _x1 = e.getX();
      _y1 = e.getY();
      if (boundsCheck(_x1, _y1)) {
         // reset painting coordinates
         _deltaX = _x1 - x0;
         _deltaY = _y1 - y0;
         x0 = _x1;
         y0 = _y1;
         // because we move oppositely
         currentX = currentX + _deltaX;
         currentY = currentY + _deltaY;
         // update viewport coords
         viewportUL.x = -currentX;
         viewportUL.y = -currentY;
         viewportLR.x = viewportUL.x + getWidth() - 1;
         viewportLR.y = viewportUL.y + getHeight() - 1;
         // update coords of viewport in image coords
         imgViewportUL.x -= _deltaX;
         imgViewportUL.y -= _deltaY;
         imgViewportLR.x = imgViewportUL.x + getWidth() - 1;
         imgViewportLR.y = imgViewportUL.y + getHeight() - 1;
         if (tiledViewerPanel != null) {
            tiledViewerPanel.drawViewport(imgViewportUL, imgViewportLR);
         }
         repaint(); // using currentX, currentY as UL corner
      }
   } // scrollViewport


   private void displayCoords(MouseEvent e) {
      if (tiledViewerPanel != null) {
         tiledViewerPanel.showX(e.getX() + imgViewportUL.x);
         tiledViewerPanel.showY(e.getY() + imgViewportUL.y);
         scaledX = tiers[levelNumToDisplay].reductionFactor *
            (imgViewportUL.x + e.getX());
         scaledY = tiers[levelNumToDisplay].reductionFactor *
            (imgViewportUL.y + e.getY());
         tiledViewerPanel.showScaledX(scaledX);
         tiledViewerPanel.showScaledY(scaledY);
      } // if (tiledViewerPanel != null)
   } // displayCoords


   public void mouseMoved(MouseEvent e) {
      displayCoords(e);
   } // mouseMoved


   public void componentShown(java.awt.event.ComponentEvent e) {}
   public void componentMoved(java.awt.event.ComponentEvent e) {}
   public void componentHidden(java.awt.event.ComponentEvent e) {}


   /*
     * used to set the LR bounds of the viewport both at initialization
     * and at any subsequent resizing of the viewport
     */
   public void componentResized(ComponentEvent e) {
      Component _comp;

      _comp = e.getComponent();
      viewportWidth  = _comp.getSize().width;
      viewportHeight = _comp.getSize().height;
      viewportLR.x = viewportUL.x + viewportWidth  - 1;
      viewportLR.y = viewportUL.y + viewportHeight - 1;
      imgViewportLR.x = imgViewportUL.x + viewportWidth  - 1;
      imgViewportLR.y = imgViewportUL.y + viewportHeight - 1;
      // bring in more image if needed
      while ((imgViewportLR.x > imageBuffLR.x) && !atLastColumn()) {
         fetchBufferRightColumn();
      }
      while ((imgViewportLR.y > imageBuffLR.y) && !atLastRow()) {
         fetchBufferBottomRow();
      }
      if (tiledViewerPanel != null) {
         tiledViewerPanel.drawViewport(imgViewportUL, imgViewportLR);
      }
   } // componentResized


   class TierData {
      public int numCols; //
      public int numRows; //
      public int numTiles; //
      public int startingTileGroup;
      public int tilesInStartGroup;
      public int endingTileGroup;
      public int tilesInEndGroup;
      public int imageWidthInPixels;  // of possibly reduced image we show
      public int imageHeightInPixels; // of possibly reduced image we show
      public int rowNumLastTileOfImage; //
      public int colNumLastTileOfImage; //
      public int pixelsInLastCol; //
      public int pixelsInLastRow; //
      public int imgBufWidthInTiles;
      public int imgBufHeightInTiles;
      public int reductionFactor; // full size = 1, 1/4 size = 4, etc.
   } // class TierData

} // class TiledAnnot













FILE TiledViewerPanel.java
=============================================

//package whatever;

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.imageio.*;
import javax.media.jai.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.FileImageOutputStream;


class TiledViewerPanel extends JPanel
    implements MouseListener, MouseMotionListener {

   private static final String ICON_DIR    = "./Icons/";
   private static final int    MOVE_UP     =        0;
   private static final int    MOVE_DOWN   =        1;
   private static final int    MOVE_RIGHT  =        2;
   private static final int    MOVE_LEFT   =        3;
   private static final int    MOVE_CENTER =        4;
   private static final int    IN          =        0;
   private static final int    OUT         =        1;
   private static final int    NAV_SIZE    =      128;
   private static final int    SUBPANEL_W  =      128;
   private static final int    SUBPANEL_H  =      388;

   
   private TiledViewer tiledViewer;
   private JTextField percentFullsize;
   private JTextField rawXField;
   private JTextField rawYField;
   private JTextField scaledXField;
   private JTextField scaledYField;
   private Box        mainControlPanel;
   private Locator    locatorPanel;
   private Dimension  locPanelSize;
     
   private String id;            // id of the image that this panel displays
   private String title;         // image title

   private Image    backingBuffer;
   private Graphics backingBufferG;

   
   public TiledViewerPanel(String pUrl, int pInitialZoom) {
      super();
      locPanelSize = new Dimension(NAV_SIZE, NAV_SIZE);
      setLayout(new BorderLayout());
      initComponents(pUrl, pInitialZoom);
   } // constructor

   public String toString() {
      return "id = "+id+", title = "+title;
   }
   

   public static TiledViewerPanel getInstance(String pUrl,
                                         String pId,
                                         String pTitle) {
      TiledViewerPanel _retVal;
      int _initialZoom;
     
      _initialZoom = 0;
      _retVal = new TiledViewerPanel(pUrl, _initialZoom);
      _retVal.id = pId;
      _retVal.title = pTitle;
      return _retVal;
   } // getInstance


   public int getReductionFactor() {
      return tiledViewer.getReductionFactor();
   }

 
   public Image getBackingBuffer() {
      return backingBuffer;
   } // getBackingBuffer


   public void  setBackingBuffer(Image pBuffer) {
      backingBuffer = pBuffer;
   } // setBackingBuffer


   public Graphics getBackBufferG() {
      return backingBufferG;
   } // getBackBufferG

   
   /**************** interface MouseListener methods ***********/
   public void mouseEntered(MouseEvent e)  {}
   public void mouseExited(MouseEvent e)   {}
   public void mousePressed(MouseEvent e)  {}
   public void mouseReleased(MouseEvent e) {}
   public void mouseClicked(MouseEvent e)  {}
   
   /**************** interface MouseMotionListener methods ***********/
   public void mouseMoved(MouseEvent e)   {}
   public void mouseDragged(MouseEvent e) {}



   private void initComponents(String pUrl, int pInitialZoom) {
      JPanel _navSubPanel;
      JPanel _zoomSubPanel;
      JPanel _reductionSubPanel;
      JPanel _rawCoordPanel;
      JPanel _scaledCoordPanel;
      JPanel _controlSubPanel;
      PlanarImage _planarImage;
      BufferedImage _bufferedImage;

      locatorPanel = new Locator();
      _navSubPanel = makeNavPanel();
      _zoomSubPanel = makeZoomPanel();
      _reductionSubPanel = makeReductionPanel();
      _rawCoordPanel = makeRawPanel();
      _scaledCoordPanel = makeScaledPanel();
      _controlSubPanel = new JPanel();
      _controlSubPanel.setMaximumSize(new Dimension(SUBPANEL_W, SUBPANEL_H));
      _controlSubPanel.setBackground(Color.WHITE);
      _controlSubPanel.setLayout(
              new BoxLayout(_controlSubPanel, BoxLayout.Y_AXIS));
      _controlSubPanel.add(locatorPanel);
      _controlSubPanel.add(_navSubPanel);
      _controlSubPanel.add(_zoomSubPanel);
      _controlSubPanel.add(_reductionSubPanel);
      _controlSubPanel.add(_rawCoordPanel);
      _controlSubPanel.add(_scaledCoordPanel);
      mainControlPanel = Box.createVerticalBox();
      mainControlPanel.add(_controlSubPanel);
      mainControlPanel.add(Box.createVerticalGlue());
      add(mainControlPanel, BorderLayout.WEST);
      tiledViewer = new TiledViewer(this);
      add(tiledViewer, BorderLayout.CENTER);
      tiledViewer.displayNewImage(pUrl, pInitialZoom);
   } // initComponents

   
   private void processNavButton(ActionEvent pEvent, int pHow) {
      int _deltaX;
      int _deltaY;
      boolean _panelShift; // true => shift by panel[H | W] else tile
     
      if (pHow == MOVE_CENTER) {
         tiledViewer.recenterImage();
      } else {
         _panelShift = (ActionEvent.SHIFT_MASK & pEvent.getModifiers()) != 0;
         switch(pHow) {
         case MOVE_UP:
            _deltaX = 0;
            _deltaY = _panelShift ? -tiledViewer.getHeight()  
                                  : -tiledViewer.tileSizeInPixels;
            break;
         case MOVE_DOWN:
            _deltaX = 0;
            _deltaY = _panelShift ? tiledViewer.getHeight()  
                                  : tiledViewer.tileSizeInPixels;
            break;
         case MOVE_LEFT:
            _deltaX = _panelShift ? -tiledViewer.getWidth()  
                                  : -tiledViewer.tileSizeInPixels;
            _deltaY = 0;
            break;
         case MOVE_RIGHT:
            _deltaX = _panelShift ? tiledViewer.getWidth()  
                                  : tiledViewer.tileSizeInPixels;
            _deltaY = 0;
            break;
         default:
            _deltaX = 0;
            _deltaY = 0;
            break;
         } // switch
         tiledViewer.moveULCornerRelative(_deltaX, _deltaY);
      } // else
   } // processNavButton
   

   private void zoom(int pDir) {
      switch(pDir) {
      case IN:
         if (tiledViewer.levelNumToDisplay != tiledViewer.numLevels-1) {
            tiledViewer.displayNewLevel(tiledViewer.levelNumToDisplay+1);
         }
         break;
      case OUT:
         if (tiledViewer.levelNumToDisplay != 0) {
            tiledViewer.displayNewLevel(tiledViewer.levelNumToDisplay-1);
         }
         break;
      default:
         break;
      }
   } // zoom
   
   private JPanel makeNavPanel() {
      Insets _insets;
      JPanel _retVal;
      JButton _upButton;
      JButton _downButton;
      JButton _leftButton;
      JButton _rightButton;
      JButton _centerButton;
      ClassLoader _cl;
     
      _retVal = new JPanel(new GridLayout(3, 3));
      _insets = new Insets(0,0,0,0);
      _cl = this.getClass().getClassLoader();
      _upButton = new JButton(new ImageIcon(_cl.getResource("images/up.gif")));
      _upButton.setMargin(_insets);
      _upButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
               processNavButton(e, MOVE_UP);
            }
         });
      _downButton = new JButton(
                    new ImageIcon(_cl.getResource("images/down.gif")));
      _downButton.setMargin(_insets);
      _downButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
               processNavButton(e, MOVE_DOWN);
            }
         });
      _leftButton = new JButton(
                    new ImageIcon(_cl.getResource("images/left.gif")));
      _leftButton.setMargin(_insets);
      _leftButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
               processNavButton(e, MOVE_LEFT);
            }
         });
      _rightButton = new JButton(
                     new ImageIcon(_cl.getResource("images/right.gif")));
      _rightButton.setMargin(_insets);
      _rightButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
               processNavButton(e, MOVE_RIGHT);
            }
         });
      _centerButton = new JButton(
                      new ImageIcon(_cl.getResource("images/center.gif")));
      _centerButton.setMargin(_insets);
      _centerButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
               processNavButton(e, MOVE_CENTER);
            }
         });
      _retVal.add(new JLabel(" "));
      _retVal.add(_upButton);
      _retVal.add(new JLabel(" "));
      _retVal.add(_leftButton);
      _retVal.add(_centerButton);
      _retVal.add(_rightButton);
      _retVal.add(new JLabel(" "));
      _retVal.add(_downButton);
      _retVal.add(new JLabel(" "));
      return _retVal;
   } // makeNavPanel
   
   private JPanel makeReductionPanel() {
      JPanel _retVal;
     
      _retVal = new JPanel();
      _retVal.setBorder(BorderFactory.createTitledBorder(
              LineBorder.createBlackLineBorder(),
              "Original size %", TitledBorder.CENTER, TitledBorder.TOP));
      percentFullsize = new JTextField(8);
      percentFullsize.setEditable(false);
      percentFullsize.setHorizontalAlignment(JTextField.CENTER);
      _retVal.add(percentFullsize);
      return _retVal;
   } // makeReductionPanel
   
   private JPanel makeZoomPanel() {
      JPanel _retVal;
      JButton _inButton;
      JButton _outButton;
      ClassLoader _cl;
     
      _cl = this.getClass().getClassLoader(); // new
      _retVal = new JPanel(new GridLayout(1,2));
      _inButton = new JButton(
                  new ImageIcon(_cl.getResource("images/zoomIn.gif")));
      _inButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
               zoom(IN);
            }
         });
      _outButton = new JButton(
                   new ImageIcon(_cl.getResource("images/zoomOut.gif")));
      _outButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
               zoom(OUT);
            }
         });
      _retVal.add(_outButton);
      _retVal.add(_inButton);
      return _retVal;
   } // makeZoomPanel
   
   private JPanel makeRawPanel() {
      JPanel _retVal;
     
      _retVal = new JPanel(new GridLayout(2, 1));
      _retVal.setBorder(BorderFactory.createTitledBorder(
              LineBorder.createBlackLineBorder(),
              "x,y in pixels", TitledBorder.CENTER, TitledBorder.TOP));
      rawXField = new JTextField(5);
      rawXField.setEditable(false);
      rawXField.setHorizontalAlignment(JTextField.CENTER);
      rawYField = new JTextField(5);
      rawYField.setEditable(false);
      rawYField.setHorizontalAlignment(JTextField.CENTER);
      _retVal.add(rawXField);
      _retVal.add(rawYField);
      return _retVal;
   } // makeRawPanel
   
   public void showPercentFullSize(double pScale) {
      String pStr;
     
      pStr = (100.0 * pScale) +"";
      if (pStr.length() > 7) {
         pStr = pStr.substring(0, 7);
      }
      percentFullsize.setText(pStr);
   }
   
   public void showX(int pX) {
      rawXField.setText("" + pX);
   } // showX
   
   public void showScaledX(double pX) {
      scaledXField.setText("" + pX);
   } // showScaledX
   
   public void showY(int pY) {
      rawYField.setText("" + pY);
   } // showY
   
   public void showScaledY(double pY) {
      scaledYField.setText("" + pY);
   } // showScaledY
   
   private JPanel makeScaledPanel() {
      JPanel _retVal;
     
      _retVal = new JPanel(new GridLayout(2, 1));
      _retVal.setBorder(BorderFactory.createTitledBorder(
              LineBorder.createBlackLineBorder(),
              "Scaled x,y", TitledBorder.CENTER, TitledBorder.TOP));
      scaledXField = new JTextField(5);
      scaledXField.setEditable(false);
      scaledXField.setHorizontalAlignment(JTextField.CENTER);
      scaledYField = new JTextField(5);
      scaledYField.setEditable(false);
      scaledYField.setHorizontalAlignment(JTextField.CENTER);
      _retVal.add(scaledXField);
      _retVal.add(scaledYField);
      return _retVal;
   } // makeScaledPanel
   

   /*
    * the public interface that enables changing an image.
    *
    * Pass-thru method so we don't have to expose the Locator class
    *
    * INPUT
    *   pImg: a buffered image whose contents are 0-0-0.jpg
    *   pImageW,
    *   pImageH: W & H of reduced image in pixels
    *   pUL:  viewport upper left corner in reduced image pixel coords
    *   pLR:  viewport lower right corner in reduced image pixel coords
    */
   public void changeImage(BufferedImage pImg, int pImageW, int pImageH,
                           Point pUL, Point pLR) {
      locatorPanel.changeImage(pImg, pImageW, pImageH, pUL, pLR);
   } // initImage
   
   
   public void drawViewport(Point pUL, Point pLR) {
      locatorPanel.drawViewport(pUL, pLR);
   } // drawViewport


   public void setNewZoom(Point pUL, Point pLR, int pImageWidthInPixels) {
      locatorPanel.setNewZoom(pUL, pLR, pImageWidthInPixels);
   }

   
   
   public static void main(String[] args) {
      TiledViewerPanel _tvp;
      JFrame _frame;
      String _url;
      int _initialZoom;

      _url = "http://brainmaps.org/HBP2/m.mulatta/RH10/RH10-highres/0804";
      _initialZoom = 0;
      _tvp = new TiledViewerPanel(_url, _initialZoom);
      _frame = new JFrame("Tiled Viewer Test");
      _frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      _frame.getContentPane().add(_tvp);
      _frame.setSize(new Dimension(640, 480));
      _frame.setVisible(true);
   } // main
   

   class Locator extends JPanel {

      private Image image; // scaled such that savedImage.getWidth() <= 128
      private BufferedImage savedImage;           // 0-0-0.jpg from zoomify
      private Rectangle vpRect;           // in reduced image coords!!
      // thumbnailScaleFactor is used to convert from reduced image ...
      // ... coords to the displayed thumbnail coords
      private double thumbnailScaleFactor;
      private int desiredH;            // height of the displayed thumbnail
      private int desiredW;             // width of the displayed thumbnail
      private int xOffset;
      private int yOffset;

     
      public Locator() {
         super();
         setMinimumSize(locPanelSize);
         setPreferredSize(locPanelSize);
         setMaximumSize(locPanelSize);
         vpRect = new Rectangle();
      } // no-arg constructor
     

      /*
       * used to change to a NEW image
       * INPUT
       *      pImage:    the smallest (0-0-0.jpg) image in the zoomify image
       *                 hierarchy, converted to a BufferedImage
       *      pReducedImageW,
       *      pReducedImageH: the dimensions of the reduced image in pixels
       *      pImgVpUL, pImgVpLRt: the viewport dimensions in reduced image
       *                 coords: The drawn rectangle will be clipped to the
       *                 dimensions of the scaled image
       */
      public void changeImage(BufferedImage pImage,  // small image: 0-0-0.jpg
                              int pReducedImageW,    // WxH for reduced image
                              int pReducedImageH,
                              Point pImgVpUL,//viewport UL: reduced image pixels
                              Point pImgVpLR) {//viewport LR:        "
         int _savedImageH;
         int _savedImageW;
                       
         savedImage = pImage;
         _savedImageH = savedImage.getHeight();
         _savedImageW = savedImage.getWidth();
         if ((_savedImageW > NAV_SIZE) && (_savedImageH > NAV_SIZE)) {
            if (_savedImageW > _savedImageH) {
               desiredW = NAV_SIZE;
               thumbnailScaleFactor = (double) NAV_SIZE
                                    / (double) pReducedImageW;
               desiredH = (int) (pReducedImageH * thumbnailScaleFactor);
            } else {
               desiredH = NAV_SIZE;
               thumbnailScaleFactor = (double) NAV_SIZE
                                    / (double) pReducedImageH;
               desiredW = (int) (pReducedImageW * thumbnailScaleFactor);
            }
         } else if (_savedImageW > NAV_SIZE) { // _savedImageH <= NAV_SIZE
            desiredW = NAV_SIZE;
            thumbnailScaleFactor = (double) NAV_SIZE
                                 / (double) pReducedImageW;
            desiredH = (int) ((double) pReducedImageH * thumbnailScaleFactor);
         } else if (_savedImageH > NAV_SIZE) { // _savedImageW <= NAV_SIZE
            desiredH = NAV_SIZE;
            thumbnailScaleFactor = (double) NAV_SIZE
                                 / (double) pReducedImageH;
            desiredW = (int) (pReducedImageW * thumbnailScaleFactor);
         } else {
            thumbnailScaleFactor = (double) _savedImageH
                                 / (double) pReducedImageH;
            desiredH = _savedImageH;
            desiredW = _savedImageW;
         }
         xOffset = (NAV_SIZE - desiredW) / 2;
         yOffset = (NAV_SIZE - desiredH) / 2;
         clipAndScaleVpRect(pImgVpUL, pImgVpLR);
         scaleImage();
         repaint();
      } // changeImage


      /*
       * used to change the zoom level, and hence the viewport rectangle,
       * on an existing image.
       *
       * INPUT
       *   pImgViewport[UL|LR]: upper left and lower right of viewport
       *                        in reduced image pixel coordinates
       *   pImageWidthInPixels: width of reduced image in pixels
       *
       */
      public void setNewZoom(Point pImgViewportUL, Point pImgViewportLR,
                             int pImageWidthInPixels) {
         thumbnailScaleFactor = ((double) desiredW)
                              / ((double) pImageWidthInPixels);
         clipAndScaleVpRect(pImgViewportUL, pImgViewportLR);
         repaint();
      } // setNewZoom


      /*
       * for drawing a viewport upon its moving or when the viewport is resized
       *
       * pUL, pLR: upper left and lower right coords of viewport in
       * reduced image coords
       *
       */
      public void drawViewport(Point pUL, Point pLR) {
         clipAndScaleVpRect(pUL, pLR);
         repaint();
      } // drawViewport


      private void clipAndScaleVpRect(Point pImgViewportUL,
                                    Point pImgViewportLR) {
         int _wOrH;

         if (pImgViewportUL.x < 0) {
            vpRect.x = xOffset;
         } else {
            vpRect.x = xOffset + (int) (pImgViewportUL.x
                                        * thumbnailScaleFactor);
         }
         if (pImgViewportUL.y < 0) {
            vpRect.y = yOffset;
         } else {
            vpRect.y = yOffset + (int) (pImgViewportUL.y
                                        * thumbnailScaleFactor);
         }
         // clip W or H  to fit
         _wOrH = (int)((pImgViewportLR.x - pImgViewportUL.x)
                       * thumbnailScaleFactor);
         vpRect.width = (_wOrH > desiredW) ? desiredW : _wOrH;
         if (vpRect.width <= 2) {
            vpRect.width = 2;
         } else {
            vpRect.width--;
         }
         _wOrH = (int) ((pImgViewportLR.y - pImgViewportUL.y)
                        * thumbnailScaleFactor);
         vpRect.height = (_wOrH > desiredH) ? desiredH : _wOrH;
         if (vpRect.height <= 2) {
            vpRect.height = 2;
         } else {
            vpRect.height--;
         }
      } // clipAndScaleVpRect


      private void scaleImage() {
         MediaTracker _tracker;
         
         _tracker = new MediaTracker(this);
         image = savedImage.getScaledInstance(desiredW, desiredH,
                                              Image.SCALE_SMOOTH);
         _tracker.addImage(image, 0); // assign id = 0 to this image
         try {
            _tracker.waitForID(0);
         } catch (InterruptedException e) {
            System.err.println("scaleImage(): image scaling interrupted!");
         }
      } // scaleImage
     

      public void paint(Graphics pG) {
         pG.clearRect(0, 0, NAV_SIZE, NAV_SIZE);
         pG.drawImage(image, xOffset, yOffset, null);
         pG.drawRect(vpRect.x, vpRect.y, vpRect.width, vpRect.height);
      } // paint

   } // class Locator

} // class TiledViewerPanel





--
Shawn Mikula, Ph.D.,
Postdoctoral Scholar
Center for Neuroscience
University of California-Davis,
1544 Newton Court,
Davis, CA 95618,
Phone: 530-754-9209
Fax: 530-754-9136
mail: [hidden email]
web: http://brainmaps.org