import java.io.*; import java.net.*; import java.util.*; import java.awt.*; import java.awt.image.*; import java.applet.*; // Wiper v1.0 // // Copyright (c) 1996 Bryan McNett // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or (at // your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program (as the file COPYING in the main directory of // the distribution); if not, write to the Free Software Foundation, // Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // // Wiper is an advertising billboard that cycles through a list of images // by applying user-defined special effects. When the user clicks // on an advertisement, Wiper takes her to the advertiser's home page. // // Here is a list of Wiper's parameters, all of which assume reasonable // default values if left unspecified: // // name type description default // ----------------------------------------------------------------------------- // src URL location of play list play.txt // secondsBetweenSleeps double seconds between sleeps 0.1 // secondsBetweenWipes double seconds between wipe effects 3.0 // secondsBetweenFrames double seconds between display frames 0.1 // wipeBits int frame count equal to two to this power 5 // // The play list is text with one advertisement per line as follows: // // [url of fade effect] // // for example: // // http://www.bigpanda.com/ bigpanda.gif wackywipe.gif // http://www.intrasteve.com/ intrasteve.gif // // Due to the security restrictions imposed upon applets, the applet code, // advertisement, and fade effect URLs must refer to the same hostname. // public class Wiper extends Applet implements Runnable { String pinfo[][] = { { "src" , "URL" , "location of play list" }, { "secondsBetweenSleeps", "double", "seconds between sleeps" }, { "secondsBetweenWipes" , "double", "seconds between wipe effects" }, { "secondsBetweenFrames", "double", "seconds between display frames" }, { "wipeBits" , "int" , "frame count equal to two to this power" } }; public String[][] getParameterInfo() { return pinfo; } String ainfo = "Wiper v1.0 (c) 1996 Bryan McNett - distributed under the terms of the GNU General Public License"; public String getAppletInfo() { return ainfo; } Thread thread = null; public void start() { if( thread == null ) { thread = new Thread( this ); thread.start(); } } public void stop() { if( thread != null ) { thread.stop(); thread = null; } } String getParameterSafely( String name, String otherwise ) { String value = null; value = getParameter( name ); if( value == null ) return otherwise; return value; } int getIntParamSafely( String name, String otherwise ) { String s = getParameterSafely( name, otherwise ); Integer i = Integer.valueOf( s ); return i.intValue(); } double getDoubleParamSafely( String name, String otherwise ) { String s = getParameterSafely( name, otherwise ); Double d = Double.valueOf( s ); return d.doubleValue(); } // initialize the applet int secondsBetweenSleeps; int secondsBetweenWipes; int secondsBetweenFrames; Vector effectvect = null; Hashtable bitmaphash = null; Hashtable wipehash = null; LineReader linereader = null; int width = 0; int height = 0; Bitmap bitmap = null; public static int wipeBits = 5; public static int wipeFactor = 32; public void init() { Rectangle r = bounds(); width = r.width; height = r.height; bitmap = new Bitmap( width, height ); backdrop = createImage( bitmap.prepare() ); effectvect = new Vector(); bitmaphash = new Hashtable(); wipehash = new Hashtable(); secondsBetweenSleeps = (int)(1000 * getDoubleParamSafely( "secondsBetweenSleeps", "0.100" ) ); secondsBetweenWipes = (int)(1000 * getDoubleParamSafely( "secondsBetweenWipes", "3.000" ) ); secondsBetweenFrames = (int)(1000 * getDoubleParamSafely( "secondsBetweenFrames", "0.100" ) ); wipeBits = getIntParamSafely ( "wipeBits", "5" ); wipeFactor = 1 << wipeBits; wipeBits = 8 - wipeBits; String src = getParameterSafely( "src", "play.txt" ); URL u = null; DataInputStream d = null; try { u = new URL( getDocumentBase(), src ); d = new DataInputStream( u.openStream() ); } catch( Exception e ); linereader = new LineReader( d, this ); } // make sure to load each unique image URL exactly once // to conserve bandwidth, cpu and memory Bitmap loadBitmap( String s ) { URL u = null; try { u = new URL( getDocumentBase(), s ); } catch( Exception e ) {;} if( bitmaphash.containsKey( u ) ) { return (Bitmap)bitmaphash.get( u ); } Bitmap b = new Bitmap( getImage( u ), width, height ); bitmaphash.put( u, b ); return b; } // make sure to create wipe data at most once per Bitmap. Wipe loadWipe( Bitmap b ) { if( wipehash.containsKey( b ) ) { return (Wipe)wipehash.get( b ); } Wipe w = new Wipe( b ); wipehash.put( b, w ); return w; } public void addLine( String line ) { StringTokenizer token; String u,b,w; Bitmap bitmap, wipe; URL url; token = new StringTokenizer( line ); u = token.nextToken(); b = token.nextToken(); w = b; if( token.hasMoreElements() ) w = token.nextToken(); url = getDocumentBase(); try { url = new URL( getDocumentBase(), u ); } catch( Exception e ); bitmap = loadBitmap( b ); wipe = loadBitmap( w ); effectvect.addElement( new Effect( url, bitmap, loadWipe( wipe ) ) ); } Image backdrop; public void paint( Graphics g ) { synchronized( backdrop ) g.drawImage( backdrop, 0, 0, this ); } public void update( Graphics g ) { paint( g ); } Effect lastEffect = null; Effect effect = null; URL url = null; boolean mouseInside = false; int mouseX = 0; int mouseY = 0; void mouseIt( int x, int y ) { mouseX = x; mouseY = y; if( effect == null ) return; Effect e = lastEffect; if( effect.get(x,y) ) e = effect; if( e == null ) return; URL u = e.getURL(); if( u == url ) return; url = u; showStatus( url.toString() ); } public boolean mouseMove( Event evt, int x, int y ) { mouseIt( x, y ); return true; } public boolean mouseUp( Event evt, int x, int y ) { mouseIt( x, y ); if( url != null ) getAppletContext().showDocument( url ); return true; } public boolean mouseEnter( Event evt, int x, int y ) { mouseInside = true; mouseIt( x, y ); return true; } public boolean mouseExit( Event evt, int x, int y ) { mouseInside = false; return true; } int index = 0; public void run() { Waiter wipewaiter = new Waiter( secondsBetweenWipes ); Waiter framewaiter = new Waiter( secondsBetweenFrames ); while( effectvect.isEmpty() == true ) { try { Thread.sleep( secondsBetweenSleeps ); } catch( Exception e ) {;} } while( true ) { if( index >= effectvect.size() ) index = 0; lastEffect = effect; wipewaiter.go(); effect = (Effect)effectvect.elementAt( index++ ); effect.start(); boolean done; do { done = effect.go(bitmap); if( mouseInside ) mouseIt( mouseX, mouseY ); framewaiter.go(); backdrop = createImage( bitmap.prepare() ); update( getGraphics() ); } while( done == false ); wipewaiter.go(); } } } // A LineReader spawns a thread to read text lines from a DataInputStream. // It updates Wiper's play list as lines arrive. class LineReader extends Thread { DataInputStream d; Wiper w; Thread thread; public LineReader( DataInputStream d, Wiper w ) { super( "line reader" ); this.setPriority( 1 ); this.d = d; this.w = w; this.start(); } public void run() { String line; while( true ) { try { line = d.readLine(); } catch( IOException e ) break; if( line == null ) break; synchronized ( w ) w.addLine( line ); } } } // A PixelMuncher spawns a thread to fill an array of ints with the pixels of // an image. class PixelMuncher extends Thread { static int muncher_number = 0; int done=0; PixelGrabber p = null; Thread thread = null; public PixelMuncher( Image image, int width, int height, int[] data ) { super( "pixel muncher number " + muncher_number++ ); this.setPriority( 1 ); p = new PixelGrabber( image.getSource(), 0, 0, width, height, data, 0, width ); this.start(); } public void run() { try { p.grabPixels(); done = 1; } catch( Exception e ) {} } public int ready() { return done; } } // A Bitmap knows how to play with its pixels. class Bitmap { int[] data = null; int width=0,height=0; PixelMuncher muncher = null; public Bitmap( Image image, int width, int height ) { this.width = width; this.height = height; data = new int[width*height]; clear(); muncher = new PixelMuncher( image, width, height, data ); } public Bitmap( int width, int height ) { this.width = width; this.height = height; data = new int[width*height]; clear(); } void clear( ) { for( int i=0; i>16 & 0xff )+( i>>8 & 0xff )+( i & 0xff )) / 3) >> Wiper.wipeBits; return (data[index] & 0xff) >> Wiper.wipeBits; } public int get( int x, int y ) { return get( y*width + x ); } public void paste( Bitmap src, int index, int count ) { System.arraycopy( src.data, index, data, index, count ); } public int pixels() { return width*height; } public int ready() { return muncher.ready(); } public MemoryImageSource prepare() { return new MemoryImageSource( width, height, data, 0, width ); } } // A Wipe knows how to decompose an image into 256 RLE compressed // representations of a single gray value. class Wipe { Bitmap bitmap = null; int[][] data = null; public Wipe( Bitmap bitmap ) { this.bitmap = bitmap; data = new int[256][]; for( int i=0; i<256; i++ ) { data[i] = null; } } public void go( Bitmap dest, Bitmap src, int value ) { int j=0,index=0; int r[] = rle(value); while( j < r.length ) { index += r[j++]; dest.paste( src, index, r[j] ); index += r[j++]; } } public int get( int x, int y ) { return bitmap.get( x, y ); } int[] rle( int value ) { int ready = 0; if( data[value] != null ) return data[value]; int j=0, skip, run, index = 0, count; int[] r = new int[ count = bitmap.pixels() ]; // set this flag if the bitmap has completed loading if( bitmap.ready() != 0 ) ready=1; // rle encode all pixels with a particular gray value while( count>0 ) { skip = run = 0; while( (count>0) && ( bitmap.get(index) != value ) ) { skip++; index++; count--; } while( (count>0) && ( bitmap.get(index) == value ) ) { run++; index++; count--; } if( j == r.length ) { int temp[] = new int[ j + bitmap.pixels() ]; System.arraycopy( r, 0, temp, 0, j ); r = temp; } r[j++] = skip; r[j++] = run; } if( j != r.length ) { int temp[] = new int[j]; System.arraycopy( r, 0, temp, 0, j ); r = temp; } // if we set this flag earlier, it means that we can cache the results of our encoding. if( ready != 0 ) data[value] = r; return r; } } // An Effect knows how to run a Wipe. class Effect { Bitmap src = null, map = null; Wipe wipe = null; int ticker = 0; boolean complete = false; URL url; public Effect( URL url, Bitmap bitmap, Wipe wipe ) { this.url = url; this.src = bitmap; this.wipe = wipe; this.start(); } public URL getURL() { return url; } public void start() { this.ticker=0; this.complete=false; } public boolean go( Bitmap dest ) { if( this.complete == true ) return this.complete; wipe.go( dest, this.src, this.ticker++ ); if( this.ticker >= Wiper.wipeFactor ) this.complete=true; return this.complete; } public boolean get( int x, int y ) { if( this.wipe.get( x, y ) < this.ticker ) return true; return false; } } // a waiter is something that knows how to wait. class Waiter { int time=0, delay=0; int set() { int now = (int)System.currentTimeMillis(); time = now+delay; return now; } public Waiter( int delay ) { this.delay = delay; set(); } public void go() { int ticks = time; ticks -= set(); if( ticks > 0 ) try Thread.sleep( ticks ); catch( Exception e ) ; } }