// // WaveletGen.java // // Simple 2D Wavelet Transform Generator // // Copyright (c) 1997, Benjamin Nason Lipchak // import java.applet.Applet; import java.net.*; import java.awt.*; import java.awt.image.*; import benj.awt.image.*; public class WaveletGen extends Applet implements Runnable { TextField imageURLField; Label imageURLLabel; Label errorMessage; ImageCanvas origCanvas; Image origImage; ImageCanvas waveletCanvas; Image waveletImage; String inputURL; Image animation[]; AnimationCanvas animCanvas; Thread workThread; boolean processNewImage = false; Object processNewImageSemaphore; public void init() { processNewImageSemaphore = new Object(); imageURLField = new TextField("http://"); imageURLLabel = new Label("image URL: ", Label.RIGHT); errorMessage = new Label("", Label.LEFT); origCanvas = new ImageCanvas(this, 256, 256); waveletCanvas = new ImageCanvas(this, 256, 256); animCanvas = new AnimationCanvas(this, 256, 256); GridBagLayout myGridBag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); setLayout(myGridBag); c.fill = GridBagConstraints.HORIZONTAL; c.insets = new Insets(10, 10, 0, 0); c.gridx = 0; c.gridwidth = 1; myGridBag.setConstraints(imageURLLabel, c); add(imageURLLabel); c.insets = new Insets(10, 0, 0, 10); c.gridx = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.weightx = 1.0; myGridBag.setConstraints(imageURLField, c); add(imageURLField); c.insets = new Insets(0, 0, 5, 10); myGridBag.setConstraints(errorMessage, c); add(errorMessage); c.gridwidth = 2; c.gridx = 0; c.insets = new Insets(5, 10, 5, 5); myGridBag.setConstraints(origCanvas, c); add(origCanvas); c.gridx = 2; c.insets = new Insets(5, 5, 5, 10); myGridBag.setConstraints(waveletCanvas, c); add(waveletCanvas); c.gridwidth = GridBagConstraints.REMAINDER; c.gridx = 1; c.insets = new Insets(5, 50, 10, 50); myGridBag.setConstraints(animCanvas, c); add(animCanvas); validate(); } public void start() { if (workThread == null) { workThread = new Thread(this, "Work Thread"); workThread.start(); } } public void run() { while (workThread != null) { synchronized (processNewImageSemaphore) { while (processNewImage == false) { try { processNewImageSemaphore.wait(); } catch (InterruptedException e) { } } } origCanvas.setImage(null); waveletCanvas.setImage(null); //Track image loading. Indicate failure. Image sourceImage; try { sourceImage = getImage(new URL(inputURL)); } catch (MalformedURLException e) { errorMessage.setText("ERROR: Bad URL!"); synchronized (processNewImageSemaphore) { processNewImage = false; processNewImageSemaphore.notify(); } continue; } MediaTracker tracker = new MediaTracker(this); tracker.addImage(sourceImage, 0); boolean doneWaiting = false; while (!doneWaiting) { try { tracker.waitForAll(); doneWaiting = true; } catch (InterruptedException e) { continue; } } if (tracker.isErrorAny()) { errorMessage.setText("ERROR: Unable to load image!"); synchronized (processNewImageSemaphore) { processNewImage = false; processNewImageSemaphore.notify(); } continue; } //Crop if necessary int imageWidth = sourceImage.getWidth(this); int imageHeight = sourceImage.getHeight(this); if (imageWidth <= 0 || imageHeight <= 0) { errorMessage.setText("ERROR: Bad image!"); synchronized (processNewImageSemaphore) { processNewImage = false; processNewImageSemaphore.notify(); } continue; } int newWidth = nextLowerPowerOfTwo(imageWidth); int newHeight = nextLowerPowerOfTwo(imageHeight); if (newWidth > newHeight) newWidth = newHeight; else if (newHeight > newWidth) newHeight = newWidth; if (imageWidth != newWidth || imageHeight != newHeight) { errorMessage.setText("Cropping original image..."); ImageFilter filter = new CropImageFilter(0, 0, newWidth, newHeight); ImageProducer producer = new FilteredImageSource(sourceImage.getSource(), filter); origImage = createImage(producer); tracker.addImage(origImage, 0); doneWaiting = false; while (!doneWaiting) { try { tracker.waitForAll(); doneWaiting = true; } catch (InterruptedException e) { continue; } } if (tracker.isErrorAny()) { errorMessage.setText("ERROR: Unable to crop image!"); synchronized (processNewImageSemaphore) { processNewImage = false; processNewImageSemaphore.notify(); } continue; } } else { origImage = sourceImage; } origCanvas.setImage(origImage); errorMessage.setText("Generating wavelet transform..."); ImageFilter filter = new WaveletTransformFilter(); ImageProducer producer = new FilteredImageSource(origImage.getSource(), filter); waveletImage = createImage(producer); tracker.addImage(waveletImage, 0); doneWaiting = false; while (!doneWaiting) { try { tracker.waitForAll(); doneWaiting = true; } catch (InterruptedException e) { continue; } } if (tracker.isErrorAny()) { errorMessage.setText("ERROR: Unable to transform image!"); synchronized (processNewImageSemaphore) { processNewImage = false; processNewImageSemaphore.notify(); } continue; } waveletCanvas.setImage(waveletImage); //Generate frames for animation errorMessage.setText("Generating wavelet subimages..."); int n = logBase2(newWidth); animation = new Image[n + 1]; for (int j = 0; j <= n; j++) { filter = new WaveletTransformFilter(j); producer = new FilteredImageSource(origImage.getSource(), filter); animation[j] = createImage(producer); tracker.addImage(animation[j], 0); } doneWaiting = false; while (!doneWaiting) { try { tracker.waitForAll(); doneWaiting = true; } catch (InterruptedException e) { continue; } } if (tracker.isErrorAny()) { errorMessage.setText("ERROR: Unable to generate animation!"); synchronized (processNewImageSemaphore) { processNewImage = false; processNewImageSemaphore.notify(); } continue; } animCanvas.setAnim(animation, n + 1); //Ready for another synchronized (processNewImageSemaphore) { processNewImage = false; processNewImageSemaphore.notify(); } errorMessage.setText("Done."); } } public boolean action(Event event, Object what) { if (event.target == imageURLField) { synchronized (processNewImageSemaphore) { if (processNewImage) return true; } errorMessage.setText("Loading image..."); inputURL = imageURLField.getText(); synchronized (processNewImageSemaphore) { processNewImage = true; processNewImageSemaphore.notify(); } return true; } return false; } int pow2(int degree) { if (degree < 0) return -1; int result = 1; while (degree > 0) { result *= 2; degree--; } return result; } int logBase2(int number) { int result = 0; while (pow2(result) != number) { result++; if (result >= number) return -1; } return result; } int nextLowerPowerOfTwo(int i) { int a = 1, b = 2; if (i < 1) return 0; while (i > b) { a += a; b += b; } return a; } } class ImageCanvas extends Canvas { Container pappy; Image image = null; Dimension minSize; int w, h; public ImageCanvas(Container parent, int initialWidth, int initialHeight) { pappy = parent; w = initialWidth; h = initialHeight; minSize = new Dimension(w, h); } public synchronized void setImage(Image image) { this.image = image; repaint(); } public Dimension preferredSize() { return minimumSize(); } public Dimension minimumSize() { return minSize; } public void paint(Graphics g) { update(g); } public void update(Graphics g) { if (image == null) { g.setColor(Color.black); g.drawRect(0, 0, w - 1, h - 1); } else { g.drawImage(image, 0, 0, w, h, this); } } } class AnimationCanvas extends Canvas implements Runnable { Container pappy; Image[] animation = null; int numImages; Dimension minSize; int w, h; int frameNumber = 0; int delay = 200; Thread animatorThread; Dimension offDimension; Image offImage; Graphics offGraphics; public AnimationCanvas(Container parent, int initialWidth, int initialHeight) { pappy = parent; w = initialWidth; h = initialHeight; minSize = new Dimension(w, h); animatorThread = new Thread(this); animatorThread.start(); } public synchronized void setAnim(Image[] animation, int n) { this.animation = animation; numImages = n; } public void run() { Thread.currentThread().setPriority(Thread.MIN_PRIORITY); long startTime = System.currentTimeMillis(); boolean goingUp = true; while (Thread.currentThread() == animatorThread) { if (animation != null) { if (goingUp) { frameNumber++; if (frameNumber >= (numImages - 1)) { goingUp = false; frameNumber = numImages - 1; } } else { frameNumber--; if (frameNumber <= 0) { goingUp = true; frameNumber = 0; } } repaint(); } try { startTime += delay; Thread.sleep(Math.max(0, startTime - System.currentTimeMillis())); } catch (InterruptedException e) { break; } } } public Dimension preferredSize() { return minimumSize(); } public Dimension minimumSize() { return minSize; } public void paint(Graphics g) { update(g); } public void update(Graphics g) { if (animation == null) { g.setColor(Color.black); g.drawRect(0, 0, w - 1, h - 1); } else { Dimension d = size(); if ((offGraphics == null) || (d.width != offDimension.width) || (d.height != offDimension.height)) { offDimension = d; offImage = createImage(d.width, d.height); offGraphics = offImage.getGraphics(); } offGraphics.setColor(getBackground()); offGraphics.fillRect(0, 0, d.width, d.height); offGraphics.setColor(Color.black); offGraphics.drawImage(animation[frameNumber], 0, 0, w, h, this); g.drawImage(offImage, 0, 0, d.width, d.height, this); } } }