/* ImageStack.java is based on the animator applet that * comes with Java and was written by Herb Jellinek. * You can get the animator applet in the Java Development Kit from * Sun Microsystems. (http://www.javasoft.com) * * ImageStack.java was heavily edited by Paul Houle (ph18@cornell.edu) * to transform the animator into a widget that can be controlled by * JavaScript through Netscape's LiveConnect. * * How it works: see http://www.msc.cornell.edu/~houle/javascript/ImageStack.html * * The routine that is really interesting (that interfaces with Javascript * is called "changeImage". * * Feel free to use this source code as a starting point for your own * programs, just maintain the chain of attribution. */ package honeylocust; import java.io.InputStream; import java.awt.*; import java.awt.image.ImageProducer; import java.applet.Applet; import java.util.Vector; import java.util.Hashtable; import java.util.Enumeration; import java.io.File; import java.net.URL; import java.net.MalformedURLException; /** * An applet that plays a sequence of images, as a loop or a one-shot. * Can have a soundtrack and/or sound effects tied to individual frames. * * @author Herb Jellinek * @version 1.1, 3/4/96 */ public class ImageStack extends Applet implements Runnable { /** * The images, in display order (Images). */ Vector images = null; /** * Start-up image URL, if any. */ URL startUpImageURL = null; /** * Start-up image, if any. */ Image startUpImage = null; int startImage = 1; /** * Largest width. */ int maxWidth = 0; /** * Largest height. */ int maxHeight = 0; /** * Was there a problem loading the current image? */ boolean imageLoadError = false; /** * The directory or URL from which the images are loaded */ URL imageSource = null; /** * The thread animating the images. */ Thread engine = null; /** * The current loop slot - index into 'images.' */ int frameNum=0; /** * frameNum as an Object - suitable for use as a Hashtable key. */ Integer frameNumKey; /** * The current X position (for painting). */ int xPos = 0; /** * The current Y position (for painting). */ int yPos = 0; /** * Load all images before starting display, or do it asynchronously? */ boolean loadFirst; /** * The offscreen image, used in double buffering */ Image offScrImage; /** * The offscreen graphics context, used in double buffering */ Graphics offScrGC; /** * Can we paint yet? */ boolean loaded = false; /** * Was there an initialization error? */ boolean error = false; /** * What we call an image file in messages. */ final static String imageLabel = "image"; /** * Print silly debugging info? */ boolean debug_flag = false; /** * Awful hack to make sure first image loads correctly */ boolean firstpaint=false; /** * Info. */ public String getAppletInfo() { return "MultiWindow by Paul Houle (ph18@cornell.edu)"; } /** * Parameter Info */ public String[][] getParameterInfo() { String[][] info = { {"imagesource", "url", "a directory"}, {"startup", "url", "displayed at startup"}, {"startimage", "int", "start index"}, }; return info; } /** * I'm the method who JavaScript talks to. I tell MultiWindow to change it's * image to the n'th image. * * @param n image number to change to * */ public void changeImage(int n) { setFrameNum(n); repaint(); }; /** * * I am also designed to be talked to by JavaScript. I return the * number of pictures that I hold. * * @return number of pictures held */ public int numberImages() { return images.size(); }; /** * Print silly debugging info. */ void dbg(String s) { if (debug_flag) { System.out.println(s); } } final int setFrameNum(int newFrameNum) { frameNumKey = new Integer(frameNum = newFrameNum); return frameNum; } void updateMaxDims(Dimension dim) { maxWidth = Math.max(dim.width, maxWidth); maxHeight = Math.max(dim.height, maxHeight); } /** * Parse the IMAGES parameter. It looks like * 1|2|3|4|5, etc., where each number (item) names a source image. * * Returns a Vector of image file names. */ Vector parseImages(String attr) { Vector result = new Vector(10); for (int i = 0; i < attr.length(); ) { int next = attr.indexOf('|', i); if (next == -1) next = attr.length(); try { URL file = new URL(imageSource,attr.substring(i, next)); result.addElement(file); } catch (MalformedURLException mue) { result.addElement("Could not form URL"); }; i = next + 1; } return result; } /** * Fetch the images named in the argument, updating * maxWidth and maxHeight as we go. * Is restartable. * * @return URL of the first bogus file we hit, null if OK. */ URL fetchImages(Vector images) { dbg("fetchImages: loading "+images.size()+" images."); for (int i = 0; i < images.size(); i++) { Object o = images.elementAt(i); System.out.println("image #"+i+" is "+o); if (o instanceof URL) { URL url = (URL)o; tellLoadingMsg(url, imageLabel); dbg("Attempting to load image from URL: "+url); Image im = getImage(url); images.setElementAt(im,i); } } return null; } /** * Initialize the applet. Get parameters. */ public void init() { String param; try { if (getParameter("DEBUG") != null) { debug_flag=true; } else debug_flag=false; param = getParameter("IMAGESOURCE"); imageSource = (param == null) ? getDocumentBase() : new URL(getDocumentBase(), param + "/"); dbg("IMAGESOURCE = "+param); param = getParameter("STARTIMAGE"); dbg("STARTIMAGE = "+param); if (param != null) { startImage = Integer.parseInt(param); frameNum=startImage; } param = getParameter("IMAGES"); dbg("IMAGES = " +param); if (param == null) { showStatus("No legal IMAGES, STARTIMAGE, or ENDIMAGE "+ "specified."); return; } else { dbg("Parsing images."); images = parseImages(param); dbg("I found "+images.size()+ " images"); }; param = getParameter("STARTUP"); dbg("STARTUP = "+param); if (param != null) { startUpImageURL = new URL(imageSource, param); } } catch(Exception e) {}; } void tellLoadingMsg(String file, String fileType) { showStatus("MultiWindow: loading "+fileType+" "+abridge(file, 20)); } void tellLoadingMsg(URL url, String fileType) { tellLoadingMsg(url.toExternalForm(), fileType); } void clearLoadingMessage() { showStatus(""); } /** * Cut the string down to length=len, while still keeping it readable. */ static String abridge(String s, int len) { String ellipsis = "..."; if (len >= s.length()) { return s; } int trim = len - ellipsis.length(); return s.substring(0, trim / 2)+ellipsis+ s.substring(s.length() - trim / 2); } void loadError(URL badURL, String fileType) { String errorMsg = "Animator: Couldn't load "+fileType+" "+ badURL.toExternalForm(); showStatus(errorMsg); System.err.println(errorMsg); error = true; repaint(); } void showParseError(Exception e) { String errorMsg = "Animator: Parse error: "+e; showStatus(errorMsg); System.err.println(errorMsg); error = true; repaint(); } /** * Run the animation. This method is called by class Thread. * @see java.lang.Thread */ synchronized public void run() { Thread me = Thread.currentThread(); me.setPriority(Thread.MIN_PRIORITY); updateMaxDims(size()); if (! loaded) { try { // ... to do a bunch of loading. if (startUpImageURL != null) { tellLoadingMsg(startUpImageURL, imageLabel); startUpImage = getImage(startUpImageURL); // resize(maxWidth, maxHeight); // repaint(); } URL badURL = fetchImages(images); if (badURL != null) { loadError(badURL, imageLabel); return; } clearLoadingMessage(); dbg("attempted to create image size: ("+maxWidth+","+maxHeight+")"); offScrImage = createImage(maxWidth, maxHeight); offScrGC = offScrImage.getGraphics(); offScrGC.setColor(Color.lightGray); resize(maxWidth, maxHeight); loaded = true; error = false; } catch (Exception e) { error = true; e.printStackTrace(); } /* get the start image loaded */ prepareImage((Image) images.elementAt(startImage),this); while(!firstpaint) try {wait();} catch(Exception e) {}; /* and then load the rest */ for(int i=0;i 0)) { dbg("Images loaded"); if (frameNum < images.size()) { offScrGC.fillRect(0, 0, maxWidth, maxHeight); dbg("Attempting to draw image # "+frameNum); Image image = (Image)images.elementAt(frameNum); offScrGC.drawImage(image, xPos, yPos, this); g.drawImage(offScrImage, 0, 0, this); } else { // no more animation, but need to draw something dbg("No more animation; drawing last image."); g.drawImage((Image)images.lastElement(), 0, 0, this); } } } } synchronized public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) { dbg("imageUpdate called!"); notifyAll(); if ((infoflags & ALLBITS) != 0) { dbg("Image ready!"); if(img == images.elementAt(frameNum)) { repaint(); firstpaint=true; }; return false; } else { dbg("Image not ready yet!"); return true; }; } /** * Start the applet by forking the loader thread */ public void start() { if (engine == null) { engine = new Thread(this); engine.start(); } } /** * Stop the insanity, um, applet. */ public void stop() { if (engine != null && engine.isAlive()) { engine.stop(); } engine = null; } };