001    package railo.runtime.img.gif;
002    
003    import java.awt.AlphaComposite;
004    import java.awt.Color;
005    import java.awt.Dimension;
006    import java.awt.Graphics2D;
007    import java.awt.Rectangle;
008    import java.awt.image.BufferedImage;
009    import java.awt.image.DataBufferInt;
010    import java.io.BufferedInputStream;
011    import java.io.FileInputStream;
012    import java.io.IOException;
013    import java.io.InputStream;
014    import java.net.URL;
015    import java.util.ArrayList;
016    
017    public class GifDecoder {
018    
019            /**
020             * File read status: No errors.
021             */
022            public static final int STATUS_OK = 0;
023    
024            /**
025             * File read status: Error decoding file (may be partially decoded)
026             */
027            public static final int STATUS_FORMAT_ERROR = 1;
028    
029            /**
030             * File read status: Unable to open source.
031             */
032            public static final int STATUS_OPEN_ERROR = 2;
033    
034            protected BufferedInputStream in;
035            protected int status;
036    
037            protected int width; // full image width
038            protected int height; // full image height
039            protected boolean gctFlag; // global color table used
040            protected int gctSize; // size of global color table
041            protected int loopCount = 1; // iterations; 0 = repeat forever
042    
043            protected int[] gct; // global color table
044            protected int[] lct; // local color table
045            protected int[] act; // active color table
046    
047            protected int bgIndex; // background color index
048            protected int bgColor; // background color
049            protected int lastBgColor; // previous bg color
050            protected int pixelAspect; // pixel aspect ratio
051    
052            protected boolean lctFlag; // local color table flag
053            protected boolean interlace; // interlace flag
054            protected int lctSize; // local color table size
055    
056            protected int ix, iy, iw, ih; // current image rectangle
057            protected Rectangle lastRect; // last image rect
058            protected BufferedImage image; // current frame
059            protected BufferedImage lastImage; // previous frame
060    
061            protected byte[] block = new byte[256]; // current data block
062            protected int blockSize = 0; // block size
063    
064            // last graphic control extension info
065            protected int dispose = 0;
066            // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev
067            protected int lastDispose = 0;
068            protected boolean transparency = false; // use transparent color
069            protected int delay = 0; // delay in milliseconds
070            protected int transIndex; // transparent color index
071    
072            protected static final int MaxStackSize = 4096;
073            // max decoder pixel stack size
074    
075            // LZW decoder working arrays
076            protected short[] prefix;
077            protected byte[] suffix;
078            protected byte[] pixelStack;
079            protected byte[] pixels;
080    
081            protected ArrayList frames; // frames read from current file
082            protected int frameCount;
083    
084            static class GifFrame {
085                    public GifFrame(BufferedImage im, int del) {
086                            image = im;
087                            delay = del;
088                    }
089                    public BufferedImage image;
090                    public int delay;
091            }
092    
093            /**
094             * Gets display duration for specified frame.
095             *
096             * @param n int index of frame
097             * @return delay in milliseconds
098             */
099            public int getDelay(int n) {
100                    //
101                    delay = -1;
102                    if ((n >= 0) && (n < frameCount)) {
103                            delay = ((GifFrame) frames.get(n)).delay;
104                    }
105                    return delay;
106            }
107    
108            /**
109             * Gets the number of frames read from file.
110             * @return frame count
111             */
112            public int getFrameCount() {
113                    return frameCount;
114            }
115    
116            /**
117             * Gets the first (or only) image read.
118             *
119             * @return BufferedImage containing first frame, or null if none.
120             */
121            public BufferedImage getImage() {
122                    return getFrame(0);
123            }
124    
125            /**
126             * Gets the "Netscape" iteration count, if any.
127             * A count of 0 means repeat indefinitiely.
128             *
129             * @return iteration count if one was specified, else 1.
130             */
131            public int getLoopCount() {
132                    return loopCount;
133            }
134    
135            /**
136             * Creates new frame image from current data (and previous
137             * frames as specified by their disposition codes).
138             */
139            protected void setPixels() {
140                    // expose destination image's pixels as int array
141                    int[] dest =
142                            ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
143    
144                    // fill in starting image contents based on last image's dispose code
145                    if (lastDispose > 0) {
146                            if (lastDispose == 3) {
147                                    // use image before last
148                                    int n = frameCount - 2;
149                                    if (n > 0) {
150                                            lastImage = getFrame(n - 1);
151                                    } else {
152                                            lastImage = null;
153                                    }
154                            }
155    
156                            if (lastImage != null) {
157                                    int[] prev =
158                                            ((DataBufferInt) lastImage.getRaster().getDataBuffer()).getData();
159                                    System.arraycopy(prev, 0, dest, 0, width * height);
160                                    // copy pixels
161    
162                                    if (lastDispose == 2) {
163                                            // fill last image rect area with background color
164                                            Graphics2D g = image.createGraphics();
165                                            Color c = null;
166                                            if (transparency) {
167                                                    c = new Color(0, 0, 0, 0);      // assume background is transparent
168                                            } else {
169                                                    c = new Color(lastBgColor); // use given background color
170                                            }
171                                            g.setColor(c);
172                                            g.setComposite(AlphaComposite.Src); // replace area
173                                            g.fill(lastRect);
174                                            g.dispose();
175                                    }
176                            }
177                    }
178    
179                    // copy each source line to the appropriate place in the destination
180                    int pass = 1;
181                    int inc = 8;
182                    int iline = 0;
183                    for (int i = 0; i < ih; i++) {
184                            int line = i;
185                            if (interlace) {
186                                    if (iline >= ih) {
187                                            pass++;
188                                            switch (pass) {
189                                                    case 2 :
190                                                            iline = 4;
191                                                            break;
192                                                    case 3 :
193                                                            iline = 2;
194                                                            inc = 4;
195                                                            break;
196                                                    case 4 :
197                                                            iline = 1;
198                                                            inc = 2;
199                                            }
200                                    }
201                                    line = iline;
202                                    iline += inc;
203                            }
204                            line += iy;
205                            if (line < height) {
206                                    int k = line * width;
207                                    int dx = k + ix; // start of line in dest
208                                    int dlim = dx + iw; // end of dest line
209                                    if ((k + width) < dlim) {
210                                            dlim = k + width; // past dest edge
211                                    }
212                                    int sx = i * iw; // start of line in source
213                                    while (dx < dlim) {
214                                            // map color and insert in destination
215                                            int index = pixels[sx++] & 0xff;
216                                            int c = act[index];
217                                            if (c != 0) {
218                                                    dest[dx] = c;
219                                            }
220                                            dx++;
221                                    }
222                            }
223                    }
224            }
225    
226            /**
227             * Gets the image contents of frame n.
228             *
229             * @return BufferedImage representation of frame, or null if n is invalid.
230             */
231            public BufferedImage getFrame(int n) {
232                    BufferedImage im = null;
233                    if ((n >= 0) && (n < frameCount)) {
234                            im = ((GifFrame) frames.get(n)).image;
235                    }
236                    return im;
237            }
238    
239            /**
240             * Gets image size.
241             *
242             * @return GIF image dimensions
243             */
244            public Dimension getFrameSize() {
245                    return new Dimension(width, height);
246            }
247    
248            /**
249             * Reads GIF image from stream
250             *
251             * @param BufferedInputStream containing GIF file.
252             * @return read status code (0 = no errors)
253             */
254            public int read(BufferedInputStream is) {
255                    init();
256                    if (is != null) {
257                            in = is;
258                            readHeader();
259                            if (!err()) {
260                                    readContents();
261                                    if (frameCount < 0) {
262                                            status = STATUS_FORMAT_ERROR;
263                                    }
264                            }
265                    } else {
266                            status = STATUS_OPEN_ERROR;
267                    }
268                    try {
269                            is.close();
270                    } catch (IOException e) {
271                    }
272                    return status;
273            }
274    
275            /**
276             * Reads GIF image from stream
277             *
278             * @param InputStream containing GIF file.
279             * @return read status code (0 = no errors)
280             */
281            public int read(InputStream is) {
282                    init();
283                    if (is != null) {
284                            if (!(is instanceof BufferedInputStream))
285                                    is = new BufferedInputStream(is);
286                            in = (BufferedInputStream) is;
287                            readHeader();
288                            if (!err()) {
289                                    readContents();
290                                    if (frameCount < 0) {
291                                            status = STATUS_FORMAT_ERROR;
292                                    }
293                            }
294                    } else {
295                            status = STATUS_OPEN_ERROR;
296                    }
297                    try {
298                            is.close();
299                    } catch (IOException e) {
300                    }
301                    return status;
302            }
303    
304            /**
305             * Reads GIF file from specified file/URL source  
306             * (URL assumed if name contains ":/" or "file:")
307             *
308             * @param name String containing source
309             * @return read status code (0 = no errors)
310             */
311            public int read(String name) {
312                    status = STATUS_OK;
313                    try {
314                            name = name.trim().toLowerCase();
315                            if ((name.indexOf("file:") >= 0) ||
316                                    (name.indexOf(":/") > 0)) {
317                                    URL url = new URL(name);
318                                    in = new BufferedInputStream(url.openStream());
319                            } else {
320                                    in = new BufferedInputStream(new FileInputStream(name));
321                            }
322                            status = read(in);
323                    } catch (IOException e) {
324                            status = STATUS_OPEN_ERROR;
325                    }
326    
327                    return status;
328            }
329    
330            /**
331             * Decodes LZW image data into pixel array.
332             * Adapted from John Cristy's ImageMagick.
333             */
334            protected void decodeImageData() {
335                    int NullCode = -1;
336                    int npix = iw * ih;
337                    int available, 
338                            clear,
339                            code_mask,
340                            code_size,
341                            end_of_information,
342                            in_code,
343                            old_code,
344                            bits,
345                            code,
346                            count,
347                            i,
348                            datum,
349                            data_size,
350                            first,
351                            top,
352                            bi,
353                            pi;
354    
355                    if ((pixels == null) || (pixels.length < npix)) {
356                            pixels = new byte[npix]; // allocate new pixel array
357                    }
358                    if (prefix == null) prefix = new short[MaxStackSize];
359                    if (suffix == null) suffix = new byte[MaxStackSize];
360                    if (pixelStack == null) pixelStack = new byte[MaxStackSize + 1];
361    
362                    //  Initialize GIF data stream decoder.
363    
364                    data_size = read();
365                    clear = 1 << data_size;
366                    end_of_information = clear + 1;
367                    available = clear + 2;
368                    old_code = NullCode;
369                    code_size = data_size + 1;
370                    code_mask = (1 << code_size) - 1;
371                    for (code = 0; code < clear; code++) {
372                            prefix[code] = 0;
373                            suffix[code] = (byte) code;
374                    }
375    
376                    //  Decode GIF pixel stream.
377    
378                    datum = bits = count = first = top = pi = bi = 0;
379    
380                    for (i = 0; i < npix;) {
381                            if (top == 0) {
382                                    if (bits < code_size) {
383                                            //  Load bytes until there are enough bits for a code.
384                                            if (count == 0) {
385                                                    // Read a new data block.
386                                                    count = readBlock();
387                                                    if (count <= 0)
388                                                            break;
389                                                    bi = 0;
390                                            }
391                                            datum += (block[bi] & 0xff) << bits;
392                                            bits += 8;
393                                            bi++;
394                                            count--;
395                                            continue;
396                                    }
397    
398                                    //  Get the next code.
399    
400                                    code = datum & code_mask;
401                                    datum >>= code_size;
402                                    bits -= code_size;
403    
404                                    //  Interpret the code
405    
406                                    if ((code > available) || (code == end_of_information))
407                                            break;
408                                    if (code == clear) {
409                                            //  Reset decoder.
410                                            code_size = data_size + 1;
411                                            code_mask = (1 << code_size) - 1;
412                                            available = clear + 2;
413                                            old_code = NullCode;
414                                            continue;
415                                    }
416                                    if (old_code == NullCode) {
417                                            pixelStack[top++] = suffix[code];
418                                            old_code = code;
419                                            first = code;
420                                            continue;
421                                    }
422                                    in_code = code;
423                                    if (code == available) {
424                                            pixelStack[top++] = (byte) first;
425                                            code = old_code;
426                                    }
427                                    while (code > clear) {
428                                            pixelStack[top++] = suffix[code];
429                                            code = prefix[code];
430                                    }
431                                    first = suffix[code] & 0xff;
432    
433                                    //  Add a new string to the string table,
434    
435                                    if (available >= MaxStackSize)
436                                            break;
437                                    pixelStack[top++] = (byte) first;
438                                    prefix[available] = (short) old_code;
439                                    suffix[available] = (byte) first;
440                                    available++;
441                                    if (((available & code_mask) == 0)
442                                            && (available < MaxStackSize)) {
443                                            code_size++;
444                                            code_mask += available;
445                                    }
446                                    old_code = in_code;
447                            }
448    
449                            //  Pop a pixel off the pixel stack.
450    
451                            top--;
452                            pixels[pi++] = pixelStack[top];
453                            i++;
454                    }
455    
456                    for (i = pi; i < npix; i++) {
457                            pixels[i] = 0; // clear missing pixels
458                    }
459    
460            }
461    
462            /**
463             * Returns true if an error was encountered during reading/decoding
464             */
465            protected boolean err() {
466                    return status != STATUS_OK;
467            }
468    
469            /**
470             * Initializes or re-initializes reader
471             */
472            protected void init() {
473                    status = STATUS_OK;
474                    frameCount = 0;
475                    frames = new ArrayList();
476                    gct = null;
477                    lct = null;
478            }
479    
480            /**
481             * Reads a single byte from the input stream.
482             */
483            protected int read() {
484                    int curByte = 0;
485                    try {
486                            curByte = in.read();
487                    } catch (IOException e) {
488                            status = STATUS_FORMAT_ERROR;
489                    }
490                    return curByte;
491            }
492    
493            /**
494             * Reads next variable length block from input.
495             *
496             * @return number of bytes stored in "buffer"
497             */
498            protected int readBlock() {
499                    blockSize = read();
500                    int n = 0;
501                    if (blockSize > 0) {
502                            try {
503                                    int count = 0;
504                                    while (n < blockSize) {
505                                            count = in.read(block, n, blockSize - n);
506                                            if (count == -1) 
507                                                    break;
508                                            n += count;
509                                    }
510                            } catch (IOException e) {
511                            }
512    
513                            if (n < blockSize) {
514                                    status = STATUS_FORMAT_ERROR;
515                            }
516                    }
517                    return n;
518            }
519    
520            /**
521             * Reads color table as 256 RGB integer values
522             *
523             * @param ncolors int number of colors to read
524             * @return int array containing 256 colors (packed ARGB with full alpha)
525             */
526            protected int[] readColorTable(int ncolors) {
527                    int nbytes = 3 * ncolors;
528                    int[] tab = null;
529                    byte[] c = new byte[nbytes];
530                    int n = 0;
531                    try {
532                            n = in.read(c);
533                    } catch (IOException e) {
534                    }
535                    if (n < nbytes) {
536                            status = STATUS_FORMAT_ERROR;
537                    } else {
538                            tab = new int[256]; // max size to avoid bounds checks
539                            int i = 0;
540                            int j = 0;
541                            while (i < ncolors) {
542                                    int r = c[j++] & 0xff;
543                                    int g = c[j++] & 0xff;
544                                    int b = c[j++] & 0xff;
545                                    tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
546                            }
547                    }
548                    return tab;
549            }
550    
551            /**
552             * Main file parser.  Reads GIF content blocks.
553             */
554            protected void readContents() {
555                    // read GIF file content blocks
556                    boolean done = false;
557                    while (!(done || err())) {
558                            int code = read();
559                            switch (code) {
560    
561                                    case 0x2C : // image separator
562                                            readImage();
563                                            break;
564    
565                                    case 0x21 : // extension
566                                            code = read();
567                                            switch (code) {
568                                                    case 0xf9 : // graphics control extension
569                                                            readGraphicControlExt();
570                                                            break;
571    
572                                                    case 0xff : // application extension
573                                                            readBlock();
574                                                            String app = "";
575                                                            for (int i = 0; i < 11; i++) {
576                                                                    app += (char) block[i];
577                                                            }
578                                                            if (app.equals("NETSCAPE2.0")) {
579                                                                    readNetscapeExt();
580                                                            }
581                                                            else
582                                                                    skip(); // don't care
583                                                            break;
584    
585                                                    default : // uninteresting extension
586                                                            skip();
587                                            }
588                                            break;
589    
590                                    case 0x3b : // terminator
591                                            done = true;
592                                            break;
593    
594                                    case 0x00 : // bad byte, but keep going and see what happens
595                                            break;
596    
597                                    default :
598                                            status = STATUS_FORMAT_ERROR;
599                            }
600                    }
601            }
602    
603            /**
604             * Reads Graphics Control Extension values
605             */
606            protected void readGraphicControlExt() {
607                    read(); // block size
608                    int packed = read(); // packed fields
609                    dispose = (packed & 0x1c) >> 2; // disposal method
610                    if (dispose == 0) {
611                            dispose = 1; // elect to keep old image if discretionary
612                    }
613                    transparency = (packed & 1) != 0;
614                    delay = readShort() * 10; // delay in milliseconds
615                    transIndex = read(); // transparent color index
616                    read(); // block terminator
617            }
618    
619            /**
620             * Reads GIF file header information.
621             */
622            protected void readHeader() {
623                    String id = "";
624                    for (int i = 0; i < 6; i++) {
625                            id += (char) read();
626                    }
627                    if (!id.startsWith("GIF")) {
628                            status = STATUS_FORMAT_ERROR;
629                            return;
630                    }
631    
632                    readLSD();
633                    if (gctFlag && !err()) {
634                            gct = readColorTable(gctSize);
635                            bgColor = gct[bgIndex];
636                    }
637            }
638    
639            /**
640             * Reads next frame image
641             */
642            protected void readImage() {
643                    ix = readShort(); // (sub)image position & size
644                    iy = readShort();
645                    iw = readShort();
646                    ih = readShort();
647    
648                    int packed = read();
649                    lctFlag = (packed & 0x80) != 0; // 1 - local color table flag
650                    interlace = (packed & 0x40) != 0; // 2 - interlace flag
651                    // 3 - sort flag
652                    // 4-5 - reserved
653                    lctSize = 2 << (packed & 7); // 6-8 - local color table size
654    
655                    if (lctFlag) {
656                            lct = readColorTable(lctSize); // read table
657                            act = lct; // make local table active
658                    } else {
659                            act = gct; // make global table active
660                            if (bgIndex == transIndex)
661                                    bgColor = 0;
662                    }
663                    int save = 0;
664                    if (transparency) {
665                            save = act[transIndex];
666                            act[transIndex] = 0; // set transparent color if specified
667                    }
668    
669                    if (act == null) {
670                            status = STATUS_FORMAT_ERROR; // no color table defined
671                    }
672    
673                    if (err()) return;
674    
675                    decodeImageData(); // decode pixel data
676                    skip();
677    
678                    if (err()) return;
679    
680                    frameCount++;
681    
682                    // create new image to receive frame data
683                    image =
684                            new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
685    
686                    setPixels(); // transfer pixel data to image
687    
688                    frames.add(new GifFrame(image, delay)); // add image to frame list
689    
690                    if (transparency) {
691                            act[transIndex] = save;
692                    }
693                    resetFrame();
694    
695            }
696    
697            /**
698             * Reads Logical Screen Descriptor
699             */
700            protected void readLSD() {
701    
702                    // logical screen size
703                    width = readShort();
704                    height = readShort();
705    
706                    // packed fields
707                    int packed = read();
708                    gctFlag = (packed & 0x80) != 0; // 1   : global color table flag
709                    // 2-4 : color resolution
710                    // 5   : gct sort flag
711                    gctSize = 2 << (packed & 7); // 6-8 : gct size
712    
713                    bgIndex = read(); // background color index
714                    pixelAspect = read(); // pixel aspect ratio
715            }
716    
717            /**
718             * Reads Netscape extenstion to obtain iteration count
719             */
720            protected void readNetscapeExt() {
721                    do {
722                            readBlock();
723                            if (block[0] == 1) {
724                                    // loop count sub-block
725                                    int b1 = block[1] & 0xff;
726                                    int b2 = block[2] & 0xff;
727                                    loopCount = (b2 << 8) | b1;
728                            }
729                    } while ((blockSize > 0) && !err());
730            }
731    
732            /**
733             * Reads next 16-bit value, LSB first
734             */
735            protected int readShort() {
736                    // read 16-bit value, LSB first
737                    return read() | (read() << 8);
738            }
739    
740            /**
741             * Resets frame state for reading next image.
742             */
743            protected void resetFrame() {
744                    lastDispose = dispose;
745                    lastRect = new Rectangle(ix, iy, iw, ih);
746                    lastImage = image;
747                    lastBgColor = bgColor;
748                    int dispose = 0;
749                    boolean transparency = false;
750                    int delay = 0;
751                    lct = null;
752            }
753    
754            /**
755             * Skips variable length blocks up to and including
756             * next zero length block.
757             */
758            protected void skip() {
759                    do {
760                            readBlock();
761                    } while ((blockSize > 0) && !err());
762            }
763    }