/* * @(#)/SliceStack.java 1.7 96/03/31 by Andrew Barclay abb@nuccard.eushc.org * * Copyright (c) 1995 Andrew B. Barclay All Rights Reserved. */ // I like to know what I'm importing for sanity checks. import java.applet.Applet ; import java.applet.AppletContext ; import java.awt.Color ; import java.awt.Dimension ; import java.awt.Event ; import java.awt.Graphics ; import java.awt.Image ; import java.awt.image.ImageObserver ; import java.awt.Rectangle ; import java.util.Enumeration ; import SlicerControl ; import SlicerDisplay ; import SlicerDisplayInfo ; import SlicerDisplayGraphic ; /** * An applet that displays stacks (one on top of another) * of orthogonal slices from volume images. It receives the * slice data and positioning info from a Volume that is owned * by a SlicerControl applet on the same page. Communication with * the SlicerControl is through methods of the SlicerDisplay * interface. If no SlicerControl appears, the applet will wait * indefinitely. * * @see SlicerDisplay * @see SlicerControl * @see Volume * * @version %I% %D% * @author Andrew Barclay */ public class SliceStack extends Applet implements ImageObserver, SlicerDisplay { // init parameters: String controlname ; int axis ; double thickness ; double zoom ; // state info: Thread me ; SlicerControl control ; SlicerDisplayInfo sdi ; Image curimage ; int imgw = -1, imgzw = -1 ; int imgh = -1, imgzh = -1 ; double xscale = 1.0 ; double yscale = 1.0 ; boolean selected = false ; Rectangle hashrects[] = new Rectangle[4] ; Color hashcolors[] = new Color[4] ; Color bordercolor = null ; // look and feel: static final long updateRate = 100 ; static final int xpad = 3 ; static final int ypad = 3 ; static final int hashheight = 8 ; static final int highlightwidth = 3 ; boolean debug = false ; public String getAppletInfo() { return "SliceStack by Andrew Barclay" ; } public String[][] getParameterInfo() { String[][] info = { {"control", "string", "name of SlicerControl applet"}, {"axis", "0|1|2", "X, Y, or Z axis slicing"}, {"slicethickness", "double", "mm"}, {"zoom", "double", "zoom factor"}, {"bgcolor", "int-base16", "background color, no # or 0x"}, } ; return info ; } public void init() { // clear all the sizes. imgw = imgzw = imgh = imgzh = -1 ; // bug: this has got to go String param = getParameter( "AXIS" ) ; axis = Integer.parseInt( param ) ; dbg( "init - axis="+axis ) ; param = getParameter( "SLICETHICKNESS" ) ; // bug: should check > 0.0 ? thickness = (param == null) ? 0.0 : parseDouble( param ) ; dbg( "init - thickness="+thickness ) ; param = getParameter( "ZOOM" ) ; zoom = (param == null) ? 1.0 : parseDouble( param ) ; dbg( "init - zoom="+zoom ) ; controlname = getParameter( "CONTROL" ) ; dbg( "init - controlname="+controlname ) ; // For the contest only -- the image should occupy the // the whole applet. // I'm taking the easy way out on this. (just assume hex int // with no leading "0x" or "#"). param = getParameter( "BGCOLOR" ) ; if( param != null ) { dbg( "init - color string="+param ) ; Color bgcolor = new Color( Integer.parseInt( param, 16 ) ) ; dbg( "init - color="+bgcolor ) ; setBackground( bgcolor ) ; } } // why is there no parseDouble?? private double parseDouble( String s ) { return (Double.valueOf(s)).doubleValue() ; } // Once the runnable is alive, start looking for a control. public void start() { // Find the named control on this page. getControl() ; // register this with the control. sdi = control.register( this, axis, thickness ) ; // bug: need to check for null sdi here, throw exception // initialize our state if( thickness == 0.0 ) { thickness = sdi.vsize[2] ; } // make slice display isotropic double minsize = (sdi.vsize[0] < sdi.vsize[1]) ? sdi.vsize[0] : sdi.vsize[1] ; xscale = zoom * sdi.vsize[0]/minsize ; yscale = zoom * sdi.vsize[1]/minsize ; setImage( control.getSlice( this, sdi.position ) ) ; } private synchronized void getControl() { // sit here and wait for the control to appear. while( (control = (SlicerControl)getApplet(controlname)) == null ) { try { showStatus( "SliceStack"+axis+" waiting for "+controlname+" to appear..." ) ; wait(1000); } catch (InterruptedException ex) { } } } // I get a NullPointerException every time from // netscape.applet.MozillaAppletContext.getApplet(String). // getApplets() works, so I'll roll my own private Applet getApplet( String name ) { AppletContext ac = getAppletContext() ; Enumeration en = ac.getApplets() ; while( en.hasMoreElements() ) { Applet ap = (Applet)en.nextElement() ; //dbg( "applet="+ap ) ; if( (ap instanceof SlicerControl) && name.equals(ap.getParameter("NAME")) ) { dbg( "getApplet: found a SlicerControl named "+name+"!" ) ; return ap ; } } //dbg( "getApplets() done." ) ; return null ; } // For the SlicerDisplay interface. public void stopInYourTracks() { // I want to remove my image consumer, or stop the // image load thread??? destroy() ; } // This isn't being called?? public Dimension preferredSize() { dbg( "preferredSize(), zw="+imgzw+", zh="+imgzh ) ; if( imgzw < 0 || imgzh < 0 ) { return null ; } else { return new Dimension( imgzw+xpad*2, imgzh+ypad*2 ) ; } } // @return currently displayed image. public synchronized void setImage(Image img) { curimage = img ; // By using updateRate here, we give the display a chance to // catch up with rapid changes. repaint(updateRate) ; showPosition() ; } // @return true if we know image size, else false. private synchronized boolean checkSize() { if( curimage == null ) { return false ; } if (imgzw < 0 || imgzh < 0 ) { // This should be only place that we check/set these. imgw = curimage.getWidth(this) ; imgh = curimage.getHeight(this) ; if (imgw < 0 || imgh < 0 ) { return false ; } else { imgzw = (int)( 0.5 + xscale * imgw ) ; imgzh = (int)( 0.5 + yscale * imgh ) ; // doesn't work on anything but Solaris?? /* resize( imgzw+2*xpad, imgzh+2*ypad ) ; layout() ; */ } } return true ; } void paintHashmarks( Graphics gr ) { for( int i = 0 ; i < 4 ; i++ ) { if( hashcolors[i] != null ) { gr.setColor( hashcolors[i] ) ; gr.fillRect( hashrects[i].x, hashrects[i].y, hashrects[i].width, hashrects[i].height ) ; } } } // For the SlicerDisplay interface. public void updateHashmarks( SlicerDisplayGraphic sdg ) { boolean dopaint = false ; bordercolor = sdg.bordercolor ; if (imgzw < 0 || imgzh < 0 ) { return ; } if( sdg.xhashcolor != null ) { int xpos = xpad + (int)(0.5+sdg.xhashpos*imgzw) ; int ypos ; int width = (int)(0.5+sdg.xhashwidth*imgzw) ; int height = (int)(0.5+sdg.xhashlength*imgzh) ; ypos = ypad ; // If the shape changes, make a new rectangle... if( hashrects[0] == null || width != hashrects[0].width || height != hashrects[0].height ) { hashrects[0] = new Rectangle( xpos, ypos, width, height ) ; dopaint = true ; // otherwise, just move the old one. } else if( xpos != hashrects[0].x || ypos != hashrects[0].y ) { hashrects[0].move( xpos, ypos ) ; dopaint = true ; } hashcolors[0] = sdg.xhashcolor ; ypos = ypad + imgzh - height ; // If the shape changes, make a new rectangle... if( hashrects[1] == null || width != hashrects[1].width || height != hashrects[1].height ) { hashrects[1] = new Rectangle( xpos, ypos, width, height ) ; dopaint = true ; // otherwise, just move the old one. } else if( xpos != hashrects[1].x || ypos != hashrects[1].y ) { hashrects[1].move( xpos, ypos ) ; dopaint = true ; } hashcolors[1] = sdg.xhashcolor ; } if( sdg.yhashcolor != null ) { int xpos ; int ypos = ypad + (int)(0.5+sdg.yhashpos*imgzh) ; int width = (int)(0.5+sdg.yhashlength*imgzw) ; int height = (int)(0.5+sdg.yhashwidth*imgzh) ; xpos = xpad ; // If the shape changes, make a new rectangle... if( hashrects[2] == null || width != hashrects[2].width || height != hashrects[2].height ) { hashrects[2] = new Rectangle( xpos, ypos, width, height ) ; dopaint = true ; // otherwise, just move the old one. } else if( xpos != hashrects[2].x || ypos != hashrects[2].y ) { hashrects[2].move( xpos, ypos ) ; dopaint = true ; } hashcolors[2] = sdg.yhashcolor ; xpos = xpad + imgzw - width ; // If the shape changes, make a new rectangle... if( hashrects[3] == null || width != hashrects[3].width || height != hashrects[3].height ) { hashrects[3] = new Rectangle( xpos, ypos, width, height ) ; dopaint = true ; // otherwise, just move the old one. } else if( xpos != hashrects[3].x || ypos != hashrects[3].y ) { hashrects[3].move( xpos, ypos ) ; dopaint = true ; } hashcolors[3] = sdg.yhashcolor ; } if( dopaint ) { /* * This should not use repaint. I need to make a method * to set cliprects and do updates in there * (damage/repair like IV). * * By using updateRate here, we give the display a chance * to catch up with rapid changes. */ repaint(updateRate) ; } else { // Don't need a full repaint (e.g. color change). paintHashmarks( getGraphics() ) ; paintBorder( getGraphics() ) ; } } void paintBorder() { paintBorder( getGraphics() ) ; } void paintBorder( Graphics gr ) { gr.setColor( bordercolor ) ; for( int xy = 1 ; xy <= highlightwidth ; xy++ ) { gr.drawRect( xy, xy, imgzw+2*xpad-2*xy,imgzh+2*ypad-2*xy ) ; } } public void paint( Graphics g ) { // Must have imgzw and imgzh set before drawing anything. if( checkSize() ) { // Draw the current image. if( xscale == 1.0 && yscale == 1.0 ) { g.drawImage( curimage, xpad, ypad, this ) ; } else { g.drawImage( curimage, xpad, ypad, imgzw, imgzh, this ) ; } paintHashmarks( g ) ; paintBorder( g ) ; } } public synchronized boolean imageUpdate( Image img, int infoflags, int x, int y, int w, int h) { if ( curimage == null || img != curimage ) { return false ; } boolean ret = true; boolean dopaint = false; long updatetime = 0; if ((infoflags & WIDTH) != 0) { dopaint = true ; dbg( "SliceStack"+axis+": Got width = " + w ) ; } if ((infoflags & HEIGHT) != 0) { dopaint = true ; dbg( "SliceStack"+axis+": Got height = " + h ) ; } if ((infoflags & (FRAMEBITS | ALLBITS)) != 0) { dopaint = true; ret = false; dbg( "SliceStack"+axis+": Finished" ) ; } else if ((infoflags & SOMEBITS) != 0) { dopaint = true; updatetime = updateRate; //dbg( "Got some bits, x=" + x + " y=" + y ) ; dbg( "SliceStack"+axis+": Loading..." ) ; } if ((infoflags & ERROR) != 0) { perror( "SliceStack"+axis+": Image load error" ) ; ret = false; } if (dopaint) { repaint(updatetime); } return ret; } // Handler for slice selection. public boolean keyDown( Event evt, int key ) { //dbg( "slice display keyDown" ) ; if( control == null || sdi == null || !selected ) { return false ; } switch( key ) { case Event.UP: case 'k': case ' ': setImage( control.getSlice( this, sdi.position-sdi.vsize[2] ) ) ; return true ; case Event.DOWN: case 'j': // BackSpace? case 8: setImage( control.getSlice( this, sdi.position+sdi.vsize[2] ) ) ; return true ; default: perror( "key press="+key ) ; return false ; } } public boolean mouseEnter( Event evt, int x, int y ) { //dbg( "slice display mouseEnter" ) ; requestFocus() ; synchronized( this ) { selected = true ; } if( control == null ) { return false ; } else { control.setSelected( this ) ; showPosition() ; return true ; } } public boolean mouseExit( Event evt, int x, int y ) { //dbg( "slice display mouseExit" ) ; nextFocus() ; synchronized( this ) { selected = false ; } return true ; } void showPosition() { if( sdi != null ) { showStatus( "SliceStack"+sdi.axis+" at "+sdi.position+" mm" ) ; } } void dbg( String s ) { if( debug ) { System.out.println( s ) ; showStatus( s ) ; } } void perror( String s ) { System.err.println( s ) ; showStatus( s ) ; } }