package uk.co.mmscomputing.imageio.gif;
import java.io.*;
import java.util.*;
import java.awt.image.*;
import javax.imageio.*;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.metadata.IIOMetadata;
public class GIFImageReader extends ImageReader{
private boolean gotHeader = false;
private boolean gotImages = false;
private Vector images = null;
private int loops=-1;
protected GIFImageReader(ImageReaderSpi originatingProvider){
super(originatingProvider);
}
public BufferedImage read(int imageIndex, ImageReadParam param)throws IOException{
readAll();
checkIndex(imageIndex);
return (BufferedImage)images.elementAt(imageIndex);
}
public int getHeight(int imageIndex)throws IOException{
readAll();
checkIndex(imageIndex);
return ((BufferedImage)images.elementAt(imageIndex)).getHeight();
}
public int getWidth(int imageIndex)throws IOException{
readAll();
checkIndex(imageIndex);
return ((BufferedImage)images.elementAt(imageIndex)).getWidth();
}
public Iterator getImageTypes(int imageIndex)throws IOException{
readAll();
checkIndex(imageIndex);
ImageTypeSpecifier imageType = null;
java.util.List l = new ArrayList();
imageType=ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
l.add(imageType);
return l.iterator();
}
public int getNumImages(boolean allowSearch)throws IOException{
readAll();
return images.size();
}
public IIOMetadata getImageMetadata(int imageIndex)throws IOException{
readAll();
checkIndex(imageIndex);
return null;
}
public IIOMetadata getStreamMetadata() throws IOException{
return null;
}
////////////////////////////////////////////////////////////////////////////////////
private void checkIndex(int imageIndex) {
if (imageIndex > images.size()) {
throw new IndexOutOfBoundsException("mmsc - GIFImageReader : Bad index in gif image reader");
}
}
// document header
int width, height;
int background;
double aspectRatio;
boolean gcm=false; // global color map
boolean sort=false; // sort flag
int colres, colCount, bitColCount;
byte[][] gct;
// page header
int pleft, ptop, pwidth, pheight;
int pcolCount=0, pbitColCount=0;
boolean psort=false, pinterlaced=false, plcm=false;
byte[][] plct;
private byte[][] readColorTable(InputStream in, int colCount)throws IOException{
byte[] rColourTable=new byte[256];
byte[] gColourTable=new byte[256];
byte[] bColourTable=new byte[256];
for(int i=0;i<colCount;i++){
rColourTable[i]=(byte)in.read();
gColourTable[i]=(byte)in.read();
bColourTable[i]=(byte)in.read();
}
byte[][] colourTable=new byte[3][];
colourTable[0]=rColourTable;
colourTable[1]=gColourTable;
colourTable[2]=bColourTable;
return colourTable;
}
private int readShort(InputStream in)throws IOException{
return (in.read()&0x00FF)
|((in.read()&0x00FF)<<8)
;
}
private void readHeader(InputStream in)throws IOException{
if(gotHeader){return;}
gotHeader=true;
byte[] type=new byte[3];
type[0]=(byte)in.read();type[1]=(byte)in.read();type[2]=(byte)in.read();
if(((type[0]!='G')||(type[1]!='I')||(type[2]!='F'))){
throw new IOException(getClass().getName()+".readHeader:\n\tInvalid gif document : Missing 'GIF' byte sequence.");
}
byte[] version = new byte[3];
version[0]=(byte)in.read();version[1]=(byte)in.read();version[2]=(byte)in.read();
if(((version[0]!='8')||(version[1]!='9')||(version[2]!='a'))){
if(((version[0]!='8')||(version[1]!='7')||(version[2]!='a'))){
throw new IOException(getClass().getName()+".readHeader:\n\tInvalid gif document : Version is missing or unsupported.");
}
}
width = readShort(in);
height = readShort(in);
int resolution = in.read();
background = in.read();
int ar = in.read();
aspectRatio=(ar==0)? 1 : (ar+15)/64;
gcm =(resolution&0x00000080)!=0;
colres =((resolution&0x00000070)>>4)+1;
sort =(resolution&0x00000008)!=0;
bitColCount =(resolution&0x00000007)+1;
colCount =1<<bitColCount;
gct=(gcm)?readColorTable(in,colCount):null;
}
private void readPageHeader(InputStream in)throws IOException{
pleft =readShort(in);
ptop =readShort(in);
pwidth =readShort(in);
pheight =readShort(in);
int flag=in.read();
pbitColCount=(flag&0x00000007)+1;
pcolCount=1<<pbitColCount;
psort=(flag&0x00000020)!=0;
pinterlaced=(flag&0x00000040)!=0;
plcm=(flag&0x00000080)!=0;
plct=(plcm)?readColorTable(in,pcolCount):null;
System.out.println("GIF: left ="+pleft);
System.out.println("GIF: top ="+ptop);
System.out.println("GIF: width ="+pwidth);
System.out.println("GIF: height ="+pheight);
}
public void readExtension(InputStream in)throws IOException{
int code=in.read();
///*
switch(code){
case 1: // Plain Text Extension 0x01
System.out.println("Plain Text Extension ! ");
break;
case 249: readCntl(in); return; // Graphic Control Extension 0xF9
case 254: readComment(in); return; // Comment Extension 0xFE
case 255: readApp(in); return; // Application Extension 0xFF
default:
throw new IOException(getClass().getName()+".readExtension\n\tIllegal GIF Extension Block !");
}
//*/
int len=in.read();
while(len>0){
in.skip(len);
len=in.read();
}
}
private void readComment(InputStream in)throws IOException{
String comment="";
int len=in.read();
while(len>0){
for(int i=0;i<len;i++){
comment+=(char)in.read();
}
len=in.read();
}
System.out.println("Comment: "+comment);
}
private void readApp(InputStream in)throws IOException{
String id="";
int len=in.read(); // read application identifier
for(int i=0;i<len;i++){
id+=(char)in.read();
}
len=in.read(); // next sub-block length
if(id.equals("NETSCAPE2.0")){
int i=0;
if(len==3){
int no=in.read();i++;
switch(no){
case 1:
loops=in.read()|(in.read()<<8);i+=2;
System.out.println("loops="+loops);
break;
default:
System.out.println("NO:"+no);
break;
}
}
for(;i<len;i++){
int b=in.read();
System.out.println("appl["+i+"] 0x"+Integer.toHexString(b)+" "+(char)((b>=' ')?b:' ')+" "+b);
}
len=in.read(); // next sub-block length
}else{
System.out.println("ID:"+id);
}
while(len>0){
for(int i=0;i<len;i++){
int b=in.read();
System.out.println("appl["+i+"] 0x"+Integer.toHexString(b)+" "+(char)((b>=' ')?b:' ')+" "+b);
}
len=in.read();
}
}
private void readCntl(InputStream in)throws IOException{
int len=in.read();
if(len==4){ // BlockSize should be 4
String str="\n\nGraphic Control Extension:";
int flags=in.read();
switch((flags>>2)&0x07){
case 0: str+="\n\tNo disposal specified.";break;
case 1: str+="\n\tDo not dispose.";break;
case 2: str+="\n\tRestore to background color.";break;
case 3: str+="\n\tRestore to previous.";break;
default: str+="\n\tUnknown disposal mode.";break;
}
switch((flags>>1)&0x01){
case 0: str+="\n\tUser input is not expected.";break;
case 1: str+="\n\tUser input is expected.";break;
}
switch(flags&0x01){
case 0: str+="\n\tTransparent Index is not given.";break;
case 1: str+="\n\tTransparent Index is given.";break;
}
int delay=in.read()|(in.read()<<8);
str+="\n\tDelay: "+delay;
int background=in.read();
str+="\n\tTransparency Index: "+background+"\n";
System.out.println(str);
len=in.read(); // Block Terminator
}
while(len>0){
for(int i=0;i<len;i++){
int b=in.read();
System.out.println("cntl["+i+"] 0x"+Integer.toHexString(b)+" "+(char)((b>=' ')?b:' ')+" "+b);
}
len=in.read();
}
}
private BufferedImage read(InputStream in)throws IOException{
readHeader(in);
int code=in.read();
while(code>0){
switch(code){
case ',': // Image Separator
readPageHeader(in);
GIFLZWInputStream is=new GIFLZWInputStream(in);
byte[][] ct=(plct!=null)?plct:gct;
int cc=(plct!=null)?pcolCount:colCount;
int bcc=(plct!=null)?pbitColCount:bitColCount;
IndexColorModel cm = new IndexColorModel(bcc,colCount,ct[0],ct[1],ct[2]);
BufferedImage image = new BufferedImage(pwidth,pheight,BufferedImage.TYPE_BYTE_INDEXED,cm);
Raster raster = image.getRaster();
DataBufferByte db = (DataBufferByte)raster.getDataBuffer();
byte[] data = db.getData();
is.read(data); // read codes
int eoi=is.read(); // read eoi code
return image;
case 0x21: // Extension block
readExtension(in);
break;
case ';': // GIF-Terminator
return null;
default:
throw new IOException(getClass().getName()+".read\n\tIllegal GIF block : "+(char)code+" [0x"+Integer.toHexString(code)+"]");
}
code=in.read();
}
throw new IOException(getClass().getName()+".read\n\tDid not find GIF Image Descriptor AND/OR GIF Terminator!");
}
private void readAll()throws IOException{
if(!gotImages){
gotImages = true;
GIFFilterInputStream in=new GIFFilterInputStream((ImageInputStream)getInput());
images=new Vector();
BufferedImage image;
while((image=read(in))!=null){
images.add(image);
}
}
}
static private class GIFFilterInputStream extends InputStream{
private ImageInputStream in;
private byte[] buffer;
private int count,max;
int pos=0;
public GIFFilterInputStream(ImageInputStream in){
this.in=in;
buffer=new byte[4096];
count=0;max=0;
}
public int read()throws IOException{
if(count==max){
max=in.read(buffer);
if(max==-1){count=-1;return -1;}
count=0;
}
pos++;
return (buffer[count++]&0x00FF);
}
}
}