alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (GifImageDecoder.java)

This example Java source code file (GifImageDecoder.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

awt, colormapmask, disposal_none, disposal_save, ex_application, ex_comment, extractbyte, extractword, gif, gifframe, gifimagedecoder, image, imageformatexception, indexcolormodel, ioexception, string, util

The GifImageDecoder.java Java example source code

/*
 * Copyright (c) 1995, 2006, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*-
 *      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
 *
 * @author Arthur van Hoff
 * @author Jim Graham
 */
public class GifImageDecoder extends ImageDecoder {
    private static 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 TRANSPARENCYMASK   = 0x01;
    private static final int INTERLACEMASK      = 0x40;
    private static final int COLORMAPMASK       = 0x80;

    int num_global_colors;
    byte[] global_colormap;
    int trans_pixel = -1;
    IndexColorModel global_model;

    Hashtable props = new Hashtable();

    byte[] saved_image;
    IndexColorModel saved_model;

    int global_width;
    int global_height;
    int global_bgpixel;

    GifFrame curframe;

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

    /**
     * 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.
     * @return number of bytes that were not read due to EOF or error
     */
    private int readBytes(byte buf[], int off, int len) {
        while (len > 0) {
            try {
                int n = input.read(buf, off, len);
                if (n < 0) {
                    break;
                }
                off += n;
                len -= n;
            } catch (IOException e) {
                break;
            }
        }
        return len;
    }

    private static final int ExtractByte(byte buf[], int off) {
        return (buf[off] & 0xFF);
    }

    private static final int ExtractWord(byte buf[], int off) {
        return (buf[off] & 0xFF) | ((buf[off + 1] & 0xFF) << 8);
    }

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

            int totalframes = 0;
            int frameno = 0;
            int nloops = -1;
            int disposal_method = 0;
            int delay = -1;
            boolean loopsRead = false;
            boolean isAnimation = false;

            while (!aborted) {
                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) != 0) {
                            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
                        delay = ExtractWord(buf, 2) * 10;
                        if (delay > 0 && !isAnimation) {
                            isAnimation = true;
                            ImageFetcher.startingAnimation();
                        }
                        disposal_method = (buf[1] >> 2) & 7;
                        if ((buf[1] & TRANSPARENCYMASK) != 0) {
                            trans_pixel = ExtractByte(buf, 4);
                        } else {
                            trans_pixel = -1;
                        }
                        break;
                      }

                      case EX_COMMENT:
                      case EX_APPLICATION:
                      default:
                        boolean loop_tag = false;
                        String comment = "";
                        while (true) {
                            int n = input.read();
                            if (n <= 0) {
                                break;
                            }
                            byte buf[] = new byte[n];
                            if (readBytes(buf, 0, n) != 0) {
                                return;//error("corrupt GIF file");
                            }
                            if (code == EX_COMMENT) {
                                comment += new String(buf, 0);
                            } else if (code == EX_APPLICATION) {
                                if (loop_tag) {
                                    if (n == 3 && buf[0] == 1) {
                                        if (loopsRead) {
                                            ExtractWord(buf, 1);
                                        }
                                        else {
                                            nloops = ExtractWord(buf, 1);
                                            loopsRead = true;
                                        }
                                    } else {
                                        loop_tag = false;
                                    }
                                }
                                if ("NETSCAPE2.0".equals(new String(buf, 0))) {
                                    loop_tag = true;
                                }
                            }
                        }
                        if (code == EX_COMMENT) {
                            props.put("comment", comment);
                        }
                        if (loop_tag && !isAnimation) {
                            isAnimation = true;
                            ImageFetcher.startingAnimation();
                        }
                        break;

                      case -1:
                        return; //error("corrupt GIF file");
                    }
                    break;

                  case IMAGESEP:
                    if (!isAnimation) {
                        input.mark(0); // we don't need the mark buffer
                    }
                    try {
                        if (!readImage(totalframes == 0,
                                       disposal_method,
                                       delay)) {
                            return;
                        }
                    } catch (Exception e) {
                        if (verbose) {
                            e.printStackTrace();
                        }
                        return;
                    }
                    frameno++;
                    totalframes++;
                    break;

                  default:
                  case -1:
                    if (verbose) {
                        if (code == -1) {
                            System.err.println("Premature EOF in GIF file," +
                                               " frame " + frameno);
                        } else {
                            System.err.println("corrupt GIF file (parse) ["
                                               + code + "].");
                        }
                    }
                    if (frameno == 0) {
                        return;
                    }
                    // NOBREAK

                  case TERMINATOR:
                    if (nloops == 0 || nloops-- >= 0) {
                        try {
                            if (curframe != null) {
                                curframe.dispose();
                                curframe = null;
                            }
                            input.reset();
                            saved_image = null;
                            saved_model = null;
                            frameno = 0;
                            break;
                        } catch (IOException e) {
                            return; // Unable to reset input buffer
                        }
                    }
                    if (verbose && frameno != 1) {
                        System.out.println("processing GIF terminator,"
                                           + " frames: " + frameno
                                           + " total: " + totalframes);
                    }
                    imageComplete(ImageConsumer.STATICIMAGEDONE, true);
                    return;
                }
            }
        } finally {
            close();
        }
    }

    /**
     * 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) != 0) {
            throw new IOException();
        }

        // Check header
        if ((buf[0] != 'G') || (buf[1] != 'I') || (buf[2] != 'F')) {
            error("not a GIF file.");
        }

        // 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;
    }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java GifImageDecoder.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.