import java.awt.*;import java.awt.image.BufferedImage;import java.awt.image.DataBufferInt;import java.io.BufferedInputStream;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.net.URL;import java.util.ArrayList;/**
* Class GifDecoder - Decodes a GIF file into one or more frames.
* <br><pre>
* Example:
* GifDecoder d = new GifDecoder();
* d.read("sample.gif");
* int n = d.getFrameCount();
* for (int i = 0; i < n; i++) {
* BufferedImage frame = d.getFrame(i); // frame i
* int t = d.getDelay(i); // display duration of frame in milliseconds
* // do something with frame
* }
* </pre>
* No copyright asserted on the source code of this class. May be used for
* any purpose, however, refer to the Unisys LZW patent for any additional
* restrictions. Please forward any corrections to questions at fmsware.com.
*
* @author Kevin Weiner, FM Software; LZW decoder adapted from John Cristy's ImageMagick.
* @version 1.03 November 2003
*
*/publicclassGifDecoder{/**
* File read status: No errors.
*/publicstaticfinalint STATUS_OK =0;/**
* File read status: Error decoding file (may be partially decoded)
*/publicstaticfinalint STATUS_FORMAT_ERROR =1;/**
* File read status: Unable to open source.
*/publicstaticfinalint STATUS_OPEN_ERROR =2;protected BufferedInputStream in;protectedint status;protectedint width;// full image widthprotectedint height;// full image heightprotectedboolean gctFlag;// global color table usedprotectedint gctSize;// size of global color tableprotectedint loopCount =1;// iterations; 0 = repeat foreverprotectedint[] gct;// global color tableprotectedint[] lct;// local color tableprotectedint[] act;// active color tableprotectedint bgIndex;// background color indexprotectedint bgColor;// background colorprotectedint lastBgColor;// previous bg colorprotectedint pixelAspect;// pixel aspect ratioprotectedboolean lctFlag;// local color table flagprotectedboolean interlace;// interlace flagprotectedint lctSize;// local color table sizeprotectedint ix, iy, iw, ih;// current image rectangleprotected Rectangle lastRect;// last image rectprotected BufferedImage image;// current frameprotected BufferedImage lastImage;// previous frameprotectedbyte[] block =newbyte[256];// current data blockprotectedint blockSize =0;// block size// last graphic control extension infoprotectedint dispose =0;// 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prevprotectedint lastDispose =0;protectedboolean transparency =false;// use transparent colorprotectedint delay =0;// delay in millisecondsprotectedint transIndex;// transparent color indexprotectedstaticfinalint MaxStackSize =4096;// max decoder pixel stack size// LZW decoder working arraysprotectedshort[] prefix;protectedbyte[] suffix;protectedbyte[] pixelStack;protectedbyte[] pixels;protected ArrayList frames;// frames read from current fileprotectedint frameCount;staticclassGifFrame{publicGifFrame(BufferedImage im,int del){
image = im;
delay = del;}public BufferedImage image;publicint delay;}/**
* Gets display duration for specified frame.
*
* @param n int index of frame
* @return delay in milliseconds
*/publicintgetDelay(int n){//
delay =-1;if((n >=0)&&(n < frameCount)){
delay =((GifFrame) frames.get(n)).delay;}return delay;}/**
* Gets the number of frames read from file.
* @return frame count
*/publicintgetFrameCount(){return frameCount;}/**
* Gets the first (or only) image read.
*
* @return BufferedImage containing first frame, or null if none.
*/public BufferedImage getImage(){returngetFrame(0);}/**
* Gets the "Netscape" iteration count, if any.
* A count of 0 means repeat indefinitiely.
*
* @return iteration count if one was specified, else 1.
*/publicintgetLoopCount(){return loopCount;}/**
* Creates new frame image from current data (and previous
* frames as specified by their disposition codes).
*/protectedvoidsetPixels(){// expose destination image's pixels as int arrayint[] dest =((DataBufferInt) image.getRaster().getDataBuffer()).getData();// fill in starting image contents based on last image's dispose codeif(lastDispose >0){if(lastDispose ==3){// use image before lastint n = frameCount -2;if(n >0){
lastImage =getFrame(n -1);}else{
lastImage = null;}}if(lastImage != null){int[] prev =((DataBufferInt) lastImage.getRaster().getDataBuffer()).getData();
System.arraycopy(prev,0, dest,0, width * height);// copy pixelsif(lastDispose ==2){// fill last image rect area with background color
Graphics2D g = image.createGraphics();
Color c = null;if(transparency){
c =newColor(0,0,0,0);// assume background is transparent}else{
c =newColor(lastBgColor);// use given background color}
g.setColor(c);
g.setComposite(AlphaComposite.Src);// replace area
g.fill(lastRect);
g.dispose();}}}// copy each source line to the appropriate place in the destinationint pass =1;int inc =8;int iline =0;for(int i =0; i < ih; i++){int line = i;if(interlace){if(iline >= ih){
pass++;switch(pass){case2:
iline =4;break;case3:
iline =2;
inc =4;break;case4:
iline =1;
inc =2;}}
line = iline;
iline += inc;}
line += iy;if(line < height){int k = line * width;int dx = k + ix;// start of line in destint dlim = dx + iw;// end of dest lineif((k + width)< dlim){
dlim = k + width;// past dest edge}int sx = i * iw;// start of line in sourcewhile(dx < dlim){// map color and insert in destinationint index =((int) pixels[sx++])&0xff;int c = act[index];if(c !=0){
dest[dx]= c;}
dx++;}}}}/**
* Gets the image contents of frame n.
*
* @return BufferedImage representation of frame, or null if n is invalid.
*/public BufferedImage getFrame(int n){
BufferedImage im = null;if((n >=0)&&(n < frameCount)){
im =((GifFrame) frames.get(n)).image;}return im;}/**
* Gets image size.
*
* @return GIF image dimensions
*/public Dimension getFrameSize(){returnnewDimension(width, height);}/**
* Reads GIF image from stream
*
* @param BufferedInputStream containing GIF file.
* @return read status code (0 = no errors)
*/publicintread(BufferedInputStream is){init();if(is != null){
in = is;readHeader();if(!err()){readContents();if(frameCount <0){
status = STATUS_FORMAT_ERROR;}}}else{
status = STATUS_OPEN_ERROR;}try{
is.close();}catch(IOException e){}return status;}/**
* Reads GIF image from stream
*
* @param InputStream containing GIF file.
* @return read status code (0 = no errors)
*/publicintread(InputStream is){init();if(is != null){if(!(is instanceofBufferedInputStream))
is =newBufferedInputStream(is);
in =(BufferedInputStream) is;readHeader();if(!err()){readContents();if(frameCount <0){
status = STATUS_FORMAT_ERROR;}}}else{
status = STATUS_OPEN_ERROR;}try{
is.close();}catch(IOException e){
LogFactory.error("读异常:", e);}return status;}/**
* Reads GIF file from specified file/URL source
* (URL assumed if name contains ":/" or "file:")
*
* @param name String containing source
* @return read status code (0 = no errors)
*/publicintread(String name){
status = STATUS_OK;try{
name = name.trim().toLowerCase();if((name.indexOf("file:")>=0)||(name.indexOf(":/")>0)){
URL url =newURL(name);
in =newBufferedInputStream(url.openStream());}else{
in =newBufferedInputStream(newFileInputStream(name));}
status =read(in);}catch(IOException e){
status = STATUS_OPEN_ERROR;}return status;}/**
* Decodes LZW image data into pixel array.
* Adapted from John Cristy's ImageMagick.
*/protectedvoiddecodeImageData(){int NullCode =-1;int npix = iw * ih;int available,
clear,
code_mask,
code_size,
end_of_information,
in_code,
old_code,
bits,
code,
count,
i,
datum,
data_size,
first,
top,
bi,
pi;if((pixels == null)||(pixels.length < npix)){
pixels =newbyte[npix];// allocate new pixel array}if(prefix == null) prefix =newshort[MaxStackSize];if(suffix == null) suffix =newbyte[MaxStackSize];if(pixelStack == null) pixelStack =newbyte[MaxStackSize +1];// Initialize GIF data stream decoder.
data_size =read();
clear =1<< data_size;
end_of_information = clear +1;
available = clear +2;
old_code = NullCode;
code_size = data_size +1;
code_mask =(1<< code_size)-1;for(code =0; code < clear; code++){
prefix[code]=0;
suffix[code]=(byte) code;}// Decode GIF pixel stream.
datum = bits = count = first = top = pi = bi =0;for(i =0; i < npix;){if(top ==0){if(bits < code_size){// Load bytes until there are enough bits for a code.if(count ==0){// Read a new data block.
count =readBlock();if(count <=0)break;
bi =0;}
datum +=(((int) block[bi])&0xff)<< bits;
bits +=8;
bi++;
count--;continue;}// Get the next code.
code = datum & code_mask;
datum >>= code_size;
bits -= code_size;// Interpret the codeif((code > available)||(code == end_of_information))break;if(code == clear){// Reset decoder.
code_size = data_size +1;
code_mask =(1<< code_size)-1;
available = clear +2;
old_code = NullCode;continue;}if(old_code == NullCode){
pixelStack[top++]= suffix[code];
old_code = code;
first = code;continue;}
in_code = code;if(code == available){
pixelStack[top++]=(byte) first;
code = old_code;}while(code > clear){
pixelStack[top++]= suffix[code];
code = prefix[code];}
first =((int) suffix[code])&0xff;// Add a new string to the string table,if(available >= MaxStackSize)break;
pixelStack[top++]=(byte) first;
prefix[available]=(short) old_code;
suffix[available]=(byte) first;
available++;if(((available & code_mask)==0)&&(available < MaxStackSize)){
code_size++;
code_mask += available;}
old_code = in_code;}// Pop a pixel off the pixel stack.
top--;
pixels[pi++]= pixelStack[top];
i++;}for(i = pi; i < npix; i++){
pixels[i]=0;// clear missing pixels}}/**
* Returns true if an error was encountered during reading/decoding
*/protectedbooleanerr(){return status != STATUS_OK;}/**
* Initializes or re-initializes reader
*/protectedvoidinit(){
status = STATUS_OK;
frameCount =0;
frames =newArrayList();
gct = null;
lct = null;}/**
* Reads a single byte from the input stream.
*/protectedintread(){int curByte =0;try{
curByte = in.read();}catch(IOException e){
status = STATUS_FORMAT_ERROR;}return curByte;}/**
* Reads next variable length block from input.
*
* @return number of bytes stored in "buffer"
*/protectedintreadBlock(){
blockSize =read();int n =0;if(blockSize >0){try{int count =0;while(n < blockSize){
count = in.read(block, n, blockSize - n);if(count ==-1)break;
n += count;}}catch(IOException e){}if(n < blockSize){
status = STATUS_FORMAT_ERROR;}}return n;}/**
* Reads color table as 256 RGB integer values
*
* @param ncolors int number of colors to read
* @return int array containing 256 colors (packed ARGB with full alpha)
*/protectedint[]readColorTable(int ncolors){int nbytes =3* ncolors;int[] tab = null;byte[] c =newbyte[nbytes];int n =0;try{
n = in.read(c);}catch(IOException e){}if(n < nbytes){
status = STATUS_FORMAT_ERROR;}else{
tab =newint[256];// max size to avoid bounds checksint i =0;int j =0;while(i < ncolors){int r =((int) c[j++])&0xff;int g =((int) c[j++])&0xff;int b =((int) c[j++])&0xff;
tab[i++]=0xff000000|(r <<16)|(g <<8)| b;}}return tab;}/**
* Main file parser. Reads GIF content blocks.
*/protectedvoidreadContents(){// read GIF file content blocksboolean done =false;while(!(done ||err())){int code =read();switch(code){case0x2C:// image separatorreadImage();break;case0x21:// extension
code =read();switch(code){case0xf9:// graphics control extensionreadGraphicControlExt();break;case0xff:// application extensionreadBlock();
String app ="";for(int i =0; i <11; i++){
app +=(char) block[i];}if(app.equals("NETSCAPE2.0")){readNetscapeExt();}elseskip();// don't carebreak;default:// uninteresting extensionskip();}break;case0x3b:// terminator
done =true;break;case0x00:// bad byte, but keep going and see what happensbreak;default:
status = STATUS_FORMAT_ERROR;}}}/**
* Reads Graphics Control Extension values
*/protectedvoidreadGraphicControlExt(){read();// block sizeint packed =read();// packed fields
dispose =(packed &0x1c)>>2;// disposal methodif(dispose ==0){
dispose =1;// elect to keep old image if discretionary}
transparency =(packed &1)!=0;
delay =readShort()*10;// delay in milliseconds
transIndex =read();// transparent color indexread();// block terminator}/**
* Reads GIF file header information.
*/protectedvoidreadHeader(){
String id ="";for(int i =0; i <6; i++){
id +=(char)read();}if(!id.startsWith("GIF")){
status = STATUS_FORMAT_ERROR;return;}readLSD();if(gctFlag &&!err()){
gct =readColorTable(gctSize);
bgColor = gct[bgIndex];}}/**
* Reads next frame image
*/protectedvoidreadImage(){
ix =readShort();// (sub)image position & size
iy =readShort();
iw =readShort();
ih =readShort();int packed =read();
lctFlag =(packed &0x80)!=0;// 1 - local color table flag
interlace =(packed &0x40)!=0;// 2 - interlace flag// 3 - sort flag// 4-5 - reserved
lctSize =2<<(packed &7);// 6-8 - local color table sizeif(lctFlag){
lct =readColorTable(lctSize);// read table
act = lct;// make local table active}else{
act = gct;// make global table activeif(bgIndex == transIndex)
bgColor =0;}int save =0;if(transparency){
save = act[transIndex];
act[transIndex]=0;// set transparent color if specified}if(act == null){
status = STATUS_FORMAT_ERROR;// no color table defined}if(err())return;decodeImageData();// decode pixel dataskip();if(err())return;
frameCount++;// create new image to receive frame data
image =newBufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);setPixels();// transfer pixel data to image
frames.add(newGifFrame(image, delay));// add image to frame listif(transparency){
act[transIndex]= save;}resetFrame();}/**
* Reads Logical Screen Descriptor
*/protectedvoidreadLSD(){// logical screen size
width =readShort();
height =readShort();// packed fieldsint packed =read();
gctFlag =(packed &0x80)!=0;// 1 : global color table flag// 2-4 : color resolution// 5 : gct sort flag
gctSize =2<<(packed &7);// 6-8 : gct size
bgIndex =read();// background color index
pixelAspect =read();// pixel aspect ratio}/**
* Reads Netscape extenstion to obtain iteration count
*/protectedvoidreadNetscapeExt(){do{readBlock();if(block[0]==1){// loop count sub-blockint b1 =((int) block[1])&0xff;int b2 =((int) block[2])&0xff;
loopCount =(b2 <<8)| b1;}}while((blockSize >0)&&!err());}/**
* Reads next 16-bit value, LSB first
*/protectedintreadShort(){// read 16-bit value, LSB firstreturnread()|(read()<<8);}/**
* Resets frame state for reading next image.
*/protectedvoidresetFrame(){
lastDispose = dispose;
lastRect =newRectangle(ix, iy, iw, ih);
lastImage = image;
lastBgColor = bgColor;int dispose =0;boolean transparency =false;int delay =0;
lct = null;}/**
* Skips variable length blocks up to and including
* next zero length block.
*/protectedvoidskip(){do{readBlock();}while((blockSize >0)&&!err());}}