342N/A/*
3681N/A * Copyright (c) 1995, 2006, Oracle and/or its affiliates. All rights reserved.
342N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
342N/A *
342N/A * This code is free software; you can redistribute it and/or modify it
342N/A * under the terms of the GNU General Public License version 2 only, as
342N/A * published by the Free Software Foundation. Oracle designates this
342N/A * particular file as subject to the "Classpath" exception as provided
342N/A * by Oracle in the LICENSE file that accompanied this code.
342N/A *
342N/A * This code is distributed in the hope that it will be useful, but WITHOUT
342N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
342N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
342N/A * version 2 for more details (a copy is included in the LICENSE file that
342N/A * accompanied this code).
342N/A *
342N/A * You should have received a copy of the GNU General Public License version
342N/A * 2 along with this work; if not, write to the Free Software Foundation,
1472N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1472N/A *
1472N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
342N/A * or visit www.oracle.com if you need additional information or have any
342N/A * questions.
342N/A */
1879N/A
2591N/A/*-
2591N/A * Reads GIF images from an InputStream and reports the
2591N/A * image data to an InputStreamImageSource object.
1879N/A *
1879N/A * The algorithm is copyright of CompuServe.
342N/A */
2591N/Apackage sun.awt.image;
342N/A
3681N/Aimport java.util.Vector;
3681N/Aimport java.util.Hashtable;
2591N/Aimport java.io.InputStream;
2591N/Aimport java.io.IOException;
3681N/Aimport java.awt.image.*;
342N/A
3681N/A/**
3681N/A * Gif Image converter
3681N/A *
2591N/A * @author Arthur van Hoff
2591N/A * @author Jim Graham
2591N/A */
2037N/Apublic class GifImageDecoder extends ImageDecoder {
2037N/A private static final boolean verbose = false;
2037N/A
2037N/A private static final int IMAGESEP = 0x2c;
2037N/A private static final int EXBLOCK = 0x21;
2037N/A private static final int EX_GRAPHICS_CONTROL= 0xf9;
2591N/A private static final int EX_COMMENT = 0xfe;
2037N/A private static final int EX_APPLICATION = 0xff;
2037N/A private static final int TERMINATOR = 0x3b;
2037N/A private static final int TRANSPARENCYMASK = 0x01;
2037N/A private static final int INTERLACEMASK = 0x40;
2037N/A private static final int COLORMAPMASK = 0x80;
2037N/A
2208N/A int num_global_colors;
2591N/A byte[] global_colormap;
2591N/A int trans_pixel = -1;
3681N/A IndexColorModel global_model;
2591N/A
2037N/A Hashtable props = new Hashtable();
2037N/A
2037N/A byte[] saved_image;
2037N/A IndexColorModel saved_model;
2591N/A
2037N/A int global_width;
2037N/A int global_height;
2037N/A int global_bgpixel;
2591N/A
2591N/A GifFrame curframe;
2591N/A
3681N/A public GifImageDecoder(InputStreamImageSource src, InputStream is) {
3681N/A super(src, is);
2591N/A }
3681N/A
2591N/A /**
2591N/A * An error has occurred. Throw an exception.
2591N/A */
2591N/A private static void error(String s1) throws ImageFormatException {
2591N/A throw new ImageFormatException(s1);
2591N/A }
2591N/A
2591N/A /**
2591N/A * Read a number of bytes into a buffer.
2591N/A * @return number of bytes that were not read due to EOF or error
3863N/A */
3681N/A private int readBytes(byte buf[], int off, int len) {
3681N/A while (len > 0) {
2591N/A try {
2591N/A int n = input.read(buf, off, len);
2591N/A if (n < 0) {
2591N/A break;
2591N/A }
2591N/A off += n;
2591N/A len -= n;
2591N/A } catch (IOException e) {
2591N/A break;
2591N/A }
2591N/A }
2591N/A return len;
2591N/A }
2591N/A
2591N/A private static final int ExtractByte(byte buf[], int off) {
3681N/A return (buf[off] & 0xFF);
2037N/A }
2591N/A
2591N/A private static final int ExtractWord(byte buf[], int off) {
2591N/A return (buf[off] & 0xFF) | ((buf[off + 1] & 0xFF) << 8);
2591N/A }
2591N/A
2591N/A /**
2591N/A * produce an image from the stream.
2591N/A */
2591N/A public void produceImage() throws IOException, ImageFormatException {
2591N/A try {
2591N/A readHeader();
2591N/A
2591N/A int totalframes = 0;
2591N/A int frameno = 0;
2591N/A int nloops = -1;
2591N/A int disposal_method = 0;
2591N/A int delay = -1;
2591N/A boolean loopsRead = false;
2591N/A boolean isAnimation = false;
2591N/A
2591N/A while (!aborted) {
2591N/A int code;
2591N/A
2591N/A switch (code = input.read()) {
2591N/A case EXBLOCK:
2591N/A switch (code = input.read()) {
2591N/A case EX_GRAPHICS_CONTROL: {
2037N/A byte buf[] = new byte[6];
2591N/A if (readBytes(buf, 0, 6) != 0) {
2591N/A return;//error("corrupt GIF file");
2591N/A }
2591N/A if ((buf[0] != 4) || (buf[5] != 0)) {
3681N/A return;//error("corrupt GIF file (GCE size)");
3681N/A }
3681N/A // Get the index of the transparent color
2591N/A delay = ExtractWord(buf, 2) * 10;
2591N/A if (delay > 0 && !isAnimation) {
2591N/A isAnimation = true;
2591N/A ImageFetcher.startingAnimation();
2591N/A }
2591N/A disposal_method = (buf[1] >> 2) & 7;
2037N/A if ((buf[1] & TRANSPARENCYMASK) != 0) {
342N/A trans_pixel = ExtractByte(buf, 4);
342N/A } else {
342N/A trans_pixel = -1;
3681N/A }
2591N/A break;
2591N/A }
3681N/A
2591N/A case EX_COMMENT:
2591N/A case EX_APPLICATION:
3681N/A default:
3681N/A boolean loop_tag = false;
2591N/A String comment = "";
2591N/A while (true) {
2591N/A int n = input.read();
2591N/A if (n <= 0) {
2591N/A break;
2591N/A }
3681N/A byte buf[] = new byte[n];
2591N/A if (readBytes(buf, 0, n) != 0) {
2591N/A return;//error("corrupt GIF file");
3681N/A }
2591N/A if (code == EX_COMMENT) {
2591N/A comment += new String(buf, 0);
2591N/A } else if (code == EX_APPLICATION) {
342N/A if (loop_tag) {
342N/A if (n == 3 && buf[0] == 1) {
2591N/A if (loopsRead) {
2591N/A ExtractWord(buf, 1);
2591N/A }
342N/A else {
2591N/A nloops = ExtractWord(buf, 1);
3681N/A loopsRead = true;
2591N/A }
3681N/A } else {
342N/A loop_tag = false;
2591N/A }
3681N/A }
3681N/A if ("NETSCAPE2.0".equals(new String(buf, 0))) {
2591N/A loop_tag = true;
342N/A }
342N/A }
342N/A }
342N/A if (code == EX_COMMENT) {
342N/A props.put("comment", comment);
3681N/A }
2591N/A if (loop_tag && !isAnimation) {
342N/A isAnimation = true;
342N/A ImageFetcher.startingAnimation();
342N/A }
342N/A break;
342N/A
342N/A case -1:
342N/A return; //error("corrupt GIF file");
342N/A }
3681N/A break;
2037N/A
2037N/A case IMAGESEP:
2591N/A if (!isAnimation) {
2037N/A input.mark(0); // we don't need the mark buffer
342N/A }
342N/A try {
2591N/A if (!readImage(totalframes == 0,
2591N/A disposal_method,
2591N/A delay)) {
342N/A return;
2591N/A }
3681N/A } catch (Exception e) {
2591N/A if (verbose) {
2591N/A e.printStackTrace();
2591N/A }
342N/A return;
2591N/A }
2591N/A frameno++;
2591N/A totalframes++;
2591N/A break;
2591N/A
2591N/A default:
342N/A case -1:
2591N/A if (verbose) {
2591N/A if (code == -1) {
2591N/A System.err.println("Premature EOF in GIF file," +
342N/A " frame " + frameno);
342N/A } else {
2591N/A System.err.println("corrupt GIF file (parse) ["
2591N/A + code + "].");
2591N/A }
2591N/A }
2591N/A if (frameno == 0) {
2591N/A return;
2591N/A }
342N/A // NOBREAK
342N/A
342N/A case TERMINATOR:
342N/A if (nloops == 0 || nloops-- >= 0) {
2591N/A try {
2591N/A if (curframe != null) {
2591N/A curframe.dispose();
3681N/A curframe = null;
2591N/A }
2591N/A input.reset();
3681N/A saved_image = null;
2591N/A saved_model = null;
2591N/A frameno = 0;
3681N/A break;
2591N/A } catch (IOException e) {
2591N/A return; // Unable to reset input buffer
2591N/A }
3681N/A }
2591N/A if (verbose && frameno != 1) {
3681N/A System.out.println("processing GIF terminator,"
2591N/A + " frames: " + frameno
3681N/A + " total: " + totalframes);
2591N/A }
2591N/A imageComplete(ImageConsumer.STATICIMAGEDONE, true);
3681N/A return;
2591N/A }
2591N/A }
2591N/A } finally {
2591N/A close();
2591N/A }
2591N/A }
2591N/A
2591N/A /**
2591N/A * Read Image header
2591N/A */
2591N/A private void readHeader() throws IOException, ImageFormatException {
2591N/A // Create a buffer
2591N/A byte buf[] = new byte[13];
2591N/A
2591N/A // Read the header
2591N/A if (readBytes(buf, 0, 13) != 0) {
2591N/A throw new IOException();
342N/A }
3681N/A
3681N/A // Check header
2591N/A if ((buf[0] != 'G') || (buf[1] != 'I') || (buf[2] != 'F')) {
342N/A error("not a GIF file.");
2591N/A }
// Global width&height
global_width = ExtractWord(buf, 6);
global_height = ExtractWord(buf, 8);
// colormap info
int ch = ExtractByte(buf, 10);
if ((ch & COLORMAPMASK) == 0) {
// no global colormap so make up our own
// If there is a local colormap, it will override what we
// have here. If there is not a local colormap, the rules
// for GIF89 say that we can use whatever colormap we want.
// This means that we should probably put in a full 256 colormap
// at some point. REMIND!
num_global_colors = 2;
global_bgpixel = 0;
global_colormap = new byte[2*3];
global_colormap[0] = global_colormap[1] = global_colormap[2] = (byte)0;
global_colormap[3] = global_colormap[4] = global_colormap[5] = (byte)255;
}
else {
num_global_colors = 1 << ((ch & 0x7) + 1);
global_bgpixel = ExtractByte(buf, 11);
if (buf[12] != 0) {
props.put("aspectratio", ""+((ExtractByte(buf, 12) + 15) / 64.0));
}
// Read colors
global_colormap = new byte[num_global_colors * 3];
if (readBytes(global_colormap, 0, num_global_colors * 3) != 0) {
throw new IOException();
}
}
input.mark(Integer.MAX_VALUE); // set this mark in case this is an animated GIF
}
/**
* 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 short prefix[] = new short[4096];
private byte suffix[] = new byte[4096];
private byte outCode[] = new byte[4097];
private static native void initIDs();
static {
/* ensure that the necessary native libraries are loaded */
NativeLibLoader.loadLibraries();
initIDs();
}
private native boolean parseImage(int x, int y, int width, int height,
boolean interlace, int initCodeSize,
byte block[], byte rasline[],
IndexColorModel model);
private int sendPixels(int x, int y, int width, int height,
byte rasline[], ColorModel model) {
int rasbeg, rasend, x2;
if (y < 0) {
height += y;
y = 0;
}
if (y + height > global_height) {
height = global_height - y;
}
if (height <= 0) {
return 1;
}
// rasline[0] == pixel at coordinate (x,y)
// rasline[width] == pixel at coordinate (x+width, y)
if (x < 0) {
rasbeg = -x;
width += x; // same as (width -= rasbeg)
x2 = 0; // same as (x2 = x + rasbeg)
} else {
rasbeg = 0;
// width -= 0; // same as (width -= rasbeg)
x2 = x; // same as (x2 = x + rasbeg)
}
// rasline[rasbeg] == pixel at coordinate (x2,y)
// rasline[width] == pixel at coordinate (x+width, y)
// rasline[rasbeg + width] == pixel at coordinate (x2+width, y)
if (x2 + width > global_width) {
width = global_width - x2;
}
if (width <= 0) {
return 1;
}
rasend = rasbeg + width;
// rasline[rasbeg] == pixel at coordinate (x2,y)
// rasline[rasend] == pixel at coordinate (x2+width, y)
int off = y * global_width + x2;
boolean save = (curframe.disposal_method == GifFrame.DISPOSAL_SAVE);
if (trans_pixel >= 0 && !curframe.initialframe) {
if (saved_image != null && model.equals(saved_model)) {
for (int i = rasbeg; i < rasend; i++, off++) {
byte pixel = rasline[i];
if ((pixel & 0xff) == trans_pixel) {
rasline[i] = saved_image[off];
} else if (save) {
saved_image[off] = pixel;
}
}
} else {
// We have to do this the hard way - only transmit
// the non-transparent sections of the line...
// Fix for 6301050: the interlacing is ignored in this case
// in order to avoid artefacts in case of animated images.
int runstart = -1;
int count = 1;
for (int i = rasbeg; i < rasend; i++, off++) {
byte pixel = rasline[i];
if ((pixel & 0xff) == trans_pixel) {
if (runstart >= 0) {
count = setPixels(x + runstart, y,
i - runstart, 1,
model, rasline,
runstart, 0);
if (count == 0) {
break;
}
}
runstart = -1;
} else {
if (runstart < 0) {
runstart = i;
}
if (save) {
saved_image[off] = pixel;
}
}
}
if (runstart >= 0) {
count = setPixels(x + runstart, y,
rasend - runstart, 1,
model, rasline,
runstart, 0);
}
return count;
}
} else if (save) {
System.arraycopy(rasline, rasbeg, saved_image, off, width);
}
int count = setPixels(x2, y, width, height, model,
rasline, rasbeg, 0);
return count;
}
/**
* Read Image data
*/
private boolean readImage(boolean first, int disposal_method, int delay)
throws IOException
{
if (curframe != null && !curframe.dispose()) {
abort();
return false;
}
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) != 0) {
throw new IOException();
}
int x = ExtractWord(block, 0);
int y = ExtractWord(block, 2);
int width = ExtractWord(block, 4);
int height = ExtractWord(block, 6);
/*
* Majority of gif images have
* same logical screen and frame dimensions.
* Also, Photoshop and Mozilla seem to use the logical
* screen dimension (from the global stream header)
* if frame dimension is invalid.
*
* We use similar heuristic and trying to recover
* frame width from logical screen dimension and
* frame offset.
*/
if (width == 0 && global_width != 0) {
width = global_width - x;
}
if (height == 0 && global_height != 0) {
height = global_height - y;
}
boolean interlace = (block[8] & INTERLACEMASK) != 0;
IndexColorModel model = global_model;
if ((block[8] & COLORMAPMASK) != 0) {
// We read one extra byte above so now when we must
// transfer that byte as the first colormap byte
// and manually read the code size when we are done
int num_local_colors = 1 << ((block[8] & 0x7) + 1);
// Read local colors
byte[] local_colormap = new byte[num_local_colors * 3];
local_colormap[0] = block[9];
if (readBytes(local_colormap, 1, num_local_colors * 3 - 1) != 0) {
throw new IOException();
}
// Now read the "real" code size byte which follows
// the local color table
if (readBytes(block, 9, 1) != 0) {
throw new IOException();
}
if (trans_pixel >= num_local_colors) {
// Fix for 4233748: extend colormap to contain transparent pixel
num_local_colors = trans_pixel + 1;
local_colormap = grow_colormap(local_colormap, num_local_colors);
}
model = new IndexColorModel(8, num_local_colors, local_colormap,
0, false, trans_pixel);
} else if (model == null
|| trans_pixel != model.getTransparentPixel()) {
if (trans_pixel >= num_global_colors) {
// Fix for 4233748: extend colormap to contain transparent pixel
num_global_colors = trans_pixel + 1;
global_colormap = grow_colormap(global_colormap, num_global_colors);
}
model = new IndexColorModel(8, num_global_colors, global_colormap,
0, false, trans_pixel);
global_model = model;
}
// Notify the consumers
if (first) {
if (global_width == 0) global_width = width;
if (global_height == 0) global_height = height;
setDimensions(global_width, global_height);
setProperties(props);
setColorModel(model);
headerComplete();
}
if (disposal_method == GifFrame.DISPOSAL_SAVE && saved_image == null) {
saved_image = new byte[global_width * global_height];
/*
* If height of current image is smaller than the global height,
* fill the gap with transparent pixels.
*/
if ((height < global_height) && (model != null)) {
byte tpix = (byte)model.getTransparentPixel();
if (tpix >= 0) {
byte trans_rasline[] = new byte[global_width];
for (int i=0; i<global_width;i++) {
trans_rasline[i] = tpix;
}
setPixels(0, 0, global_width, y,
model, trans_rasline, 0, 0);
setPixels(0, y+height, global_width,
global_height-height-y, model, trans_rasline,
0, 0);
}
}
}
int hints = (interlace ? interlaceflags : normalflags);
setHints(hints);
curframe = new GifFrame(this, disposal_method, delay,
(curframe == null), model,
x, y, width, height);
// allocate the raster data
byte rasline[] = new byte[width];
if (verbose) {
System.out.print("Reading a " + width + " by " + height + " " +
(interlace ? "" : "non-") + "interlaced image...");
}
int initCodeSize = ExtractByte(block, 9);
if (initCodeSize >= 12) {
if (verbose) {
System.out.println("Invalid initial code size: " +
initCodeSize);
}
return false;
}
boolean ret = parseImage(x, y, width, height,
interlace, initCodeSize,
block, rasline, model);
if (!ret) {
abort();
}
if (verbose) {
System.out.println("done in "
+ (System.currentTimeMillis() - tm)
+ "ms");
}
return ret;
}
public static byte[] grow_colormap(byte[] colormap, int newlen) {
byte[] newcm = new byte[newlen * 3];
System.arraycopy(colormap, 0, newcm, 0, colormap.length);
return newcm;
}
}
class GifFrame {
private static final boolean verbose = false;
private static IndexColorModel trans_model;
static final int DISPOSAL_NONE = 0x00;
static final int DISPOSAL_SAVE = 0x01;
static final int DISPOSAL_BGCOLOR = 0x02;
static final int DISPOSAL_PREVIOUS = 0x03;
GifImageDecoder decoder;
int disposal_method;
int delay;
IndexColorModel model;
int x;
int y;
int width;
int height;
boolean initialframe;
public GifFrame(GifImageDecoder id, int dm, int dl, boolean init,
IndexColorModel cm, int x, int y, int w, int h) {
this.decoder = id;
this.disposal_method = dm;
this.delay = dl;
this.model = cm;
this.initialframe = init;
this.x = x;
this.y = y;
this.width = w;
this.height = h;
}
private void setPixels(int x, int y, int w, int h,
ColorModel cm, byte[] pix, int off, int scan) {
decoder.setPixels(x, y, w, h, cm, pix, off, scan);
}
public boolean dispose() {
if (decoder.imageComplete(ImageConsumer.SINGLEFRAMEDONE, false) == 0) {
return false;
} else {
if (delay > 0) {
try {
if (verbose) {
System.out.println("sleeping: "+delay);
}
Thread.sleep(delay);
} catch (InterruptedException e) {
return false;
}
} else {
Thread.yield();
}
if (verbose && disposal_method != 0) {
System.out.println("disposal method: "+disposal_method);
}
int global_width = decoder.global_width;
int global_height = decoder.global_height;
if (x < 0) {
width += x;
x = 0;
}
if (x + width > global_width) {
width = global_width - x;
}
if (width <= 0) {
disposal_method = DISPOSAL_NONE;
} else {
if (y < 0) {
height += y;
y = 0;
}
if (y + height > global_height) {
height = global_height - y;
}
if (height <= 0) {
disposal_method = DISPOSAL_NONE;
}
}
switch (disposal_method) {
case DISPOSAL_PREVIOUS:
byte[] saved_image = decoder.saved_image;
IndexColorModel saved_model = decoder.saved_model;
if (saved_image != null) {
setPixels(x, y, width, height,
saved_model, saved_image,
y * global_width + x, global_width);
}
break;
case DISPOSAL_BGCOLOR:
byte tpix;
if (model.getTransparentPixel() < 0) {
model = trans_model;
if (model == null) {
model = new IndexColorModel(8, 1,
new byte[4], 0, true);
trans_model = model;
}
tpix = 0;
} else {
tpix = (byte) model.getTransparentPixel();
}
byte[] rasline = new byte[width];
if (tpix != 0) {
for (int i = 0; i < width; i++) {
rasline[i] = tpix;
}
}
// clear saved_image using transparent pixels
// this will be used as the background in the next display
if( decoder.saved_image != null ) {
for( int i = 0; i < global_width * global_height; i ++ )
decoder.saved_image[i] = tpix;
}
setPixels(x, y, width, height, model, rasline, 0, 0);
break;
case DISPOSAL_SAVE:
decoder.saved_model = model;
break;
}
}
return true;
}
}