diff --git a/src/Acme/Fmt.java b/src/Acme/Fmt.java new file mode 100644 index 00000000..cba4bf2e --- /dev/null +++ b/src/Acme/Fmt.java @@ -0,0 +1,613 @@ +// Fmt - some simple single-arg sprintf-like routines +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme; + +/// Some simple single-arg sprintf-like routines. +//

+// It is apparently impossible to declare a Java method that accepts +// variable numbers of any type of argument. You can declare it to take +// Objects, but numeric variables and constants are not in fact Objects. +//

+// However, using the built-in string concatenation, it's almost as +// convenient to make a series of single-argument formatting routines. +//

+// Fmt can format the following types: +//

+// byte short int long float double char String Object +//
+// For each type there is a set of overloaded methods, each returning +// a formatted String. There's the plain formatting version: +//
+// Fmt.fmt( x )
+// 
+// There's a version specifying a minimum field width: +//
+// Fmt.fmt( x, minWidth )
+// 
+// And there's a version that takes flags: +//
+// Fmt.fmt( x, minWidth, flags )
+// 
+// Currently available flags are: +//
+// Fmt.ZF - zero-fill
+// Fmt.LJ - left justify
+// Fmt.HX - hexadecimal
+// Fmt.OC - octal
+// 
+// The HX and OC flags imply unsigned output. +//

+// For doubles and floats, there's a significant-figures parameter before +// the flags: +//

+// Fmt.fmt( d )
+// Fmt.fmt( d, minWidth )
+// Fmt.fmt( d, minWidth, sigFigs )
+// Fmt.fmt( d, minWidth, sigFigs, flags )
+// 
+//

+// Fetch the software.
+// Fetch the entire Acme package. +//


+// Similar classes: +// + +public class Fmt + { + + // Flags. + /// Zero-fill. + public static final int ZF = 1; + /// Left justify. + public static final int LJ = 2; + /// Hexadecimal. + public static final int HX = 4; + /// Octal. + public static final int OC = 8; + // Was a number - internal use. + private static final int WN = 16; + + // byte + public static String fmt( byte b ) + { + return fmt( b, 0, 0 ); + } + public static String fmt( byte b, int minWidth ) + { + return fmt( b, minWidth, 0 ); + } + public static String fmt( byte b, int minWidth, int flags ) + { + boolean hexadecimal = ( ( flags & HX ) != 0 ); + boolean octal = ( ( flags & OC ) != 0 ); + if ( hexadecimal ) + return fmt( Integer.toString( b & 0xff, 16 ), minWidth, flags|WN ); + else if ( octal ) + return fmt( Integer.toString( b & 0xff, 8 ), minWidth, flags|WN ); + else + return fmt( Integer.toString( b & 0xff ), minWidth, flags|WN ); + } + + // short + public static String fmt( short s ) + { + return fmt( s, 0, 0 ); + } + public static String fmt( short s, int minWidth ) + { + return fmt( s, minWidth, 0 ); + } + public static String fmt( short s, int minWidth, int flags ) + { + boolean hexadecimal = ( ( flags & HX ) != 0 ); + boolean octal = ( ( flags & OC ) != 0 ); + if ( hexadecimal ) + return fmt( + Integer.toString( s & 0xffff, 16 ), minWidth, flags|WN ); + else if ( octal ) + return fmt( + Integer.toString( s & 0xffff, 8 ), minWidth, flags|WN ); + else + return fmt( Integer.toString( s ), minWidth, flags|WN ); + } + + // int + public static String fmt( int i ) + { + return fmt( i, 0, 0 ); + } + public static String fmt( int i, int minWidth ) + { + return fmt( i, minWidth, 0 ); + } + public static String fmt( int i, int minWidth, int flags ) + { + boolean hexadecimal = ( ( flags & HX ) != 0 ); + boolean octal = ( ( flags & OC ) != 0 ); + if ( hexadecimal ) + return fmt( + Long.toString( i & 0xffffffffL, 16 ), minWidth, flags|WN ); + else if ( octal ) + return fmt( + Long.toString( i & 0xffffffffL, 8 ), minWidth, flags|WN ); + else + return fmt( Integer.toString( i ), minWidth, flags|WN ); + } + + // long + public static String fmt( long l ) + { + return fmt( l, 0, 0 ); + } + public static String fmt( long l, int minWidth ) + { + return fmt( l, minWidth, 0 ); + } + public static String fmt( long l, int minWidth, int flags ) + { + boolean hexadecimal = ( ( flags & HX ) != 0 ); + boolean octal = ( ( flags & OC ) != 0 ); + if ( hexadecimal ) + { + if ( ( l & 0xf000000000000000L ) != 0 ) + return fmt( + Long.toString( l >>> 60, 16 ) + + fmt( l & 0x0fffffffffffffffL, 15, HX|ZF ), + minWidth, flags|WN ); + else + return fmt( Long.toString( l, 16 ), minWidth, flags|WN ); + } + else if ( octal ) + { + if ( ( l & 0x8000000000000000L ) != 0 ) + return fmt( + Long.toString( l >>> 63, 8 ) + + fmt( l & 0x7fffffffffffffffL, 21, OC|ZF ), + minWidth, flags|WN ); + else + return fmt( Long.toString( l, 8 ), minWidth, flags|WN ); + } + else + return fmt( Long.toString( l ), minWidth, flags|WN ); + } + + // float + public static String fmt( float f ) + { + return fmt( f, 0, 0, 0 ); + } + public static String fmt( float f, int minWidth ) + { + return fmt( f, minWidth, 0, 0 ); + } + public static String fmt( float f, int minWidth, int sigFigs ) + { + return fmt( f, minWidth, sigFigs, 0 ); + } + public static String fmt( float f, int minWidth, int sigFigs, int flags ) + { + if ( sigFigs != 0 ) + return fmt( + sigFigFix( Float.toString( f ), sigFigs ), minWidth, + flags|WN ); + else + return fmt( Float.toString( f ), minWidth, flags|WN ); + } + + // double + public static String fmt( double d ) + { + return fmt( d, 0, 0, 0 ); + } + public static String fmt( double d, int minWidth ) + { + return fmt( d, minWidth, 0, 0 ); + } + public static String fmt( double d, int minWidth, int sigFigs ) + { + return fmt( d, minWidth, sigFigs, 0 ); + } + public static String fmt( double d, int minWidth, int sigFigs, int flags ) + { + if ( sigFigs != 0 ) + return fmt( + sigFigFix( doubleToString( d ), sigFigs ), minWidth, + flags|WN ); + else + return fmt( doubleToString( d ), minWidth, flags|WN ); + } + + // char + public static String fmt( char c ) + { + return fmt( c, 0, 0 ); + } + public static String fmt( char c, int minWidth ) + { + return fmt( c, minWidth, 0 ); + } + public static String fmt( char c, int minWidth, int flags ) + { + // return fmt( Character.toString( c ), minWidth, flags ); + // Character currently lacks a static toString method. Workaround + // is to make a temporary instance and use the instance toString. + return fmt( new Character( c ).toString(), minWidth, flags ); + } + + // Object + public static String fmt( Object o ) + { + return fmt( o, 0, 0 ); + } + public static String fmt( Object o, int minWidth ) + { + return fmt( o, minWidth, 0 ); + } + public static String fmt( Object o, int minWidth, int flags ) + { + return fmt( o.toString(), minWidth, flags ); + } + + // String + public static String fmt( String s ) + { + return fmt( s, 0, 0 ); + } + public static String fmt( String s, int minWidth ) + { + return fmt( s, minWidth, 0 ); + } + public static String fmt( String s, int minWidth, int flags ) + { + int len = s.length(); + boolean zeroFill = ( ( flags & ZF ) != 0 ); + boolean leftJustify = ( ( flags & LJ ) != 0 ); + boolean hexadecimal = ( ( flags & HX ) != 0 ); + boolean octal = ( ( flags & OC ) != 0 ); + boolean wasNumber = ( ( flags & WN ) != 0 ); + if ( ( hexadecimal || octal || zeroFill ) && ! wasNumber ) + throw new InternalError( "Acme.Fmt: number flag on a non-number" ); + if ( zeroFill && leftJustify ) + throw new InternalError( "Acme.Fmt: zero-fill left-justify is silly" ); + if ( hexadecimal && octal ) + throw new InternalError( "Acme.Fmt: can't do both hex and octal" ); + if ( len >= minWidth ) + return s; + int fillWidth = minWidth - len; + StringBuffer fill = new StringBuffer( fillWidth ); + for ( int i = 0; i < fillWidth; ++i ) + if ( zeroFill ) + fill.append( '0' ); + else + fill.append( ' ' ); + if ( leftJustify ) + return s + fill; + else if ( zeroFill && s.startsWith( "-" ) ) + return "-" + fill + s.substring( 1 ); + else + return fill + s; + } + + + // Internal routines. + + private static String sigFigFix( String s, int sigFigs ) + { + // First dissect the floating-point number string into sign, + // integer part, fraction part, and exponent. + String sign; + String unsigned; + if ( s.startsWith( "-" ) || s.startsWith( "+" ) ) + { + sign = s.substring( 0, 1 ); + unsigned = s.substring( 1 ); + } + else + { + sign = ""; + unsigned = s; + } + String mantissa; + String exponent; + int eInd = unsigned.indexOf( 'e' ); + if ( eInd == -1 ) // it may be 'e' or 'E' + eInd = unsigned.indexOf( 'E' ); + if ( eInd == -1 ) + { + mantissa = unsigned; + exponent = ""; + } + else + { + mantissa = unsigned.substring( 0, eInd ); + exponent = unsigned.substring( eInd ); + } + StringBuffer number, fraction; + int dotInd = mantissa.indexOf( '.' ); + if ( dotInd == -1 ) + { + number = new StringBuffer( mantissa ); + fraction = new StringBuffer( "" ); + } + else + { + number = new StringBuffer( mantissa.substring( 0, dotInd ) ); + fraction = new StringBuffer( mantissa.substring( dotInd + 1 ) ); + } + + int numFigs = number.length(); + int fracFigs = fraction.length(); + if ( ( numFigs == 0 || number.equals( "0" ) ) && fracFigs > 0 ) + { + // Don't count leading zeros in the fraction. + numFigs = 0; + for ( int i = 0; i < fraction.length(); ++i ) + { + if ( fraction.charAt( i ) != '0' ) + break; + --fracFigs; + } + } + int mantFigs = numFigs + fracFigs; + if ( sigFigs > mantFigs ) + { + // We want more figures; just append zeros to the fraction. + for ( int i = mantFigs; i < sigFigs; ++i ) + fraction.append( '0' ); + } + else if ( sigFigs < mantFigs && sigFigs >= numFigs ) + { + // Want fewer figures in the fraction; chop. + fraction.setLength( + fraction.length() - ( fracFigs - ( sigFigs - numFigs ) ) ); + // Round? + } + else if ( sigFigs < numFigs ) + { + // Want fewer figures in the number; turn them to zeros. + fraction.setLength( 0 ); // should already be zero, but make sure + for ( int i = sigFigs; i < numFigs; ++i ) + number.setCharAt( i, '0' ); + // Round? + } + // Else sigFigs == mantFigs, which is fine. + + if ( fraction.length() == 0 ) + return sign + number + exponent; + else + return sign + number + "." + fraction + exponent; + } + + + /// Improved version of Double.toString(), returns more decimal places. + //

+ // The JDK 1.0.2 version of Double.toString() returns only six decimal + // places on some systems. In JDK 1.1 full precision is returned on + // all platforms. + // @deprecated + // @see java.lang.Double#toString + public static String doubleToString( double d ) + { + // Handle special numbers first, to avoid complications. + if ( Double.isNaN( d ) ) + return "NaN"; + if ( d == Double.NEGATIVE_INFINITY ) + return "-Inf"; + if ( d == Double.POSITIVE_INFINITY ) + return "Inf"; + + // Grab the sign, and then make the number positive for simplicity. + boolean negative = false; + if ( d < 0.0D ) + { + negative = true; + d = -d; + } + + // Get the native version of the unsigned value, as a template. + String unsStr = Double.toString( d ); + + // Dissect out the exponent. + String mantStr, expStr; + int exp; + int eInd = unsStr.indexOf( 'e' ); + if ( eInd == -1 ) // it may be 'e' or 'E' + eInd = unsStr.indexOf( 'E' ); + if ( eInd == -1 ) + { + mantStr = unsStr; + expStr = ""; + exp = 0; + } + else + { + mantStr = unsStr.substring( 0, eInd ); + expStr = unsStr.substring( eInd + 1 ); + if ( expStr.startsWith( "+" ) ) + exp = Integer.parseInt( expStr.substring( 1 ) ); + else + exp = Integer.parseInt( expStr ); + } + + // Dissect out the number part. + String numStr; + int dotInd = mantStr.indexOf( '.' ); + if ( dotInd == -1 ) + numStr = mantStr; + else + numStr = mantStr.substring( 0, dotInd ); + long num; + if ( numStr.length() == 0 ) + num = 0; + else + num = Integer.parseInt( numStr ); + + // Build the new mantissa. + StringBuffer newMantBuf = new StringBuffer( numStr + "." ); + double p = Math.pow( 10, exp ); + double frac = d - num * p; + String digits = "0123456789"; + int nDigits = 16 - numStr.length(); // about 16 digits in a double + for ( int i = 0; i < nDigits; ++i ) + { + p /= 10.0D; + int dig = (int) ( frac / p ); + if ( dig < 0 ) dig = 0; + if ( dig > 9 ) dig = 9; + newMantBuf.append( digits.charAt( dig ) ); + frac -= dig * p; + } + + if ( (int) ( frac / p + 0.5D ) == 1 ) + { + // Round up. + boolean roundMore = true; + for ( int i = newMantBuf.length() - 1; i >= 0; --i ) + { + int dig = digits.indexOf( newMantBuf.charAt( i ) ); + if ( dig == -1 ) + continue; + ++dig; + if ( dig == 10 ) + { + newMantBuf.setCharAt( i, '0' ); + continue; + } + newMantBuf.setCharAt( i, digits.charAt( dig ) ); + roundMore = false; + break; + } + if ( roundMore ) + { + // If this happens, we need to prepend a 1. But I haven't + // found a test case yet, so I'm leaving it out for now. + // But if you get this message, please let me know! + newMantBuf.append( "ROUNDMORE" ); + } + } + + // Chop any trailing zeros. + int len = newMantBuf.length(); + while ( newMantBuf.charAt( len - 1 ) == '0' ) + newMantBuf.setLength( --len ); + // And chop a trailing dot, if any. + if ( newMantBuf.charAt( len - 1 ) == '.' ) + newMantBuf.setLength( --len ); + + // Done. + return ( negative ? "-" : "" ) + + newMantBuf + + ( expStr.length() != 0 ? ( "e" + expStr ) : "" ); + } + + +/****************************************************************************** + /// Test program. + public static void main( String[] args ) + { + System.out.println( "Starting tests." ); + show( Fmt.fmt( "Hello there." ) ); + show( Fmt.fmt( 123 ) ); + show( Fmt.fmt( 123, 10 ) ); + show( Fmt.fmt( 123, 10, Fmt.ZF ) ); + show( Fmt.fmt( 123, 10, Fmt.LJ ) ); + show( Fmt.fmt( -123 ) ); + show( Fmt.fmt( -123, 10 ) ); + show( Fmt.fmt( -123, 10, Fmt.ZF ) ); + show( Fmt.fmt( -123, 10, Fmt.LJ ) ); + show( Fmt.fmt( (byte) 0xbe, 22, Fmt.OC ) ); + show( Fmt.fmt( (short) 0xbabe, 22, Fmt.OC ) ); + show( Fmt.fmt( 0xcafebabe, 22, Fmt.OC ) ); + show( Fmt.fmt( 0xdeadbeefcafebabeL, 22, Fmt.OC ) ); + show( Fmt.fmt( 0x8000000000000000L, 22, Fmt.OC ) ); + show( Fmt.fmt( (byte) 0xbe, 16, Fmt.HX ) ); + show( Fmt.fmt( (short) 0xbabe, 16, Fmt.HX ) ); + show( Fmt.fmt( 0xcafebabe, 16, Fmt.HX ) ); + show( Fmt.fmt( 0xdeadbeefcafebabeL, 16, Fmt.HX ) ); + show( Fmt.fmt( 0x8000000000000000L, 16, Fmt.HX ) ); + show( Fmt.fmt( 'c' ) ); + show( Fmt.fmt( new java.util.Date() ) ); + show( Fmt.fmt( 123.456F ) ); + show( Fmt.fmt( 123456000000000000.0F ) ); + show( Fmt.fmt( 123.456F, 0, 8 ) ); + show( Fmt.fmt( 123.456F, 0, 7 ) ); + show( Fmt.fmt( 123.456F, 0, 6 ) ); + show( Fmt.fmt( 123.456F, 0, 5 ) ); + show( Fmt.fmt( 123.456F, 0, 4 ) ); + show( Fmt.fmt( 123.456F, 0, 3 ) ); + show( Fmt.fmt( 123.456F, 0, 2 ) ); + show( Fmt.fmt( 123.456F, 0, 1 ) ); + show( Fmt.fmt( 123456000000000000.0F, 0, 4 ) ); + show( Fmt.fmt( -123.456F, 0, 4 ) ); + show( Fmt.fmt( -123456000000000000.0F, 0, 4 ) ); + show( Fmt.fmt( 123.0F ) ); + show( Fmt.fmt( 123.0D ) ); + show( Fmt.fmt( 1.234567890123456789F ) ); + show( Fmt.fmt( 1.234567890123456789D ) ); + show( Fmt.fmt( 1234567890123456789F ) ); + show( Fmt.fmt( 1234567890123456789D ) ); + show( Fmt.fmt( 0.000000000000000000001234567890123456789F ) ); + show( Fmt.fmt( 0.000000000000000000001234567890123456789D ) ); + show( Fmt.fmt( 12300.0F ) ); + show( Fmt.fmt( 12300.0D ) ); + show( Fmt.fmt( 123000.0F ) ); + show( Fmt.fmt( 123000.0D ) ); + show( Fmt.fmt( 1230000.0F ) ); + show( Fmt.fmt( 1230000.0D ) ); + show( Fmt.fmt( 12300000.0F ) ); + show( Fmt.fmt( 12300000.0D ) ); + show( Fmt.fmt( Float.NaN ) ); + show( Fmt.fmt( Float.POSITIVE_INFINITY ) ); + show( Fmt.fmt( Float.NEGATIVE_INFINITY ) ); + show( Fmt.fmt( Double.NaN ) ); + show( Fmt.fmt( Double.POSITIVE_INFINITY ) ); + show( Fmt.fmt( Double.NEGATIVE_INFINITY ) ); + show( Fmt.fmt( 1.0F / 8.0F ) ); + show( Fmt.fmt( 1.0D / 8.0D ) ); + System.out.println( "Done with tests." ); + } + + private static void show( String str ) + { + System.out.println( "#" + str + "#" ); + } +******************************************************************************/ + + } diff --git a/src/Acme/JPM/Decoders/ImageDecoder.java b/src/Acme/JPM/Decoders/ImageDecoder.java new file mode 100644 index 00000000..01dec95c --- /dev/null +++ b/src/Acme/JPM/Decoders/ImageDecoder.java @@ -0,0 +1,314 @@ +// ImageDecoder - abstract class for reading in an image +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Decoders; + +import java.util.*; +import java.io.*; +import java.awt.image.*; + +/// Abstract class for reading in an image. +//

+// A framework for classes that read in and decode an image in +// a particular file format. +//

+// This provides a very simplified rendition of the ImageProducer interface. +// It requires the decoder to read the image a row at a time. It requires +// use of the RGBdefault color model. +// If you want more flexibility you can always implement ImageProducer +// directly. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @see PpmDecoder +// @see Acme.JPM.Encoders.ImageEncoder + +public abstract class ImageDecoder implements ImageProducer + { + + private InputStream in; + private int width, height; + private boolean[] rowsRead; + private int[][] rgbPixels; + private boolean startedRead = false; + private boolean gotSize = false; + private boolean err = false; + private boolean producing = false; + private Vector consumers = new Vector(); + private static final ColorModel model = ColorModel.getRGBdefault(); + + + /// Constructor. + // @param in The stream to read the bytes from. + public ImageDecoder( InputStream in ) + { + this.in = in; + } + + + // Methods that subclasses implement. + + /// Subclasses implement this to read in enough of the image stream + // to figure out the width and height. + abstract void readHeader( InputStream in ) throws IOException; + + /// Subclasses implement this to return the width, or -1 if not known. + abstract int getWidth(); + + /// Subclasses implement this to return the height, or -1 if not known. + abstract int getHeight(); + + /// Subclasses implement this to read pixel data into the rgbRow + // array, an int[width]. One int per pixel, no offsets or padding, + // RGBdefault (AARRGGBB) color model. + abstract void readRow( InputStream in, int row, int[] rgbRow ) throws IOException; + + + // Our own methods. + + void readImage() + { + try + { + readHeader( in ); + width = getWidth(); + height = getHeight(); + if ( width == -1 || height == -1 ) + err = true; + else + { + rowsRead = new boolean[height]; + for ( int row = 0; row < height; ++row ) + rowsRead[row] = false; + gotSize = true; + notifyThem(); + rgbPixels = new int[height][width]; + for ( int row = 0; row < height; ++row ) + { + readRow( in, row, rgbPixels[row] ); + rowsRead[row] = true; + notifyThem(); + } + } + } + catch ( IOException e ) + { + err = true; + width = -1; + height = -1; + rowsRead = null; + rgbPixels = null; + } + } + + private synchronized void notifyThem() + { + notifyAll(); + } + + void sendImage() + { + // Grab the list of consumers, in case it changes while we're sending. + ImageConsumer[] c = new ImageConsumer[consumers.size()]; + int i; + for ( i = 0; i < c.length; ++i ) + c[i] = (ImageConsumer) consumers.elementAt( i ); + // Try to be as parallel as possible. + waitForSize(); + for ( i = 0; i < c.length; ++i ) + sendHead( c[i] ); + for ( int row = 0; row < height; ++row ) + for ( i = 0; i < c.length; ++i ) + sendPixelRow( c[i], row ); + for ( i = 0; i < c.length; ++i ) + sendTail( c[i] ); + producing = false; + } + + private synchronized void waitForSize() + { + while ( ( ! err ) && ( ! gotSize )) + { + try + { + wait(); + } + catch ( InterruptedException ignore ) {} + } + } + + private synchronized void waitForRow( int row ) + { + while ( ( ! err ) && ( ! rowsRead[row] ) ) + { + try + { + wait(); + } + catch ( InterruptedException ignore ) {} + } + } + + private void sendHead( ImageConsumer ic ) + { + if ( err ) + return; + ic.setDimensions( width, height ); + ic.setColorModel( model ); + ic.setHints( + ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES | + ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME ); + } + + private void sendPixelRow( ImageConsumer ic, int row ) + { + if ( err ) + return; + waitForRow( row ); + if ( err ) + return; + ic.setPixels( 0, row, width, 1, model, rgbPixels[row], 0, width ); + } + + private void sendTail( ImageConsumer ic ) + { + if ( err ) + ic.imageComplete( ImageConsumer.IMAGEERROR ); + else + ic.imageComplete( ImageConsumer.STATICIMAGEDONE ); + + } + + + // Methods from ImageProducer. + + /// This method is used to register an ImageConsumer with the + // ImageProducer for access to the image data during a later + // reconstruction of the Image. The ImageProducer may, at its + // discretion, start delivering the image data to the consumer + // using the ImageConsumer interface immediately, or when the + // next available image reconstruction is triggered by a call + // to the startProduction method. + // @see #startProduction + public void addConsumer( ImageConsumer ic ) + { + if ( ic != null && ! isConsumer( ic ) ) + consumers.addElement( ic ); + } + + /// This method determines if a given ImageConsumer object + // is currently registered with this ImageProducer as one + // of its consumers. + public boolean isConsumer( ImageConsumer ic ) + { + return consumers.contains( ic ); + } + + /// This method removes the given ImageConsumer object + // from the list of consumers currently registered to + // receive image data. It is not considered an error + // to remove a consumer that is not currently registered. + // The ImageProducer should stop sending data to this + // consumer as soon as is feasible. + public void removeConsumer( ImageConsumer ic ) + { + consumers.removeElement( ic ); + } + + /// This method both registers the given ImageConsumer object + // as a consumer and starts an immediate reconstruction of + // the image data which will then be delivered to this + // consumer and any other consumer which may have already + // been registered with the producer. This method differs + // from the addConsumer method in that a reproduction of + // the image data should be triggered as soon as possible. + // @see #addConsumer + public void startProduction( ImageConsumer ic ) + { + addConsumer( ic ); + if ( ! startedRead ) + { + startedRead = true; + new ImageDecoderRead( this ); + } + if ( ! producing ) + { + producing = true; + sendImage(); + } + } + + /// This method is used by an ImageConsumer to request that + // the ImageProducer attempt to resend the image data one + // more time in TOPDOWNLEFTRIGHT order so that higher + // quality conversion algorithms which depend on receiving + // pixels in order can be used to produce a better output + // version of the image. The ImageProducer is free to + // ignore this call if it cannot resend the data in that + // order. If the data can be resent, then the ImageProducer + // should respond by executing the following minimum set of + // ImageConsumer method calls: + //

+    //     ic.setHints( TOPDOWNLEFTRIGHT | [otherhints] );
+    //     ic.setPixels( [...] );    // as many times as needed
+    //     ic.imageComplete( [status] );
+    // 
+ // @see ImageConsumer#setHints + public void requestTopDownLeftRightResend( ImageConsumer ic ) + { + addConsumer( ic ); + waitForSize(); + sendHead( ic ); + for ( int row = 0; row < height; ++row ) + sendPixelRow( ic, row ); + sendTail( ic ); + } + + } + + +class ImageDecoderRead extends Thread + { + + private ImageDecoder parent; + + public ImageDecoderRead( ImageDecoder parent ) + { + this.parent = parent; + start(); + } + + // Methods from Runnable. + + public void run() + { + parent.readImage(); + } + + } diff --git a/src/Acme/JPM/Decoders/PpmDecoder.java b/src/Acme/JPM/Decoders/PpmDecoder.java new file mode 100644 index 00000000..8c39f9ce --- /dev/null +++ b/src/Acme/JPM/Decoders/PpmDecoder.java @@ -0,0 +1,267 @@ +// PpmDecoder - read in a PPM image +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Decoders; + +import java.io.*; +import java.awt.image.*; + +/// Read in a PPM image. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @see Acme.JPM.Encoders.PpmEncoder + +public class PpmDecoder extends ImageDecoder + { + + /// Constructor. + // @param in The stream to read the bytes from. + public PpmDecoder( InputStream in ) + { + super( in ); + } + + + private int type; + private static final int PBM_ASCII = 1; + private static final int PGM_ASCII = 2; + private static final int PPM_ASCII = 3; + private static final int PBM_RAW = 4; + private static final int PGM_RAW = 5; + private static final int PPM_RAW = 6; + + private int width = -1, height = -1; + private int maxval; + + /// Subclasses implement this to read in enough of the image stream + // to figure out the width and height. + void readHeader( InputStream in ) throws IOException + { + char c1, c2; + + c1 = (char) readByte( in ); + c2 = (char) readByte( in ); + + if ( c1 != 'P' ) + throw new IOException( "not a PBM/PGM/PPM file" ); + switch ( c2 ) + { + case '1': + type = PBM_ASCII; + break; + case '2': + type = PGM_ASCII; + break; + case '3': + type = PPM_ASCII; + break; + case '4': + type = PBM_RAW; + break; + case '5': + type = PGM_RAW; + break; + case '6': + type = PPM_RAW; + break; + default: + throw new IOException( "not a standard PBM/PGM/PPM file" ); + } + width = readInt( in ); + height = readInt( in ); + if ( type != PBM_ASCII && type != PBM_RAW ) + maxval = readInt( in ); + } + + /// Subclasses implement this to return the width, or -1 if not known. + int getWidth() + { + return width; + } + + /// Subclasses implement this to return the height, or -1 if not known. + int getHeight() + { + return height; + } + + /// Subclasses implement this to read pixel data into the rgbRow + // array, an int[width]. One int per pixel, no offsets or padding, + // RGBdefault (AARRGGBB) color model + void readRow( InputStream in, int row, int[] rgbRow ) throws IOException + { + int col, r, g, b; + int rgb = 0; + char c; + + for ( col = 0; col < width; ++col ) + { + switch ( type ) + { + case PBM_ASCII: + c = readChar( in ); + if ( c == '1' ) + rgb = 0xff000000; + else if ( c == '0' ) + rgb = 0xffffffff; + else + throw new IOException( "illegal PBM bit" ); + break; + case PGM_ASCII: + g = readInt( in ); + rgb = makeRgb( g, g, g ); + break; + case PPM_ASCII: + r = readInt( in ); + g = readInt( in ); + b = readInt( in ); + rgb = makeRgb( r, g, b ); + break; + case PBM_RAW: + if ( readBit( in ) ) + rgb = 0xff000000; + else + rgb = 0xffffffff; + break; + case PGM_RAW: + g = readByte( in ); + if ( maxval != 255 ) + g = fixDepth( g ); + rgb = makeRgb( g, g, g ); + break; + case PPM_RAW: + r = readByte( in ); + g = readByte( in ); + b = readByte( in ); + if ( maxval != 255 ) + { + r = fixDepth( r ); + g = fixDepth( g ); + b = fixDepth( b ); + } + rgb = makeRgb( r, g, b ); + break; + } + rgbRow[col] = rgb; + } + } + + /// Utility routine to read a byte. Instead of returning -1 on + // EOF, it throws an exception. + private static int readByte( InputStream in ) throws IOException + { + int b = in.read(); + if ( b == -1 ) + throw new EOFException(); + return b; + } + + private int bitshift = -1; + private int bits; + + /// Utility routine to read a bit, packed eight to a byte, big-endian. + private boolean readBit( InputStream in ) throws IOException + { + if ( bitshift == -1 ) + { + bits = readByte( in ); + bitshift = 7; + } + boolean bit = ( ( ( bits >> bitshift ) & 1 ) != 0 ); + --bitshift; + return bit; + } + + /// Utility routine to read a character, ignoring comments. + private static char readChar( InputStream in ) throws IOException + { + char c; + + c = (char) readByte( in ); + if ( c == '#' ) + { + do + { + c = (char) readByte( in ); + } + while ( c != '\n' && c != '\r' ); + } + + return c; + } + + /// Utility routine to read the first non-whitespace character. + private static char readNonwhiteChar( InputStream in ) throws IOException + { + char c; + + do + { + c = readChar( in ); + } + while ( c == ' ' || c == '\t' || c == '\n' || c == '\r' ); + + return c; + } + + /// Utility routine to read an ASCII integer, ignoring comments. + private static int readInt( InputStream in ) throws IOException + { + char c; + int i; + + c = readNonwhiteChar( in ); + if ( c < '0' || c > '9' ) + throw new IOException( "junk in file where integer should be" ); + + i = 0; + do + { + i = i * 10 + c - '0'; + c = readChar( in ); + } + while ( c >= '0' && c <= '9' ); + + return i; + } + + /// Utility routine to rescale a pixel value from a non-eight-bit maxval. + private int fixDepth( int p ) + { + return ( p * 255 + maxval / 2 ) / maxval; + } + + /// Utility routine make an RGBdefault pixel from three color values. + private static int makeRgb( int r, int g, int b ) + { + return 0xff000000 | ( r << 16 ) | ( g << 8 ) | b; + } + + } diff --git a/src/Acme/JPM/Encoders/GifEncoder.java b/src/Acme/JPM/Encoders/GifEncoder.java new file mode 100644 index 00000000..18022542 --- /dev/null +++ b/src/Acme/JPM/Encoders/GifEncoder.java @@ -0,0 +1,691 @@ +// GifEncoder - write out an image as a GIF +// +// Transparency handling and variable bit size courtesy of Jack Palevich. +// +// 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Encoders; + +import java.util.*; +import java.io.*; +import java.awt.Image; +import java.awt.image.*; + +/// Write out an image as a GIF. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @see ToGif + +public class GifEncoder extends ImageEncoder + { + + private boolean interlace = false; + + /// Constructor from Image. + // @param img The image to encode. + // @param out The stream to write the GIF to. + public GifEncoder( Image img, OutputStream out ) throws IOException + { + super( img, out ); + } + + /// Constructor from Image with interlace setting. + // @param img The image to encode. + // @param out The stream to write the GIF to. + // @param interlace Whether to interlace. + public GifEncoder( Image img, OutputStream out, boolean interlace ) throws IOException + { + super( img, out ); + this.interlace = interlace; + } + + /// Constructor from ImageProducer. + // @param prod The ImageProducer to encode. + // @param out The stream to write the GIF to. + public GifEncoder( ImageProducer prod, OutputStream out ) throws IOException + { + super( prod, out ); + } + + /// Constructor from ImageProducer with interlace setting. + // @param prod The ImageProducer to encode. + // @param out The stream to write the GIF to. + public GifEncoder( ImageProducer prod, OutputStream out, boolean interlace ) throws IOException + { + super( prod, out ); + this.interlace = interlace; + } + + + int width, height; + int[][] rgbPixels; + + void encodeStart( int width, int height ) throws IOException + { + this.width = width; + this.height = height; + rgbPixels = new int[height][width]; + } + + void encodePixels( + int x, int y, int w, int h, int[] rgbPixels, int off, int scansize ) + throws IOException + { + // Save the pixels. + for ( int row = 0; row < h; ++row ) + System.arraycopy( + rgbPixels, row * scansize + off, + this.rgbPixels[y + row], x, w ); + + } + + Acme.IntHashtable colorHash; + + void encodeDone() throws IOException + { + int transparentIndex = -1; + int transparentRgb = -1; + // Put all the pixels into a hash table. + colorHash = new Acme.IntHashtable(); + int index = 0; + for ( int row = 0; row < height; ++row ) + { + int rowOffset = row * width; + for ( int col = 0; col < width; ++col ) + { + int rgb = rgbPixels[row][col]; + boolean isTransparent = ( ( rgb >>> 24 ) < 0x80 ); + if ( isTransparent ) + { + if ( transparentIndex < 0 ) + { + // First transparent color; remember it. + transparentIndex = index; + transparentRgb = rgb; + } + else if ( rgb != transparentRgb ) + { + // A second transparent color; replace it with + // the first one. + rgbPixels[row][col] = rgb = transparentRgb; + } + } + GifEncoderHashitem item = + (GifEncoderHashitem) colorHash.get( rgb ); + if ( item == null ) + { + if ( index >= 256 ) + throw new IOException( "too many colors for a GIF" ); + item = new GifEncoderHashitem( + rgb, 1, index, isTransparent ); + ++index; + colorHash.put( rgb, item ); + } + else + ++item.count; + } + } + + // Figure out how many bits to use. + int logColors; + if ( index <= 2 ) + logColors = 1; + else if ( index <= 4 ) + logColors = 2; + else if ( index <= 16 ) + logColors = 4; + else + logColors = 8; + + // Turn colors into colormap entries. + int mapSize = 1 << logColors; + byte[] reds = new byte[mapSize]; + byte[] grns = new byte[mapSize]; + byte[] blus = new byte[mapSize]; + for ( Enumeration e = colorHash.elements(); e.hasMoreElements(); ) + { + GifEncoderHashitem item = (GifEncoderHashitem) e.nextElement(); + reds[item.index] = (byte) ( ( item.rgb >> 16 ) & 0xff ); + grns[item.index] = (byte) ( ( item.rgb >> 8 ) & 0xff ); + blus[item.index] = (byte) ( item.rgb & 0xff ); + } + + GIFEncode( + out, width, height, interlace, (byte) 0, transparentIndex, + logColors, reds, grns, blus ); + } + + byte GetPixel( int x, int y ) throws IOException + { + GifEncoderHashitem item = + (GifEncoderHashitem) colorHash.get( rgbPixels[y][x] ); + if ( item == null ) + throw new IOException( "color not found" ); + return (byte) item.index; + } + + static void writeString( OutputStream out, String str ) throws IOException + { + byte[] buf = str.getBytes(); + out.write( buf ); + } + + // Adapted from ppmtogif, which is based on GIFENCOD by David + // Rowley . Lempel-Zim compression + // based on "compress". + + int Width, Height; + boolean Interlace; + int curx, cury; + int CountDown; + int Pass = 0; + + void GIFEncode( + OutputStream outs, int Width, int Height, boolean Interlace, byte Background, int Transparent, int BitsPerPixel, byte[] Red, byte[] Green, byte[] Blue ) + throws IOException + { + byte B; + int LeftOfs, TopOfs; + int ColorMapSize; + int InitCodeSize; + int i; + + this.Width = Width; + this.Height = Height; + this.Interlace = Interlace; + ColorMapSize = 1 << BitsPerPixel; + LeftOfs = TopOfs = 0; + + // Calculate number of bits we are expecting + CountDown = Width * Height; + + // Indicate which pass we are on (if interlace) + Pass = 0; + + // The initial code size + if ( BitsPerPixel <= 1 ) + InitCodeSize = 2; + else + InitCodeSize = BitsPerPixel; + + // Set up the current x and y position + curx = 0; + cury = 0; + + // Write the Magic header + writeString( outs, "GIF89a" ); + + // Write out the screen width and height + Putword( Width, outs ); + Putword( Height, outs ); + + // Indicate that there is a global colour map + B = (byte) 0x80; // Yes, there is a color map + // OR in the resolution + B |= (byte) ( ( 8 - 1 ) << 4 ); + // Not sorted + // OR in the Bits per Pixel + B |= (byte) ( ( BitsPerPixel - 1 ) ); + + // Write it out + Putbyte( B, outs ); + + // Write out the Background colour + Putbyte( Background, outs ); + + // Pixel aspect ratio - 1:1. + //Putbyte( (byte) 49, outs ); + // Java's GIF reader currently has a bug, if the aspect ratio byte is + // not zero it throws an ImageFormatException. It doesn't know that + // 49 means a 1:1 aspect ratio. Well, whatever, zero works with all + // the other decoders I've tried so it probably doesn't hurt. + Putbyte( (byte) 0, outs ); + + // Write out the Global Colour Map + for ( i = 0; i < ColorMapSize; ++i ) + { + Putbyte( Red[i], outs ); + Putbyte( Green[i], outs ); + Putbyte( Blue[i], outs ); + } + + // Write out extension for transparent colour index, if necessary. + if ( Transparent != -1 ) + { + Putbyte( (byte) '!', outs ); + Putbyte( (byte) 0xf9, outs ); + Putbyte( (byte) 4, outs ); + Putbyte( (byte) 1, outs ); + Putbyte( (byte) 0, outs ); + Putbyte( (byte) 0, outs ); + Putbyte( (byte) Transparent, outs ); + Putbyte( (byte) 0, outs ); + } + + // Write an Image separator + Putbyte( (byte) ',', outs ); + + // Write the Image header + Putword( LeftOfs, outs ); + Putword( TopOfs, outs ); + Putword( Width, outs ); + Putword( Height, outs ); + + // Write out whether or not the image is interlaced + if ( Interlace ) + Putbyte( (byte) 0x40, outs ); + else + Putbyte( (byte) 0x00, outs ); + + // Write out the initial code size + Putbyte( (byte) InitCodeSize, outs ); + + // Go and actually compress the data + compress( InitCodeSize+1, outs ); + + // Write out a Zero-length packet (to end the series) + Putbyte( (byte) 0, outs ); + + // Write the GIF file terminator + Putbyte( (byte) ';', outs ); + } + + // Bump the 'curx' and 'cury' to point to the next pixel + void BumpPixel() + { + // Bump the current X position + ++curx; + + // If we are at the end of a scan line, set curx back to the beginning + // If we are interlaced, bump the cury to the appropriate spot, + // otherwise, just increment it. + if ( curx == Width ) + { + curx = 0; + + if ( ! Interlace ) + ++cury; + else + { + switch( Pass ) + { + case 0: + cury += 8; + if ( cury >= Height ) + { + ++Pass; + cury = 4; + } + break; + + case 1: + cury += 8; + if ( cury >= Height ) + { + ++Pass; + cury = 2; + } + break; + + case 2: + cury += 4; + if ( cury >= Height ) + { + ++Pass; + cury = 1; + } + break; + + case 3: + cury += 2; + break; + } + } + } + } + + static final int EOF = -1; + + // Return the next pixel from the image + int GIFNextPixel() throws IOException + { + byte r; + + if ( CountDown == 0 ) + return EOF; + + --CountDown; + + r = GetPixel( curx, cury ); + + BumpPixel(); + + return r & 0xff; + } + + // Write out a word to the GIF file + void Putword( int w, OutputStream outs ) throws IOException + { + Putbyte( (byte) ( w & 0xff ), outs ); + Putbyte( (byte) ( ( w >> 8 ) & 0xff ), outs ); + } + + // Write out a byte to the GIF file + void Putbyte( byte b, OutputStream outs ) throws IOException + { + outs.write( b ); + } + + + // GIFCOMPR.C - GIF Image compression routines + // + // Lempel-Ziv compression based on 'compress'. GIF modifications by + // David Rowley (mgardi@watdcsu.waterloo.edu) + + // General DEFINEs + + static final int BITS = 12; + + static final int HSIZE = 5003; // 80% occupancy + + // GIF Image compression - modified 'compress' + // + // Based on: compress.c - File compression ala IEEE Computer, June 1984. + // + // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + // Jim McKie (decvax!mcvax!jim) + // Steve Davies (decvax!vax135!petsd!peora!srd) + // Ken Turkowski (decvax!decwrl!turtlevax!ken) + // James A. Woods (decvax!ihnp4!ames!jaw) + // Joe Orost (decvax!vax135!petsd!joe) + + int n_bits; // number of bits/code + int maxbits = BITS; // user settable max # bits/code + int maxcode; // maximum code, given n_bits + int maxmaxcode = 1 << BITS; // should NEVER generate this code + + final int MAXCODE( int n_bits ) + { + return ( 1 << n_bits ) - 1; + } + + int[] htab = new int[HSIZE]; + int[] codetab = new int[HSIZE]; + + int hsize = HSIZE; // for dynamic table sizing + + int free_ent = 0; // first unused entry + + // block compression parameters -- after all codes are used up, + // and compression rate changes, start over. + boolean clear_flg = false; + + // Algorithm: use open addressing double hashing (no chaining) on the + // prefix code / next character combination. We do a variant of Knuth's + // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + // secondary probe. Here, the modular division first probe is gives way + // to a faster exclusive-or manipulation. Also do block compression with + // an adaptive reset, whereby the code table is cleared when the compression + // ratio decreases, but after the table fills. The variable-length output + // codes are re-sized at this point, and a special CLEAR code is generated + // for the decompressor. Late addition: construct the table according to + // file size for noticeable speed improvement on small files. Please direct + // questions about this implementation to ames!jaw. + + int g_init_bits; + + int ClearCode; + int EOFCode; + + void compress( int init_bits, OutputStream outs ) throws IOException + { + int fcode; + int i /* = 0 */; + int c; + int ent; + int disp; + int hsize_reg; + int hshift; + + // Set up the globals: g_init_bits - initial number of bits + g_init_bits = init_bits; + + // Set up the necessary values + clear_flg = false; + n_bits = g_init_bits; + maxcode = MAXCODE( n_bits ); + + ClearCode = 1 << ( init_bits - 1 ); + EOFCode = ClearCode + 1; + free_ent = ClearCode + 2; + + char_init(); + + ent = GIFNextPixel(); + + hshift = 0; + for ( fcode = hsize; fcode < 65536; fcode *= 2 ) + ++hshift; + hshift = 8 - hshift; // set hash code range bound + + hsize_reg = hsize; + cl_hash( hsize_reg ); // clear hash table + + output( ClearCode, outs ); + + outer_loop: + while ( (c = GIFNextPixel()) != EOF ) + { + fcode = ( c << maxbits ) + ent; + i = ( c << hshift ) ^ ent; // xor hashing + + if ( htab[i] == fcode ) + { + ent = codetab[i]; + continue; + } + else if ( htab[i] >= 0 ) // non-empty slot + { + disp = hsize_reg - i; // secondary hash (after G. Knott) + if ( i == 0 ) + disp = 1; + do + { + if ( (i -= disp) < 0 ) + i += hsize_reg; + + if ( htab[i] == fcode ) + { + ent = codetab[i]; + continue outer_loop; + } + } + while ( htab[i] >= 0 ); + } + output( ent, outs ); + ent = c; + if ( free_ent < maxmaxcode ) + { + codetab[i] = free_ent++; // code -> hashtable + htab[i] = fcode; + } + else + cl_block( outs ); + } + // Put out the final code. + output( ent, outs ); + output( EOFCode, outs ); + } + + // output + // + // Output the given code. + // Inputs: + // code: A n_bits-bit integer. If == -1, then EOF. This assumes + // that n_bits =< wordsize - 1. + // Outputs: + // Outputs code to the file. + // Assumptions: + // Chars are 8 bits long. + // Algorithm: + // Maintain a BITS character long buffer (so that 8 codes will + // fit in it exactly). Use the VAX insv instruction to insert each + // code in turn. When the buffer fills up empty it and start over. + + int cur_accum = 0; + int cur_bits = 0; + + int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, + 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, + 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + + void output( int code, OutputStream outs ) throws IOException + { + cur_accum &= masks[cur_bits]; + + if ( cur_bits > 0 ) + cur_accum |= ( code << cur_bits ); + else + cur_accum = code; + + cur_bits += n_bits; + + while ( cur_bits >= 8 ) + { + char_out( (byte) ( cur_accum & 0xff ), outs ); + cur_accum >>= 8; + cur_bits -= 8; + } + + // If the next entry is going to be too big for the code size, + // then increase it, if possible. + if ( free_ent > maxcode || clear_flg ) + { + if ( clear_flg ) + { + maxcode = MAXCODE(n_bits = g_init_bits); + clear_flg = false; + } + else + { + ++n_bits; + if ( n_bits == maxbits ) + maxcode = maxmaxcode; + else + maxcode = MAXCODE(n_bits); + } + } + + if ( code == EOFCode ) + { + // At EOF, write the rest of the buffer. + while ( cur_bits > 0 ) + { + char_out( (byte) ( cur_accum & 0xff ), outs ); + cur_accum >>= 8; + cur_bits -= 8; + } + + flush_char( outs ); + } + } + + // Clear out the hash table + + // table clear for block compress + void cl_block( OutputStream outs ) throws IOException + { + cl_hash( hsize ); + free_ent = ClearCode + 2; + clear_flg = true; + + output( ClearCode, outs ); + } + + // reset code table + void cl_hash( int hsize ) + { + for ( int i = 0; i < hsize; ++i ) + htab[i] = -1; + } + + // GIF Specific routines + + // Number of characters so far in this 'packet' + int a_count; + + // Set up the 'byte output' routine + void char_init() + { + a_count = 0; + } + + // Define the storage for the packet accumulator + byte[] accum = new byte[256]; + + // Add a character to the end of the current packet, and if it is 254 + // characters, flush the packet to disk. + void char_out( byte c, OutputStream outs ) throws IOException + { + accum[a_count++] = c; + if ( a_count >= 254 ) + flush_char( outs ); + } + + // Flush the packet to disk, and reset the accumulator + void flush_char( OutputStream outs ) throws IOException + { + if ( a_count > 0 ) + { + outs.write( a_count ); + outs.write( accum, 0, a_count ); + a_count = 0; + } + } + + } + +class GifEncoderHashitem + { + + public int rgb; + public int count; + public int index; + public boolean isTransparent; + + public GifEncoderHashitem( int rgb, int count, int index, boolean isTransparent ) + { + this.rgb = rgb; + this.count = count; + this.index = index; + this.isTransparent = isTransparent; + } + + } diff --git a/src/Acme/JPM/Encoders/GrayJPEG.java b/src/Acme/JPM/Encoders/GrayJPEG.java new file mode 100644 index 00000000..77ba40a6 --- /dev/null +++ b/src/Acme/JPM/Encoders/GrayJPEG.java @@ -0,0 +1,583 @@ +//////////////////////// DISCLAIMER \\\\\\\\\\\\\\\\\\\\\\\\\ +// \\ +// This software is provided by Sean Patrick Breslin for \\ +// your use or abuse, free of charge and royalties. This \\ +// software is provide 'AS IS' and as such there are no \\ +// warrenties expressed or implied. This software is by \\ +// no means robust, optimized, efficient, tight or any of \\ +// a number of adjectives used to describe software. This \\ +// in mind, this software is not to be used for nuclear \\ +// applications, world domination schemes or discount \\ +// remote laser surgery. You may copy or modify this \\ +// code as you see fit. This code works on my PC other \\ +// than that I cannot help you with this code in any way. \\ +// I have not decided whether or not I will support this \\ +// code with bug fixes or enhancements but I'll think \\ +// about it. \\ +// \\ +// USAGE: instanciate the class: \\ +// GrayJPEG jpg = new GrayJPEG(); \\ +// \\ +// and call the method: \\ +// jpg.compress(Image i, OutputStream os) \\ +// \\ +// Where 'i' is a standard Java Image object and \\ +// 'os' is an output stream where you want the \\ +// JPEG file written. \\ +// \\ +// NOTE: This code will only compress a grayscale image \\ +// i.e. Black and White, though a color image will \\ +// not crash the code the results are unknown. \\ +// \\ +// PS: I have compiled this with Java 1.0.2 and 1.1.3 \\ +// and it works under both versions using windows \\ +// 95 operating system, so knock yourself out and \\ +// have fun with my code. \\ +// \\ +///////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ + +package Acme.JPM.Encoders; + +import java.io.*; +import java.net.*; +import java.awt.*; +import java.awt.image.*; + +public class GrayJPEG { + // quantization table in zigzag form for output file + private byte[] QNT = {-1, -37, 0, 67, 0, + 13,9,10,11,10,8,13,11, + 10,11,14,14,13,15,19,32, + 21,19,18,18,19,39,28,30, + 23,32,46,41,49,48,46,41, + 45,44,51,58,74,62,51,54, + 70,55,44,45,64,87,65,70, + 76,78,82,83,82,50,62,90, + 97,90,80,96,74,81,82,79}; + + // table for doing quantization + private float[][] QT = {{13, 9, 8,13,19,32,41,49}, + {10,10,11,15,21,46,48,44}, + {11,10,13,19,32,46,55,45}, + {11,14,18,23,41,70,64,50}, + {14,18,30,45,54,87,82,62}, + {19,28,44,51,65,83,90,74}, + {39,51,62,70,82,97,96,81}, + {58,74,76,78,90,80,82,79}}; + + // Zig Zag array + private int[][] ZZ = {{ 0, 1, 5, 6,14,15,27,28}, + { 2, 4, 7,13,16,26,29,42}, + { 3, 8,12,17,25,30,41,43}, + { 9,11,18,24,31,40,44,53}, + {10,19,23,32,39,45,52,54}, + {20,22,33,38,46,51,55,60}, + {21,34,37,47,50,56,59,61}, + {35,36,48,49,57,58,62,63}}; + + // AC Huffman table + private byte[] Bits = {0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125}; + private byte[] Huffval = { + 1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20, + 50,-127,-111,-95,8,35,66,-79,-63,21,82,-47,-16,36, + 51,98,114,-126,9,10,22,23,24,25,26,37,38,39,40,41, + 42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83, + 84,85,86,87,88,89,90,99,100,101,102,103,104,105,106, + 115,116,117,118,119,120,121,122,-125,-124,-123,-122, + -121,-120,-119,-118,-110,-109,-108,-107,-106,-105, + -104,-103,-102,-94,-93,-92,-91,-90,-89,-88,-87,-86, + -78,-77,-76,-75,-74,-73,-72,-71,-70,-62,-61,-60,-59, + -58,-57,-56,-55,-54,-46,-45,-44,-43,-42,-41,-40,-39, + -38,-31,-30,-29,-28,-27,-26,-25,-24,-23,-22,-15,-14, + -13,-12,-11,-10,-9,-8,-7,-6}; + + // Header data + private String str = "JFIF Breslin Engineering JPEG Image Compression"; + private byte[] BE, APP0; + private byte[] SOI = {-1,-40}; + private byte[] EOI = {-1,-39}; + private byte[] SOF = {-1,-64,0,11,8,0,0,0,0,1,1,17,0}; + private byte[] HuffDC = {-1,-60,0,31,0,0,1,5,1,1,1,1,1,1,0,0, + 0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11}; + private byte[] HuffACHeader = {-1,-60,0,-75,16}; + private byte[] SOS = {-1,-38,0,8,1,1,0,0,63,0}; + + // Global variables + private int ln, X, Y, LASTK, I, J; + private int BitCnt, K, R, CODE, SI, SSSS; + private int[] BITS = new int[17]; + private int[] HUFFVAL = new int[162]; + private int[] HUFFCODE = new int[257]; + private int[] HUFFSIZE = new int[257]; + private int[] EHUFCO = new int[257]; + private int[] EHUFSI = new int[257]; + private OutputStream fos; + private Image image; + private long DATA=0; + + public GrayJPEG() { + int x, y, ln = str.length(); + BE = new byte[ln]; + str.getBytes(0, ln, BE, 0); + ln += 2; + byte[] ap = {-1, -32, 0, (byte)ln}; + APP0 = ap; + + // Generate AC Huffman tables + // Get BITS data + BITS[0]=0; + for(x=1;x<17;x++) + BITS[x] = Bits[x-1]; + + // Get HUFFVAL data + for(x=0;x<162;x++) { + HUFFVAL[x] = Huffval[x]; + if(HUFFVAL[x]<0) HUFFVAL[x] = 256 + HUFFVAL[x]; + } + + Generate_size_table(); + Generate_code_table(); + Order_codes(); + } + + public void compress(Image i, OutputStream os){ + int bkk, x, y, w, h, a, b, c, d; + int[][] blocks; + int[][] blk = new int[8][8]; + + image = i; + fos = os; + X = image.getWidth(null); + Y = image.getHeight(null); + ln = X*Y; + int[] data = new int[ln]; + w = (X>>3)<<3; + h = (Y>>3)<<3; + getPixels(data); + + // Set Image dimensions in Start of Frame header + SOF[5] = (byte)((h & 0x0000ff00)>>8); + SOF[6] = (byte)(h & 0x000000ff); + SOF[7] = (byte)((w & 0x0000ff00)>>8); + SOF[8] = (byte)(w & 0x000000ff); + + // Set number of blocks + w = X/8; h = Y/8; ln = w*h; + blocks = new int[ln][]; + bkk=0; + + // break image in to 8x8 blocks + for(a=0;a<(h*8);a+=8) { + // Get 8 lines of image + for(b=0;b<(w*8);b+=8) { + // Get block for FDCT + for(c=0;c<8;c++) + for(d=0;d<8;d++) + // mask off 3 upper bytes of image data + blk[c][d] = (data[(a+c)*X+b+d] & 0x000000ff)-128; + + //Do FDCT on Block + blocks[bkk++] = FDCT(blk); + } + } + // Write out header data + writeHeaders(); + + // Encode image scan + Huffman(blocks); + + // Write out last bits of image scan + writeEndData(); + + // Write End of Image marker + writeEnd(); + } + + private void Huffman(int[][] blocks) { + int tmp, lp, DIFF, dat, PRED, bits; + int[] blk; + byte[] data = new byte[1]; + int[][] HuffTbl = {{2,0},{3,2},{3,3},{3,4},{3,5},{3,6},{4,14},{5,30},{6,62},{7,126},{8,254},{9,510}}; + long buffer; + + // Encode data blocks + // Initialize predictor for new scan + PRED = 0; BitCnt=0; + for(lp=0;lp15) { + R-=16; + // Mark RLZ in scan + code = EHUFCO[0xf0]; + size = EHUFSI[0xf0]; + writeData(code, size); + continue; + } + Encode_R(ZZ[K]); + R=0; + if(K==63) done=true; + break; + } + } + } + } + + private void Encode_R(int ZZ) { + int dat, RS, size, code; + dat = ZZ; + if(ZZ<0) { + dat=-dat; + ZZ--; + } + + // Get AC magnitude category + SSSS = MagCat(dat); + RS = (R<<4) + SSSS; + + // append bits + code = EHUFCO[RS]; + size = EHUFSI[RS]; + writeData(code, size); + + // Mask off upper bits of ZZ + ZZ &= ((1<BITS[I]) { + J=1; I++; + if(I > 16) break; + } else { + HUFFSIZE[K++]=I; + J++; + } + } + HUFFSIZE[K] = 0; + LASTK = K; + } + + private void Generate_code_table() { + // Generate Code table Flow Chart C.2 + K = 0; CODE = 0; SI = HUFFSIZE[0]; + while(true) { + HUFFCODE[K++] = CODE++; + if(HUFFSIZE[K] == SI) continue; + if(HUFFSIZE[K] == 0) break; + while(true){ + CODE<<=1; SI++; + if(HUFFSIZE[K] == SI) + break; + } + } + } + + private void Order_codes() { + // Order Codes Flow Chart C.3 + K=0; + while(true) { + I = HUFFVAL[K]; + EHUFCO[I] = HUFFCODE[K]; + EHUFSI[I] = HUFFSIZE[K++]; + if(K >= LASTK) break; + } + } + + private int MagCat(int dat) { + int ln; + for(;;) { + if(dat==0) { ln = 0; break; } + if(dat==1) { ln = 1; break; } + if(dat<=3) { ln = 2; break; } + if(dat<=7) { ln = 3; break; } + if(dat<=15) { ln = 4; break; } + if(dat<=31) { ln = 5; break; } + if(dat<=63) { ln = 6; break; } + if(dat<=127) { ln = 7; break; } + if(dat<=255) { ln = 8; break; } + if(dat<=511) { ln = 9; break; } + if(dat<=1023) { ln = 10; break; } + if(dat<=2047) { ln = 11; break; } + } + return ln; + } + + private void writeHeaders() { + try { + fos.write(SOI); // Start of Image marker + fos.write(APP0); // Application header + fos.write(BE); // JFIF BE jpg compression + fos.write(QNT); // Quantization table + fos.write(SOF); // Start of Frame marker + fos.write(HuffDC); // DC Huffman Table + fos.write(HuffACHeader); // AC Huffman Table Header + fos.write(Bits); // AC Huffman Table + fos.write(Huffval); // AC Huffman Table + fos.write(SOS); // Start of Scan header + } catch(IOException ioe) { System.err.println("IOException: " + ioe); } + } + + private void writeEnd() { + try { + fos.write(EOI); // End of Image/File + fos.close(); + } catch(IOException ioe) { System.err.println("IOException: " + ioe); } + } + + private void getPixels(int[] data) { + PixelGrabber pg = new PixelGrabber(image.getSource(), 0, 0, X, Y, data, 0, X); + try { if(pg.grabPixels() != true) { + try { + throw new AWTException("Grabber returned false: " + pg.status()); + } catch (Exception ex) { + System.err.println("System Failed to get Pixels! - " + ex); + System.exit(0); + }; + } + } catch (InterruptedException ent) {}; + } + + private void writeData(long dat, int bits) { + int tmp; + byte[] stuff = {0}; + byte[] d = new byte[1]; + + if(bits>0) { + DATA<<=bits; + DATA+=dat; + BitCnt+=bits; + }else + return; + + // output bytes untill cnt is less then 8 + while(BitCnt>7) { + BitCnt-=8; + tmp=(int)(DATA>>BitCnt); + d[0] = (byte)tmp; + + // mask off 8 msb of DATA + DATA &= ((1<0) { + DATA<<=(8-BitCnt); + DATA+=((1<<(8-BitCnt))-1); + d[0] = (byte)DATA; + try { + fos.write(d); + } catch(IOException ioe) { System.err.println("IOException: " + ioe); } + } + } + + private int[] FDCT(int[][] block) { + int j1, i, j; + int[] blk = new int[64]; + float[] b = new float[8]; + float temp; + float[] b1 = new float[8]; + float[][] d = new float[8][8]; + float f0=(float)0.7071068,f1=(float)0.4903926; + float f2=(float)0.4619398,f3=(float)0.4157348; + float f4=(float)0.3535534,f5=(float)0.2777851; + float f6=(float)0.1913417,f7=(float)0.0975452; + + float df7f1=(float)-0.3928475, sf7f1=(float)0.5879378 ; + float df3f5=(float)0.1379497, sf3f5=(float)0.6935199 ; + float df6f2=(float)-0.27059805, sf6f2=(float).6532815 ; + + for(i = 0; i < 8; i++) { + for(j = 0; j < 8; j++) { + b[j] = block[i][j]; + } + // Horizontal transform + for(j = 0; j < 4; j++) { + j1 = 7 - j; + b1[j] = b[j] + b[j1]; + b1[j1] = b[j] - b[j1]; + } + b[0] = b1[0] + b1[3]; + b[1] = b1[1] + b1[2]; + b[2] = b1[1] - b1[2]; + b[3] = b1[0] - b1[3]; + b[4] = b1[4]; + b[5] = (b1[6] - b1[5]) * f0; + b[6] = (b1[6] + b1[5]) * f0; + b[7] = b1[7]; + d[i][0] = (b[0] + b[1]) * f4; + d[i][4] = (b[0] - b[1]) * f4; + + temp = (b[3]+b[2])*f6 ; + d[i][2] = temp - b[3]*df6f2 ; + d[i][6] = temp - b[2]*sf6f2 ; + + b1[4] = b[4] + b[5]; + b1[7] = b[7] + b[6]; + b1[5] = b[4] - b[5]; + b1[6] = b[7] - b[6]; + + temp = (b1[7]+b1[4])*f7; + d[i][1] = temp - b1[7]*df7f1 ; + d[i][7] = temp - b1[4]*sf7f1 ; + + temp = (b1[6]+b1[5])*f3; + d[i][5] = temp - b1[6]*df3f5 ; + d[i][3] = temp - b1[5]*sf3f5 ; + } + + // Vertical transform + for(i = 0; i < 8; i++) { + for(j = 0; j < 4; j++) { + j1 = 7 - j; + b1[j] = d[j][i] + d[j1][i]; + b1[j1] = d[j][i] - d[j1][i]; + } + b[0] = b1[0] + b1[3]; + b[1] = b1[1] + b1[2]; + b[2] = b1[1] - b1[2]; + b[3] = b1[0] - b1[3]; + b[4] = b1[4]; + b[5] = (b1[6] - b1[5]) * f0; + b[6] = (b1[6] + b1[5]) * f0; + b[7] = b1[7]; + d[0][i] = (b[0] + b[1]) * f4; + d[4][i] = (b[0] - b[1]) * f4; + + temp = (b[3]+b[2])*f6 ; + d[2][i] = temp - b[3]*df6f2 ; + d[6][i] = temp - b[2]*sf6f2 ; + + b1[4] = b[4] + b[5]; + b1[7] = b[7] + b[6]; + b1[5] = b[4] - b[5]; + b1[6] = b[7] - b[6]; + + temp = (b1[7]+b1[4])*f7; + d[1][i] = temp - b1[7]*df7f1 ; + d[7][i] = temp - b1[4]*sf7f1 ; + + temp = (b1[6]+b1[5])*f3; + d[5][i] = temp - b1[6]*df3f5 ; + d[3][i] = temp - b1[5]*sf3f5 ; + + } + for(i = 0; i < 8; i++) { + for(j = 0; j < 8; j++) { + // Quantize and ZigZag data block + blk[ZZ[i][j]] = (int)(d[i][j]/QT[i][j]); + + } + } + return blk; + } + + public static void main(String[] args) { + try { + URL url = new URL(args[0]); + URLConnection uc = url.openConnection(); + uc.connect(); + + // Got to open a stream to get around Java NullPointerException bug. + InputStream f = uc.getInputStream(); + + String mimeType = uc.getContentType(); + + // Java prints (not throws!) a ClassNotFoundException if you try + // a getContent on text.html or audio/basic (or no doubt many + // other types). + if ( ! mimeType.startsWith( "image/" ) ) + System.err.println( args[0] + " is not an image" ); + else { + Object content = uc.getContent(); + if ( ! ( content instanceof ImageProducer ) ) + System.err.println( args[0] + " is not a known image type" ); + else { + ImageProducer prod = (ImageProducer) content; + Toolkit tk = new Acme.JPM.StubToolkit(); + Image img = tk.createImage(prod); + GrayJPEG jpg = new GrayJPEG(); + jpg.compress(img, System.out); + } + } + + // Done with unnecessary stream. + f.close(); + } + catch ( Exception e ) { + System.err.println( e ); + } + } +} diff --git a/src/Acme/JPM/Encoders/ImageEncoder.java b/src/Acme/JPM/Encoders/ImageEncoder.java new file mode 100644 index 00000000..9b5e8e56 --- /dev/null +++ b/src/Acme/JPM/Encoders/ImageEncoder.java @@ -0,0 +1,271 @@ +// ImageEncoder - abstract class for writing out an image +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Encoders; + +import java.util.*; +import java.io.*; +import java.awt.Image; +import java.awt.image.*; + +/// Abstract class for writing out an image. +//

+// A framework for classes that encode and write out an image in +// a particular file format. +//

+// This provides a simplified rendition of the ImageConsumer interface. +// It always delivers the pixels as ints in the RGBdefault color model. +// It always provides them in top-down left-right order. +// If you want more flexibility you can always implement ImageConsumer +// directly. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @see GifEncoder +// @see PpmEncoder +// @see Acme.JPM.Decoders.ImageDecoder + +public abstract class ImageEncoder implements ImageConsumer + { + + protected OutputStream out; + + private ImageProducer producer; + private int width = -1; + private int height = -1; + private int hintflags = 0; + private boolean started = false; + private boolean encoding; + private IOException iox; + private static final ColorModel rgbModel = ColorModel.getRGBdefault(); + private Hashtable props = null; + + /// Constructor. + // @param img The image to encode. + // @param out The stream to write the bytes to. + public ImageEncoder( Image img, OutputStream out ) throws IOException + { + this( img.getSource(), out ); + } + + /// Constructor. + // @param producer The ImageProducer to encode. + // @param out The stream to write the bytes to. + public ImageEncoder( ImageProducer producer, OutputStream out ) throws IOException + { + this.producer = producer; + this.out = out; + } + + + // Methods that subclasses implement. + + /// Subclasses implement this to initialize an encoding. + abstract void encodeStart( int w, int h ) throws IOException; + + /// Subclasses implement this to actually write out some bits. They + // are guaranteed to be delivered in top-down-left-right order. + // One int per pixel, index is row * scansize + off + col, + // RGBdefault (AARRGGBB) color model. + abstract void encodePixels( + int x, int y, int w, int h, int[] rgbPixels, int off, int scansize ) + throws IOException; + + /// Subclasses implement this to finish an encoding. + abstract void encodeDone() throws IOException; + + + // Our own methods. + + /// Call this after initialization to get things going. + public synchronized void encode() throws IOException + { + encoding = true; + iox = null; + producer.startProduction( this ); + while ( encoding ) + try + { + wait(); + } + catch ( InterruptedException e ) {} + if ( iox != null ) + throw iox; + } + + private boolean accumulate = false; + private int[] accumulator; + + private void encodePixelsWrapper( + int x, int y, int w, int h, int[] rgbPixels, int off, int scansize ) + throws IOException + { + if ( ! started ) + { + started = true; + encodeStart( width, height ); + if ( ( hintflags & TOPDOWNLEFTRIGHT ) == 0 ) + { + accumulate = true; + accumulator = new int[width * height]; + } + } + if ( accumulate ) + for ( int row = 0; row < h; ++row ) + System.arraycopy( + rgbPixels, row * scansize + off, + accumulator, ( y + row ) * width + x, + w ); + else + encodePixels( x, y, w, h, rgbPixels, off, scansize ); + } + + private void encodeFinish() throws IOException + { + if ( accumulate ) + { + encodePixels( 0, 0, width, height, accumulator, 0, width ); + accumulator = null; + accumulate = false; + } + } + + private synchronized void stop() + { + encoding = false; + notifyAll(); + } + + + // Methods from ImageConsumer. + + public void setDimensions( int width, int height ) + { + this.width = width; + this.height = height; + } + + public void setProperties( Hashtable props ) + { + this.props = props; + } + + public void setColorModel( ColorModel model ) + { + // Ignore. + } + + public void setHints( int hintflags ) + { + this.hintflags = hintflags; + } + + public void setPixels( + int x, int y, int w, int h, ColorModel model, byte[] pixels, + int off, int scansize ) + { + int[] rgbPixels = new int[w]; + for ( int row = 0; row < h; ++row ) + { + int rowOff = off + row * scansize; + for ( int col = 0; col < w; ++col ) + rgbPixels[col] = model.getRGB( pixels[rowOff + col] & 0xff ); + try + { + encodePixelsWrapper( x, y + row, w, 1, rgbPixels, 0, w ); + } + catch ( IOException e ) + { + iox = e; + stop(); + return; + } + } + } + + public void setPixels( + int x, int y, int w, int h, ColorModel model, int[] pixels, + int off, int scansize ) + { + if ( model == rgbModel ) + { + try + { + encodePixelsWrapper( x, y, w, h, pixels, off, scansize ); + } + catch ( IOException e ) + { + iox = e; + stop(); + return; + } + } + else + { + int[] rgbPixels = new int[w]; + for ( int row = 0; row < h; ++row ) + { + int rowOff = off + row * scansize; + for ( int col = 0; col < w; ++col ) + rgbPixels[col] = model.getRGB( pixels[rowOff + col] ); + try + { + encodePixelsWrapper( x, y + row, w, 1, rgbPixels, 0, w ); + } + catch ( IOException e ) + { + iox = e; + stop(); + return; + } + } + } + } + + public void imageComplete( int status ) + { + producer.removeConsumer( this ); + if ( status == ImageConsumer.IMAGEABORTED ) + iox = new IOException( "image aborted" ); + else + { + try + { + encodeFinish(); + encodeDone(); + } + catch ( IOException e ) + { + iox = e; + } + } + stop(); + } + + } diff --git a/src/Acme/JPM/Encoders/JpegEncoder.java b/src/Acme/JPM/Encoders/JpegEncoder.java new file mode 100644 index 00000000..8f434d2b --- /dev/null +++ b/src/Acme/JPM/Encoders/JpegEncoder.java @@ -0,0 +1,448 @@ +// JpegEncoder - write out an image as a JPEG +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Encoders; + +import java.util.*; +import java.io.*; +import java.awt.Image; +import java.awt.image.*; + +/// Write out an image as a JPEG. +// DOESN'T WORK YET. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @see ToJpeg + +public class JpegEncoder extends ImageEncoder + { + + /// Constructor. + // @param img The image to encode. + // @param out The stream to write the JPEG to. + public JpegEncoder( Image img, OutputStream out ) throws IOException + { + super( img, out ); + } + + /// Constructor. + // @param prod The ImageProducer to encode. + // @param out The stream to write the JPEG to. + public JpegEncoder( ImageProducer prod, OutputStream out ) throws IOException + { + super( prod, out ); + } + + int qfactor = 100; + + /// Set the Q-factor. + public void setQfactor( int qfactor ) + { + this.qfactor = qfactor; + } + + int width, height; + int[][] rgbPixels; + + void encodeStart( int width, int height ) throws IOException + { + this.width = width; + this.height = height; + rgbPixels = new int[height][width]; + } + + void encodePixels( + int x, int y, int w, int h, int[] rgbPixels, int off, int scansize ) + throws IOException + { + // Save the pixels. + for ( int row = 0; row < h; ++row ) + System.arraycopy( + rgbPixels, row * scansize + off, + this.rgbPixels[y + row], x, w ); + + } + + void encodeDone() throws IOException + { + writeJfifHuffHeader(); + // !!! + } + + + // Some of the following code is derived from the Berkeley Continuous + // Media Toolkit (http://bmrc.berkeley.edu/projects/cmt/), which is + // Copyright (c) 1996 The Regents of the University of California. + // All rights reserved. + // + // IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR + // DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING + // OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE + // UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + // DAMAGE. + // + // THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, + // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + // AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + // ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION + // TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + + + // This array represents the default JFIF header for quality = 100 and + // size = 640x480, with Huffman tables. The values are adjusted when a + // file is generated. + private static byte[] jfifHuff100Header = { + // SOI + (byte) 0xFF, (byte) 0xD8, + + // JFIF header + (byte) 0xFF, (byte) 0xE0, // Marker + (byte) 0x00, (byte) 0x10, // Length = 16 bytes + (byte) 0x4A, (byte) 0x46, (byte) 0x49, (byte) 0x46, // "JFIF" + (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, + + // Start of frame (section B.2.2) + (byte) 0xFF, (byte) 0xC0, // Baseline DCT + (byte) 0x00, (byte) 0x11, // Length = 17 bytes + (byte) 0x08, // Sample precision + (byte) 0x01, (byte) 0xE0, // Height + (byte) 0x02, (byte) 0x80, // Width + (byte) 0x03, // Number of components = 3 + // Scan 1: 2:1 horiz, (byte) 1:1 vertical, (byte) use QT 0 + (byte) 0x01, (byte) 0x21, (byte) 0x00, + // Scan 2: 1:1 horiz, (byte) 1:1 vertical, (byte) use QT 1 + (byte) 0x02, (byte) 0x11, (byte) 0x01, + // Scan 3: 1:1 horiz, (byte) 1:1 vertical, (byte) use QT 1 + (byte) 0x03, (byte) 0x11, (byte) 0x01, + + // Define Quant table (section B.2.4.1) + (byte) 0xFF, (byte) 0xDB, // Marker + (byte) 0x00, (byte) 0x84, // Length (both tables) + (byte) 0x00, // 8 bit values, (byte) table 0 + (byte) 0x10, (byte) 0x0B, (byte) 0x0C, (byte) 0x0E, (byte) 0x0C, + (byte) 0x0A, (byte) 0x10, (byte) 0x0E, (byte) 0x0D, (byte) 0x0E, + (byte) 0x12, (byte) 0x11, (byte) 0x10, (byte) 0x13, (byte) 0x18, + (byte) 0x28, (byte) 0x1A, (byte) 0x18, (byte) 0x16, (byte) 0x16, + (byte) 0x18, (byte) 0x31, (byte) 0x23, (byte) 0x25, (byte) 0x1D, + (byte) 0x28, (byte) 0x3A, (byte) 0x33, (byte) 0x3D, (byte) 0x3C, + (byte) 0x39, (byte) 0x33, (byte) 0x38, (byte) 0x37, (byte) 0x40, + (byte) 0x48, (byte) 0x5C, (byte) 0x4E, (byte) 0x40, (byte) 0x44, + (byte) 0x57, (byte) 0x45, (byte) 0x37, (byte) 0x38, (byte) 0x50, + (byte) 0x6D, (byte) 0x51, (byte) 0x57, (byte) 0x5F, (byte) 0x62, + (byte) 0x67, (byte) 0x68, (byte) 0x67, (byte) 0x3E, (byte) 0x4D, + (byte) 0x71, (byte) 0x79, (byte) 0x70, (byte) 0x64, (byte) 0x78, + (byte) 0x5C, (byte) 0x65, (byte) 0x67, (byte) 0x63, + + (byte) 0x01, // 8 bit values, (byte) table 1 + (byte) 0x11, (byte) 0x12, (byte) 0x12, (byte) 0x18, (byte) 0x15, + (byte) 0x18, (byte) 0x2F, (byte) 0x1A, (byte) 0x1A, (byte) 0x2F, + (byte) 0x63, (byte) 0x42, (byte) 0x38, (byte) 0x42, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + + // Define huffman table (section B.2.4.1) + (byte) 0xFF, (byte) 0xC4, // Marker + (byte) 0x00, (byte) 0x1F, // Length (31 bytes) + (byte) 0x00, // DC, (byte) table 0 + (byte) 0x00, (byte) 0x01, (byte) 0x05, (byte) 0x01, (byte) 0x01, + (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, + (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08, + (byte) 0x09, (byte) 0x0A, (byte) 0x0B, + + // Define huffman table (section B.2.4.1) + (byte) 0xFF, (byte) 0xC4, // Marker + (byte) 0x00, (byte) 0xB5, // Length (181 bytes) + (byte) 0x10, // AC, (byte) table 0 + (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x03, (byte) 0x03, + (byte) 0x02, (byte) 0x04, (byte) 0x03, (byte) 0x05, (byte) 0x05, + (byte) 0x04, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x7D, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x00, + (byte) 0x04, (byte) 0x11, (byte) 0x05, (byte) 0x12, (byte) 0x21, + (byte) 0x31, (byte) 0x41, (byte) 0x06, (byte) 0x13, (byte) 0x51, + (byte) 0x61, (byte) 0x07, (byte) 0x22, (byte) 0x71, (byte) 0x14, + (byte) 0x32, (byte) 0x81, (byte) 0x91, (byte) 0xA1, (byte) 0x08, + (byte) 0x23, (byte) 0x42, (byte) 0xB1, (byte) 0xC1, (byte) 0x15, + (byte) 0x52, (byte) 0xD1, (byte) 0xF0, (byte) 0x24, (byte) 0x33, + (byte) 0x62, (byte) 0x72, (byte) 0x82, (byte) 0x09, (byte) 0x0A, + (byte) 0x16, (byte) 0x17, (byte) 0x18, (byte) 0x19, (byte) 0x1A, + (byte) 0x25, (byte) 0x26, (byte) 0x27, (byte) 0x28, (byte) 0x29, + (byte) 0x2A, (byte) 0x34, (byte) 0x35, (byte) 0x36, (byte) 0x37, + (byte) 0x38, (byte) 0x39, (byte) 0x3A, (byte) 0x43, (byte) 0x44, + (byte) 0x45, (byte) 0x46, (byte) 0x47, (byte) 0x48, (byte) 0x49, + (byte) 0x4A, (byte) 0x53, (byte) 0x54, (byte) 0x55, (byte) 0x56, + (byte) 0x57, (byte) 0x58, (byte) 0x59, (byte) 0x5A, (byte) 0x63, + (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, + (byte) 0x69, (byte) 0x6A, (byte) 0x73, (byte) 0x74, (byte) 0x75, + (byte) 0x76, (byte) 0x77, (byte) 0x78, (byte) 0x79, (byte) 0x7A, + (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, + (byte) 0x88, (byte) 0x89, (byte) 0x8A, (byte) 0x92, (byte) 0x93, + (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, + (byte) 0x99, (byte) 0x9A, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, + (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, + (byte) 0xAA, (byte) 0xB2, (byte) 0xB3, (byte) 0xB4, (byte) 0xB5, + (byte) 0xB6, (byte) 0xB7, (byte) 0xB8, (byte) 0xB9, (byte) 0xBA, + (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, + (byte) 0xC7, (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xD2, + (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7, + (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xE1, (byte) 0xE2, + (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, + (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xF1, (byte) 0xF2, + (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, + (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, + + // Define huffman table (section B.2.4.1) + (byte) 0xFF, (byte) 0xC4, // Marker + (byte) 0x00, (byte) 0x1F, // Length (31 bytes) + (byte) 0x01, // DC, (byte) table 1 + (byte) 0x00, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0x01, + (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, + (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08, + (byte) 0x09, (byte) 0x0A, (byte) 0x0B, + + // Define huffman table (section B.2.4.1) + (byte) 0xFF, (byte) 0xC4, // Marker + (byte) 0x00, (byte) 0xB5, // Length (181 bytes) + (byte) 0x11, // AC, (byte) table 1 + (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x02, (byte) 0x04, + (byte) 0x04, (byte) 0x03, (byte) 0x04, (byte) 0x07, (byte) 0x05, + (byte) 0x04, (byte) 0x04, (byte) 0x00, (byte) 0x01, (byte) 0x02, + (byte) 0x77, (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, + (byte) 0x11, (byte) 0x04, (byte) 0x05, (byte) 0x21, (byte) 0x31, + (byte) 0x06, (byte) 0x12, (byte) 0x41, (byte) 0x51, (byte) 0x07, + (byte) 0x61, (byte) 0x71, (byte) 0x13, (byte) 0x22, (byte) 0x32, + (byte) 0x81, (byte) 0x08, (byte) 0x14, (byte) 0x42, (byte) 0x91, + (byte) 0xA1, (byte) 0xB1, (byte) 0xC1, (byte) 0x09, (byte) 0x23, + (byte) 0x33, (byte) 0x52, (byte) 0xF0, (byte) 0x15, (byte) 0x62, + (byte) 0x72, (byte) 0xD1, (byte) 0x0A, (byte) 0x16, (byte) 0x24, + (byte) 0x34, (byte) 0xE1, (byte) 0x25, (byte) 0xF1, (byte) 0x17, + (byte) 0x18, (byte) 0x19, (byte) 0x1A, (byte) 0x26, (byte) 0x27, + (byte) 0x28, (byte) 0x29, (byte) 0x2A, (byte) 0x35, (byte) 0x36, + (byte) 0x37, (byte) 0x38, (byte) 0x39, (byte) 0x3A, (byte) 0x43, + (byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, (byte) 0x48, + (byte) 0x49, (byte) 0x4A, (byte) 0x53, (byte) 0x54, (byte) 0x55, + (byte) 0x56, (byte) 0x57, (byte) 0x58, (byte) 0x59, (byte) 0x5A, + (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, + (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x73, (byte) 0x74, + (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x78, (byte) 0x79, + (byte) 0x7A, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, + (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x8A, + (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, + (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x9A, (byte) 0xA2, + (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, + (byte) 0xA8, (byte) 0xA9, (byte) 0xAA, (byte) 0xB2, (byte) 0xB3, + (byte) 0xB4, (byte) 0xB5, (byte) 0xB6, (byte) 0xB7, (byte) 0xB8, + (byte) 0xB9, (byte) 0xBA, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, + (byte) 0xC5, (byte) 0xC6, (byte) 0xC7, (byte) 0xC8, (byte) 0xC9, + (byte) 0xCA, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, + (byte) 0xD6, (byte) 0xD7, (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, + (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, + (byte) 0xE7, (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xF2, + (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, + (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, + + // Start of Scan (section B.2.3) + (byte) 0xFF, (byte) 0xDA, // Marker + (byte) 0x00, (byte) 0x0C, // Length of header + (byte) 0x03, // Number of image components + // Scan 1: use DC/AC huff tables 0/0 + (byte) 0x01, (byte) 0x00, + // Scan 2: use DC/AC huff tables 1/1 + (byte) 0x02, (byte) 0x11, + // Scan 3: use DC/AC huff tables 1/1 + (byte) 0x03, (byte) 0x11, + (byte) 0x00, (byte) 0x3F, (byte) 0x00 // Not used + }; + + // This array represents the default JFIF header for quality = 100 and + // size = 640x480, without Huffman tables. The values are adjusted when a + // file is generated. + private static byte[] jfifNoHuff100Header = { + // SOI + (byte) 0xFF, (byte) 0xD8, + + // JFIF header + (byte) 0xFF, (byte) 0xE0, // Marker + (byte) 0x00, (byte) 0x10, // Length = 16 bytes + (byte) 0x4A, (byte) 0x46, (byte) 0x49, (byte) 0x46, // "JFIF" + (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, + + // Start of frame (section B.2.2) + (byte) 0xFF, (byte) 0xC0, // Baseline DCT + (byte) 0x00, (byte) 0x11, // Length = 17 bytes + (byte) 0x08, // Sample precision + (byte) 0x01, (byte) 0xE0, // Height + (byte) 0x02, (byte) 0x80, // Width + (byte) 0x03, // Number of components = 3 + // Scan 1: 2:1 horiz, (byte) 1:1 vertical, (byte) use QT 0 + (byte) 0x01, (byte) 0x21, (byte) 0x00, + // Scan 2: 1:1 horiz, (byte) 1:1 vertical, (byte) use QT 1 + (byte) 0x02, (byte) 0x11, (byte) 0x01, + // Scan 3: 1:1 horiz, (byte) 1:1 vertical, (byte) use QT 1 + (byte) 0x03, (byte) 0x11, (byte) 0x01, + + // Define Quant table (section B.2.4.1) + (byte) 0xFF, (byte) 0xDB, // Marker + (byte) 0x00, (byte) 0x84, // Length (both tables) + (byte) 0x00, // 8 bit values, (byte) table 0 + (byte) 0x10, (byte) 0x0B, (byte) 0x0C, (byte) 0x0E, (byte) 0x0C, + (byte) 0x0A, (byte) 0x10, (byte) 0x0E, (byte) 0x0D, (byte) 0x0E, + (byte) 0x12, (byte) 0x11, (byte) 0x10, (byte) 0x13, (byte) 0x18, + (byte) 0x28, (byte) 0x1A, (byte) 0x18, (byte) 0x16, (byte) 0x16, + (byte) 0x18, (byte) 0x31, (byte) 0x23, (byte) 0x25, (byte) 0x1D, + (byte) 0x28, (byte) 0x3A, (byte) 0x33, (byte) 0x3D, (byte) 0x3C, + (byte) 0x39, (byte) 0x33, (byte) 0x38, (byte) 0x37, (byte) 0x40, + (byte) 0x48, (byte) 0x5C, (byte) 0x4E, (byte) 0x40, (byte) 0x44, + (byte) 0x57, (byte) 0x45, (byte) 0x37, (byte) 0x38, (byte) 0x50, + (byte) 0x6D, (byte) 0x51, (byte) 0x57, (byte) 0x5F, (byte) 0x62, + (byte) 0x67, (byte) 0x68, (byte) 0x67, (byte) 0x3E, (byte) 0x4D, + (byte) 0x71, (byte) 0x79, (byte) 0x70, (byte) 0x64, (byte) 0x78, + (byte) 0x5C, (byte) 0x65, (byte) 0x67, (byte) 0x63, + + (byte) 0x01, // 8 bit values, (byte) table 1 + (byte) 0x11, (byte) 0x12, (byte) 0x12, (byte) 0x18, (byte) 0x15, + (byte) 0x18, (byte) 0x2F, (byte) 0x1A, (byte) 0x1A, (byte) 0x2F, + (byte) 0x63, (byte) 0x42, (byte) 0x38, (byte) 0x42, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + (byte) 0x63, (byte) 0x63, (byte) 0x63, (byte) 0x63, + + // Start of Scan (section B.2.3) + (byte) 0xFF, (byte) 0xDA, // Marker + (byte) 0x00, (byte) 0x0C, // Length of header + (byte) 0x03, // Number of image components + // Scan 1: use DC/AC huff tables 0/0 + (byte) 0x01, (byte) 0x00, + // Scan 2: use DC/AC huff tables 1/1 + (byte) 0x02, (byte) 0x11, + // Scan 3: use DC/AC huff tables 1/1 + (byte) 0x03, (byte) 0x11, + (byte) 0x00, (byte) 0x3F, (byte) 0x00 // Not used + }; + + private void writeJfifHuffHeader() throws IOException + { + byte[] newHeader = new byte[jfifHuff100Header.length]; + + System.arraycopy( + jfifHuff100Header, 0, newHeader, 0, jfifHuff100Header.length ); + + // Set image width in JFIF header. + newHeader[27] = (byte) ( ( width >>> 8 ) & 0xff ); + newHeader[28] = (byte) ( width & 0xff ); + + // Set image height in JFIF header. + newHeader[25] = (byte) ( ( height >>> 8 ) & 0xff ); + newHeader[26] = (byte) ( height & 0xff ); + + // Adjust the quality factor. + // + // The default quality factor is 100, therefore if + // our quality factor does not equal 100 we must + // scale the quantization matrices in the JFIF header. + // Note that values are clipped to a max of 255. + if ( qfactor != 100 ) + { + for ( int i = 44; i < 108; ++i ) + { + int t = ( newHeader[i] * qfactor ) / 100; + newHeader[i] = (byte) Math.max( t, 0xff ); + } + for ( int i = 109; i < 173; ++i ) + { + int t = ( newHeader[i] * qfactor ) / 100; + newHeader[i] = (byte) Math.max( t, 0xff ); + } + } + + // Write out buffer. + out.write( newHeader ); + } + + private void writeJfifNoHuffHeader() throws IOException + { + byte[] newHeader = new byte[jfifNoHuff100Header.length]; + + System.arraycopy( + jfifNoHuff100Header, 0, newHeader, 0, jfifNoHuff100Header.length ); + + // Set image width in JFIF header. + newHeader[27] = (byte) ( ( width >>> 8 ) & 0xff ); + newHeader[28] = (byte) ( width & 0xff ); + + // Set image height in JFIF header. + newHeader[25] = (byte) ( ( height >>> 8 ) & 0xff ); + newHeader[26] = (byte) ( height & 0xff ); + + // Adjust the quality factor. + // + // The default quality factor is 100, therefore if + // our quality factor does not equal 100 we must + // scale the quantization matrices in the JFIF header. + // Note that values are clipped to a max of 255. + if ( qfactor != 100 ) + { + for ( int i = 44; i < 108; ++i ) + { + int t = ( newHeader[i] * qfactor ) / 100; + newHeader[i] = (byte) Math.max( t, 0xff ); + } + for ( int i = 109; i < 173; ++i ) + { + int t = ( newHeader[i] * qfactor ) / 100; + newHeader[i] = (byte) Math.max( t, 0xff ); + } + } + + // Write out buffer. + out.write( newHeader ); + } + + } diff --git a/src/Acme/JPM/Encoders/PpmEncoder.java b/src/Acme/JPM/Encoders/PpmEncoder.java new file mode 100644 index 00000000..bb15382e --- /dev/null +++ b/src/Acme/JPM/Encoders/PpmEncoder.java @@ -0,0 +1,103 @@ +// PpmEncoder - write out an image as a PPM +// +// 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Encoders; + +import java.util.*; +import java.io.*; +import java.awt.Image; +import java.awt.image.*; + +/// Write out an image as a PPM. +//

+// Writes an image onto a specified OutputStream in the PPM file format. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @see ToPpm + +public class PpmEncoder extends ImageEncoder + { + + /// Constructor. + // @param img The image to encode. + // @param out The stream to write the PPM to. + public PpmEncoder( Image img, OutputStream out ) throws IOException + { + super( img, out ); + } + + /// Constructor. + // @param prod The ImageProducer to encode. + // @param out The stream to write the PPM to. + public PpmEncoder( ImageProducer prod, OutputStream out ) throws IOException + { + super( prod, out ); + } + + + void encodeStart( int width, int height ) throws IOException + { + writeString( out, "P6\n" ); + writeString( out, width + " " + height + "\n" ); + writeString( out, "255\n" ); + } + + static void writeString( OutputStream out, String str ) throws IOException + { + byte[] buf = str.getBytes(); + out.write( buf ); + } + + void encodePixels( + int x, int y, int w, int h, int[] rgbPixels, int off, int scansize ) + throws IOException + { + byte[] ppmPixels = new byte[w * 3]; + for ( int row = 0; row < h; ++row ) + { + int rowOff = off + row * scansize; + for ( int col = 0; col < w; ++col ) + { + int i = rowOff + col; + int j = col * 3; + ppmPixels[j ] = (byte) ( ( rgbPixels[i] & 0xff0000 ) >> 16 ); + ppmPixels[j + 1] = (byte) ( ( rgbPixels[i] & 0x00ff00 ) >> 8 ); + ppmPixels[j + 2] = (byte) ( rgbPixels[i] & 0x0000ff ); + } + out.write( ppmPixels ); + } + } + + void encodeDone() throws IOException + { + // Nothing. + } + + } diff --git a/src/Acme/JPM/Filters/CompositeFilter.java b/src/Acme/JPM/Filters/CompositeFilter.java new file mode 100644 index 00000000..0ed59c1f --- /dev/null +++ b/src/Acme/JPM/Filters/CompositeFilter.java @@ -0,0 +1,106 @@ +// CompositeFilter - compose two filters into one +// +// Copyright (C) 1997 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; +import java.util.*; + +/// Compose two filters into one. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class CompositeFilter extends ImageFilterPlus + { + + private ImageFilterPlus filterOne, filterTwo; + private ImageFilter instanceOne, instanceTwo; + + /// Constructor. Builds a filter chain with a FilteredImageSource as + // the glue. + public CompositeFilter( ImageProducer producer, ImageFilterPlus filterOne, ImageFilterPlus filterTwo ) + { + super( producer ); + this.filterOne = filterOne; + this.filterTwo = filterTwo; + + filterOne.setSource( producer ); + ImageProducer producerOne = + new FilteredImageSource( producer, filterOne ); + filterTwo.setSource( producerOne ); + } + + public ImageFilter getFilterInstance( ImageConsumer consumer ) + { + CompositeFilter instance = (CompositeFilter) clone(); + instance.instanceTwo = filterTwo.getFilterInstance( consumer ); + instance.instanceOne = filterOne.getFilterInstance( instanceTwo ); + return (ImageFilter) instance; + } + + + // The rest of the methods just delegate to instanceOne. + + public void setColorModel( ColorModel model ) + { + instanceOne.setColorModel( model ); + } + + public void setDimensions( int width, int height ) + { + instanceOne.setDimensions( width, height ); + } + + public void setHints( int hintflags ) + { + instanceOne.setHints( hintflags ); + } + + public void setProperties( Hashtable props ) + { + instanceOne.setProperties( props ); + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize ) + { + instanceOne.setPixels( x, y, w, h, model, pixels, off, scansize ); + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize ) + { + instanceOne.setPixels( x, y, w, h, model, pixels, off, scansize ); + } + + public void imageComplete( int status ) + { + //super.imageComplete( status ); + instanceOne.imageComplete( status ); + } + + } diff --git a/src/Acme/JPM/Filters/EdgeDetect.java b/src/Acme/JPM/Filters/EdgeDetect.java new file mode 100644 index 00000000..f5758b07 --- /dev/null +++ b/src/Acme/JPM/Filters/EdgeDetect.java @@ -0,0 +1,169 @@ +// EdgeDetect - edge-detection filter +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.io.*; +import java.awt.image.*; +import Acme.JPM.Decoders.*; +import Acme.JPM.Encoders.*; + +/// Edge-detection filter. +// +// +//

+// Outlines the edges of an image. +// The edge detection technique used is to take the Pythagorean sum of +// two Sobel gradient operators at 90 degrees to each other, separately +// for each color component. +// For more details see "Digital Image Processing" by Gonzalez and Wintz, +// chapter 7. +//

+// This filter is slow. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class EdgeDetect extends RGBAllFilter + { + + // Constructor. + public EdgeDetect( ImageProducer producer ) + { + super( producer ); + } + + + private static final double SCALE = 1.8D; + + public void filterRGBAll( int width, int height, int[][] rgbPixels ) + { + int[][] newPixels = new int[height][width]; + long sum1, sum2; + double sum; + int r, g, b; + + // First and last rows are black. + for ( int col = 0; col < width; ++col ) + { + newPixels[0][col] = 0xff000000; + newPixels[height - 1][col] = 0xff000000; + } + for ( int row = 1; row < height - 1; ++row ) + { + // First and last columns are black too. + newPixels[row][0] = 0xff000000; + newPixels[row][width - 1] = 0xff000000; + // The real pixels. + for ( int col = 1; col < width - 1; ++col ) + { + sum1 = + rgbModel.getRed( rgbPixels[row - 1][col + 1] ) - + rgbModel.getRed( rgbPixels[row - 1][col - 1] ) + + 2 * ( + rgbModel.getRed( rgbPixels[row][col + 1] ) - + rgbModel.getRed( rgbPixels[row][col - 1] ) ) + + rgbModel.getRed( rgbPixels[row + 1][col + 1] ) - + rgbModel.getRed( rgbPixels[row + 1][col - 1] ); + sum2 = ( + rgbModel.getRed( rgbPixels[row + 1][col - 1] ) + + 2 * rgbModel.getRed( rgbPixels[row + 1][col] ) + + rgbModel.getRed( rgbPixels[row + 1][col + 1] ) + ) - ( + rgbModel.getRed( rgbPixels[row - 1][col - 1] ) + + 2 * rgbModel.getRed( rgbPixels[row - 1][col] ) + + rgbModel.getRed( rgbPixels[row - 1][col + 1] ) + ); + sum = Math.sqrt( (double) ( sum1*sum1 + sum2*sum2 ) ) / SCALE; + r = Math.min( (int) sum, 255 ); + + sum1 = + rgbModel.getGreen( rgbPixels[row - 1][col + 1] ) - + rgbModel.getGreen( rgbPixels[row - 1][col - 1] ) + + 2 * ( + rgbModel.getGreen( rgbPixels[row][col + 1] ) - + rgbModel.getGreen( rgbPixels[row][col - 1] ) ) + + rgbModel.getGreen( rgbPixels[row + 1][col + 1] ) - + rgbModel.getGreen( rgbPixels[row + 1][col - 1] ); + sum2 = ( + rgbModel.getGreen( rgbPixels[row + 1][col - 1] ) + + 2 * rgbModel.getGreen( rgbPixels[row + 1][col] ) + + rgbModel.getGreen( rgbPixels[row + 1][col + 1] ) + ) - ( + rgbModel.getGreen( rgbPixels[row - 1][col - 1] ) + + 2 * rgbModel.getGreen( rgbPixels[row - 1][col] ) + + rgbModel.getGreen( rgbPixels[row - 1][col + 1] ) + ); + sum = Math.sqrt( (double) ( sum1*sum1 + sum2*sum2 ) ) / SCALE; + g = Math.min( (int) sum, 255 ); + + sum1 = + rgbModel.getBlue( rgbPixels[row - 1][col + 1] ) - + rgbModel.getBlue( rgbPixels[row - 1][col - 1] ) + + 2 * ( + rgbModel.getBlue( rgbPixels[row][col + 1] ) - + rgbModel.getBlue( rgbPixels[row][col - 1] ) ) + + rgbModel.getBlue( rgbPixels[row + 1][col + 1] ) - + rgbModel.getBlue( rgbPixels[row + 1][col - 1] ); + sum2 = ( + rgbModel.getBlue( rgbPixels[row + 1][col - 1] ) + + 2 * rgbModel.getBlue( rgbPixels[row + 1][col] ) + + rgbModel.getBlue( rgbPixels[row + 1][col + 1] ) + ) - ( + rgbModel.getBlue( rgbPixels[row - 1][col - 1] ) + + 2 * rgbModel.getBlue( rgbPixels[row - 1][col] ) + + rgbModel.getBlue( rgbPixels[row - 1][col + 1] ) + ); + sum = Math.sqrt( (double) ( sum1*sum1 + sum2*sum2 ) ) / SCALE; + b = Math.min( (int) sum, 255 ); + + newPixels[row][col] = + 0xff000000 | ( r << 16 ) | ( g << 8 ) | b; + } + } + setPixels( width, height, newPixels ); + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + if ( args.length != 0 ) + usage(); + ImageFilterPlus filter = new EdgeDetect( null ); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: EdgeDetect" ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/Filters/Enlarge.java b/src/Acme/JPM/Filters/Enlarge.java new file mode 100644 index 00000000..e090b2bd --- /dev/null +++ b/src/Acme/JPM/Filters/Enlarge.java @@ -0,0 +1,140 @@ +// Enlarge - an ImageFilter that enlarges by pixel replication +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// An ImageFilter that enlarges by pixel replication. +//

+// Enlarges an image an integral factor by replicating pixels. +// The output uses the same color model as the input. +// This filter is very fast. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @see Shrink +// @see ScaleCopy + +public class Enlarge extends ImageFilterPlus + { + + private int multiplier; + private int newWidth, newHeight; + + /// Constructor. + public Enlarge( ImageProducer producer, int multiplier ) + { + super( producer ); + this.multiplier = multiplier; + } + + + public void setDimensions( int width, int height ) + { + newWidth = width * multiplier; + newHeight = height * multiplier; + consumer.setDimensions( newWidth, newHeight ); + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize ) + { + int newX = Math.min( x * multiplier, newWidth - 1 ); + int newY = Math.min( y * multiplier, newHeight - 1 ); + int newW = w * multiplier; + if ( newX + newW > newWidth ) + newW = newWidth - newX; + int newH = h * multiplier; + if ( newY + newH > newHeight ) + newH = newHeight - newY; + byte[] newPixels = new byte[newW * newH]; + for ( int row = 0; row < h; ++row ) + { + for ( int col = 0; col < w; ++col ) + { + byte pixel = pixels[row * scansize + off + col]; + for ( int i = 0; i < multiplier; ++i ) + for ( int j = 0; j < multiplier; ++j ) + { + int newRow = row * multiplier + i; + int newCol = col * multiplier + j; + newPixels[newRow * newW + newCol] = pixel; + } + } + } + consumer.setPixels( newX, newY, newW, newH, model, newPixels, 0, newW ); + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize ) + { + int newX = Math.min( x * multiplier, newWidth - 1 ); + int newY = Math.min( y * multiplier, newHeight - 1 ); + int newW = w * multiplier; + if ( newX + newW > newWidth ) + newW = newWidth - newX; + int newH = h * multiplier; + if ( newY + newH > newHeight ) + newH = newHeight - newY; + int[] newPixels = new int[newW * newH]; + for ( int row = 0; row < h; ++row ) + { + for ( int col = 0; col < w; ++col ) + { + int pixel = pixels[row * scansize + off + col]; + for ( int i = 0; i < multiplier; ++i ) + for ( int j = 0; j < multiplier; ++j ) + { + int newRow = row * multiplier + i; + int newCol = col * multiplier + j; + newPixels[newRow * newW + newCol] = pixel; + } + } + } + consumer.setPixels( newX, newY, newW, newH, model, newPixels, 0, newW ); + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + if ( args.length != 1 ) + usage(); + ImageFilterPlus filter = + new Enlarge( null, Integer.parseInt( args[0] ) ); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: Enlarge " ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/Filters/Flip.java b/src/Acme/JPM/Filters/Flip.java new file mode 100644 index 00000000..25c25b6c --- /dev/null +++ b/src/Acme/JPM/Filters/Flip.java @@ -0,0 +1,295 @@ +// Flip - an ImageFilter that flips or rotates the image +// +// Copyright (C) 1997 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// An ImageFilter that flips or rotates the image. +//

+// Flips the image left-right, top-bottom, rotates clockwise or +// counter-clockwise, or otherwise munges the image as specified. +// This filter is fast. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class Flip extends ImageFilterPlus + { + + /// The null transformation. + public static final int FLIP_NULL = 0; + + /// Flip left to right. + public static final int FLIP_LR = 1; + + /// Flip top to bottom. + public static final int FLIP_TB = 2; + + /// Transpose X and Y - reflection along a diagonal. + public static final int FLIP_XY = 3; + + /// Rotate clockwise 90 degrees. + public static final int FLIP_CW = 4; + + /// Rotate counter-clockwise 90 degrees. + public static final int FLIP_CCW = 5; + + /// Rotate 180 degrees. + public static final int FLIP_R180 = 6; + + private int flipType; + private int width, height; + private int newWidth, newHeight; + + /// Constructor. + public Flip( ImageProducer producer, int flipType ) + { + super( producer, true ); + this.flipType = flipType; + } + + + public void setDimensions( int width, int height ) + { + this.width = width; + this.height = height; + switch ( flipType ) + { + case FLIP_NULL: + case FLIP_LR: + case FLIP_TB: + case FLIP_R180: + newWidth = width; + newHeight = height; + break; + case FLIP_XY: + case FLIP_CW: + case FLIP_CCW: + newWidth = height; + newHeight = width; + break; + } + consumer.setDimensions( newWidth, newHeight ); + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize ) + { + int newX = x; + int newY = y; + int newW = w; + int newH = h; + switch ( flipType ) + { + case FLIP_NULL: + break; + case FLIP_LR: + newX = width - ( x + w ); + break; + case FLIP_TB: + newY = height - ( y + h ); + break; + case FLIP_XY: + newW = h; + newH = w; + newX = y; + newY = x; + break; + case FLIP_CW: + newW = h; + newH = w; + newX = height - ( y + h ); + newY = x; + break; + case FLIP_CCW: + newW = h; + newH = w; + newX = y; + newY = width - ( x + w ); + break; + case FLIP_R180: + newX = width - ( x + w ); + newY = height - ( y + h ); + break; + } + byte[] newPixels = new byte[newW * newH]; + for ( int row = 0; row < h; ++row ) + { + for ( int col = 0; col < w; ++col ) + { + int index = row * scansize + off + col; + int newRow = row; + int newCol = col; + switch ( flipType ) + { + case FLIP_NULL: + break; + case FLIP_LR: + newCol = w - col - 1; + break; + case FLIP_TB: + newRow = h - row - 1; + break; + case FLIP_XY: + newRow = col; + newCol = row; + break; + case FLIP_CW: + newRow = col; + newCol = h - row - 1;; + break; + case FLIP_CCW: + newRow = w - col - 1; + newCol = row; + break; + case FLIP_R180: + newRow = h - row - 1; + newCol = w - col - 1; + break; + } + int newIndex = newRow * newW + newCol; + newPixels[newIndex] = pixels[index]; + } + } + consumer.setPixels( newX, newY, newW, newH, model, newPixels, 0, newW ); + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize ) + { + int newX = x; + int newY = y; + int newW = w; + int newH = h; + switch ( flipType ) + { + case FLIP_NULL: + break; + case FLIP_LR: + newX = width - ( x + w ); + break; + case FLIP_TB: + newY = height - ( y + h ); + break; + case FLIP_XY: + newW = h; + newH = w; + newX = y; + newY = x; + break; + case FLIP_CW: + newW = h; + newH = w; + newX = height - ( y + h ); + newY = x; + break; + case FLIP_CCW: + newW = h; + newH = w; + newX = y; + newY = width - ( x + w ); + break; + case FLIP_R180: + newX = width - ( x + w ); + newY = height - ( y + h ); + break; + } + int[] newPixels = new int[newW * newH]; + for ( int row = 0; row < h; ++row ) + { + for ( int col = 0; col < w; ++col ) + { + int index = row * scansize + off + col; + int newRow = row; + int newCol = col; + switch ( flipType ) + { + case FLIP_NULL: + break; + case FLIP_LR: + newCol = w - col - 1; + break; + case FLIP_TB: + newRow = h - row - 1; + break; + case FLIP_XY: + newRow = col; + newCol = row; + break; + case FLIP_CW: + newRow = col; + newCol = h - row - 1;; + break; + case FLIP_CCW: + newRow = w - col - 1; + newCol = row; + break; + case FLIP_R180: + newRow = h - row - 1; + newCol = w - col - 1; + break; + } + int newIndex = newRow * newW + newCol; + newPixels[newIndex] = pixels[index]; + } + } + consumer.setPixels( newX, newY, newW, newH, model, newPixels, 0, newW ); + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + if ( args.length != 1 ) + usage(); + int flipType = FLIP_NULL; + if ( args[0].equalsIgnoreCase( "-lr" ) ) + flipType = FLIP_LR; + else if ( args[0].equalsIgnoreCase( "-tb" ) ) + flipType = FLIP_TB; + else if ( args[0].equalsIgnoreCase( "-xy" ) ) + flipType = FLIP_XY; + else if ( args[0].equalsIgnoreCase( "-cw" ) ) + flipType = FLIP_CW; + else if ( args[0].equalsIgnoreCase( "-ccw" ) ) + flipType = FLIP_CCW; + else if ( args[0].equalsIgnoreCase( "-r180" ) ) + flipType = FLIP_R180; + else + usage(); + ImageFilterPlus filter = new Flip( null, flipType ); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: Flip -lr|-tb|-xy|-cw|-ccw|-r180" ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/Filters/Gamma.java b/src/Acme/JPM/Filters/Gamma.java new file mode 100644 index 00000000..765c7fd7 --- /dev/null +++ b/src/Acme/JPM/Filters/Gamma.java @@ -0,0 +1,148 @@ +// Gamma - gamma-correction filter +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// Gamma-correction filter. +//

+// Gamma correction fixes a form of color distortion common to many monitors. +// Values less than 1.0 darken the image, and greater than 1.0 lighten it. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class Gamma extends RGBBlockFilter + { + + private double rValue, gValue, bValue; + + /// Constructor, single exponent. + public Gamma( ImageProducer producer, double value ) + { + this( producer, value, value, value ); + } + + /// Constructor, different exponents for R G and B. + public Gamma( ImageProducer producer, double rValue, double gValue, double bValue ) + { + super( producer ); + this.rValue = rValue; + this.gValue = gValue; + this.bValue = bValue; + } + + + private int[] rTable, gTable, bTable; + + public int[][] filterRGBBlock( int x, int y, int width, int height, int[][] rgbPixels ) + { + initialize(); + for ( int row = 0; row < height; ++row ) + for ( int col = 0; col < width; ++col ) + { + int rgb = rgbPixels[row][col]; + int a = ( rgb >> 24 ) & 0xff; + int r = ( rgb >> 16 ) & 0xff; + int g = ( rgb >> 8 ) & 0xff; + int b = rgb & 0xff; + r = rTable[r]; + g = gTable[g]; + b = bTable[b]; + rgbPixels[row][col] = + ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b; + } + return rgbPixels; + } + + + private boolean initialized = false; + + private void initialize() + { + if ( initialized ) + return; + initialized = true; + + rTable = buildTable( rValue ); + + if ( gValue == rValue ) + gTable = rTable; + else + gTable = buildTable( gValue ); + + if ( bValue == rValue ) + bTable = rTable; + else if ( bValue == gValue ) + bTable = gTable; + else + bTable = buildTable( bValue ); + } + + private int[] buildTable( double gamma ) + { + int[] table = new int[256]; + double oneOverGamma = 1.0D / gamma; + for ( int i = 0; i < 256; ++i ) + { + int v = (int) ( + ( 255.0D * Math.pow( i / 255.0D, oneOverGamma ) ) + 0.5D ); + if ( v > 255 ) + v = 255; + table[i] = v; + } + return table; + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + ImageFilterPlus filter = null; + if ( args.length == 1 ) + filter = new Gamma( null, Double.valueOf( args[0] ).doubleValue() ); + else if ( args.length == 3 ) + filter = new Gamma( null, + Double.valueOf( args[0] ).doubleValue(), + Double.valueOf( args[1] ).doubleValue(), + Double.valueOf( args[2] ).doubleValue() ); + else + usage(); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: Gamma " ); + System.err.println( "or: Gamma " ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/Filters/ImageFilterPlus.java b/src/Acme/JPM/Filters/ImageFilterPlus.java new file mode 100644 index 00000000..7c36b8d9 --- /dev/null +++ b/src/Acme/JPM/Filters/ImageFilterPlus.java @@ -0,0 +1,160 @@ +// ImageFilterPlus - an ImageFilter with some extra features and bug fixes +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; +import java.io.*; +import Acme.JPM.Decoders.*; +import Acme.JPM.Encoders.*; + +/// An ImageFilter with some extra features and bug fixes. +//

+// You can use an image filter to turn one Image into another via +// a FilteredImageSource, e.g.: +//

+// Image newImage = comp.createImage( new FilteredImageSource(
+//     oldImage.getSource(), new SomeFilter( oldImage.getSource() ) ) );
+// 
+// Or use the convenient utility JPMUtils.filterImage(): +//
+// Image newImage = JPMUtils.filterImage(
+//     comp, SomeFilter( oldImage.getSource() ) );
+// 
+//

+// You can also use image filters from the command line, reading PPM +// from stdin and writing PPM to stdout, if you add code like the following +// to each filter: +//

+// System.exit( 
+//     ImageFilterPlus.filterStream(
+//         System.in, System.out,
+//         new SomeFilter( null ) ) );
+// 
+//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class ImageFilterPlus extends ImageFilter + { + + private ImageProducer producer; + private boolean pixelOrderChanges; + + /// Constructor. + // @param producer The ImageProducer is required, so that we can + // remove ourself from its consumers list when we're done. + // However, if you don't have the producer available when you want + // to create the filter, you can pass in null and set it later + // via setSource(). + public ImageFilterPlus( ImageProducer producer ) + { + this( producer, false ); + } + + /// Constructor, with pixel order change. + // @param producer The ImageProducer is required, so that we can + // remove ourself from its consumers list when we're done. + // However, if you don't have the producer available when you want + // to create the filter, you can pass in null and set it later + // via setSource(). + // @param pixelOrderChanges If the filter may output pixels in a different + // order from the one they were delivered in, this flag must be set. + public ImageFilterPlus( ImageProducer producer, boolean pixelOrderChanges ) + { + setSource( producer ); + this.pixelOrderChanges = pixelOrderChanges; + } + + + /// The default color model - useful for comparisons. + public static final ColorModel rgbModel = ColorModel.getRGBdefault(); + + + /// Return the ImageProducer for this filter. + public ImageProducer getSource() + { + return producer; + } + + /// Set the ImageProducer for this filter, if it wasn't set by the + // constructor. + public void setSource( ImageProducer producer ) + { + this.producer = producer; + } + + + /// Set the hint flags. If the pixel order may change, we have to + // turn off the TOPDOWNLEFTRIGHT flag; otherwise the flags are passed + // through unmodified. + public void setHints( int hintflags ) + { + if ( pixelOrderChanges ) + hintflags &= ~TOPDOWNLEFTRIGHT; + consumer.setHints( hintflags ); + } + + + /// This routine fixes a bug in java.awt.image.ImageFilter. All + // ImageConsumers are required remove themselves from the producer's + // list when they're done reading. If they don't do this then some + // producers will generate an error. The standard ImageFilter class + // fails to do this, but this one does it. + public void imageComplete( int status ) + { + if ( status != ImageConsumer.SINGLEFRAMEDONE ) + producer.removeConsumer( this ); + super.imageComplete( status ); + } + + + /// Filter a PPM InputStream to a PPM OutputStream. + //

+ // Create the filter with a null producer, and this routine will + // fill it in for you. + // @return a status code suitable for use with System.exit(). + public static int filterStream( InputStream in, OutputStream out, ImageFilterPlus filter ) + { + ImageDecoder producer = new PpmDecoder( in ); + filter.setSource( producer ); + try + { + ImageEncoder consumer = new PpmEncoder( + new FilteredImageSource( producer, filter ), out ); + consumer.encode(); + } + catch ( IOException e ) + { + System.err.println( e.toString() ); + return 1; + } + return 0; + } + + } diff --git a/src/Acme/JPM/Filters/Invert.java b/src/Acme/JPM/Filters/Invert.java new file mode 100644 index 00000000..c4d65b32 --- /dev/null +++ b/src/Acme/JPM/Filters/Invert.java @@ -0,0 +1,83 @@ +// Invert - color-inversion filter +// +// Copyright (C) 1997 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// Color-inversion filter. +//

+// Switches black for white, and all the other colors too. +// This filter is very fast. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class Invert extends RGBBlockFilter + { + + /// Constructor. + public Invert( ImageProducer producer ) + { + super( producer ); + } + + + public int[][] filterRGBBlock( int x, int y, int width, int height, int[][] rgbPixels ) + { + for ( int row = 0; row < height; ++row ) + for ( int col = 0; col < width; ++col ) + { + int rgb = rgbPixels[row][col]; + int alpha = rgb & 0xff000000; + int rest = ( ~ rgb ) & 0x00ffffff; + rgbPixels[row][col] = alpha | rest; + } + return rgbPixels; + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + ImageFilterPlus filter = null; + if ( args.length == 0 ) + filter = new Invert( null ); + else + usage(); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: Invert" ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/Filters/Margin.java b/src/Acme/JPM/Filters/Margin.java new file mode 100644 index 00000000..5375bb54 --- /dev/null +++ b/src/Acme/JPM/Filters/Margin.java @@ -0,0 +1,133 @@ +// Margin - an ImageFilter that adds a margin to an image +// +// Copyright (C) 1997 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.*; +import java.awt.image.*; + +/// An ImageFilter that adds a margin to an image. +//

+// Adds a margin of a specified color and width around an image. +// The output uses the same color model as the input. +// This filter is very fast. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class Margin extends ImageFilterPlus + { + + private Color color; + private int size; + private int width, height; + private int newWidth; + + /// Constructor. + public Margin( ImageProducer producer, Color color, int size ) + { + super( producer, true ); + this.color = color; + this.size = size; + } + + + public void setDimensions( int width, int height ) + { + this.width = width; + this.height = height; + newWidth = width + size * 2; + consumer.setDimensions( newWidth, height + size * 2 ); + } + + private boolean started = false; + + private void start() + { + started = true; + int rgb = color.getRGB(); + + int[] fullRow = new int[newWidth]; + for ( int col = 0; col < newWidth; ++col ) + fullRow[col] = rgb; + for ( int row = 0; row < size; ++row ) + { + consumer.setPixels( + 0, row, newWidth, 1, rgbModel, fullRow, 0, newWidth ); + consumer.setPixels( + 0, size + height + row, newWidth, 1, rgbModel, fullRow, 0, + newWidth ); + } + + int[] sideRow = new int[size]; + for ( int col = 0; col < size; ++col ) + sideRow[col] = rgb; + for ( int row = 0; row < height; ++row ) + { + consumer.setPixels( + 0, size + row, size, 1, rgbModel, sideRow, 0, size ); + consumer.setPixels( + size + width, size + row, size, 1, rgbModel, sideRow, 0, size ); + } + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize ) + { + if ( ! started ) + start(); + consumer.setPixels( + x + size, y + size, w, h, model, pixels, off, scansize ); + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize ) + { + if ( ! started ) + start(); + consumer.setPixels( + x + size, y + size, w, h, model, pixels, off, scansize ); + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + if ( args.length != 1 ) + usage(); + ImageFilterPlus filter = + new Margin( null, Color.black, Integer.parseInt( args[0] ) ); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: Margin " ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/Filters/Oil.java b/src/Acme/JPM/Filters/Oil.java new file mode 100644 index 00000000..cd39df9e --- /dev/null +++ b/src/Acme/JPM/Filters/Oil.java @@ -0,0 +1,141 @@ +// Oil - oil-transfer filter +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// Oil-transfer filter. +// +// +//

+// The oil transfer is described in "Beyond Photography" by Holzmann, +// chapter 4, photo 7. +// It's a sort of localized smearing. +// The parameter controls the size of the smeared area, with a default of 3. +//

+// This filter is very slow. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class Oil extends RGBAllFilter + { + + private int n; + + /// Constructor. + public Oil( ImageProducer producer, int n ) + { + super( producer ); + this.n = n; + } + + /// Constructor, default value. + public Oil( ImageProducer producer ) + { + this( producer, 3 ); + } + + + public void filterRGBAll( int width, int height, int[][] rgbPixels ) + { + int[][] newPixels = new int[height][width]; + int[] rHist = new int[256]; + int[] gHist = new int[256]; + int[] bHist = new int[256]; + + for ( int row = 0; row < height; ++row ) + { + for ( int col = 0; col < width; ++col ) + { + for ( int i = 0; i < 256; ++i ) + rHist[i] = gHist[i] = bHist[i] =0; + for ( int drow = row - n; drow <= row + n; ++drow ) + if ( drow >= 0 && drow < height ) + for ( int dcol = col - n; dcol <= col + n; ++dcol ) + if ( dcol >= 0 && dcol < width ) + { + int rgb = rgbPixels[drow][dcol]; + rHist[( rgb >> 16 ) & 0xff]++; + gHist[( rgb >> 8 ) & 0xff]++; + bHist[rgb & 0xff]++; + } + int r = 0, g = 0, b = 0; + for ( int i = 1; i < 256; ++i ) + { + if ( rHist[i] > rHist[r] ) + r = i; + if ( gHist[i] > gHist[g] ) + g = i; + if ( bHist[i] > bHist[b] ) + b = i; + } + newPixels[row][col] = + 0xff000000 | ( r << 16 ) | ( g << 8 ) | b; + } + } + setPixels( width, height, newPixels ); + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + int n = -1; + int argc = args.length; + int argn; + for ( argn = 0; argn < argc && args[argn].charAt( 0 ) == '-'; ++argn ) + { + if ( args[argn].equals( "-n" ) && argn + 1 < argc ) + { + ++argn; + n = Integer.parseInt( args[argn] ); + } + else + usage(); + } + if ( argn != argc ) + usage(); + + ImageFilterPlus filter; + if ( n == -1 ) + filter = new Oil( null ); + else + filter = new Oil( null, n ); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: Oil [-n N]" ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/Filters/RGBAllFilter.java b/src/Acme/JPM/Filters/RGBAllFilter.java new file mode 100644 index 00000000..b9da544f --- /dev/null +++ b/src/Acme/JPM/Filters/RGBAllFilter.java @@ -0,0 +1,170 @@ +// RGBAllFilter - an ImageFilter that grabs the whole image +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// An ImageFilter that grabs the whole image. +//

+// Many image filters need to work on the whole image at once. +// This class collects up the image and hands it to a single +// routine for processing. +//

+// Also, because Java's image classes allow each setPixels() call to +// use a different color model, we have to convert all pixels into +// the default RGB model. +//

+// Here's a sample RGBAllFilter that smooths an image by averaging +// nine adjacent pixels. +//

+// class SmoothFilter extends RGBAllFilter
+//     {
+//     public void filterRGBAll( int width, int height, int[][] rgbPixels )
+//         {
+//         int[][] newPixels = new int[height][width];
+//         for ( int row = 0; row < height; ++row )
+//             for ( int col = 0; col < width; ++col )
+//                 {
+//                 int a = 0, r = 0, g = 0, b = 0, c = 0;
+//                 for ( int subrow = row - 1; subrow <= row + 1; ++subrow )
+//                     if ( subrow >= 0 && subrow < height )
+//                         for ( int subcol = col - 1; subcol <= col + 1; ++subcol )
+//                             if ( subcol >= 0 && subcol < width )
+//                                 {
+//                                 int pixel = rgbPixels[subrow][subcol];
+//                                 a += rgbModel.getAlpha( pixel );
+//                                 r += rgbModel.getRed( pixel );
+//                                 g += rgbModel.getGreen( pixel );
+//                                 b += rgbModel.getBlue( pixel );
+//                                 ++c;
+//                                 }
+//                 a /= c;
+//                 r /= c;
+//                 g /= c;
+//                 b /= c;
+//                 newPixels[row][col] =
+//                     ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b;
+//                 }
+//         setPixels( width, height, newPixels );
+//         }
+//     }
+// 
+//

+// Fetch the software.
+// Fetch the entire Acme package. + +public abstract class RGBAllFilter extends ImageFilterPlus + { + + private int width = -1, height = -1; + private int[][] rgbPixels = null; + + public RGBAllFilter( ImageProducer producer ) + { + super( producer ); + } + + + /// This is the routine that subclasses must implement. + // It gets the entire image as an int[height][width] in the default + // RGB color model. It should call setPixels() with a filtered array, + // same color model. + public abstract void filterRGBAll( int width, int height, int[][] rgbPixels ); + + + /// The version of setPixels() that gets called by the subclass. + public void setPixels( int newWidth, int newHeight, int[][] newPixels ) + { + // Send it on to the consumer. + consumer.setDimensions( newWidth, newHeight ); + for ( int row = 0; row < newHeight; ++row ) + consumer.setPixels( + 0, row, newWidth, 1, rgbModel, newPixels[row], 0, newWidth ); + + } + + + public void setColorModel( ColorModel model ) + { + consumer.setColorModel( rgbModel ); + } + + public void setDimensions( int width, int height ) + { + if ( width == this.width && height == this.height ) + return; + this.width = width; + this.height = height; + rgbPixels = new int[height][width]; + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize ) + { + for ( int row = 0; row < h; ++row ) + { + int rowOffsetIn = row * scansize + off; + for ( int col = 0; col < w; ++col ) + rgbPixels[y + row][x + col] = + model.getRGB( pixels[rowOffsetIn + col] & 0xff ); + } + } + + + public void setPixels( int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize ) + { + for ( int row = 0; row < h; ++row ) + { + int rowOffsetIn = row * scansize + off; + if ( model == rgbModel ) + System.arraycopy( + pixels, rowOffsetIn, rgbPixels[y + row], x, w ); + else + for ( int col = 0; col < w; ++col ) + rgbPixels[y + row][x + col] = + model.getRGB( pixels[rowOffsetIn + col] ); + } + } + + public void imageComplete( int status ) + { + if ( status == ImageConsumer.IMAGEERROR || + status == ImageConsumer.IMAGEABORTED ) + { + super.imageComplete( status ); + return; + } + + // Do the actual work. + filterRGBAll( width, height, rgbPixels ); + + // And we're done. + super.imageComplete( status ); + } + + } diff --git a/src/Acme/JPM/Filters/RGBBlockFilter.java b/src/Acme/JPM/Filters/RGBBlockFilter.java new file mode 100644 index 00000000..5fc60e07 --- /dev/null +++ b/src/Acme/JPM/Filters/RGBBlockFilter.java @@ -0,0 +1,135 @@ +// RGBBlockFilter - more efficient RGB ImageFilter +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// More efficient RGB ImageFilter. +//

+// Similar in concept to java.awt.image.RGBImageFilter, but designed +// to run more efficiently when filtering large images. +//

+// As with RGBImageFilter, you only have to implement a single routine +// to use the filter. However, RGBImageFilter's routine filters a single +// pixel at a time. This means a lot of routine-calling overhead. +// RGBBlockFilter filters a block at a time. +//

+// Here's a sample RGBBlockFilter that makes an image translucent +// by setting all the alpha values to 0x80: +//

+// class TranslucentFilter extends RGBBlockFilter
+//     {
+//     public int[] filterRGBBlock(
+//         int x, int y, int width, int height, int[][] rgbPixels )
+//         {
+//         for ( int row = 0; row < height; ++row )
+//             for ( int col = 0; col < width; ++col )
+//                 rgbPixels[row][col] =
+//                     ( rgbPixels[row][col] & 0x00ffffff ) | 0x80000000;
+//         return rgbPixels;
+//         }
+//     }
+// 
+//

+// Fetch the software.
+// Fetch the entire Acme package. + +public abstract class RGBBlockFilter extends ImageFilterPlus + { + + public RGBBlockFilter( ImageProducer producer ) + { + super( producer ); + } + + + /// This is the routine that subclasses must implement. + // It gets a block of the image as an int[height][width] in the default + // RGB color model. It should return a filtered array, same size + // and same model. + public abstract int[][] filterRGBBlock( + int x, int y, int width, int height, int[][] rgbPixels ); + + + public void setColorModel( ColorModel model ) + { + consumer.setColorModel( rgbModel ); + } + + /// Byte version of setPixels reformats the pixels to RGB and the + // array to 2 dimensions. + public void setPixels( + int x, int y, int width, int height, ColorModel model, + byte[] pixels, int offset, int scansize ) + { + int[][] rgbPixels = new int[height][width]; + for ( int row = 0; row < height; ++row ) + { + int rowOffsetIn = offset + row * scansize; + for ( int col = 0; col < width; ++col ) + rgbPixels[row][col] = + model.getRGB( pixels[rowOffsetIn + col] & 0xff ); + } + setPixels( x, y, width, height, rgbPixels ); + } + + /// Int version of setPixels reformats the array to 2 dimensions. + public void setPixels( + int x, int y, int width, int height, ColorModel model, + int[] pixels, int offset, int scansize ) + { + int[][] rgbPixels = new int[height][width]; + for ( int row = 0; row < height; ++row ) + { + int rowOffsetIn = offset + row * scansize; + int rowOffsetOut = row * width; + // Convert color models if necessary. + if ( model == rgbModel ) + System.arraycopy( + pixels, rowOffsetIn, rgbPixels[row], 0, width ); + else + for ( int col = 0; col < width; ++col ) + rgbPixels[row][col] = + model.getRGB( pixels[rowOffsetIn + col] ); + } + setPixels( x, y, width, height, rgbPixels ); + } + + /// Call the filter routine, and send the results to the consumer. + private void setPixels( int x, int y, int width, int height, int[][] rgbPixels ) + { + int[][] newPixels = filterRGBBlock( x, y, width, height, rgbPixels ); + + // And send it on to the consumer. + for ( int row = 0; row < height; ++row ) + consumer.setPixels( + x, y + row, width, 1, rgbModel, newPixels[row], 0, width ); + } + + } diff --git a/src/Acme/JPM/Filters/Rotate.java b/src/Acme/JPM/Filters/Rotate.java new file mode 100644 index 00000000..93d7fcc0 --- /dev/null +++ b/src/Acme/JPM/Filters/Rotate.java @@ -0,0 +1,81 @@ +// Rotate - rotate an image by some angle +// +// Copyright (C) 1997 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// Rotate an image by some angle. +//

+// Rotates an image by the specified angle. +// The angle is in degrees measured counter-clockwise. +// It can be negative, but it should be between -90 and 90 +// or the resulting image will be unreasonably large. +// Staying between -45 and 45 is best. +//

+// The rotation algorithm is Alan Paeth's three-shear method, described +// in "A Fast Algorithm for General Raster Rotation", Graphics Interface +// '86, pp. 77-81. +//

+// This filter is slow. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class Rotate extends CompositeFilter + { + + private double angle; + + /// Constructor. + public Rotate( ImageProducer producer, double angle ) + { + super( producer, new Shear( null, angle ), new Flip( null, Flip.FLIP_XY ) ); + this.angle = angle * Math.PI / 180.0; + double xshearfac = Math.tan( angle / 2.0 ); + double yshearfac = Math.sin( angle ); + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + if ( args.length != 1 ) + usage(); + ImageFilterPlus filter = new Rotate( null, Integer.parseInt( args[0] ) ); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: Rotate " ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/Filters/ScaleCopy.java b/src/Acme/JPM/Filters/ScaleCopy.java new file mode 100644 index 00000000..1c568e17 --- /dev/null +++ b/src/Acme/JPM/Filters/ScaleCopy.java @@ -0,0 +1,159 @@ +// ScaleCopy - an ImageFilter that scales by pixel copying +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// An ImageFilter that scales by pixel copying. +//

+// Scales an image by copying pixels. +// If the image is being enlarged, pixels get replicated; +// if the image is being shrunk, pixels get dropped. +// The output uses the same color model as the input. +// For enlarging, this filter is slightly slower than Enlarge due +// to the floating-point arithmetic; +// for shrinking, it's much faster than Shrink, but +// the results aren't as nice. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @see Enlarge +// @see Shrink + +public class ScaleCopy extends ImageFilterPlus + { + + private double xScale, yScale; + private int newWidth, newHeight; + + /// Constructor, same X and Y scale factor. + public ScaleCopy( ImageProducer producer, double scale ) + { + this( producer, scale, scale ); + } + + /// Constructor, different X and Y scale factors. + public ScaleCopy( ImageProducer producer, double xScale, double yScale ) + { + super( producer ); + this.xScale = xScale; + this.yScale = yScale; + } + + + public void setDimensions( int width, int height ) + { + newWidth = (int) ( width * xScale ); + newHeight = (int) ( height * yScale ); + consumer.setDimensions( newWidth, newHeight ); + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize ) + { + int newX = Math.min( (int) ( x * xScale ), newWidth - 1 ); + int newY = Math.min( (int) ( y * yScale ), newHeight - 1 ); + int newW = Math.max( (int) ( w * xScale ), 1 ); + if ( newX + newW > newWidth ) + newW = newWidth - newX; + int newH = Math.max( (int) ( h * yScale ), 1 ); + if ( newY + newH > newHeight ) + newH = newHeight - newY; + byte[] newPixels = new byte[newW * newH]; + for ( int newRow = 0; newRow < newH; ++newRow ) + { + int row = (int) ( newRow / yScale ); + if ( row >= h ) + continue; + for ( int newCol = 0; newCol < newW; ++newCol ) + { + int col = (int) ( newCol / xScale ); + if ( col >= w ) + continue; + newPixels[newRow * newW + newCol] = + pixels[row * scansize + off + col]; + } + } + consumer.setPixels( newX, newY, newW, newH, model, newPixels, 0, newW ); + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize ) + { + int newX = Math.min( (int) ( x * xScale ), newWidth - 1 ); + int newY = Math.min( (int) ( y * yScale ), newHeight - 1 ); + int newW = Math.max( (int) ( w * xScale ), 1 ); + if ( newX + newW > newWidth ) + newW = newWidth - newX; + int newH = Math.max( (int) ( h * yScale ), 1 ); + if ( newY + newH > newHeight ) + newH = newHeight - newY; + int[] newPixels = new int[newW * newH]; + for ( int newRow = 0; newRow < newH; ++newRow ) + { + int row = (int) ( newRow / yScale ); + if ( row >= h ) + continue; + for ( int newCol = 0; newCol < newW; ++newCol ) + { + int col = (int) ( newCol / xScale ); + if ( col >= w ) + continue; + newPixels[newRow * newW + newCol] = + pixels[row * scansize + off + col]; + } + } + consumer.setPixels( newX, newY, newW, newH, model, newPixels, 0, newW ); + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + ImageFilterPlus filter = null; + if ( args.length == 1 ) + filter = new ScaleCopy( null, + Double.valueOf( args[0] ).doubleValue() ); + else if ( args.length == 2 ) + filter = new ScaleCopy( null, + Double.valueOf( args[0] ).doubleValue(), + Double.valueOf( args[1] ).doubleValue() ); + else + usage(); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: ScaleCopy scale" ); + System.err.println( "or: ScaleCopy xScale yScale" ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/Filters/Shear.java b/src/Acme/JPM/Filters/Shear.java new file mode 100644 index 00000000..31bd2054 --- /dev/null +++ b/src/Acme/JPM/Filters/Shear.java @@ -0,0 +1,144 @@ +// Shear - shear an image by some angle +// +// Copyright (C) 1997 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// Shear an image by some angle. +//

+// Shears an image by the specified angle. +// The angle is in degrees (floating point), and measures this: +//


+// +-------+  +-------+
+// |       |  |\       \
+// |  OLD  |  | \  NEW  \
+// |       |  |an\       \
+// +-------+  |gle+-------+
+// 
+// If the angle is negative, it shears the other way: +//

+// +-------+  |-an+-------+
+// |       |  |gl/       /
+// |  OLD  |  |e/  NEW  /
+// |       |  |/       /
+// +-------+  +-------+
+// 
+// The angle should not get too close to 90 or -90, or the resulting +// image will be unreasonably wide. Staying between -45 and 45 is best. +//

+// The shearing is implemented by looping over the source pixels and +// distributing fractions to each of the destination pixels. +// This has an "anti-aliasing" effect - it avoids jagged edges and similar +// artifacts. +//

+// This filter is fast. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class Shear extends RGBAllFilter + { + + private double angle; + + /// Constructor. + public Shear( ImageProducer producer, double angle ) + { + super( producer ); + this.angle = angle * Math.PI / 180.0; + } + + + public void filterRGBAll( int width, int height, int[][] rgbPixels ) + { + double shearfac = Math.tan( angle ); + if ( shearfac < 0.0 ) + shearfac = -shearfac; + int newWidth = (int) ( height * shearfac + width + 0.999999 ); + int[][] newPixels = new int[height][newWidth]; + for ( int row = 0; row < height; ++row ) + { + double new0; + if ( angle > 0.0 ) + new0 = row * shearfac; + else + new0 = ( height - row ) * shearfac; + int intnew0 = (int) new0; + double fracnew0 = new0 - intnew0; + double omfracnew0 = 1.0 - fracnew0; + + for ( int col = 0; col < newWidth; ++col ) + newPixels[row][col] = 0x00000000; + + int preva = 0; + int prevr = ( rgbPixels[row][0] >> 16 ) & 0xff; + int prevg = ( rgbPixels[row][0] >> 8 ) & 0xff; + int prevb = rgbPixels[row][0] & 0xff; + for ( int col = 0; col < width; ++col ) + { + int rgb = rgbPixels[row][col]; + int a = ( rgb >> 24 ) & 0xff; + int r = ( rgb >> 16 ) & 0xff; + int g = ( rgb >> 8 ) & 0xff; + int b = rgb & 0xff; + newPixels[row][intnew0 + col] = + ( (int) ( fracnew0 * preva + omfracnew0 * a ) << 24 ) | + ( (int) ( fracnew0 * prevr + omfracnew0 * r ) << 16 ) | + ( (int) ( fracnew0 * prevg + omfracnew0 * g ) << 8 ) | + ( (int) ( fracnew0 * prevb + omfracnew0 * b ) ); + preva = a; + prevr = r; + prevg = g; + prevb = b; + } + newPixels[row][intnew0 + width] = + ( (int) ( fracnew0 * preva ) << 24 ) | + ( prevr << 16 ) | ( prevg << 8 ) | prevb; + } + setPixels( newWidth, height, newPixels ); + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + if ( args.length != 1 ) + usage(); + ImageFilterPlus filter = new Shear( null, Integer.parseInt( args[0] ) ); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: Shear " ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/Filters/Shrink.java b/src/Acme/JPM/Filters/Shrink.java new file mode 100644 index 00000000..4599cb1b --- /dev/null +++ b/src/Acme/JPM/Filters/Shrink.java @@ -0,0 +1,116 @@ +// Shrink - an ImageFilter that shrinks by pixel averaging +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// An ImageFilter that shrinks by pixel averaging. +//

+// Shrinks an image an integral factor by averaging pixels. +// Because the resulting pixels might not fit into the input's +// color model, the output is always in the default RGB color model. +// This filter is somewhat slow. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @see Enlarge +// @see ScaleCopy + +public class Shrink extends RGBAllFilter + { + + private int divisor; + + /// Constructor. + public Shrink( ImageProducer producer, int divisor ) + { + super( producer ); + this.divisor = divisor; + } + + + public void filterRGBAll( int width, int height, int[][] rgbPixels ) + { + int divisor2 = divisor * divisor; + int newWidth = Math.max( width / divisor, 1 ); + int newHeight = Math.max( height / divisor, 1 ); + int[][] newPixels = new int[newHeight][newWidth]; + for ( int newRow = 0; newRow < newHeight; ++newRow ) + { + for ( int newCol = 0; newCol < newWidth; ++newCol ) + { + int a = 0, r = 0, g = 0, b = 0; + for ( int i = 0; i < divisor; ++i ) + { + int row = newRow * divisor + i; + if ( row >= height ) + continue; + for ( int j = 0; j < divisor; ++j ) + { + int col = newCol * divisor + j; + if ( col >= width ) + continue; + int rgb = rgbPixels[row][col]; + a += ( rgb >> 24 ) & 0xff; + r += ( rgb >> 16 ) & 0xff; + g += ( rgb >> 8 ) & 0xff; + b += rgb & 0xff; + } + } + a /= divisor2; + r /= divisor2; + g /= divisor2; + b /= divisor2; + newPixels[newRow][newCol] = + ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b; + } + } + setPixels( newWidth, newHeight, newPixels ); + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + if ( args.length != 1 ) + usage(); + ImageFilterPlus filter = new Enlarge( + null, Integer.parseInt( args[0] ) ); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: Shrink " ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/Filters/Smooth.java b/src/Acme/JPM/Filters/Smooth.java new file mode 100644 index 00000000..aadab230 --- /dev/null +++ b/src/Acme/JPM/Filters/Smooth.java @@ -0,0 +1,123 @@ +// Smooth - smoothing filter +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// Smoothing filter. +//

+// Smooths an image by averaging adjacent pixels. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class Smooth extends RGBAllFilter + { + + private int n; + + /// Constructor. + public Smooth( ImageProducer producer, int n ) + { + super( producer ); + this.n = n; + } + + /// Constructor, default value. + public Smooth( ImageProducer producer ) + { + this( producer, 1 ); + } + + + public void filterRGBAll( int width, int height, int[][] rgbPixels ) + { + int[][] newPixels = new int[height][width]; + for ( int row = 0; row < height; ++row ) + for ( int col = 0; col < width; ++col ) + { + int a = 0, r = 0, g = 0, b = 0, c = 0; + for ( int subrow = row - n; subrow <= row + n; ++subrow ) + if ( subrow >= 0 && subrow < height ) + for ( int subcol = col - n; subcol <= col + n; ++subcol ) + if ( subcol >= 0 && subcol < width ) + { + int rgb = rgbPixels[subrow][subcol]; + a += ( rgb >> 24 ) & 0xff; + r += ( rgb >> 16 ) & 0xff; + g += ( rgb >> 8 ) & 0xff; + b += rgb & 0xff; + ++c; + } + a /= c; + r /= c; + g /= c; + b /= c; + newPixels[row][col] = + ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b; + } + setPixels( width, height, newPixels ); + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + int n = -1; + int argc = args.length; + int argn; + for ( argn = 0; argn < argc && args[argn].charAt( 0 ) == '-'; ++argn ) + { + if ( args[argn].equals( "-n" ) && argn + 1 < argc ) + { + ++argn; + n = Integer.parseInt( args[argn] ); + } + else + usage(); + } + if ( argn != argc ) + usage(); + + ImageFilterPlus filter; + if ( n == -1 ) + filter = new Smooth( null ); + else + filter = new Smooth( null, n ); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: Smooth [-n N]" ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/Filters/Tile.java b/src/Acme/JPM/Filters/Tile.java new file mode 100644 index 00000000..1d374d15 --- /dev/null +++ b/src/Acme/JPM/Filters/Tile.java @@ -0,0 +1,126 @@ +// Tile - an ImageFilter that replicates an image in a tiled pattern +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM.Filters; + +import java.awt.image.*; + +/// An ImageFilter that replicates an image in a tiled pattern. +//

+// Tiles the image onto an output image of a specified size. +// The output uses the same color model as the input. +// This filter is very fast. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class Tile extends ImageFilterPlus + { + + private int width, height; + private int newWidth, newHeight; + private int nWide, nHigh; + + /// Constructor. + public Tile( ImageProducer producer, int newWidth, int newHeight ) + { + super( producer, true ); + this.newWidth = newWidth; + this.newHeight = newHeight; + } + + + public void setDimensions( int width, int height ) + { + this.width = width; + this.height = height; + consumer.setDimensions( newWidth, newHeight ); + nWide = ( newWidth + width - 1 ) / width; + nHigh = ( newHeight + height - 1 ) / height; + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize ) + { + for ( int r = 0; r < nHigh; ++r ) + { + int ty = r * height + y; + int th = h; + if ( ty + th > newHeight ) + th = newHeight - ty; + for ( int c = 0; c < nWide; ++c ) + { + int tx = c * width + x; + int tw = w; + if ( tx + tw > newWidth ) + tw = newWidth - tx; + consumer.setPixels( + tx, ty, tw, th, model, pixels, off, scansize ); + } + } + } + + public void setPixels( int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize ) + { + for ( int r = 0; r < nHigh; ++r ) + { + int ty = r * height + y; + int th = h; + if ( ty + th > newHeight ) + th = newHeight - ty; + for ( int c = 0; c < nWide; ++c ) + { + int tx = c * width + x; + int tw = w; + if ( tx + tw > newWidth ) + tw = newWidth - tx; + consumer.setPixels( + tx, ty, tw, th, model, pixels, off, scansize ); + } + } + } + + + // Main routine for command-line interface. + public static void main( String[] args ) + { + if ( args.length != 2 ) + usage(); + ImageFilterPlus filter = + new Tile( null, + Integer.parseInt( args[0] ), Integer.parseInt( args[1] ) ); + System.exit( + ImageFilterPlus.filterStream( System.in, System.out, filter ) ); + } + + private static void usage() + { + System.err.println( "usage: Tile " ); + System.exit( 1 ); + } + + } diff --git a/src/Acme/JPM/JPMTest.java b/src/Acme/JPM/JPMTest.java new file mode 100644 index 00000000..045b75df --- /dev/null +++ b/src/Acme/JPM/JPMTest.java @@ -0,0 +1,312 @@ +// JPMTest - test program for the ACME Java pixmap utilities +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM; + +import java.awt.*; +import java.io.*; +import Acme.JPM.Filters.*; +import Acme.JPM.Encoders.*; +import Acme.Widgets.*; + +/// Test program for the ACME Java pixmap utilities. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class JPMTest extends Frame + { + + private static JPMTest jpmTest; + + public static void main( String[] args ) + { + if ( args.length != 1 ) + { + System.err.println( "usage: JPMTest [filename]" ); + System.exit( 1 ); + } + jpmTest = new JPMTest( args[0] ); + } + + + private ImageLabel origImageLabel, filt1ImageLabel, filt2ImageLabel; + private Image origImage, filt1Image, filt2Image; + private Choice filt1Choice, filt2Choice, outputChoice; + + /// Constructor. + public JPMTest( String fileName ) + { + + setTitle( "JPMTest" ); + + GridBagLayout gb = new GridBagLayout(); + setLayout( gb ); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.insets = new Insets( 5, 5, 5, 5 ); + + Panel panel = new Panel(); + GridBagLayout gb2 = new GridBagLayout(); + panel.setLayout( gb2 ); + GridBagConstraints gbc2 = new GridBagConstraints(); + gbc2.insets = new Insets( 5, 5, 5, 5 ); + + Label label = new Label( fileName ); + gbc2.gridwidth = 1; + gb2.setConstraints( label, gbc2 ); + panel.add( label ); + + filt1Choice = new Choice(); + addChoiceItems( filt1Choice ); + gbc2.gridwidth = 1; + gb2.setConstraints( filt1Choice, gbc2 ); + panel.add( filt1Choice ); + + filt2Choice = new Choice(); + addChoiceItems( filt2Choice ); + gbc2.gridwidth = GridBagConstraints.REMAINDER; + gb2.setConstraints( filt2Choice, gbc2 ); + panel.add( filt2Choice ); + + origImageLabel = new ImageLabel( Acme.GuiUtils.brokenIcon( this ) ); + gbc2.gridwidth = 1; + gb2.setConstraints( origImageLabel, gbc2 ); + panel.add( origImageLabel ); + + filt1ImageLabel = new ImageLabel( Acme.GuiUtils.brokenIcon( this ) ); + gbc2.gridwidth = 1; + gb2.setConstraints( filt1ImageLabel, gbc2 ); + panel.add( filt1ImageLabel ); + + filt2ImageLabel = new ImageLabel( Acme.GuiUtils.brokenIcon( this ) ); + gbc2.gridwidth = GridBagConstraints.REMAINDER; + gb2.setConstraints( filt2ImageLabel, gbc2 ); + panel.add( filt2ImageLabel ); + + gb.setConstraints( panel, gbc ); + add( panel ); + + panel = new Panel(); + gb2 = new GridBagLayout(); + panel.setLayout( gb2 ); + + outputChoice = new Choice(); + outputChoice.addItem( "GIF" ); + outputChoice.addItem( "PPM" ); + gbc2.gridwidth = 1; + gb2.setConstraints( outputChoice, gbc2 ); + panel.add( outputChoice ); + + Button button = new Button( "Write" ); + gbc2.gridwidth = 1; + gb2.setConstraints( button, gbc2 ); + panel.add( button ); + + button = new Button( "Quit" ); + gbc2.gridwidth = GridBagConstraints.REMAINDER; + gb2.setConstraints( button, gbc2 ); + panel.add( button ); + + gb.setConstraints( panel, gbc ); + add( panel ); + + pack(); + validate(); + show(); + + origImage = getToolkit().getImage( fileName ); + origImageLabel.setImage( origImage ); + filt1Choice.select( NONE ); + filt2Choice.select( NONE ); + filter1(); + filter2(); + } + + private void addChoiceItems( Choice choice ) + { + // These items must match the order of the enumeration. + choice.addItem( "NONE" ); + choice.addItem( "Shrink 2" ); + choice.addItem( "Enlarge 2" ); + choice.addItem( "ScaleCopy 0.51" ); + choice.addItem( "ScaleCopy 1.99" ); + choice.addItem( "Oil" ); + choice.addItem( "Smooth 2" ); + choice.addItem( "EdgeDetect" ); + choice.addItem( "Gamma 2" ); + choice.addItem( "Tile 400" ); + choice.addItem( "Flip -lr" ); + choice.addItem( "Flip -tb" ); + choice.addItem( "Flip -cw" ); + choice.addItem( "Invert" ); + choice.addItem( "Margin" ); + choice.addItem( "Shear 30" ); + choice.addItem( "Rotate 30" ); + } + + + /// Event handler. + public boolean handleEvent( Event evt ) + { + switch ( evt.id ) + { + case Event.ACTION_EVENT: + if ( evt.arg.equals( "Quit" ) ) + System.exit( 0 ); + else if ( evt.arg.equals( "Write" ) ) + write(); + else if ( evt.target == filt1Choice ) + { + filter1(); + filter2(); + } + else if ( evt.target == filt2Choice ) + filter2(); + break; + } + return super.handleEvent( evt ); + } + + + // This must match the order of the choice items. + private static final int GIF = 0; + private static final int PPM = 1; + + private void write() + { + try + { + ImageEncoder encoder = null; + switch ( outputChoice.getSelectedIndex() ) + { + case GIF: + encoder = new GifEncoder( filt2Image.getSource(), System.out ); + break; + case PPM: + encoder = new PpmEncoder( filt2Image.getSource(), System.out ); + break; + } + encoder.encode(); + } + catch ( IOException e ) + { + System.err.println( e.toString() ); + } + } + + // This must match the order of the choice items. + private static final int NONE = 0; + private static final int SHRINK2 = 1; + private static final int ENLARGE2 = 2; + private static final int SCALE051 = 3; + private static final int SCALE199 = 4; + private static final int OIL = 5; + private static final int SMOOTH2 = 6; + private static final int EDGEDETECT = 7; + private static final int GAMMA2 = 8; + private static final int TILE400 = 9; + private static final int FLIPLR = 10; + private static final int FLIPTB = 11; + private static final int FLIPCW = 12; + private static final int INVERT = 13; + private static final int MARGIN = 14; + private static final int SHEAR30 = 15; + private static final int ROTATE30 = 16; + + private void filter1() + { + filt1Image = filterImage( filt1Choice.getSelectedIndex(), origImage ); + filt1ImageLabel.setImage( filt1Image ); + } + + private void filter2() + { + filt2Image = filterImage( filt2Choice.getSelectedIndex(), filt1Image ); + filt2ImageLabel.setImage( filt2Image ); + } + + private Image filterImage( int which, Image image ) + { + switch ( which ) + { + case NONE: + return image; + case SHRINK2: + return JPMUtils.filterImage( + this, new Shrink( image.getSource(), 2 ) ); + case ENLARGE2: + return JPMUtils.filterImage( + this, new Enlarge( image.getSource(), 2 ) ); + case SCALE051: + return JPMUtils.filterImage( + this, new ScaleCopy( image.getSource(), 0.51 ) ); + case SCALE199: + return JPMUtils.filterImage( + this, new ScaleCopy( image.getSource(), 1.99 ) ); + case OIL: + return JPMUtils.filterImage( + this, new Oil( image.getSource() ) ); + case SMOOTH2: + return JPMUtils.filterImage( + this, new Smooth( image.getSource(), 2 ) ); + case EDGEDETECT: + return JPMUtils.filterImage( + this, new EdgeDetect( image.getSource() ) ); + case GAMMA2: + return JPMUtils.filterImage( + this, new Gamma( image.getSource(), 2.0 ) ); + case TILE400: + return JPMUtils.filterImage( + this, new Tile( image.getSource(), 400, 400 ) ); + case FLIPLR: + return JPMUtils.filterImage( + this, new Flip( image.getSource(), Flip.FLIP_LR ) ); + case FLIPTB: + return JPMUtils.filterImage( + this, new Flip( image.getSource(), Flip.FLIP_TB ) ); + case FLIPCW: + return JPMUtils.filterImage( + this, new Flip( image.getSource(), Flip.FLIP_CW ) ); + case INVERT: + return JPMUtils.filterImage( + this, new Invert( image.getSource() ) ); + case MARGIN: + return JPMUtils.filterImage( + this, new Margin( image.getSource(), Color.black, 10 ) ); + case SHEAR30: + return JPMUtils.filterImage( + this, new Shear( image.getSource(), 30.0 ) ); + case ROTATE30: + return JPMUtils.filterImage( + this, new Rotate( image.getSource(), 30.0 ) ); + } + return null; + } + + } diff --git a/src/Acme/JPM/JPMUtils.java b/src/Acme/JPM/JPMUtils.java new file mode 100644 index 00000000..003c987a --- /dev/null +++ b/src/Acme/JPM/JPMUtils.java @@ -0,0 +1,50 @@ +// JPMUtils - static utility routines for the JPM packages +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme.JPM; + +import java.awt.*; +import java.awt.image.*; +import Acme.JPM.Filters.*; + +/// Static utility routines for the JPM packages. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class JPMUtils + { + + /// Filter one image into another. + public static Image filterImage( Component comp, ImageFilterPlus filter ) + { + return comp.createImage( + new FilteredImageSource( filter.getSource(), filter ) ); + } + + } diff --git a/src/Acme/Utils.java b/src/Acme/Utils.java new file mode 100644 index 00000000..6adf82ef --- /dev/null +++ b/src/Acme/Utils.java @@ -0,0 +1,1053 @@ +// Utils - assorted static utility routines +// +// 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme; + +import java.util.*; +import java.io.*; +import java.net.*; + +/// Assorted static utility routines. +//

+// Whenever I come up with a static routine that might be of general use, +// I put it here. So far the class includes: +//

+// and lots more. +//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class Utils + { + + /// Returns a date string formatted in Unix ls style - if it's within + // six months of now, Mmm dd hh:ss, else Mmm dd yyyy. + public static String lsDateStr( Date date ) + { + Calendar cal = new GregorianCalendar(); + cal.setTime( date ); + long dateTime = date.getTime(); + if ( dateTime == -1L ) + return "------------"; + long nowTime = (new Date()).getTime(); + String[] months = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + String part1 = + months[cal.get(Calendar.MONTH)] + + Fmt.fmt( cal.get(Calendar.DATE), 3 ); + if ( Math.abs( nowTime - dateTime ) < 183L * 24L * 60L * 60L * 1000L ) + return part1 + Fmt.fmt( cal.get(Calendar.HOUR_OF_DAY), 3 ) + ":" + + Fmt.fmt( cal.get(Calendar.MINUTE), 2, Fmt.ZF ); + else + return part1 + Fmt.fmt( cal.get(Calendar.YEAR), 6 ); + } + + + /// Returns "s" for numbers other than one, and "" for one. + public static String pluralStr( long n ) + { + if ( n == 1 ) + return ""; + else + return "s"; + } + + + // Various interval constants. Some are only approximate. + public static final long INT_SECOND = 1000L; + public static final long INT_MINUTE = INT_SECOND * 60L; + public static final long INT_HOUR = INT_MINUTE * 60L; + public static final long INT_DAY = INT_HOUR * 24L; + public static final long INT_WEEK = INT_DAY * 7L; + public static final long INT_MONTH = INT_DAY * 30L; + public static final long INT_YEAR = INT_DAY * 365L; + public static final long INT_DECADE = INT_DAY * 3652L; + + /// Returns a string approximately describing a given time interval. + // @param interval the interval, in milliseconds + public static String intervalStr( long interval ) + { + long decades, years, months, weeks, days, hours, minutes, seconds, millis; + + decades = interval / INT_DECADE; + interval -= decades * INT_DECADE; + years = interval / INT_YEAR; + interval -= years * INT_YEAR; + months = interval / INT_MONTH; + interval -= months * INT_MONTH; + weeks = interval / INT_WEEK; + interval -= weeks * INT_WEEK; + days = interval / INT_DAY; + interval -= days * INT_DAY; + hours = interval / INT_HOUR; + interval -= hours * INT_HOUR; + minutes = interval / INT_MINUTE; + interval -= minutes * INT_MINUTE; + seconds = interval / INT_SECOND; + interval -= seconds * INT_SECOND; + millis = interval; + + if ( decades > 0 ) + if ( years == 0 ) + return decades + " decade" + pluralStr( decades ); + else + return + decades + " decade" + pluralStr( decades ) + ", " + + years + " years" + pluralStr( years ); + else if ( years > 0 ) + if ( months == 0 ) + return years + " year" + pluralStr( years ); + else + return + years + " year" + pluralStr( years ) + ", " + + months + " month" + pluralStr( months ); + else if ( months > 0 ) + if ( weeks == 0 ) + return months + " month" + pluralStr( months ); + else + return + months + " month" + pluralStr( months ) + ", " + + weeks + " week" + pluralStr( weeks ); + else if ( weeks > 0 ) + if ( days == 0 ) + return weeks + " week" + pluralStr( weeks ); + else + return + weeks + " week" + pluralStr( weeks ) + ", " + + days + " day" + pluralStr( days ); + else if ( days > 0 ) + if ( hours == 0 ) + return days + " day" + pluralStr( days ); + else + return + days + " day" + pluralStr( days ) + ", " + + hours + " hour" + pluralStr( hours ); + else if ( hours > 0 ) + if ( minutes == 0 ) + return hours + " hour" + pluralStr( hours ); + else + return + hours + " hour" + pluralStr( hours ) + ", " + + minutes + " minute" + pluralStr( minutes ); + else if ( minutes > 0 ) + if ( seconds == 0 ) + return minutes + " minute" + pluralStr( minutes ); + else + return + minutes + " minute" + pluralStr( minutes ) + ", " + + seconds + " second" + pluralStr( seconds ); + else if ( seconds > 0 ) + if ( millis == 0 ) + return seconds + " second" + pluralStr( seconds ); + else + return + seconds + " second" + pluralStr( seconds ) + ", " + + millis + " millisecond" + pluralStr( millis ); + else + return millis + " millisecond" + pluralStr( millis ); + } + + + /// Returns the length of the initial segment of str which consists + // entirely of characters from charSet. + public static int strSpan( String str, String charSet ) + { + return strSpan( str, charSet, 0 ); + } + + /// Returns the length of the initial segment of str which consists + // entirely of characters from charSet, starting at the given index. + public static int strSpan( String str, String charSet, int fromIdx ) + { + int i; + for ( i = fromIdx; i < str.length(); ++i ) + if ( charSet.indexOf( str.charAt( i ) ) == -1 ) + break; + return i - fromIdx; + } + + /// Returns the length of the initial segment of str which consists + // entirely of characters NOT from charSet. + public static int strCSpan( String str, String charSet ) + { + return strCSpan( str, charSet, 0 ); + } + + /// Returns the length of the initial segment of str which consists + // entirely of characters NOT from charSet, starting at the given index. + public static int strCSpan( String str, String charSet, int fromIdx ) + { + int i; + for ( i = fromIdx; i < str.length(); ++i ) + if ( charSet.indexOf( str.charAt( i ) ) != -1 ) + break; + return i - fromIdx; + } + + /// Checks whether a string matches a given wildcard pattern. + // Only does ? and *, and multiple patterns separated by |. + public static boolean match( String pattern, String string ) + { + for ( int p = 0; ; ++p ) + { + for ( int s = 0; ; ++p, ++s ) + { + boolean sEnd = ( s >= string.length() ); + boolean pEnd = ( p >= pattern.length() || + pattern.charAt( p ) == '|' ); + if ( sEnd && pEnd ) + return true; + if ( sEnd || pEnd ) + break; + if ( pattern.charAt( p ) == '?' ) + continue; + if ( pattern.charAt( p ) == '*' ) + { + int i; + ++p; + for ( i = string.length(); i >= s; --i ) + if ( match( + pattern.substring( p ), + string.substring( i ) ) ) /* not quite right */ + return true; + break; + } + if ( pattern.charAt( p ) != string.charAt( s ) ) + break; + } + p = pattern.indexOf( '|', p ); + if ( p == -1 ) + return false; + } + } + +// /// Finds the maximum length of a string that matches a given wildcard +// // pattern. Only does ? and *, and multiple patterns separated by |. +// public static int matchSpan( String pattern, String string ) +// { +// // !!! +// return 0; +// } + + /// Returns the length of the initial segment of str1 that equals str2. + public static int sameSpan( String str1, String str2 ) + { + int i; + for ( i = 0; + i < str1.length() && i < str2.length() && + str1.charAt( i ) == str2.charAt( i ); + ++i ) + ; + return i; + } + + /// Returns the number of times the given character appears in the string. + public static int charCount( String str, char c ) + { + int n = 0; + for ( int i = 0; i < str.length(); ++i ) + if ( str.charAt( i ) == c ) + ++n; + return n; + } + + + /// Turns a String into an array of Strings, by using StringTokenizer + // to split it up at whitespace. + public static String[] splitStr( String str ) + { + StringTokenizer st = new StringTokenizer( str ); + int n = st.countTokens(); + String[] strs = new String[n]; + for ( int i = 0; i < n; ++i ) + strs[i] = st.nextToken(); + return strs; + } + + /// Turns a String into an array of Strings, by splitting it at + // the specified character. This does not use StringTokenizer, + // and therefore can handle empty fields. + public static String[] splitStr( String str, char delim ) + { + int n = 1; + int index = -1; + while ( true ) + { + index = str.indexOf( delim, index + 1 ); + if ( index == -1 ) + break; + ++n; + } + String[] strs = new String[n]; + index = -1; + for ( int i = 0; i < n - 1; ++i ) + { + int nextIndex = str.indexOf( delim, index + 1 ); + strs[i] = str.substring( index + 1, nextIndex ); + index = nextIndex; + } + strs[n - 1] = str.substring( index + 1 ); + return strs; + } + + /// Turns an array of Strings into a single String, with the components + // separated by spaces. + public static String flattenStrarr( String[] strs ) + { + StringBuffer sb = new StringBuffer(); + for ( int i = 0; i < strs.length; ++i ) + { + if ( i > 0 ) + sb.append( ' ' ); + sb.append( strs[i] ); + } + return sb.toString(); + } + + /// Sorts an array of Strings. + // Java currently has no general sort function. Sorting Strings is + // common enough that it's worth making a special case. + public static void sortStrings( String[] strings ) + { + // Just does a bubblesort. + for ( int i = 0; i < strings.length - 1; ++i ) + { + for ( int j = i + 1; j < strings.length; ++j ) + { + if ( strings[i].compareTo( strings[j] ) > 0 ) + { + String t = strings[i]; + strings[i] = strings[j]; + strings[j] = t; + } + } + } + } + + /// Locates a String in an array of Strings. + // Returns -1 if the String is not found. + public static int indexOfString( String[] strings, String string ) + { + for ( int i = 0; i < strings.length; ++i ) + if ( string.equals( strings[i] ) ) + return i; + return -1; + } + + /// Locates a String in an array of Strings, ignoring case. + // Returns -1 if the String is not found. + public static int indexOfStringIgnoreCase( String[] strings, String string ) + { + for ( int i = 0; i < strings.length; ++i ) + if ( string.equalsIgnoreCase( strings[i] ) ) + return i; + return -1; + } + + /// Compares two arrays of Strings for equality. + public static boolean equalsStrings( String[] strings1, String[] strings2 ) + { + if ( strings1.length != strings2.length ) + return false; + for ( int i = 0; i < strings1.length; ++i ) + if ( ! strings1[i].equals( strings2[i] ) ) + return false; + return true; + } + + + /// Returns the number a raised to the power of b. Long version + // of Math.pow(). Throws ArithmeticException if b is negative. + public static long pow( long a, long b ) throws ArithmeticException + { + if ( b < 0 ) + throw new ArithmeticException(); + long r = 1; + while ( b != 0 ) + { + if ( odd( b ) ) + r *= a; + b >>>= 1; + a *= a; + } + return r; + } + + + /// Parse an integer, returning a default value on errors. + public static int parseInt( String str, int def ) + { + try + { + return Integer.parseInt( str ); + } + catch ( Exception e ) + { + return def; + } + } + + /// Parse a long, returning a default value on errors. + public static long parseLong( String str, long def ) + { + try + { + return Long.parseLong( str ); + } + catch ( Exception e ) + { + return def; + } + } + + + /// An array-to-String routine. Handles arrays of arbitrary + // type, including nested arrays. Sample output: + //

+    // byte[]:    { (byte)0, (byte)1, (byte)2 }
+    // char[]:    { '0', '1', '2' }
+    // short[]:   { (short)0, (short)1, (short)2 }
+    // int[]:     { 0, 1, 2 }
+    // long[]:    { 0L, 1L, 2L }
+    // float[]:   { 0F, 1F, 2F }
+    // double[]:  { 0D, 1D, 2D }
+    // String[]:  { "0", "1", "2" }
+    // int[][]:   { { 0, 1, 2 }, { 3, 4, 5 } }
+    // 
+ public static String arrayToString( Object o ) + { + if ( o == null ) + return "null"; + String cl = o.getClass().getName(); + if ( ! cl.startsWith( "[" ) ) + // It's not an array; just call its toString method. + return o.toString(); + StringBuffer sb = new StringBuffer( "{ " ); + if ( o instanceof byte[] ) + { + byte[] ba = (byte[]) o; + for ( int i = 0; i < ba.length; ++i ) + { + if ( i > 0 ) sb.append( ", " ); + sb.append( "(byte)" ); + sb.append( ba[i] ); + } + } + else if ( o instanceof char[] ) + { + char[] ca = (char[]) o; + for ( int i = 0; i < ca.length; ++i ) + { + if ( i > 0 ) sb.append( ", " ); + sb.append( "'" ); + sb.append( ca[i] ); + sb.append( "'" ); + } + } + else if ( o instanceof short[] ) + { + short[] sa = (short[]) o; + for ( int i = 0; i < sa.length; ++i ) + { + if ( i > 0 ) sb.append( ", " ); + sb.append( "(short)" ); + sb.append( sa[i] ); + } + } + else if ( o instanceof int[] ) + { + int[] ia = (int[]) o; + for ( int i = 0; i < ia.length; ++i ) + { + if ( i > 0 ) sb.append( ", " ); + sb.append( ia[i] ); + } + } + else if ( o instanceof long[] ) + { + long[] la = (long[]) o; + for ( int i = 0; i < la.length; ++i ) + { + if ( i > 0 ) sb.append( ", " ); + sb.append( la[i] ); + sb.append( "L" ); + } + } + else if ( o instanceof float[] ) + { + float[] fa = (float[]) o; + for ( int i = 0; i < fa.length; ++i ) + { + if ( i > 0 ) sb.append( ", " ); + sb.append( fa[i] ); + sb.append( "F" ); + } + } + else if ( o instanceof double[] ) + { + double[] da = (double[]) o; + for ( int i = 0; i < da.length; ++i ) + { + if ( i > 0 ) sb.append( ", " ); + sb.append( da[i] ); + sb.append( "D" ); + } + } + else if ( o instanceof String ) + { + // Special-case Strings so we can surround them with quotes. + String[] sa = (String[]) o; + for ( int i = 0; i < sa.length; ++i ) + { + if ( i > 0 ) sb.append( ", " ); + sb.append( "\"" ); + sb.append( sa[i] ); + sb.append( "\"" ); + } + } + else if ( cl.startsWith( "[L" ) ) + { + // Some random class. + Object[] oa = (Object[]) o; + for ( int i = 0; i < oa.length; ++i ) + { + if ( i > 0 ) sb.append( ", " ); + sb.append( oa[i] ); + } + } + else if ( cl.startsWith( "[[" ) ) + { + // Nested arrays. + Object[] aa = (Object[]) o; + for ( int i = 0; i < aa.length; ++i ) + { + if ( i > 0 ) sb.append( ", " ); + sb.append( arrayToString( aa[i] ) ); + } + } + else + sb.append( "(unknown array type)" ); + sb.append( " }" ); + return sb.toString(); + } + + + /// Check if an object extends a given class or one of its superclasses. + // An instanceof that works on Class objects at runtime, instead + // of type descriptors at compile time. + public static boolean instanceOf( Object o, Class cl ) + { + // Null check. + if ( o == null || cl == null ) + return false; + Class ocl = o.getClass(); + // Check if they are the same class. + if ( ocl.equals( cl ) ) + return true; + // If the class is not itself an interface, then check its interfaces. + if ( ! cl.isInterface() ) + { + Class ifs[] = cl.getInterfaces(); + for ( int i = 0; i < ifs.length; ++i ) + if ( instanceOf( o, ifs[i] ) ) + return true; + } + // And check supeclasses. + Class scl = cl.getSuperclass(); + if ( scl != null ) + if ( instanceOf( o, scl ) ) + return true; + // Guess not. + return false; + } + + + /// Test is a number is even. + public static boolean even( long n ) + { + return ( n & 1 ) == 0; + } + + /// Test is a number is odd. + public static boolean odd( long n ) + { + return ( n & 1 ) != 0; + } + + + /// Count the number of 1-bits in a byte. + public static int countOnes( byte n ) + { + return countOnes( n & 0xffL ); + } + + /// Count the number of 1-bits in an int. + public static int countOnes( int n ) + { + return countOnes( n & 0xffffffffL ); + } + + /// Count the number of 1-bits in a long. + public static int countOnes( long n ) + { + // There are faster ways to do this, all the way up to looking + // up bytes in a 256-element table. But this is not too bad. + int count = 0; + while ( n != 0 ) + { + if ( odd( n ) ) + ++count; + n >>>= 1; + } + return count; + } + + + /// A fixed version of java.io.InputStream.read(byte[], int, int). The + // standard version catches and ignores IOExceptions from below. + // This version sends them on to the caller. + public static int read( InputStream in, byte[] b, int off, int len ) throws IOException + { + if ( len <= 0 ) + return 0; + int c = in.read(); + if ( c == -1 ) + return -1; + if ( b != null ) + b[off] = (byte) c; + int i; + for ( i = 1; i < len ; ++i ) + { + c = in.read(); + if ( c == -1 ) + break; + if ( b != null ) + b[off + i] = (byte) c; + } + return i; + } + + /// A version of read that reads the entire requested block, instead + // of sometimes terminating early. + // @return -1 on EOF, otherwise len + public static int readFully( InputStream in, byte[] b, int off, int len ) throws IOException + { + int l, r; + for ( l = 0; l < len; ) + { + r = read( in, b, l, len - l ); + if ( r == -1 ) + return -1; + l += r; + } + return len; + } + + + /// Make a URL with no ref part and no query string. Also, if it's + // a directory then make sure there's a trailing slash. + public static URL plainUrl( URL context, String urlStr ) throws MalformedURLException + { + URL url = new URL( context, urlStr ); + String fileStr = url.getFile(); + int i = fileStr.indexOf( '?' ); + if ( i != -1 ) + fileStr = fileStr.substring( 0, i ); + url = new URL( + url.getProtocol(), url.getHost(), url.getPort(), fileStr ); + if ( ( ! fileStr.endsWith( "/" ) ) && + urlStrIsDir( url.toExternalForm() ) ) + { + fileStr = fileStr + "/"; + url = new URL( + url.getProtocol(), url.getHost(), url.getPort(), fileStr ); + } + return url; + } + + /// Make a URL with no ref part and no query string. Also, if it's + // a directory then make sure there's a trailing slash. + public static URL plainUrl( String urlStr ) throws MalformedURLException + { + return plainUrl( null, urlStr ); + } + + /// Figure out the base URL for a given URL. What this means is + // if the URL points to a directory, you get that directory; if the + // URL points to a file, you get the directory the file is in. + public static String baseUrlStr( String urlStr ) + { + if ( urlStr.endsWith( "/" ) ) + return urlStr; + if ( urlStrIsDir( urlStr ) ) + return urlStr + "/"; + return urlStr.substring( 0, urlStr.lastIndexOf( '/' ) + 1 ); + } + + /// Makes sure if a URL is a directory, it ends with a slash. + public static String fixDirUrlStr( String urlStr ) + { + if ( urlStr.endsWith( "/" ) ) + return urlStr; + if ( urlStrIsDir( urlStr ) ) + return urlStr + "/"; + return urlStr; + } + + /// Figures out whether a URL points to a directory or not. + // Web servers are lenient and accept directory-URLs without + // the trailing slash. What they actually do is return a + // redirect to the same URL with the trailing slash appended. + // Unfortunately, Java doesn't let us see that such a redirect + // happened. Instead we have to figure out it's a directory + // indirectly and heuristically. + public static boolean urlStrIsDir( String urlStr ) + { + // If it ends with a slash, it's probably a directory. + if ( urlStr.endsWith( "/" ) ) + return true; + + // If the last component has a dot, it's probably not a directory. + int lastSlash = urlStr.lastIndexOf( '/' ); + int lastPeriod = urlStr.lastIndexOf( '.' ); + if ( lastPeriod != -1 && ( lastSlash == -1 || lastPeriod > lastSlash ) ) + return false; + + // Otherwise, append a slash and try to connect. This is + // fairly expensive. + String urlStrWithSlash = urlStr + "/"; + try + { + URL url = new URL( urlStrWithSlash ); + InputStream f = url.openStream(); + f.close(); + // Worked fine - it's probably a directory. + return true; + } + catch ( Exception e ) + { + // Got an error - must not be a directory. + return false; + } + } + + + // Figures out whether a URL is absolute or not. + public static boolean urlStrIsAbsolute( String urlStr ) + { + if ( urlStr.startsWith( "/" ) || urlStr.indexOf( ":/" ) != -1 ) + return true; + // Should handle :8000/ and such too. + return false; + } + + // Returns an equivalent URL string that is guaranteed to be absolute. + public static String absoluteUrlStr( String urlStr, URL contextUrl ) throws MalformedURLException + { + URL url = new URL( contextUrl, urlStr ); + return url.toExternalForm(); + } + + + /// URLDecoder to go along with java.net.URLEncoder. Why there isn't + // already a decoder in the standard library is a mystery to me. + public static String urlDecoder( String encoded ) + { + StringBuffer decoded = new StringBuffer(); + int len = encoded.length(); + for ( int i = 0; i < len; ++i ) + { + if ( encoded.charAt( i ) == '%' && i + 2 < len ) + { + int d1 = Character.digit( encoded.charAt( i + 1 ), 16 ); + int d2 = Character.digit( encoded.charAt( i + 2 ), 16 ); + if ( d1 != -1 && d2 != -1 ) + decoded.append( (char) ( ( d1 << 4 ) + d2 ) ); + i += 2; + } + else if ( encoded.charAt( i ) == '+' ) + decoded.append( ' ' ); + else + decoded.append( encoded.charAt( i ) ); + } + return decoded.toString(); + } + + + /// A base-64 encoder, necessary for doing the client side of Basic + // Authentication. This encodes binary data as printable ASCII + // characters. Three 8-bit binary bytes are turned into four 6-bit + // values, like so: + // + // [11111111] [22222222] [33333333] + // + // [111111] [112222] [222233] [333333] + // + // Then the 6-bit values are represented using the characters "A-Za-z0-9+/". + public static String base64Encode( byte[] src ) + { + StringBuffer encoded = new StringBuffer(); + int i, phase = 0; + char c = 0; + + for ( i = 0; i < src.length; ++i ) + { + switch ( phase ) + { + case 0: + c = b64EncodeTable[( src[i] >> 2 ) & 0x3f]; + encoded.append( c ); + c = b64EncodeTable[( src[i] & 0x3 ) << 4]; + encoded.append( c ); + ++phase; + break; + case 1: + c = b64EncodeTable[ + ( b64DecodeTable[c] | ( src[i] >> 4 ) ) & 0x3f]; + encoded.setCharAt( encoded.length() - 1, c ); + c = b64EncodeTable[( src[i] & 0xf ) << 2]; + encoded.append( c ); + ++phase; + break; + case 2: + c = b64EncodeTable[ + ( b64DecodeTable[c] | ( src[i] >> 6 ) ) & 0x3f]; + encoded.setCharAt( encoded.length() - 1, c ); + c = b64EncodeTable[src[i] & 0x3f]; + encoded.append( c ); + phase = 0; + break; + } + } + /* Pad with ='s. */ + while ( phase++ < 3 ) + encoded.append( '=' ); + return encoded.toString(); + } + + private static char b64EncodeTable[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 00-07 + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 08-15 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 16-23 + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 24-31 + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 32-39 + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 40-47 + 'w', 'x', 'y', 'z', '0', '1', '2', '3', // 48-55 + '4', '5', '6', '7', '8', '9', '+', '/' // 56-63 + }; + + private static int b64DecodeTable[] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 00-0F + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 10-1F + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, // 20-2F + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, // 30-3F + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, // 40-4F + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, // 50-5F + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, // 60-6F + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, // 70-7F + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 80-8F + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 90-9F + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // A0-AF + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // B0-BF + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // C0-CF + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // D0-DF + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // E0-EF + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 // F0-FF + }; + + /// A base-64 encoder that takes a String, for convenience. + public static String base64Encode( String srcString ) + { + byte[] src = new byte[srcString.length()]; + srcString.getBytes( 0, src.length, src, 0 ); + return base64Encode( src ); + } + + + /// Check if an array contains a given element. + public static boolean arraycontains( Object[] array, Object element ) + { + for ( int i = 0; i < array.length; ++i ) + if ( array[i].equals( element ) ) + return true; + return false; + } + + + /// Run a program on the host system. + //

+ // This routine runs the specified command, waits for it to + // finish, and returns the exit status. + // This is like the Unix system() routine. Unlike the Unix version, + // though, stdout and stderr get thrown away unless you redirect them. + public static int system( String cmd ) + { + try + { + return runCommand( cmd ).waitFor(); + } + catch ( IOException e ) + { + return -1; + } + catch ( InterruptedException e ) + { + return -1; + } + } + + /// Run a program on the host system, and capture the output. + //

+ // This routine runs the specified command, and returns an InputStream + // for reading the output of the program. + //

+ // WARNING: In JDK1.0.2 there is a serious bug in the process + // IO routines, such that reading all the way to the end of a process's + // output will invariably get you an IOException( "read error" ). + // In some cases you will also lose the last bufferload of + // the output. The workaround is to add a " ; sleep 1" to the end of + // your command, and to ignore the "read error" IOException. + public static InputStream popenr( String cmd ) + { + try + { + return runCommand( cmd ).getInputStream(); + } + catch ( IOException e ) + { + return null; + } + } + + /// Run a program on the host system, and send it some input. + //

+ // This routine runs the specified command, and returns an OutputStream + // for writing the program's input. + public static OutputStream popenw( String cmd ) + { + try + { + return runCommand( cmd ).getOutputStream(); + } + catch ( IOException e ) + { + return null; + } + } + + /// Run a program on the host system. + //

+ // This routine runs the specified command, and returns a Process + // object so you can do what you like with it. + //

+ // WARNING: In JDK1.0.2 there is a serious bug in the process + // IO routines, such that reading all the way to the end of a process's + // output will invariably get you an IOException( "read error" ). + // In some cases you will also lose the last bufferload of + // the output. The workaround is to add a " ; sleep 1" to the end of + // your command, and to ignore the "read error" IOException. + public static Process runCommand( String cmd ) throws IOException + { + Runtime runtime = Runtime.getRuntime(); + String[] shCmd = new String[3]; + shCmd[0] = "/bin/sh"; + shCmd[1] = "-c"; + shCmd[2] = cmd; + return runtime.exec( shCmd ); + } + + + /// Copy the input to the output until EOF. + public static void copyStream( InputStream in, OutputStream out ) throws IOException + { + byte[] buf = new byte[4096]; + int len; + while ( ( len = in.read( buf ) ) != -1 ) + out.write( buf, 0, len ); + } + + /// Copy the input to the output until EOF. + public static void copyStream( Reader in, Writer out ) throws IOException + { + char[] buf = new char[4096]; + int len; + while ( ( len = in.read( buf ) ) != -1 ) + out.write( buf, 0, len ); + } + + /// Copy the input to the output until EOF. + public static void copyStream( InputStream in, Writer out ) throws IOException + { + byte[] buf1 = new byte[4096]; + char[] buf2 = new char[4096]; + int len, i; + while ( ( len = in.read( buf1 ) ) != -1 ) + { + for ( i = 0; i < len; ++i ) + buf2[i] = (char) buf1[i]; + out.write( buf2, 0, len ); + } + } + + /// Copy the input to the output until EOF. + public static void copyStream( Reader in, OutputStream out ) throws IOException + { + char[] buf1 = new char[4096]; + byte[] buf2 = new byte[4096]; + int len, i; + while ( ( len = in.read( buf1 ) ) != -1 ) + { + for ( i = 0; i < len; ++i ) + buf2[i] = (byte) buf1[i]; + out.write( buf2, 0, len ); + } + } + + + /// Dump out the current call stack. + public static void dumpStack( PrintStream p ) + { + (new Throwable()).printStackTrace( p ); + } + + /// Dump out the current call stack onto System.err. + public static void dumpStack() + { + (new Throwable()).printStackTrace(); + } + + } diff --git a/src/Acme/WildcardDictionary.java b/src/Acme/WildcardDictionary.java new file mode 100644 index 00000000..ee957be7 --- /dev/null +++ b/src/Acme/WildcardDictionary.java @@ -0,0 +1,147 @@ +// WildcardDictionary - a dictionary with wildcard lookups +// +// Copyright (C) 1996 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. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +package Acme; + +import java.util.*; + +/// A dictionary with wildcard lookups. +//

+// The keys in this dictionary are wildcard patterns. When you do a get(), +// the string you pass in is matched against all the patterns, and the +// first match is returned. +//

+// The wildcard matcher is fairly simple, it implements * meaning any +// string, ? meaning any single character, and | separating multiple +// patterns. All other characters must match literally. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @see Acme.Utils#match + +public class WildcardDictionary extends Dictionary + { + + private Vector keys; + private Vector elements; + + /// Constructor. + public WildcardDictionary() + { + keys = new Vector(); + elements = new Vector(); + } + + /// Returns the number of elements contained within the dictionary. + public int size() + { + return elements.size(); + } + + /// Returns true if the dictionary contains no elements. + public boolean isEmpty() + { + return size() == 0; + } + + /// Returns an enumeration of the dictionary's keys. + public Enumeration keys() + { + return keys.elements(); + } + + /// Returns an enumeration of the elements. Use the Enumeration methods + // on the returned object to fetch the elements sequentially. + public Enumeration elements() + { + return elements.elements(); + } + + /// Gets the object associated with the specified key in the dictionary. + // The key is assumed to be a String, which is matched against + // the wildcard-pattern keys in the dictionary. + // @param key the string to match + // @returns the element for the key, or null if there's no match + // @see Acme.Utils#match + public synchronized Object get( Object key ) + { + String sKey = (String) key; + for ( int i = 0; i < keys.size(); ++i ) + { + String thisKey = (String) keys.elementAt( i ); + if ( Acme.Utils.match( thisKey, sKey ) ) + return elements.elementAt( i ); + } + return null; + } + + /// Puts the specified element into the Dictionary, using the specified + // key. The element may be retrieved by doing a get() with the same + // key. The key and the element cannot be null. + // @param key the specified wildcard-pattern key + // @param value the specified element + // @return the old value of the key, or null if it did not have one. + // @exception NullPointerException If the value of the specified + // element is null. + public synchronized Object put( Object key, Object element ) + { + int i = keys.indexOf( key ); + if ( i != -1 ) + { + Object oldElement = elements.elementAt( i ); + elements.setElementAt( element, i ); + return oldElement; + } + else + { + keys.addElement( key ); + elements.addElement( element ); + return null; + } + } + + /// Removes the element corresponding to the key. Does nothing if the + // key is not present. + // @param key the key that needs to be removed + // @return the value of key, or null if the key was not found. + public synchronized Object remove( Object key ) + { + int i = keys.indexOf( key ); + if ( i != -1 ) + { + Object oldElement = elements.elementAt( i ); + keys.removeElementAt( i ); + elements.removeElementAt( i ); + return oldElement; + } + else + return null; + } + + }