// Phoon - show the current PHase of the mOON // // Shows the moon in its current phase, including the partial lighting of // the dark side by reflected earthlight. // // A Java version of xphoon, which is itself an X version of phoon. // // Copyright (C)1996,1998 by Jef Poskanzer . All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. import java.applet.*; import java.awt.*; import java.awt.image.*; import java.util.*; import java.net.*; public class Phoon extends Applet implements Runnable { // Applet info. public String getAppletInfo() { String val = getClass().getName() + " - Copyright (C) 1996 by Jef Poskanzer . All rights reserved."; return val; } // We have pre-scaled images in various sizes available on the server. // We could instead fetch a large image and scale it down to size on // the client side, but that would waste bandwidth. // Number of different base image sizes. private static final int nSizes = 5; // Data for the different base image sizes. private static final String[] baseNames = { "Phoon400.jpg", "Phoon300.jpg", "Phoon200.jpg", "Phoon100.jpg", "Phoon50.jpg" }; private static final int[] baseWidths = { 400, 300, 200, 100, 50 }; private static final int[] baseHeights = { 400, 300, 200, 100, 50 }; private static final int[] baseCxs = { 199, 149, 99, 48, 24 }; private static final int[] baseCys = { 199, 149, 99, 49, 24 }; private static final int[] baseRadiuses = { 188, 141, 94, 47, 23 }; private static final long delayMinutes = 15; protected static final boolean testMode = false; // These are static so that they stick around when the user leaves // the page and comes back, and also so multiple applets can share them. // But there's still (potentially) one for each different base image size. private static Image[] baseImgs = new Image[nSizes]; private static PhoonFilter[] phoonFilters = new PhoonFilter[nSizes]; private static Image[] hackedImgs = new Image[nSizes]; // Keep track of the last known status flags, so we know whether the // load completed or not. private static int[] hackedImgsLastFlags = new int[nSizes]; // Random number seed for generating stars. private static long[] starSeeds = new long[nSizes]; // Static initializer! Very cool. static { for ( int i = 0; i < nSizes; ++i ) { baseImgs[i] = null; phoonFilters[i] = null; hackedImgs[i] = null; hackedImgsLastFlags[i] = 0; } } // And here are the variables for each applet instance. private int appletWidth, appletHeight; private int whichSize = -1; private String baseName = null; private int baseWidth; private int baseHeight; private int baseCx; private int baseCy; private int baseRadius; private long testTime = System.currentTimeMillis(); // Called when the applet is first created. public synchronized void init() { setBackground( Color.black ); // Figure out which size to use - first one we can fit. Dimension d = size(); // Dimension d = getSize(); appletWidth = d.width; appletHeight = d.height; for ( whichSize = 0; whichSize < nSizes; ++whichSize ) { if ( baseWidths[whichSize] <= appletWidth && baseHeights[whichSize] <= appletHeight ) { baseName = baseNames[whichSize]; baseWidth = baseWidths[whichSize]; baseHeight = baseHeights[whichSize]; baseCx = baseCxs[whichSize]; baseCy = baseCys[whichSize]; baseRadius = baseRadiuses[whichSize]; break; } } if ( whichSize >= nSizes ) { System.err.println( "Can't find an image size that will fit into " + appletWidth + "x" + appletHeight ); whichSize = -1; } } private Thread thread = null; private static int threadNum = 0; // Called when the applet should start itself. public synchronized void start() { // If we don't have a base image in the desired size, start that // loading. if ( baseName != null && baseImgs[whichSize] == null ) { URL context = getCodeBase(); try { baseImgs[whichSize] = getImage( new URL( context, baseName ) ); } catch ( Exception e ) { System.err.println( e ); } } if ( thread == null ) { // Start the thread. ++threadNum; thread = new Thread( this, getClass().getName() + "Thread-" + threadNum ); thread.start(); } } // Called when the applet should stop itself. public synchronized void stop() { if ( thread != null ) { // Stop the thread. if ( thread.isAlive() ) thread.stop(); thread = null; } // If we didn't get a complete load, force a complete reload next // time we start(). if ( hackedImgs[whichSize] != null && ( hackedImgsLastFlags[whichSize] & ALLBITS ) == 0 ) { baseImgs[whichSize].flush(); hackedImgs[whichSize].flush(); } } // This is the part of Runnable that we implement - the routine that // gets called when the thread is started. public void run() { Thread me = Thread.currentThread(); me.setPriority( Thread.MIN_PRIORITY ); if ( whichSize != -1 && baseImgs[whichSize] != null ) { for (;;) { // Sleep a bit. try { if ( testMode ) Thread.sleep( 30L * 1000L ); else Thread.sleep( delayMinutes * 60L * 1000L ); } catch ( InterruptedException e ) {} if ( thread != me ) break; if ( phoonFilters[whichSize] != null && hackedImgs[whichSize] != null ) { if ( ( hackedImgsLastFlags[whichSize] & ALLBITS ) != 0 ) { if ( testMode ) { testTime += 86400000L; phoonFilters[whichSize].setDate( new Date( testTime ), hackedImgs[whichSize] ); } else { phoonFilters[whichSize].setDate( new Date(), hackedImgs[whichSize] ); } repaint(); } } } } } // The default update() clears the background and then calls paint(). // This one doesn't clear the background. public void update( Graphics graphics ) { paint( graphics ); } // Called when the applet should paint itself. public synchronized void paint( Graphics graphics ) { if ( whichSize == -1 || baseImgs[whichSize] == null ) return; if ( phoonFilters[whichSize] == null || hackedImgs[whichSize] == null ) { phoonFilters[whichSize] = new PhoonFilter( baseImgs[whichSize].getSource(), new Date(), baseWidth, baseHeight, baseCx, baseCy, baseRadius, starSeeds[whichSize] ); hackedImgs[whichSize] = Acme.JPM.JPMUtils.filterImage( this, phoonFilters[whichSize] ); starSeeds[whichSize] = (new Random()).nextLong(); } graphics.drawImage( hackedImgs[whichSize], ( appletWidth - baseWidth ) / 2, ( appletHeight - baseHeight ) / 2, this ); } // Override the default imageUpdate() to keep track of the last known // status flags. public synchronized boolean imageUpdate( Image img, int infoflags, int x, int y, int width, int height ) { // Save infoflags. if ( img == hackedImgs[whichSize] ) hackedImgsLastFlags[whichSize] = infoflags; boolean val; if ( ( infoflags & ERROR ) != 0 ) { showStatus( "Error loading an image." ); stop(); val = false; } else { // Call default imageUpdate. val = super.imageUpdate( img, infoflags, x, y, width, height ); } return val; } // Main program, so we can run as an application too. public static void main( String[] args ) { new Acme.MainFrame( new Phoon(), args, 400, 400 ); } } // An ImageFilter to convert the raw moon picture into one with the // correct phase. class PhoonFilter extends Acme.JPM.Filters.RGBBlockFilter { private static final boolean testMode = Phoon.testMode; public PhoonFilter( ImageProducer producer, Date t, int width, int height, int cx, int cy, int radius, long starSeed ) { super( producer ); setDate( t ); this.width = width; this.height = height; this.cx = cx; this.cy = cy; this.radius = radius; this.starSeed = starSeed; } private Date t; private int width; private int height; private int cx; private int cy; private int radius; private long starSeed; private double angphase; private double cap; private double cphase; private int shademult; public void setDate( Date t ) { this.t = t; double jd = Acme.Phase.jtime( this.t ); Acme.RefDouble cphaseR = new Acme.RefDouble(); Acme.RefDouble aomR = new Acme.RefDouble(); Acme.RefDouble cdistR = new Acme.RefDouble(); Acme.RefDouble cangdiaR = new Acme.RefDouble(); Acme.RefDouble csundR = new Acme.RefDouble(); Acme.RefDouble csuangR = new Acme.RefDouble(); angphase = Acme.Phase.phase( jd, cphaseR, aomR, cdistR, cangdiaR, csundR, csuangR ); cap = Math.cos( angphase ); cphase = cphaseR.val; // Hack to figure approximate earthlighting. if ( cphase < 0.1D ) cphase = 0.1D; if ( cphase > 0.9D ) cphase = 0.9D; // Ratio varies from 0.111 to 9.0 double ratio = ( 1.0D - cphase ) / cphase; // shademult varies from 0 to 63 shademult = (int) ( ratio / 9.0D * 63.999D ); } public void setDate( Date t, Image img ) { // Set the date. setDate( t ); // And flush any previously computed image. img.flush(); } public int[][] filterRGBBlock( int x, int y, int width, int height, int[][] rgbPixels ) { for ( int row = 0; row < height; ++row ) { int ty = y + row; int my = ty - cy; if ( Math.abs( my ) <= radius ) { double mbxright = radius * Math.sqrt( 1.0D - (double) (my * my) / (double) (radius * radius) ); double mbxleft = - mbxright; double msxright = mbxright; double msxleft = mbxleft; if ( angphase >= 0.0D && angphase < Math.PI ) msxright *= cap; else msxleft *= cap; int bxleft = (int) ( mbxleft + cx + 0.5D ); int bxright = (int) ( mbxright + cx + 0.5D ); int sxleft = (int) ( msxleft + cx + 0.5D ); int sxright = (int) ( msxright + cx + 0.5D ); for ( int col = 0; col < width; ++col ) { int tx = x + col; if ( testMode && ( tx == bxleft || tx == bxright ) ) { // Mark the border pixels. int v = rgbPixels[row][col] & 0xff; if ( v < 10 ) // Mark black pixels in solid red. rgbPixels[row][col] = 0xffff0000; else // Turn the rest greenish. rgbPixels[row][col] &= 0xff00ff00; } else if ( tx >= sxleft && tx <= sxright ) { // Darken the shadow pixels. int argb = rgbPixels[row][col]; int alpha = argb & 0xff000000; int v = argb & 0x0000ff; // it's gray, choose any color v = v * shademult / 256; rgbPixels[row][col] = alpha | ( v << 16 ) | ( v << 8 ) | v; } else if ( tx < bxleft || tx > bxright ) { // Outside the moon - either star or black. rgbPixels[row][col] = star( tx, ty ); } } } else if ( ! testMode ) { // Outside the moon, top and bottom - either star or black. for ( int col = 0; col < width; ++col ) { int tx = x + col; rgbPixels[row][col] = star( tx, ty ); } } } return rgbPixels; } private static final float starProb = 0.002F; private BitSet starBits = null; // Decide whether or not there should be a star at the given coordinates. private int star( int x, int y ) { if ( starBits == null ) { starBits = new BitSet(width * height); Random r = new Random( starSeed ); for ( int i = 0; i < width * height; ++i ) if ( r.nextFloat() <= starProb ) starBits.set( i ); } if ( starBits.get( x * width + y ) ) return 0xffffffff; else return 0xff000000; } }