Package fmg.fmg8.gif

Source Code of fmg.fmg8.gif.ReverseColorMap$ColorRecord

//****************************************************************************
// Gif89Encoder.java
//****************************************************************************
package fmg.fmg8.gif;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Toolkit;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Vector;

/**
* @author Internet.
*/
public class Gif89Encoder {

    /**
     *
     */
    private Dimension dispDim = new Dimension(0, 0);
   
    /**
     *
     */
    private GifColorTable colorTable;
   
    /**
     *
     */
    private int bgIndex = 0;
   
    /**
     *
     */
    private int loopCount = 1;
   
    /**
     *
     */
    private String theComments;

    /**
     *
     */
    @SuppressWarnings(value = { "unchecked" })
    private Vector vFrames = new Vector();

    /**
     * Use this default constructor if you'll be adding multiple frames
     * constructed from RGB data (i.e., AWT Image objects or ARGB-pixel arrays).
     */
    public Gif89Encoder() {
        // empty color table puts us into "palette autodetect" mode
        colorTable = new GifColorTable();
    }

    /**
     * Like the default except that it also adds a single frame, for
     * conveniently encoding a static GIF from an image.
     *
     * @param staticImage
     *            Any Image object that supports pixel-grabbing.
     * @exception IOException
     *                See the addFrame() methods.
     */
    public Gif89Encoder(final Image staticImage) throws IOException {
        this();
        addFrame(staticImage);
    }

    /**
     * This constructor installs a user color table, overriding the detection of
     * of a palette from ARBG pixels.
     *
     * Use of this constructor imposes a couple of restrictions: (1) Frame
     * objects can't be of type DirectGif89Frame (2) Transparency, if desired,
     * must be set explicitly.
     *
     * @param colors
     *            Array of color values; no more than 256 colors will be read,
     *            since that's the limit for a GIF.
     */
    public Gif89Encoder(final Color[] colors) {
        colorTable = new GifColorTable(colors);
    }

    /**
     * Convenience constructor for encoding a static GIF from index-model data.
     * Adds a single frame as specified.
     *
     * @param colors
     *            Array of color values; no more than 256 colors will be read,
     *            since that's the limit for a GIF.
     * @param width
     *            Width of the GIF bitmap.
     * @param height
     *            Height of same.
     * @param ciPixels
     *            Array of color-index pixels no less than width * height in
     *            length.
     * @exception IOException
     *                See the addFrame() methods.
     */
    public Gif89Encoder(
            final Color[] colors,
            final int width,
            final int height,
            final byte[] ciPixels)
            throws IOException {
        this(colors);
        addFrame(width, height, ciPixels);
    }

    /**
     * Get the number of frames that have been added so far.
     *
     * @return Number of frame items.
     */
    public int getFrameCount() {
        return vFrames.size();
    }

    /**
     * Get a reference back to a Gif89Frame object by position.
     *
     * @param index
     *            Zero-based index of the frame in the sequence.
     * @return Gif89Frame object at the specified position (or null if no such
     *         frame).
     */
    public AbstractGif89Frame getFrameAt(final int index) {
        if (isOk(index)) {
            return (AbstractGif89Frame) vFrames.elementAt(index);
        } else {
            return null;
        }
       
/*
* return isOk(index) ? (AbstractGif89Frame) vFrames.elementAt(index) : null;
*/
    }

    /**
     * Add a Gif89Frame frame to the end of the internal sequence. Note that
     * there are restrictions on the Gif89Frame type: if the encoder object was
     * constructed with an explicit color table, an attempt to add a
     * DirectGif89Frame will throw an exception.
     *
     * @param gf
     *            An externally constructed Gif89Frame.
     * @exception IOException
     *                If Gif89Frame can't be accommodated. This could happen if
     *                either (1) the aggregate cross-frame RGB color count
     *                exceeds 256, or (2) the Gif89Frame subclass is
     *                incompatible with the present encoder object.
     */
    @SuppressWarnings(value = { "unchecked" })
    public void addFrame(final AbstractGif89Frame gf) throws IOException {
        accommodateFrame(gf);
        vFrames.addElement(gf);
    }

    /**
     * Convenience version of addFrame() that takes a Java Image, internally
     * constructing the requisite DirectGif89Frame.
     *
     * @param image
     *            Any Image object that supports pixel-grabbing.
     * @exception IOException
     *                If either (1) pixel-grabbing fails, (2) the aggregate
     *                cross-frame RGB color count exceeds 256, or (3) this
     *                encoder object was constructed with an explicit color
     *                table.
     */
    public void addFrame(final Image image) throws IOException {
        addFrame(new DirectGif89Frame(image));
    }

    /**
     * The index-model convenience version of addFrame().
     *
     * @param width
     *            Width of the GIF bitmap.
     * @param height
     *            Height of same.
     * @param ciPixels
     *            Array of color-index pixels no less than width * height in
     *            length.
     * @exception IOException
     *                Actually, in the present implementation, there aren't any
     *                unchecked exceptions that can be thrown when adding an
     *                IndexGif89Frame <i>per se</i>. But I might add some
     *                pedantic check later, to justify the generality :)
     */
    public void addFrame(
            final int width,
            final int height,
            final byte[] ciPixels)
            throws IOException {
        addFrame(new IndexGif89Frame(width, height, ciPixels));
    }

    /**
     * Like addFrame() except that the frame is inserted at a specific point in
     * the sequence rather than appended.
     *
     * @param index
     *            Zero-based index at which to insert frame.
     * @param gf
     *            An externally constructed Gif89Frame.
     * @exception IOException
     *                If Gif89Frame can't be accommodated. This could happen if
     *                either (1) the aggregate cross-frame RGB color count
     *                exceeds 256, or (2) the Gif89Frame subclass is
     *                incompatible with the present encoder object.
     */
    @SuppressWarnings(value = { "unchecked" })
    public void insertFrame(
            final int index,
            final AbstractGif89Frame gf) throws IOException {
        accommodateFrame(gf);
        vFrames.insertElementAt(gf, index);
    }

    /**
     * Set the color table index for the transparent color, if any.
     *
     * @param index
     *            Index of the color that should be rendered as transparent, if
     *            any. A value of -1 turns off transparency. (Default: -1)
     */
    public void setTransparentIndex(final int index) {
        colorTable.setTransparent(index);
    }

    /**
     * Sets attributes of the multi-image display area, if applicable.
     *
     * @param dim
     *            Width/height of display. (Default: largest detected frame
     *            size)
     * @param background
     *            Color table index of background color. (Default: 0)
     * @see AbstractGif89Frame#setPosition
     */
    public void setLogicalDisplay(
            final Dimension dim,
            final int background) {
        dispDim = new Dimension(dim);
        bgIndex = background;
    }

    /**
     * Set animation looping parameter, if applicable.
     *
     * @param count
     *            Number of times to play sequence. Special value of 0 specifies
     *            indefinite looping. (Default: 1)
     */
    public void setLoopCount(final int count) {
        loopCount = count;
    }

    /**
     * Specify some textual comments to be embedded in GIF.
     *
     * @param comments
     *            String containing ASCII comments.
     */
    public void setComments(final String comments) {
        theComments = comments;
    }

    /**
     * A convenience method for setting the "animation speed". It simply sets
     * the delay parameter for each frame in the sequence to the supplied value.
     * Since this is actually frame-level rather than animation-level data, take
     * care to add your frames before calling this method.
     *
     * @param interval
     *            Interframe interval in centiseconds.
     */
    public void setUniformDelay(final int interval) {
        for (int i = 0; i < vFrames.size(); i++) { // Urspr�nglich i++ TODO
            ((AbstractGif89Frame) vFrames.elementAt(i)).setDelay(interval);
        }
    }

    /**
     * After adding your frame(s) and setting your options, simply call this
     * method to write the GIF to the passed stream. Multiple calls are
     * permissible if for some reason that is useful to your application. (The
     * method simply encodes the current state of the object with no thought to
     * previous calls.)
     *
     * @param out
     *            The stream you want the GIF written to.
     * @exception IOException
     *                If a write error is encountered.
     */
    public void encode(final OutputStream out) throws IOException {
        int nframes = getFrameCount();
        boolean isSequence = nframes > 1;


        // N.B. must be called before writing screen descriptor
        colorTable.closePixelProcessing();

        // write GIF HEADER
        Put.ascii("GIF89a", out);

        // write global blocks
        writeLogicalScreenDescriptor(out);
        colorTable.encode(out);
        if (isSequence && loopCount != 1) {
            writeNetscapeExtension(out);
        }
        if (theComments != null && theComments.length() > 0) {
            writeCommentExtension(out);
        }

        // write out the control and rendering data for each frame
        for (int i = 0; i < nframes; ++i) {
            ((AbstractGif89Frame) vFrames.elementAt(i)).encode(out, isSequence,
                    colorTable.getDepth(), colorTable.getTransparent());
        }
       
        // write GIF TRAILER
        out.write((int) ';');

        out.flush();
    }

    /**
     * A simple driver to test the installation and to demo usage. Put the JAR
     * on your classpath and run ala <blockquote>java net.jmge.gif.Gif89Encoder
     * {filename}</blockquote> The filename must be either (1) a JPEG file with
     * extension 'jpg', for conversion to a static GIF, or (2) a file containing
     * a list of GIFs and/or JPEGs, one per line, to be combined into an
     * animated GIF. The output will appear in the current directory as
     * 'gif89out.gif'.
     * <p>
     * (N.B. This test program will abort if the input file(s) exceed(s) 256
     * total RGB colors, so in its present form it has no value as a generic
     * JPEG to GIF converter. Also, when multiple files are input, you need to
     * be wary of the total color count, regardless of file type.)
     *
     * @param args
     *            Command-line arguments, only the first of which is used, as
     *            mentioned above.
     */
    public static void main(final String[] args) {
        try {

            Toolkit tk = Toolkit.getDefaultToolkit();
            OutputStream out = new BufferedOutputStream(new FileOutputStream(
                    "gif89out.gif"));

            if (args[0].toUpperCase().endsWith(".JPG")) {
                new Gif89Encoder(tk.getImage(args[0])).encode(out);
            } else {
                BufferedReader in = new BufferedReader(new FileReader(args[0]));
                Gif89Encoder ge = new Gif89Encoder();

                String line;
                while ((line = in.readLine()) != null) {
                    ge.addFrame(tk.getImage(line.trim()));
                }
                ge.setLoopCount(0); // let's loop indefinitely
                ge.encode(out);

                in.close();
            }
            out.close();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.exit(0);
        } // must kill VM explicitly (Toolkit thread?)
    }

    /**
     * @param gf  Parameter.
     *
     * @throws IOException  Fehler.
     */
    private void accommodateFrame(final AbstractGif89Frame gf)
        throws IOException {
        dispDim.width = Math.max(dispDim.width, gf.getWidth());
        dispDim.height = Math.max(dispDim.height, gf.getHeight());
        colorTable.processPixels(gf);
    }

    /**
     * @param os  Parameter.
     *
     * @throws IOException  Fehler.
     */
    private void writeLogicalScreenDescriptor(final OutputStream os)
            throws IOException {
        Put.leShort(dispDim.width, os);
        Put.leShort(dispDim.height, os);

        // write 4 fields, packed into a byte (bitfieldsize:value)
        // global color map present? (1:1)
        // bits per primary color less 1 (3:7)
        // sorted color table? (1:0)
        // bits per pixel less 1 (3:varies)
        os.write(0xf0 | colorTable.getDepth() - 1);

        // write background color index
        os.write(bgIndex);

        // Jef Poskanzer's notes on the next field, for our possible
        // edification:
        // Pixel aspect ratio - 1:1.
        // Putbyte( (byte) 49, outs );
        // Java's GIF reader currently has a bug, if the aspect ratio byte is
        // not zero it throws an ImageFormatException. It doesn't know that
        // 49 means a 1:1 aspect ratio. Well, whatever, zero works with all
        // the other decoders I've tried so it probably doesn't hurt.

        // OK, if it's good enough for Jef, it's definitely good enough for us:
        os.write(0);
    }

    /**
     * @param os  Parameter.
     *
     * @throws IOException  Fehler.
     */
    private void writeNetscapeExtension(final OutputStream os)
        throws IOException {
        // n.b. most software seems to interpret the count as a repeat count
        // (i.e., interations beyond 1) rather than as an iteration count
        // (thus, to avoid repeating we have to omit the whole extension)

        os.write((int) '!'); // GIF Extension Introducer
        os.write(0xff); // Application Extension Label

        os.write(11); // application ID block size
        Put.ascii("NETSCAPE2.0", os); // application ID data

        os.write(3); // data sub-block size
        os.write(1); // a looping flag? dunno

        // we finally write the relevent data
        Put.leShort(loopCount > 1 ? loopCount - 1 : 0, os);

        os.write(0); // block terminator
    }

    /**
     * @param os  Parameter.
     *
     * @throws IOException  Fehler.
     */
    private void writeCommentExtension(final OutputStream os)
        throws IOException {
        os.write((int) '!'); // GIF Extension Introducer
        os.write(0xfe); // Comment Extension Label

        int remainder = theComments.length() % 255;
        int nsubblocksFull = theComments.length() / 255;
        int nsubblocks = nsubblocksFull + (remainder > 0 ? 1 : 0);
        int ibyte = 0;
        for (int isb = 0; isb < nsubblocks; ++isb) {
            int size = isb < nsubblocksFull ? 255 : remainder;

            os.write(size);
            Put.ascii(theComments.substring(ibyte, ibyte + size), os);
            ibyte += size;
        }

        os.write(0); // block terminator
    }

    /**
     * @param frameIndex  Parameter.
     *
     * @return  Return.
     */
    private boolean isOk(final int frameIndex) {
        return frameIndex >= 0 && frameIndex < vFrames.size();
    }
}

// ====================================================================
/**
*
*/
class GifColorTable {

    /**
     * The palette of ARGB colors, packed as returned by Color.getRGB().
     */
    private int[] theColors = new int[256];

    /**
     * Other basic attributes.
     */
    private int colorDepth;
   
    /**
     *
     */
    private int transparentIndex = -1;

    /**
     * These fields track color-index info across frames.
     * Count of distinct color indices.
     */
    private int ciCount = 0;
   
    /**
     * These fields track color-index info across frames.
     * Cumulative rgb-to-ci lookup table.
     */
    private ReverseColorMap ciLookup;

    /**
     *
     */
    GifColorTable() {
        ciLookup = new ReverseColorMap(); // puts us into "auto-detect mode"
    }

    /**
     * @param colors  Parameter.
     */
    GifColorTable(final Color[] colors) {
        int n2copy = Math.min(theColors.length, colors.length);
        for (int i = 0; i < n2copy; ++i) {
            theColors[i] = colors[i].getRGB();
        }
    }

    /**
     * @return  Return.
     */
    int getDepth() {
        return colorDepth;
    }

    /**
     * @return  Return.
     */
    int getTransparent() {
        return transparentIndex;
    }

    /**
     * Default: -1 (no transparency).
     *
     * @param colorIndex  Parameter.
     */
    void setTransparent(final int colorIndex) {
        transparentIndex = colorIndex;
    }

    /**
     * @param gf  Par.
     *
     * @throws IOException  Feh.
     */
    void processPixels(final AbstractGif89Frame gf) throws IOException {
        if (gf instanceof DirectGif89Frame) {
            filterPixels((DirectGif89Frame) gf);
        } else {
            trackPixelUsage((IndexGif89Frame) gf);
        }
    }

    /**
     * Must be called before encode().
     */
    void closePixelProcessing() {
        colorDepth = computeColorDepth(ciCount);
    }

    /**
     * @param os  Par.
     *
     * @throws IOException  Feh.
     */
    void encode(final OutputStream os) throws IOException {
        // size of palette written is the smallest power of 2 that can accomdate
        // the number of RGB colors detected (or largest color index, in case of
        // index pixels)
        int paletteSize = 1 << colorDepth;
        for (int i = 0; i < paletteSize; ++i) {
            os.write(theColors[i] >> 16 & 0xff);
            os.write(theColors[i] >> 8 & 0xff);
            os.write(theColors[i] & 0xff);
        }
    }

    /**
     * This method accomplishes three things:
     * (1) converts the passed rgb pixels to indexes into our rgb lookup table
     * (2) fills the rgb table as new colors are encountered
     * (3) looks for transparent pixels so as to set the transparent index
     * The information is cumulative across multiple calls.
     *
     *(Note: some of the logic is borrowed from Jef Poskanzer's code.)
     *
     * @param dgf  Par.
     * @exception IOException  Feh.
     */
    private void filterPixels(final DirectGif89Frame dgf) throws IOException {
        if (ciLookup == null) {
            throw new IOException("RGB frames require palette autodetection");
        }

        int[] argbPixels = (int[]) dgf.getPixelSource();
        byte[] ciPixels = dgf.getPixelSink();
        int npixels = argbPixels.length;
        for (int i = 0; i < npixels; ++i) {
            int argb = argbPixels[i];

            // handle transparency
            if ((argb >>> 24) < 0x80) { // transparent pixel?
                if (transparentIndex == -1) { // first transparent color
                    // encountered?
                    transparentIndex = ciCount; // record its index
                } else if (argb != theColors[transparentIndex]) { // different
                // pixel value?
                    // collapse all transparent pixels into one color index
                    ciPixels[i] = (byte) transparentIndex;
                    continue; // CONTINUE - index already in table
                }
            }
           
            // try to look up the index in our "reverse" color table
            int colorIndex = ciLookup.getPaletteIndex(argb & 0xffffff);

            if (colorIndex == -1) { // if it isn't in there yet
                if (ciCount == 256) {
                    throw new IOException("can't encode as GIF (> 256 colors)");
                }

                // store color in our accumulating palette
                theColors[ciCount] = argb;

                // store index in reverse color table
                ciLookup.put(argb & 0xffffff, ciCount);

                // send color index to our output array
                ciPixels[i] = (byte) ciCount;

                // increment count of distinct color indices
                ++ciCount;
            } else {
                // we've already snagged color into our palette
                ciPixels[i] = (byte) colorIndex; // just send filtered pixel
            }
        }
    }

    /**
     * @param igf  par.
     * @throws IOException  Feh.
     */
    private void trackPixelUsage(final IndexGif89Frame igf)
        throws IOException {
        byte[] ciPixels = (byte[]) igf.getPixelSource();
        int npixels = ciPixels.length;
        for (int i = 0; i < npixels; ++i) {
            if (ciPixels[i] >= ciCount) {
                ciCount = ciPixels[i] + 1;
            }
        }
    }

    /**
     * @param colorcount  Par.
     * @return  Return.
     */
    private int computeColorDepth(final int colorcount) {
        // color depth = log-base-2 of maximum number of simultaneous colors,
        // i.e.
        // bits per color-index pixel
        if (colorcount <= 2) {
            return 1;
        }
        if (colorcount <= 4) {
            return 2;
        }
        if (colorcount <= 16) {
            return 4;
        }
        return 8;
    }
}

// ===========================================================================
// We're doing a very simple linear hashing thing here, which seems sufficient
// for our needs. I make no claims for this approach other than that it seems
// an improvement over doing a brute linear search for each pixel on the one
// hand, and creating a Java object for each pixel (if we were to use a Java
// Hashtable) on the other. Doubtless my little hash could be improved by
// tuning the capacity (at the very least). Suggestions are welcome.
// ===========================================================================
/**
*
*/
class ReverseColorMap {

    /**
     * @author aifb
     */
    private static class ColorRecord {
       
        /**
         *
         */
        int rgb;
       
        /**
         *
         */
        int ipalette;

        /**
         * @param colorRGB  Par.
         * @param ipalllette  Par.
         */
        ColorRecord(final int colorRGB, final int ipalllette) {
            this.rgb = colorRGB;
            this.ipalette = ipalllette;
        }
    }

    /**
     * I wouldn't really know what a good hashing capacity is, having missed
     * out on data structures and algorithms class :) Alls I know is, we've
     * got a lot more space than we have time. So let's try a sparse table
     * with a maximum load of about 1/8 capacity.
     */
    private static final int HCAPACITY = 2053; // a nice prime number

    /**
     *  Our hash table proper.
     */
    private ColorRecord[] hTable = new ColorRecord[HCAPACITY];

    /**
     * @param rgb
     *            C.
     * @return C.
     */
    // Assert: rgb is not negative (which is the same as saying, be sure the
    // alpha transparency byte - i.e., the high byte - has been masked out).
    int getPaletteIndex(final int rgb) {
        int zwisch = rgb % hTable.length;
       
        for (int itable = rgb % hTable.length;
            hTable[itable] != null && hTable[itable].rgb != rgb;
            itable = ++itable % hTable.length) {
            zwisch = itable;
        }

        if (hTable[zwisch] != null) {
            return hTable[zwisch].ipalette;
        }

        return -1;
    }

    /**
     * @param rgb
     *            Comment.
     * @param ipalette
     *            Comment.
     */
    // Assert: (1) same as above; (2) rgb key not already present
    void put(final int rgb, final int ipalette) {
        int itable;
        String s = " ";
       
        for (itable = rgb % hTable.length;
            hTable[itable] != null;
            itable = ++itable % hTable.length) {
            s.charAt(0)// Dummy
        }

        hTable[itable] = new ColorRecord(rgb, ipalette);
    }
}
TOP

Related Classes of fmg.fmg8.gif.ReverseColorMap$ColorRecord

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.