Milán Major

netscape-revival

1 branch
Code

sun-java/classsrc/sun/awt/image/GifImageDecoder.java

/*
 * @(#)GifImageDecoder.java	1.23 95/10/09 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 from an InputStream and reports the
 *	image data to an InputStreamImageSource object.
 *
 * The algorithm is copyright of CompuServe.
 */
package sun.awt.image;

import java.util.Vector;
import java.util.Hashtable;
import java.io.InputStream;
import java.io.IOException;
import java.awt.image.*;

/**
 * Gif Image converter
 * 
 * @version 1.23 09 Oct 1995
 * @author Arthur van Hoff
 */
public class GifImageDecoder extends ImageDecoder {
    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;

    PixelStore8 store;
    int num_colors;
    byte[] colormap;
    IndexColorModel model;
    Hashtable props = new Hashtable();

    public GifImageDecoder(InputStreamImageSource src, InputStream is) {
	super(src, is);
    }

    public synchronized boolean catchupConsumer(InputStreamImageSource src,
						ImageConsumer ic)
    {
	return ((store == null) || store.replay(src, ic));
    }

    public synchronized void makeStore(int width, int height) {
	store = new PixelStore8(width, height);
    }

    /**
     * An error has occurred. Throw an exception.
     */
    private static void error(String s1) throws ImageFormatException {
	throw new ImageFormatException(s1);
    }

    /**
     * Read a number of bytes into a buffer.
     */
    boolean readBytes(byte buf[], int off, int len) {
	while (len > 0) {
	    int n;
	    try {
		n = input.read(buf, off, len);
	    } catch (IOException e) {
		n = -1;
	    }
	    if (n < 0) {
		return false;
	    }
	    off += n;
	    len -= n;
	}
	return true;
    }

    /**
     * produce an image from the stream.
     */
    public void produceImage() throws IOException, ImageFormatException {
	int trans_pixel = -1;
	try {
	    readHeader();

	    while (true) {
		int code;

		switch (code = input.read()) {
		  case EXBLOCK:
		    switch (code = input.read()) {
		      case EX_GRAPHICS_CONTROL: {
			byte buf[] = new byte[6];
			if (!readBytes(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_pixel = buf[4] & 0xFF;
			break;
		      }

		      case EX_COMMENT:
		      case EX_APPLICATION:
		      default:
			String comment = "";
			while (true) {
			    int n = input.read();
			    if (n == 0) {
				break;
			    }
			    byte buf[] = new byte[n];
			    if (!readBytes(buf, 0, n)) {
				return;//error("corrupt GIF file");
			    }
			    if (code == EX_COMMENT) {
				comment += new String(buf, 0);
			    }
			}
			if (code == EX_COMMENT) {
			    props.put("comment", comment);
			}
			break;
		    }
		    break;

		  case IMAGESEP:
		    try {
			model = new IndexColorModel(8, num_colors, colormap,
						    0, false, trans_pixel);
			colormap = null;
			if (readImage()) {
			    store.imageComplete();
			    if (store.getBitState() != PixelStore.BITS_LOST) {
				source.setPixelStore(store);
			    }
			    source.imageComplete(ImageConsumer.STATICIMAGEDONE);
			}
		    } catch (ArrayIndexOutOfBoundsException e) {
			if (verbose) {
			    e.printStackTrace();
			}
		    }
		    return;

		  case TERMINATOR:
		    return;

		  case -1:
		    return;

		  default:
		    return;//error("corrupt GIF file (parse) [" + code + "].");
		    //break;
		}
	    }
	} finally {
	    try {
		input.close();
	    } catch (IOException e) {
	    }
	}
    }

    /**
     * Read Image header
     */
    private void readHeader() throws IOException, ImageFormatException {
	// Create a buffer
	byte buf[] = new byte[13];

	// Read the header
	if (!readBytes(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 in GIF file.");
	}
	num_colors = 1 << ((ch & 0x7) + 1);

	// supposed to be NULL
	if (buf[12] != 0) {
	    error("corrupt GIF file (no null).");
	}

	// Read colors
	colormap = new byte[num_colors * 3];
	if (!readBytes(colormap, 0, num_colors * 3)) {
	    throw new IOException();
	}
    }

    /**
     * The ImageConsumer hints flag for a non-interlaced GIF image.
     */
    private static final int normalflags =
	ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES |
	ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME;

    /**
     * The ImageConsumer hints flag for an interlaced GIF image.
     */
    private static final int interlaceflags =
	ImageConsumer.RANDOMPIXELORDER | ImageConsumer.COMPLETESCANLINES |
	ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME;

    private native boolean parseImage(int width, int height,
				      boolean interlace, int initCodeSize,
				      byte block[], byte rasline[]);

    private int sendPixels(int y, int width, byte rasline[]) {
	int count = source.setPixels(0, y, width, 1, model,
				     rasline, 0, width);
	if (store.setPixels(0, y, width, 1, rasline, 0, width)) {
	    count++;
	}
	return count;
    }

    /**
     * Read Image data
     */
    private boolean readImage() throws IOException {
	long tm = 0;

	if (verbose) {
	    tm = System.currentTimeMillis();
	}

	// Allocate the buffer
	byte block[] = new byte[256 + 3];

	// Read the image descriptor
	if (!readBytes(block, 0, 10)) {
	    throw new IOException();
	}
	int width = (block[4] & 0xFF) | (block[5] << 8);
	int height = (block[6] & 0xFF) | (block[7] << 8);
	boolean interlace = (block[8] & INTERLACEMASK) != 0;
	int initCodeSize = block[9] & 0xFF;

	// Notify the consumers
	source.setDimensions(width, height);
	makeStore(width, height);

	source.setProperties(props);
	store.setProperties(props);

	source.setColorModel(model);
	store.setColorModel(model);

	int hints = (interlace ? interlaceflags : normalflags);
	source.setHints(hints);
	store.setHints(hints);

	// allocate the raster data
	byte rasline[] = new byte[width];

	if (verbose) {
	    System.out.print("Reading a " + width + " by " + height + " " +
		      (interlace ? "" : "non-") + "interlaced image...");
	}

	boolean ret = parseImage(width, height, interlace, initCodeSize,
				 block, rasline);

	if (verbose) {
	    System.out.println("done in "
			       + (System.currentTimeMillis() - tm)
			       + "ms");
	}

	return ret;
    }
}