|
Up
Design
About
<applet code="Pool.class" name="pool" mayscript> <param name="totalimages" value="3"> <param name="image0" value="a.jpg"> <param name="image1" value="b.jpg"> <param name="image2" value="c.jpg"> <param name="mask" value="poolmask.gif"> <param name="refresh" value="12"> </applet>
import java.applet.*; import java.awt.*; import java.awt.event.*; import java.awt.image.PixelGrabber; import java.awt.image.ImageObserver; import java.awt.image.MemoryImageSource; import netscape.javascript.*; public class Pool extends Applet implements MouseListener, MouseMotionListener, Runnable { private JSObject window; // allows the applet to call JavaScript private Thread runner = null; // animation thread private MemoryImageSource source = null;// animated image source private Image backBuffer = null; // animated image private Image[] image = null; // array of various images private Image waterMask = null; // black and white mask private Graphics backGraphics = null; // offscreen graphics object private int[][][] heightMap; // water surface (2 pages) private int[][] pixels; // image pixels (2 images) private int[] screen; // working area private int[][] mask; // circular mask (constrains water effect) private int[] line; // precalculated line offsets private int wScreen, hScreen, space; private int radius, height, density; private int refresh; private int page = 0; private int totalImages = 0; private int currentImage = 0; public Pool() { radius = 4; height = 200; density = 4; } public void init() { // get a reference to the JavaScript window window = JSObject.getWindow(this); wScreen = getSize().width; hScreen = getSize().height; // allocate the arrays String str = getParameter("totalImages"); if (str != null) totalImages = Integer.parseInt(str); image = new Image[totalImages]; pixels = new int[totalImages][wScreen * hScreen]; screen = new int[wScreen * hScreen]; heightMap = new int[2][wScreen][hScreen]; line = new int[hScreen]; // initialise the images, and set up the offscreen buffer MediaTracker tracker = new MediaTracker(this); try { // read in the supplied images for (int i = 0; i < totalImages; i++) image[i] = getImage(getDocumentBase(), getParameter("image" + i, "")); // read in the water mask waterMask = getImage(getDocumentBase(), getParameter("mask", "")); // wait until all the image data has been read for (int i = 0; i < totalImages; i++) tracker.addImage(image[i], 0); tracker.addImage(waterMask, 1); tracker.waitForAll(); // create the water mask if (waterMask != null) createMaskFromImage(); else createMask(); // copy the image data into the pixel arrays for (int i = 0; i < totalImages; i++) { PixelGrabber pg = new PixelGrabber(image[i], 0, 0, wScreen, hScreen, pixels[i], 0, wScreen); pg.grabPixels(); } // initialise the working area with pixels[0] System.arraycopy(pixels[0], 0, screen, 0, wScreen*hScreen); source = new MemoryImageSource(wScreen, hScreen, screen, 0, wScreen); source.setAnimated(true); backBuffer = createImage(source); // use image[0] as an offscreen image buffer image[0] = createImage(wScreen, hScreen); backGraphics = image[0].getGraphics(); } catch (Exception e) { e.printStackTrace(); } addMouseMotionListener(this); addMouseListener(this); // store the offsets for every line for (int y = 0; y < hScreen; y++) line[y] = y * wScreen; space = wScreen * hScreen - 1; // get the refresh rate (in milliseconds) try { refresh = Integer.parseInt(getParameter("refresh")); } catch (Exception e) { refresh = 5; } } /** * Create a centered, circular water mask */ private void createMask() { // get the desired mask radius int maskRadius = 0; String str = getParameter("radius"); if (str == null) maskRadius = Math.min(wScreen, hScreen) / 2; else maskRadius = Integer.parseInt(str); // generate the mask array mask = new int[wScreen][hScreen]; for (int y = 0; y < hScreen; y++) { for (int x = 0; x < wScreen; x++) { // - find the image center int centerX = wScreen/2; int centerY = hScreen/2; // - convert x and y to cartesian coordinates int cX = x - centerX; int cY = centerY - y; // - find the radius of the point int r = (int)Math.sqrt(cX*cX + cY*cY); // - mark up the map if (r < maskRadius) mask[x][y] = 1; else mask[x][y] = 0; } } } /** * Create a water mask based on the supplied image */ private void createMaskFromImage() { mask = new int[wScreen][hScreen]; int[] maskpixels = new int[wScreen * hScreen]; // populate the mask array try { PixelGrabber pg = new PixelGrabber(waterMask, 0, 0, wScreen, hScreen, maskpixels, 0, wScreen); pg.grabPixels(); // copy the image pixels into the mask array for (int y = 0; y < hScreen; y++) { int offset = y * wScreen; for (int x = 0; x < wScreen; x++) { // int alpha = (maskpixels[offset + x] >> 24) & 0xff; int red = (maskpixels[offset + x] >> 16) & 0xff; int green = (maskpixels[offset + x] >> 8) & 0xff; int blue = (maskpixels[offset + x] ) & 0xff; // black maps to 1 and white maps to 0 if (red == 255 && green == 255 && blue == 255) mask[x][y] = 0; else mask[x][y] = 1; } } } catch (Exception e) { e.printStackTrace(); } } public String getParameter(String key, String def) { return (getParameter(key) != null ? getParameter(key) : def); } /** * Raise the heightmap at position (cx, cy) */ public void makeTurbulence(int cx, int cy) { // keep turbulence within the circle mask if (mask[cx][cy] == 0) return; // precalculate the square of the radius // (note: radius = "drop" radius) int r2 = radius * radius; // find the bounds of the drop int left = cx < radius ? -cx : -radius; int right = cx > wScreen - radius - 2 ? wScreen - cx - 2 : radius; int top = cy < radius ? -cy : -radius ; int bottom = cy > hScreen - radius - 2 ? hScreen - cy - 2 : radius; // raise the height map at the drop area for (int x = left; x < right; x++) { int x2 = x*x; for (int y = top; y < bottom; y++) { int squarex = (x - cx) * (x - cx); int squarey = (y - cy) * (y - cy); double dist = Math.sqrt(squarex + squarey); if ((x2 + (y * y)) < r2) heightMap[page^1][cx + x][cy + y] += height; } } } public void mouseMoved(MouseEvent me) { makeTurbulence(me.getX(), me.getY()); } public void mousePressed(MouseEvent me) { // calls the Javascript function "pressButton()" String[] args = {""}; window.call("pressButton", args); } public void mouseReleased(MouseEvent me) { // calls the Javascript function "releaseButton()" String[] args = {""}; window.call("releaseButton", args); } /** * Drip a drop into the center of the pool */ public void drop() { // called by the Javascript function "makeDrop()" makeTurbulence(wScreen/2, hScreen/2); } /** * Rotate the pool image */ public void nextPoolImage() { // called by the Javascript function "nextPoolImage()" currentImage++; currentImage %= totalImages; setImage(currentImage); } /** * Change the current pool image */ public void setPoolImage(int imageNum) { // called by the Javascript function "setPoolImage()" currentImage = imageNum; setImage(imageNum); } private void setImage(int image) { // copy the image's pixels into the screen array System.arraycopy(pixels[image], 0, screen, 0, wScreen * hScreen); } public void mouseClicked(MouseEvent me) {} public void mouseExited(MouseEvent me) {} public void mouseEntered(MouseEvent me) {} public void mouseDragged(MouseEvent me) {} public void paint(Graphics g) { // paint the offscreen image to the screen if (backBuffer != null) g.drawImage(image[0], 0, 0, null); } public void update(Graphics g) { // refresh the image source source.newPixels(0, 0, wScreen, hScreen); // and paint it onto the offscreen image (image[0]) backGraphics.drawImage(backBuffer, 0, 0, null); paint(g); } public void start() { // start up an animation thread if (runner == null) { runner = new Thread(this, "Pool"); runner.setPriority(Thread.MIN_PRIORITY); runner.start(); } } public void stop() { // stop the animation thread runner = null; } public void run() { while (runner != null) { waterCalc(); // update the heightmap waterPaint(); // paint the working area repaint(); page ^= 1; // switch the heightmap page // take a quick nap try { Thread.currentThread().sleep(refresh); } catch(InterruptedException e) { e.printStackTrace(); } } } /** * Update the heightmap with the latest ripples */ private void waterCalc() { for (int x = 0; x < wScreen; x++) { for (int y = 0; y < hScreen; y++) { // do the water filter int n = y-1 < 0 ? 0 : y-1; int s = y+1 > (hScreen)-1 ? (hScreen)-1 : y+1; int e = x+1 > (wScreen)-1 ? (wScreen)-1 : x+1; int w = x-1 < 0 ? 0 : x-1; int value = ((heightMap[page][w][n] + heightMap[page][x][n] + heightMap[page][e][n] + heightMap[page][w][y] + heightMap[page][e][y] + heightMap[page][w][s] + heightMap[page][x][s] + heightMap[page][e][s]) >> 2) - heightMap[page^1][x][y]; heightMap[page^1][x][y] = value - (value >> density); } } } /** * Update the screen with the latest ripples */ private void waterPaint() { for (int y = 0; y < hScreen - 1; y++) { for (int x = 0; x < wScreen - 1; x++) { // don't paint pixels outside the circle mask if (mask[x][y] == 0) continue; // use deltas to distort the underlying image int deltax = heightMap[page][x][y] - heightMap[page][(x)+1][y]; int deltay = heightMap[page][x][y] - heightMap[page][x][(y)+1]; int offsetx = (deltax>>4) + x; int offsety = (deltay>>4) + y; int offset = (offsety*wScreen) + offsetx; offset = offset < 0 ? 0 : offset > space ? space : offset; // catch the light int pixel = pixels[currentImage][offset]; int red = (pixel >> 16) & 0xff; int green = (pixel >> 8) & 0xff; int blue = (pixel ) & 0xff; red += deltax>>1; green += deltax>>1; blue += deltax>>1; red = red > 255 ? 255 : red < 0 ? 0 : red; green = green > 255 ? 255 : green < 0 ? 0 : green; blue = blue > 255 ? 255 : blue < 0 ? 0 : blue; // update the image source screen[line[y]+x] = 0xff000000 | (red << 16) | (green << 8) | blue; } } } }