home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 1995 November
/
PCWK1195.iso
/
inne
/
win95
/
sieciowe
/
hotja32.lzh
/
hotjava
/
classsrc
/
awt
/
gifimage.java
< prev
next >
Wrap
Text File
|
1995-08-11
|
12KB
|
489 lines
/*
* @(#)GifImage.java 1.24 95/05/11 Patrick Naughton, Arthur van Hoff
*
* Copyright (c) 1994 Sun Microsystems, Inc. All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this software
* and its documentation for NON-COMMERCIAL purposes and without
* fee is hereby granted provided that this copyright notice
* appears in all copies. Please refer to the file "copyright.html"
* for further important copyright and licensing information.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
* THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
* ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
*/
/*-
* Reads GIF images into a DIBitmap structure.
*
* The algorithm is copyright of CompuServe.
*/
package awt;
import java.io.*;
import browser.Observer;
/**
* Gif Image converter
*
* @version 1.24 11 May 1995
* @author Arthur van Hoff
*/
public
class GifImage extends DIBitmap {
private final boolean verbose = false;
private static final int IMAGESEP = 0x2c;
private static final int EXBLOCK = 0x21;
private static final int EX_GRAPHICS_CONTROL= 0xf9;
private static final int EX_COMMENT = 0xfe;
private static final int EX_APPLICATION = 0xff;
private static final int TERMINATOR = 0x3b;
private static final int INTERLACEMASK = 0x40;
private static final int COLORMAPMASK = 0x80;
private static final int BUFSIZE = 2048;
/**
* An error has occurred. Throw an exception.
*/
private static void error(String s1) {
throw new Exception(s1);
}
/**
* Read bytes
*/
boolean readBytes(InputStream in, byte buf[], int off, int len) {
while (len > 0) {
int n = in.read(buf, off, len);
if (n < 0) {
return false;
}
off += n;
len -= n;
}
return true;
}
/**
* Constructor, read the image from a file
*/
public GifImage(String fname) {
// Don't buffer this stream, most data is read in chunks anyway...
this(new FileInputStream(fname), null);
}
public GifImage(InputStream in) {
this(in, null);
}
/**
* Constructor, read image from stream.
*/
public GifImage(InputStream in, Observer o) {
if (o != null) {
addObserver(o);
}
try {
readHeader(in);
while (true) {
int code;
switch (code = in.read()) {
case EXBLOCK:
switch (code = in.read()) {
case EX_GRAPHICS_CONTROL: {
byte buf[] = new byte[6];
if (!readBytes(in, buf, 0, 6)) {
return;//error("corrupt GIF file");
}
if ((buf[0] != 4) || (buf[5] != 0)) {
return;//error("corrupt GIF file (GCE size)");
}
// Get the index of the transparent color
trans_index = buf[4] & 0xFF;
break;
}
case EX_COMMENT:
case EX_APPLICATION:
default:
while (true) {
int n = in.read();
if (n == 0) {
break;
}
byte buf[] = new byte[n];
if (!readBytes(in, buf, 0, n)) {
return;//error("corrupt GIF file");
}
}
break;
}
break;
case IMAGESEP:
try {
readImage(in);
} catch (ArrayIndexOutOfBoundsException e) {
if (verbose) {
e.printStackTrace();
}
return;//error("corrupt gif file");
}
break;
case TERMINATOR:
return;
case -1:
return;
default:
return;//error("corrupt GIF file (parse) [" + code + "].");
//break;
}
}
} finally {
in.close();
}
}
/**
* Read Image header
*/
private void readHeader(InputStream in) {
// Create a buffer
byte buf[] = new byte[256*3];
// Read the header
if (!readBytes(in, buf, 0, 13)) {
throw new IOException();
}
// Check header
if ((buf[0] != 'G') || (buf[1] != 'I') || (buf[2] != 'F')) {
error("not a GIF file.");
}
// colormap info
int ch = buf[10] & 0xFF;
if ((ch & COLORMAPMASK) == 0) {
error("no global colormap.");
}
num_colors = 1 << ((ch & 0x7) + 1);
// supposed to be NULL
if (buf[12] != 0) {
error("corrupt GIF file (nonull).");
}
// Read colors
if (!readBytes(in, buf, 0, num_colors * 3)) {
throw new IOException();
}
// Allocate color map
red = new byte[num_colors];
green = new byte[num_colors];
blue = new byte[num_colors];
// Copy colors
for (int i = 0 ; i < num_colors ; i++) {
red[i] = buf[i*3 + 0];
green[i] = buf[i*3 + 1];
blue[i] = buf[i*3 + 2];
}
// nothing transparent yet.
trans_index = num_colors + 1;
}
private void readImage(InputStream in) {
int tm = 0;
if (verbose) {
tm = System.nowMillis();
}
// Allocate the buffer
byte block[] = new byte[256 + 3];
// Read the image descriptor
if (!readBytes(in, block, 0, 10)) {
throw new IOException();
}
width = (block[4] & 0xFF) | (block[5] << 8);
height = (block[6] & 0xFF) | (block[7] << 8);
boolean interlace = (block[8] & INTERLACEMASK) != 0;
int initCodeSize = block[9] & 0xFF;
/* observers may want to know the image size */
/* annoying I have to say setChanged */
setChanged();
notifyObservers();
// allocate the raster data
byte ras[] = raster = new byte[width * height];
if (verbose) {
System.out.print("Reading a " + width + " by " + height + " " +
(interlace ? "" : "non-") + "interlaced image...");
}
// Patrick Naughton:
// Note that I ignore the possible existence of a local color map.
// I'm told there aren't many files around that use them, and the
// spec says it's defined for future use. This could lead to an
// error reading some files.
//
// Start reading the image data. First we get the intial code size
// and compute decompressor constant values, based on this code
// size.
//
// The GIF spec has it that the code size is the code size used to
// compute the above values is the code size given in the file,
// but the code size used in compression/decompression is the code
// size given in the file plus one. (thus the ++).
// Arthur van Hoff:
// The following narly code reads LZW compressed data blocks and
// dumps it into the image data. The input stream is broken up into
// blocks of 1-255 characters, each preceded by a length byte.
// 3-12 bit codes are read from these blocks. The codes correspond to
// entry is the hashtable (the prefix, suffix stuff), and the appropriate
// pixels are written to the image.
int clearCode = (1 << initCodeSize);
int eofCode = clearCode + 1;
int bitMask = num_colors - 1;
if (verbose) {
System.out.print("Decompressing...");
System.out.flush();
}
// Variables used to form reading data
boolean blockEnd = false;
int remain = 0;
int byteoff = 0;
int accumbits = 0;
int accumdata = 0;
// Variables used to decompress the data
int codeSize = initCodeSize + 1;
int maxCode = 1 << codeSize;
int codeMask = maxCode - 1;
int freeCode = clearCode + 2;
int code = 0;
int oldCode = 0;;
byte prevChar = 0;
// Temproray storage for decompression
short prefix[] = new short[4096];
byte suffix[] = new byte[4096];
byte outCode[] = new byte[1025];
// Variables used for writing pixels
int x = width;
int y = 0;
int off = 0;
int pass = 0;
// Read codes until the eofCode is encountered
while (true) {
if (accumbits < codeSize) {
// fill the buffer if needed
remain -= 2;
while (remain < 0 && !blockEnd) {
// move remaining bytes to the beginning of the buffer
block[0] = block[byteoff];
byteoff = 0;
// read the next block length
int blockLength = in.read();
if (blockLength < 0) {
// throw new IOException();
return; // quietly accept truncated GIF images
}
if (blockLength == 0) {
blockEnd = true;
}
// fill the block
if (!readBytes(in, block, remain + 2, blockLength)) {
// throw new IOException();
return; // quietly accept truncated GIF images
}
remain += blockLength;
}
// 2 bytes at a time saves checking for accumbits < codeSize.
// We know we'll get enough and also that we can't overflow
// since codeSize <= 12.
accumdata += (block[byteoff++] & 0xff) << accumbits;
accumbits += 8;
accumdata += (block[byteoff++] & 0xff) << accumbits;
accumbits += 8;
}
// Compute the code
code = accumdata & codeMask;
accumdata >>= codeSize;
accumbits -= codeSize;
//
// Interpret the code
//
if (code == clearCode) {
// Clear code sets everything back to its initial value, then
// reads the immediately subsequent code as uncompressed data.
if (verbose) {
System.out.print(".");
System.out.flush();
}
// Note that freeCode is one less than it is supposed to be,
// this is because it will be incremented next time round the loop
freeCode = clearCode + 1;
codeSize = initCodeSize + 1;
maxCode = 1 << codeSize;
codeMask = maxCode - 1;
// Continue if we've NOT reached the end, some Gif images contain bogus
// codes after the last clear code.
if (off < raster.length) {
continue;
}
// pretend we've reached the end of the data
code = eofCode;
}
if (code == eofCode) {
// make sure we read the whole block of pixels.
if (!blockEnd) {
in.read();
}
if (verbose) {
System.out.println("done in " + (System.nowMillis() - tm) + "ms");
}
return;
}
// It must be data: save code in CurCode
int curCode = code;
int outCount = outCode.length;
// If greater or equal to freeCode, not in the hash table
// yet; repeat the last character decoded
if (curCode >= freeCode) {
curCode = oldCode;
outCode[--outCount] = prevChar;
}
// Unless this code is raw data, pursue the chain pointed
// to by curCode through the hash table to its end; each
// code in the chain puts its associated output code on
// the output queue.
while (curCode > bitMask) {
outCode[--outCount] = suffix[curCode];
curCode = prefix[curCode];
}
// The last code in the chain is treated as raw data.
prevChar = (byte)curCode;
outCode[--outCount] = prevChar;
// Now we put the data out to the Output routine. It's
// been stacked LIFO, so deal with it that way...
int len = outCode.length - outCount;
if (len > 2 && len < x) {
x -= len;
System.arraycopy(outCode, outCount, ras, off, len);
off += len;
} else while (--len >= 0) {
ras[off++] = outCode[outCount++];
// Update the X-coordinate, and if it overflows, update the
// Y-coordinate
if (--x == 0) {
// If a non-interlaced picture, just increment y to the next
// scan line. If it's interlaced, deal with the interlace as
// described in the GIF spec. Put the decoded scan line out
// to the screen if we haven't gone past the bottom of it
x = width;
if (interlace) {
switch (pass) {
case 0:
y += 8;
if (y >= height) {
pass++;
y = 4;
}
break;
case 1:
y += 8;
if (y >= height) {
pass++;
y = 2;
}
break;
case 2:
y += 4;
if (y >= height) {
pass++;
y = 1;
}
break;
case 3:
y += 2;
break;
}
off = y * width;
}
// Some files overrun the end
if (off >= ras.length) {
break;
}
}
}
// Build the hash table on-the-fly. No table is stored in
// the file.
prefix[freeCode] = (short)oldCode;
suffix[freeCode] = prevChar;
oldCode = code;
// Point to the next slot in the table. If we exceed the
// maxCode, increment the code size unless
// it's already 12. If it is, do nothing: the next code
// decompressed better be CLEAR
if (++freeCode >= maxCode) {
if (codeSize < 12) {
codeSize++;
maxCode <<= 1;
codeMask = maxCode - 1;
}
}
}
}
/**
* Testing... 1... 2...
*/
public static void main(String args[]) {
GifImage image = new GifImage(args[0]);
}
}