13a5750359cb0362d01d394032944854fdde4069eSam Juddpackage com.bumptech.glide.gifencoder;
23a5750359cb0362d01d394032944854fdde4069eSam Judd
3f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd
4f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Juddimport android.graphics.Bitmap;
5f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Juddimport android.graphics.Canvas;
63a5750359cb0362d01d394032944854fdde4069eSam Juddimport android.graphics.Color;
7284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Juddimport android.util.Log;
83a5750359cb0362d01d394032944854fdde4069eSam Judd
93a5750359cb0362d01d394032944854fdde4069eSam Juddimport java.io.BufferedOutputStream;
103a5750359cb0362d01d394032944854fdde4069eSam Juddimport java.io.FileOutputStream;
113a5750359cb0362d01d394032944854fdde4069eSam Juddimport java.io.IOException;
123a5750359cb0362d01d394032944854fdde4069eSam Juddimport java.io.OutputStream;
133a5750359cb0362d01d394032944854fdde4069eSam Judd
143a5750359cb0362d01d394032944854fdde4069eSam Judd/**
153a5750359cb0362d01d394032944854fdde4069eSam Judd * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or more
163a5750359cb0362d01d394032944854fdde4069eSam Judd * frames.
173a5750359cb0362d01d394032944854fdde4069eSam Judd *
183a5750359cb0362d01d394032944854fdde4069eSam Judd * <pre>
193a5750359cb0362d01d394032944854fdde4069eSam Judd *  Example:
203a5750359cb0362d01d394032944854fdde4069eSam Judd *     AnimatedGifEncoder e = new AnimatedGifEncoder();
213a5750359cb0362d01d394032944854fdde4069eSam Judd *     e.start(outputFileName);
223a5750359cb0362d01d394032944854fdde4069eSam Judd *     e.setDelay(1000);   // 1 frame per sec
233a5750359cb0362d01d394032944854fdde4069eSam Judd *     e.addFrame(image1);
243a5750359cb0362d01d394032944854fdde4069eSam Judd *     e.addFrame(image2);
253a5750359cb0362d01d394032944854fdde4069eSam Judd *     e.finish();
263a5750359cb0362d01d394032944854fdde4069eSam Judd * </pre>
273a5750359cb0362d01d394032944854fdde4069eSam Judd *
283a5750359cb0362d01d394032944854fdde4069eSam Judd * No copyright asserted on the source code of this class. May be used for any
293a5750359cb0362d01d394032944854fdde4069eSam Judd * purpose, however, refer to the Unisys LZW patent for restrictions on use of
303a5750359cb0362d01d394032944854fdde4069eSam Judd * the associated LZWEncoder class. Please forward any corrections to
313a5750359cb0362d01d394032944854fdde4069eSam Judd * kweiner@fmsware.com.
323a5750359cb0362d01d394032944854fdde4069eSam Judd *
333a5750359cb0362d01d394032944854fdde4069eSam Judd * @author Kevin Weiner, FM Software
343a5750359cb0362d01d394032944854fdde4069eSam Judd * @version 1.03 November 2003
353a5750359cb0362d01d394032944854fdde4069eSam Judd *
363a5750359cb0362d01d394032944854fdde4069eSam Judd */
373a5750359cb0362d01d394032944854fdde4069eSam Judd
383a5750359cb0362d01d394032944854fdde4069eSam Juddpublic class AnimatedGifEncoder {
39284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd    private static final String TAG = "AnimatedGifEncoder";
40284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd
41284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd    // The minimum % of an images pixels that must be transparent for us to set a transparent index automatically.
42284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd    private static final double MIN_TRANSPARENT_PERCENTAGE = 4d;
433a5750359cb0362d01d394032944854fdde4069eSam Judd
442c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private int width; // image size
453a5750359cb0362d01d394032944854fdde4069eSam Judd
462c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private int height;
473a5750359cb0362d01d394032944854fdde4069eSam Judd
482c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private Integer transparent = null; // transparent color if given
493a5750359cb0362d01d394032944854fdde4069eSam Judd
502c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private int transIndex; // transparent index in color table
513a5750359cb0362d01d394032944854fdde4069eSam Judd
522c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private int repeat = -1; // no repeat
533a5750359cb0362d01d394032944854fdde4069eSam Judd
542c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private int delay = 0; // frame delay (hundredths)
553a5750359cb0362d01d394032944854fdde4069eSam Judd
562c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private boolean started = false; // ready to output frames
573a5750359cb0362d01d394032944854fdde4069eSam Judd
582c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private OutputStream out;
593a5750359cb0362d01d394032944854fdde4069eSam Judd
602c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private Bitmap image; // current frame
613a5750359cb0362d01d394032944854fdde4069eSam Judd
622c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private byte[] pixels; // BGR byte array from frame
633a5750359cb0362d01d394032944854fdde4069eSam Judd
642c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private byte[] indexedPixels; // converted frame indexed to palette
653a5750359cb0362d01d394032944854fdde4069eSam Judd
662c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private int colorDepth; // number of bit planes
673a5750359cb0362d01d394032944854fdde4069eSam Judd
682c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private byte[] colorTab; // RGB palette
693a5750359cb0362d01d394032944854fdde4069eSam Judd
702c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private boolean[] usedEntry = new boolean[256]; // active palette entries
713a5750359cb0362d01d394032944854fdde4069eSam Judd
722c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private int palSize = 7; // color table size (bits-1)
733a5750359cb0362d01d394032944854fdde4069eSam Judd
742c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private int dispose = -1; // disposal code (-1 = use default)
753a5750359cb0362d01d394032944854fdde4069eSam Judd
762c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private boolean closeStream = false; // close stream when finished
773a5750359cb0362d01d394032944854fdde4069eSam Judd
782c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private boolean firstFrame = true;
793a5750359cb0362d01d394032944854fdde4069eSam Judd
802c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private boolean sizeSet = false; // if false, get size from first frame
813a5750359cb0362d01d394032944854fdde4069eSam Judd
822c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private int sample = 10; // default sample interval for quantizer
832c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd
842c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private boolean hasTransparentPixels;
853a5750359cb0362d01d394032944854fdde4069eSam Judd
863a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
873a5750359cb0362d01d394032944854fdde4069eSam Judd     * Sets the delay time between each frame, or changes it for subsequent frames
883a5750359cb0362d01d394032944854fdde4069eSam Judd     * (applies to last frame added).
893a5750359cb0362d01d394032944854fdde4069eSam Judd     *
903a5750359cb0362d01d394032944854fdde4069eSam Judd     * @param ms
913a5750359cb0362d01d394032944854fdde4069eSam Judd     *          int delay time in milliseconds
923a5750359cb0362d01d394032944854fdde4069eSam Judd     */
933a5750359cb0362d01d394032944854fdde4069eSam Judd    public void setDelay(int ms) {
943a5750359cb0362d01d394032944854fdde4069eSam Judd        delay = Math.round(ms / 10.0f);
953a5750359cb0362d01d394032944854fdde4069eSam Judd    }
963a5750359cb0362d01d394032944854fdde4069eSam Judd
973a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
983a5750359cb0362d01d394032944854fdde4069eSam Judd     * Sets the GIF frame disposal code for the last added frame and any
993a5750359cb0362d01d394032944854fdde4069eSam Judd     * subsequent frames. Default is 0 if no transparent color has been set,
1003a5750359cb0362d01d394032944854fdde4069eSam Judd     * otherwise 2.
1013a5750359cb0362d01d394032944854fdde4069eSam Judd     *
1023a5750359cb0362d01d394032944854fdde4069eSam Judd     * @param code
1033a5750359cb0362d01d394032944854fdde4069eSam Judd     *          int disposal code.
1043a5750359cb0362d01d394032944854fdde4069eSam Judd     */
1053a5750359cb0362d01d394032944854fdde4069eSam Judd    public void setDispose(int code) {
1063a5750359cb0362d01d394032944854fdde4069eSam Judd        if (code >= 0) {
1073a5750359cb0362d01d394032944854fdde4069eSam Judd            dispose = code;
1083a5750359cb0362d01d394032944854fdde4069eSam Judd        }
1093a5750359cb0362d01d394032944854fdde4069eSam Judd    }
1103a5750359cb0362d01d394032944854fdde4069eSam Judd
1113a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
1123a5750359cb0362d01d394032944854fdde4069eSam Judd     * Sets the number of times the set of GIF frames should be played. Default is
1133a5750359cb0362d01d394032944854fdde4069eSam Judd     * 1; 0 means play indefinitely. Must be invoked before the first image is
1143a5750359cb0362d01d394032944854fdde4069eSam Judd     * added.
1153a5750359cb0362d01d394032944854fdde4069eSam Judd     *
1163a5750359cb0362d01d394032944854fdde4069eSam Judd     * @param iter
1173a5750359cb0362d01d394032944854fdde4069eSam Judd     *          int number of iterations.
1183a5750359cb0362d01d394032944854fdde4069eSam Judd     */
1193a5750359cb0362d01d394032944854fdde4069eSam Judd    public void setRepeat(int iter) {
1203a5750359cb0362d01d394032944854fdde4069eSam Judd        if (iter >= 0) {
1213a5750359cb0362d01d394032944854fdde4069eSam Judd            repeat = iter;
1223a5750359cb0362d01d394032944854fdde4069eSam Judd        }
1233a5750359cb0362d01d394032944854fdde4069eSam Judd    }
1243a5750359cb0362d01d394032944854fdde4069eSam Judd
1253a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
1263a5750359cb0362d01d394032944854fdde4069eSam Judd     * Sets the transparent color for the last added frame and any subsequent
1273a5750359cb0362d01d394032944854fdde4069eSam Judd     * frames. Since all colors are subject to modification in the quantization
1283a5750359cb0362d01d394032944854fdde4069eSam Judd     * process, the color in the final palette for each frame closest to the given
1293a5750359cb0362d01d394032944854fdde4069eSam Judd     * color becomes the transparent color for that frame. May be set to null to
1303a5750359cb0362d01d394032944854fdde4069eSam Judd     * indicate no transparent color.
1313a5750359cb0362d01d394032944854fdde4069eSam Judd     *
132f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd     * @param color
1333a5750359cb0362d01d394032944854fdde4069eSam Judd     *          Color to be treated as transparent on display.
1343a5750359cb0362d01d394032944854fdde4069eSam Judd     */
135f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd    public void setTransparent(int color) {
136f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd        transparent = color;
1373a5750359cb0362d01d394032944854fdde4069eSam Judd    }
1383a5750359cb0362d01d394032944854fdde4069eSam Judd
1393a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
1403a5750359cb0362d01d394032944854fdde4069eSam Judd     * Adds next GIF frame. The frame is not written immediately, but is actually
1413a5750359cb0362d01d394032944854fdde4069eSam Judd     * deferred until the next frame is received so that timing data can be
1423a5750359cb0362d01d394032944854fdde4069eSam Judd     * inserted. Invoking <code>finish()</code> flushes all frames. If
1433a5750359cb0362d01d394032944854fdde4069eSam Judd     * <code>setSize</code> was not invoked, the size of the first image is used
1443a5750359cb0362d01d394032944854fdde4069eSam Judd     * for all subsequent frames.
1453a5750359cb0362d01d394032944854fdde4069eSam Judd     *
1463a5750359cb0362d01d394032944854fdde4069eSam Judd     * @param im
1473a5750359cb0362d01d394032944854fdde4069eSam Judd     *          BufferedImage containing frame to write.
1483a5750359cb0362d01d394032944854fdde4069eSam Judd     * @return true if successful.
1493a5750359cb0362d01d394032944854fdde4069eSam Judd     */
150f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd    public boolean addFrame(Bitmap im) {
1513a5750359cb0362d01d394032944854fdde4069eSam Judd        if ((im == null) || !started) {
1523a5750359cb0362d01d394032944854fdde4069eSam Judd            return false;
1533a5750359cb0362d01d394032944854fdde4069eSam Judd        }
1543a5750359cb0362d01d394032944854fdde4069eSam Judd        boolean ok = true;
1553a5750359cb0362d01d394032944854fdde4069eSam Judd        try {
1563a5750359cb0362d01d394032944854fdde4069eSam Judd            if (!sizeSet) {
1573a5750359cb0362d01d394032944854fdde4069eSam Judd                // use first frame's size
1583a5750359cb0362d01d394032944854fdde4069eSam Judd                setSize(im.getWidth(), im.getHeight());
1593a5750359cb0362d01d394032944854fdde4069eSam Judd            }
1603a5750359cb0362d01d394032944854fdde4069eSam Judd            image = im;
1613a5750359cb0362d01d394032944854fdde4069eSam Judd            getImagePixels(); // convert to correct format if necessary
1623a5750359cb0362d01d394032944854fdde4069eSam Judd            analyzePixels(); // build color table & map pixels
1633a5750359cb0362d01d394032944854fdde4069eSam Judd            if (firstFrame) {
1643a5750359cb0362d01d394032944854fdde4069eSam Judd                writeLSD(); // logical screen descriptior
1653a5750359cb0362d01d394032944854fdde4069eSam Judd                writePalette(); // global color table
1663a5750359cb0362d01d394032944854fdde4069eSam Judd                if (repeat >= 0) {
1673a5750359cb0362d01d394032944854fdde4069eSam Judd                    // use NS app extension to indicate reps
1683a5750359cb0362d01d394032944854fdde4069eSam Judd                    writeNetscapeExt();
1693a5750359cb0362d01d394032944854fdde4069eSam Judd                }
1703a5750359cb0362d01d394032944854fdde4069eSam Judd            }
1713a5750359cb0362d01d394032944854fdde4069eSam Judd            writeGraphicCtrlExt(); // write graphic control extension
1723a5750359cb0362d01d394032944854fdde4069eSam Judd            writeImageDesc(); // image descriptor
1733a5750359cb0362d01d394032944854fdde4069eSam Judd            if (!firstFrame) {
1743a5750359cb0362d01d394032944854fdde4069eSam Judd                writePalette(); // local color table
1753a5750359cb0362d01d394032944854fdde4069eSam Judd            }
1763a5750359cb0362d01d394032944854fdde4069eSam Judd            writePixels(); // encode and write pixel data
1773a5750359cb0362d01d394032944854fdde4069eSam Judd            firstFrame = false;
1783a5750359cb0362d01d394032944854fdde4069eSam Judd        } catch (IOException e) {
1793a5750359cb0362d01d394032944854fdde4069eSam Judd            ok = false;
1803a5750359cb0362d01d394032944854fdde4069eSam Judd        }
1813a5750359cb0362d01d394032944854fdde4069eSam Judd
1823a5750359cb0362d01d394032944854fdde4069eSam Judd        return ok;
1833a5750359cb0362d01d394032944854fdde4069eSam Judd    }
1843a5750359cb0362d01d394032944854fdde4069eSam Judd
1853a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
1863a5750359cb0362d01d394032944854fdde4069eSam Judd     * Flushes any pending data and closes output file. If writing to an
1873a5750359cb0362d01d394032944854fdde4069eSam Judd     * OutputStream, the stream is not closed.
1883a5750359cb0362d01d394032944854fdde4069eSam Judd     */
1893a5750359cb0362d01d394032944854fdde4069eSam Judd    public boolean finish() {
1903a5750359cb0362d01d394032944854fdde4069eSam Judd        if (!started)
1913a5750359cb0362d01d394032944854fdde4069eSam Judd            return false;
1923a5750359cb0362d01d394032944854fdde4069eSam Judd        boolean ok = true;
1933a5750359cb0362d01d394032944854fdde4069eSam Judd        started = false;
1943a5750359cb0362d01d394032944854fdde4069eSam Judd        try {
1953a5750359cb0362d01d394032944854fdde4069eSam Judd            out.write(0x3b); // gif trailer
1963a5750359cb0362d01d394032944854fdde4069eSam Judd            out.flush();
1973a5750359cb0362d01d394032944854fdde4069eSam Judd            if (closeStream) {
1983a5750359cb0362d01d394032944854fdde4069eSam Judd                out.close();
1993a5750359cb0362d01d394032944854fdde4069eSam Judd            }
2003a5750359cb0362d01d394032944854fdde4069eSam Judd        } catch (IOException e) {
2013a5750359cb0362d01d394032944854fdde4069eSam Judd            ok = false;
2023a5750359cb0362d01d394032944854fdde4069eSam Judd        }
2033a5750359cb0362d01d394032944854fdde4069eSam Judd
2043a5750359cb0362d01d394032944854fdde4069eSam Judd        // reset for subsequent use
2053a5750359cb0362d01d394032944854fdde4069eSam Judd        transIndex = 0;
2063a5750359cb0362d01d394032944854fdde4069eSam Judd        out = null;
2073a5750359cb0362d01d394032944854fdde4069eSam Judd        image = null;
2083a5750359cb0362d01d394032944854fdde4069eSam Judd        pixels = null;
2093a5750359cb0362d01d394032944854fdde4069eSam Judd        indexedPixels = null;
2103a5750359cb0362d01d394032944854fdde4069eSam Judd        colorTab = null;
2113a5750359cb0362d01d394032944854fdde4069eSam Judd        closeStream = false;
2123a5750359cb0362d01d394032944854fdde4069eSam Judd        firstFrame = true;
2133a5750359cb0362d01d394032944854fdde4069eSam Judd
2143a5750359cb0362d01d394032944854fdde4069eSam Judd        return ok;
2153a5750359cb0362d01d394032944854fdde4069eSam Judd    }
2163a5750359cb0362d01d394032944854fdde4069eSam Judd
2173a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
2183a5750359cb0362d01d394032944854fdde4069eSam Judd     * Sets frame rate in frames per second. Equivalent to
2193a5750359cb0362d01d394032944854fdde4069eSam Judd     * <code>setDelay(1000/fps)</code>.
2203a5750359cb0362d01d394032944854fdde4069eSam Judd     *
2213a5750359cb0362d01d394032944854fdde4069eSam Judd     * @param fps
2223a5750359cb0362d01d394032944854fdde4069eSam Judd     *          float frame rate (frames per second)
2233a5750359cb0362d01d394032944854fdde4069eSam Judd     */
2243a5750359cb0362d01d394032944854fdde4069eSam Judd    public void setFrameRate(float fps) {
2253a5750359cb0362d01d394032944854fdde4069eSam Judd        if (fps != 0f) {
2263a5750359cb0362d01d394032944854fdde4069eSam Judd            delay = Math.round(100f / fps);
2273a5750359cb0362d01d394032944854fdde4069eSam Judd        }
2283a5750359cb0362d01d394032944854fdde4069eSam Judd    }
2293a5750359cb0362d01d394032944854fdde4069eSam Judd
2303a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
2313a5750359cb0362d01d394032944854fdde4069eSam Judd     * Sets quality of color quantization (conversion of images to the maximum 256
2323a5750359cb0362d01d394032944854fdde4069eSam Judd     * colors allowed by the GIF specification). Lower values (minimum = 1)
2333a5750359cb0362d01d394032944854fdde4069eSam Judd     * produce better colors, but slow processing significantly. 10 is the
2343a5750359cb0362d01d394032944854fdde4069eSam Judd     * default, and produces good color mapping at reasonable speeds. Values
2353a5750359cb0362d01d394032944854fdde4069eSam Judd     * greater than 20 do not yield significant improvements in speed.
2363a5750359cb0362d01d394032944854fdde4069eSam Judd     *
2371737514a35eae271f1a7d5441b92f9ea9c048e27Sam Judd     * @param quality int greater than 0.
2383a5750359cb0362d01d394032944854fdde4069eSam Judd     */
2393a5750359cb0362d01d394032944854fdde4069eSam Judd    public void setQuality(int quality) {
2403a5750359cb0362d01d394032944854fdde4069eSam Judd        if (quality < 1)
2413a5750359cb0362d01d394032944854fdde4069eSam Judd            quality = 1;
2423a5750359cb0362d01d394032944854fdde4069eSam Judd        sample = quality;
2433a5750359cb0362d01d394032944854fdde4069eSam Judd    }
2443a5750359cb0362d01d394032944854fdde4069eSam Judd
2453a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
2463a5750359cb0362d01d394032944854fdde4069eSam Judd     * Sets the GIF frame size. The default size is the size of the first frame
2473a5750359cb0362d01d394032944854fdde4069eSam Judd     * added if this method is not invoked.
2483a5750359cb0362d01d394032944854fdde4069eSam Judd     *
2493a5750359cb0362d01d394032944854fdde4069eSam Judd     * @param w
2503a5750359cb0362d01d394032944854fdde4069eSam Judd     *          int frame width.
2513a5750359cb0362d01d394032944854fdde4069eSam Judd     * @param h
2523a5750359cb0362d01d394032944854fdde4069eSam Judd     *          int frame width.
2533a5750359cb0362d01d394032944854fdde4069eSam Judd     */
2543a5750359cb0362d01d394032944854fdde4069eSam Judd    public void setSize(int w, int h) {
2553a5750359cb0362d01d394032944854fdde4069eSam Judd        if (started && !firstFrame)
2563a5750359cb0362d01d394032944854fdde4069eSam Judd            return;
2573a5750359cb0362d01d394032944854fdde4069eSam Judd        width = w;
2583a5750359cb0362d01d394032944854fdde4069eSam Judd        height = h;
2593a5750359cb0362d01d394032944854fdde4069eSam Judd        if (width < 1)
2603a5750359cb0362d01d394032944854fdde4069eSam Judd            width = 320;
2613a5750359cb0362d01d394032944854fdde4069eSam Judd        if (height < 1)
2623a5750359cb0362d01d394032944854fdde4069eSam Judd            height = 240;
2633a5750359cb0362d01d394032944854fdde4069eSam Judd        sizeSet = true;
2643a5750359cb0362d01d394032944854fdde4069eSam Judd    }
2653a5750359cb0362d01d394032944854fdde4069eSam Judd
2663a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
2673a5750359cb0362d01d394032944854fdde4069eSam Judd     * Initiates GIF file creation on the given stream. The stream is not closed
2683a5750359cb0362d01d394032944854fdde4069eSam Judd     * automatically.
2693a5750359cb0362d01d394032944854fdde4069eSam Judd     *
2703a5750359cb0362d01d394032944854fdde4069eSam Judd     * @param os
2713a5750359cb0362d01d394032944854fdde4069eSam Judd     *          OutputStream on which GIF images are written.
2723a5750359cb0362d01d394032944854fdde4069eSam Judd     * @return false if initial write failed.
2733a5750359cb0362d01d394032944854fdde4069eSam Judd     */
2743a5750359cb0362d01d394032944854fdde4069eSam Judd    public boolean start(OutputStream os) {
2753a5750359cb0362d01d394032944854fdde4069eSam Judd        if (os == null)
2763a5750359cb0362d01d394032944854fdde4069eSam Judd            return false;
2773a5750359cb0362d01d394032944854fdde4069eSam Judd        boolean ok = true;
2783a5750359cb0362d01d394032944854fdde4069eSam Judd        closeStream = false;
2793a5750359cb0362d01d394032944854fdde4069eSam Judd        out = os;
2803a5750359cb0362d01d394032944854fdde4069eSam Judd        try {
2813a5750359cb0362d01d394032944854fdde4069eSam Judd            writeString("GIF89a"); // header
2823a5750359cb0362d01d394032944854fdde4069eSam Judd        } catch (IOException e) {
2833a5750359cb0362d01d394032944854fdde4069eSam Judd            ok = false;
2843a5750359cb0362d01d394032944854fdde4069eSam Judd        }
2853a5750359cb0362d01d394032944854fdde4069eSam Judd        return started = ok;
2863a5750359cb0362d01d394032944854fdde4069eSam Judd    }
2873a5750359cb0362d01d394032944854fdde4069eSam Judd
2883a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
2893a5750359cb0362d01d394032944854fdde4069eSam Judd     * Initiates writing of a GIF file with the specified name.
2903a5750359cb0362d01d394032944854fdde4069eSam Judd     *
2913a5750359cb0362d01d394032944854fdde4069eSam Judd     * @param file
2923a5750359cb0362d01d394032944854fdde4069eSam Judd     *          String containing output file name.
2933a5750359cb0362d01d394032944854fdde4069eSam Judd     * @return false if open or initial write failed.
2943a5750359cb0362d01d394032944854fdde4069eSam Judd     */
2953a5750359cb0362d01d394032944854fdde4069eSam Judd    public boolean start(String file) {
2963a5750359cb0362d01d394032944854fdde4069eSam Judd        boolean ok = true;
2973a5750359cb0362d01d394032944854fdde4069eSam Judd        try {
2983a5750359cb0362d01d394032944854fdde4069eSam Judd            out = new BufferedOutputStream(new FileOutputStream(file));
2993a5750359cb0362d01d394032944854fdde4069eSam Judd            ok = start(out);
3003a5750359cb0362d01d394032944854fdde4069eSam Judd            closeStream = true;
3013a5750359cb0362d01d394032944854fdde4069eSam Judd        } catch (IOException e) {
3023a5750359cb0362d01d394032944854fdde4069eSam Judd            ok = false;
3033a5750359cb0362d01d394032944854fdde4069eSam Judd        }
3043a5750359cb0362d01d394032944854fdde4069eSam Judd        return started = ok;
3053a5750359cb0362d01d394032944854fdde4069eSam Judd    }
3063a5750359cb0362d01d394032944854fdde4069eSam Judd
3073a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
3083a5750359cb0362d01d394032944854fdde4069eSam Judd     * Analyzes image colors and creates color map.
3093a5750359cb0362d01d394032944854fdde4069eSam Judd     */
3102c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private void analyzePixels() {
3113a5750359cb0362d01d394032944854fdde4069eSam Judd        int len = pixels.length;
3123a5750359cb0362d01d394032944854fdde4069eSam Judd        int nPix = len / 3;
3133a5750359cb0362d01d394032944854fdde4069eSam Judd        indexedPixels = new byte[nPix];
3143a5750359cb0362d01d394032944854fdde4069eSam Judd        NeuQuant nq = new NeuQuant(pixels, len, sample);
3153a5750359cb0362d01d394032944854fdde4069eSam Judd        // initialize quantizer
3163a5750359cb0362d01d394032944854fdde4069eSam Judd        colorTab = nq.process(); // create reduced palette
3173a5750359cb0362d01d394032944854fdde4069eSam Judd        // convert map from BGR to RGB
3183a5750359cb0362d01d394032944854fdde4069eSam Judd        for (int i = 0; i < colorTab.length; i += 3) {
3193a5750359cb0362d01d394032944854fdde4069eSam Judd            byte temp = colorTab[i];
3203a5750359cb0362d01d394032944854fdde4069eSam Judd            colorTab[i] = colorTab[i + 2];
3213a5750359cb0362d01d394032944854fdde4069eSam Judd            colorTab[i + 2] = temp;
3223a5750359cb0362d01d394032944854fdde4069eSam Judd            usedEntry[i / 3] = false;
3233a5750359cb0362d01d394032944854fdde4069eSam Judd        }
3243a5750359cb0362d01d394032944854fdde4069eSam Judd        // map image pixels to new palette
3253a5750359cb0362d01d394032944854fdde4069eSam Judd        int k = 0;
3263a5750359cb0362d01d394032944854fdde4069eSam Judd        for (int i = 0; i < nPix; i++) {
3273a5750359cb0362d01d394032944854fdde4069eSam Judd            int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
3283a5750359cb0362d01d394032944854fdde4069eSam Judd            usedEntry[index] = true;
3293a5750359cb0362d01d394032944854fdde4069eSam Judd            indexedPixels[i] = (byte) index;
3303a5750359cb0362d01d394032944854fdde4069eSam Judd        }
3313a5750359cb0362d01d394032944854fdde4069eSam Judd        pixels = null;
3323a5750359cb0362d01d394032944854fdde4069eSam Judd        colorDepth = 8;
3333a5750359cb0362d01d394032944854fdde4069eSam Judd        palSize = 7;
3343a5750359cb0362d01d394032944854fdde4069eSam Judd        // get closest match to transparent color if specified
3353a5750359cb0362d01d394032944854fdde4069eSam Judd        if (transparent != null) {
3363a5750359cb0362d01d394032944854fdde4069eSam Judd            transIndex = findClosest(transparent);
3372c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd        } else if (hasTransparentPixels) {
3382c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd            transIndex = findClosest(Color.TRANSPARENT);
3393a5750359cb0362d01d394032944854fdde4069eSam Judd        }
3403a5750359cb0362d01d394032944854fdde4069eSam Judd    }
3413a5750359cb0362d01d394032944854fdde4069eSam Judd
3423a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
3433a5750359cb0362d01d394032944854fdde4069eSam Judd     * Returns index of palette color closest to c
3443a5750359cb0362d01d394032944854fdde4069eSam Judd     *
3453a5750359cb0362d01d394032944854fdde4069eSam Judd     */
3462c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private int findClosest(int color) {
3473a5750359cb0362d01d394032944854fdde4069eSam Judd        if (colorTab == null)
3483a5750359cb0362d01d394032944854fdde4069eSam Judd            return -1;
349f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd        int r = Color.red(color);
350f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd        int g = Color.green(color);
351f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd        int b = Color.blue(color);
3523a5750359cb0362d01d394032944854fdde4069eSam Judd        int minpos = 0;
3533a5750359cb0362d01d394032944854fdde4069eSam Judd        int dmin = 256 * 256 * 256;
3543a5750359cb0362d01d394032944854fdde4069eSam Judd        int len = colorTab.length;
3553a5750359cb0362d01d394032944854fdde4069eSam Judd        for (int i = 0; i < len;) {
3563a5750359cb0362d01d394032944854fdde4069eSam Judd            int dr = r - (colorTab[i++] & 0xff);
3573a5750359cb0362d01d394032944854fdde4069eSam Judd            int dg = g - (colorTab[i++] & 0xff);
3583a5750359cb0362d01d394032944854fdde4069eSam Judd            int db = b - (colorTab[i] & 0xff);
3593a5750359cb0362d01d394032944854fdde4069eSam Judd            int d = dr * dr + dg * dg + db * db;
3603a5750359cb0362d01d394032944854fdde4069eSam Judd            int index = i / 3;
3613a5750359cb0362d01d394032944854fdde4069eSam Judd            if (usedEntry[index] && (d < dmin)) {
3623a5750359cb0362d01d394032944854fdde4069eSam Judd                dmin = d;
3633a5750359cb0362d01d394032944854fdde4069eSam Judd                minpos = index;
3643a5750359cb0362d01d394032944854fdde4069eSam Judd            }
3653a5750359cb0362d01d394032944854fdde4069eSam Judd            i++;
3663a5750359cb0362d01d394032944854fdde4069eSam Judd        }
3673a5750359cb0362d01d394032944854fdde4069eSam Judd        return minpos;
3683a5750359cb0362d01d394032944854fdde4069eSam Judd    }
3693a5750359cb0362d01d394032944854fdde4069eSam Judd
3703a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
3713a5750359cb0362d01d394032944854fdde4069eSam Judd     * Extracts image pixels into byte array "pixels"
3723a5750359cb0362d01d394032944854fdde4069eSam Judd     */
3732c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private void getImagePixels() {
3743a5750359cb0362d01d394032944854fdde4069eSam Judd        int w = image.getWidth();
3753a5750359cb0362d01d394032944854fdde4069eSam Judd        int h = image.getHeight();
376f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd
377f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd        if ((w != width) || (h != height)) {
3783a5750359cb0362d01d394032944854fdde4069eSam Judd            // create new image with right size/format
379f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd            Bitmap temp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
380f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd            Canvas canvas = new Canvas(temp);
381f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd            canvas.drawBitmap(temp, 0, 0, null);
3823a5750359cb0362d01d394032944854fdde4069eSam Judd            image = temp;
3833a5750359cb0362d01d394032944854fdde4069eSam Judd        }
384f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd        int[] pixelsInt = new int[w * h];
385f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd        image.getPixels(pixelsInt, 0, w, 0, 0, w, h);
386f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd
387f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd        // The algorithm requires 3 bytes per pixel as RGB.
388f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd        pixels = new byte[pixelsInt.length * 3];
389f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd
390f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd        int pixelsIndex = 0;
3912c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd        hasTransparentPixels = false;
392284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd        int totalTransparentPixels = 0;
393f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd        for (final int pixel : pixelsInt) {
3942c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd            if (pixel == Color.TRANSPARENT) {
395284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd                totalTransparentPixels++;
3962c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd            }
397f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd            pixels[pixelsIndex++] = (byte) (pixel & 0xFF);
398f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd            pixels[pixelsIndex++] = (byte) ((pixel >> 8) & 0xFF);
399f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd            pixels[pixelsIndex++] = (byte) ((pixel >> 16) & 0xFF);
400f7b3e5d7a4893fd55b3fd36be56bb37319d8aa24Sam Judd        }
401284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd
402284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd        double transparentPercentage = 100 * totalTransparentPixels / (double) pixelsInt.length;
403284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd        // Assume images with greater where more than n% of the pixels are transparent actually have transparency.
404284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd        // See issue #214.
405284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd        hasTransparentPixels = transparentPercentage > MIN_TRANSPARENT_PERCENTAGE;
406284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd        if (Log.isLoggable(TAG, Log.DEBUG)) {
407284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd            Log.d(TAG, "got pixels for frame with " + transparentPercentage + "% transparent pixels");
408284bbcd0ed2c8f747a3b6abbab9c673487842f53Sam Judd        }
4093a5750359cb0362d01d394032944854fdde4069eSam Judd    }
4103a5750359cb0362d01d394032944854fdde4069eSam Judd
4113a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
4123a5750359cb0362d01d394032944854fdde4069eSam Judd     * Writes Graphic Control Extension
4133a5750359cb0362d01d394032944854fdde4069eSam Judd     */
4142c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private void writeGraphicCtrlExt() throws IOException {
4153a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(0x21); // extension introducer
4163a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(0xf9); // GCE label
4173a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(4); // data block size
4183a5750359cb0362d01d394032944854fdde4069eSam Judd        int transp, disp;
4192c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd        if (transparent == null && !hasTransparentPixels) {
4203a5750359cb0362d01d394032944854fdde4069eSam Judd            transp = 0;
4213a5750359cb0362d01d394032944854fdde4069eSam Judd            disp = 0; // dispose = no action
4223a5750359cb0362d01d394032944854fdde4069eSam Judd        } else {
4233a5750359cb0362d01d394032944854fdde4069eSam Judd            transp = 1;
4243a5750359cb0362d01d394032944854fdde4069eSam Judd            disp = 2; // force clear if using transparent color
4253a5750359cb0362d01d394032944854fdde4069eSam Judd        }
4263a5750359cb0362d01d394032944854fdde4069eSam Judd        if (dispose >= 0) {
4273a5750359cb0362d01d394032944854fdde4069eSam Judd            disp = dispose & 7; // user override
4283a5750359cb0362d01d394032944854fdde4069eSam Judd        }
4293a5750359cb0362d01d394032944854fdde4069eSam Judd        disp <<= 2;
4303a5750359cb0362d01d394032944854fdde4069eSam Judd
4313a5750359cb0362d01d394032944854fdde4069eSam Judd        // packed fields
4323a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(0 | // 1:3 reserved
4333a5750359cb0362d01d394032944854fdde4069eSam Judd                disp | // 4:6 disposal
4343a5750359cb0362d01d394032944854fdde4069eSam Judd                0 | // 7 user input - 0 = none
4353a5750359cb0362d01d394032944854fdde4069eSam Judd                transp); // 8 transparency flag
4363a5750359cb0362d01d394032944854fdde4069eSam Judd
4373a5750359cb0362d01d394032944854fdde4069eSam Judd        writeShort(delay); // delay x 1/100 sec
4383a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(transIndex); // transparent color index
4393a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(0); // block terminator
4403a5750359cb0362d01d394032944854fdde4069eSam Judd    }
4413a5750359cb0362d01d394032944854fdde4069eSam Judd
4423a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
4433a5750359cb0362d01d394032944854fdde4069eSam Judd     * Writes Image Descriptor
4443a5750359cb0362d01d394032944854fdde4069eSam Judd     */
4452c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private void writeImageDesc() throws IOException {
4463a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(0x2c); // image separator
4473a5750359cb0362d01d394032944854fdde4069eSam Judd        writeShort(0); // image position x,y = 0,0
4483a5750359cb0362d01d394032944854fdde4069eSam Judd        writeShort(0);
4493a5750359cb0362d01d394032944854fdde4069eSam Judd        writeShort(width); // image size
4503a5750359cb0362d01d394032944854fdde4069eSam Judd        writeShort(height);
4513a5750359cb0362d01d394032944854fdde4069eSam Judd        // packed fields
4523a5750359cb0362d01d394032944854fdde4069eSam Judd        if (firstFrame) {
4533a5750359cb0362d01d394032944854fdde4069eSam Judd            // no LCT - GCT is used for first (or only) frame
4543a5750359cb0362d01d394032944854fdde4069eSam Judd            out.write(0);
4553a5750359cb0362d01d394032944854fdde4069eSam Judd        } else {
4563a5750359cb0362d01d394032944854fdde4069eSam Judd            // specify normal LCT
4573a5750359cb0362d01d394032944854fdde4069eSam Judd            out.write(0x80 | // 1 local color table 1=yes
4583a5750359cb0362d01d394032944854fdde4069eSam Judd                    0 | // 2 interlace - 0=no
4593a5750359cb0362d01d394032944854fdde4069eSam Judd                    0 | // 3 sorted - 0=no
4603a5750359cb0362d01d394032944854fdde4069eSam Judd                    0 | // 4-5 reserved
4613a5750359cb0362d01d394032944854fdde4069eSam Judd                    palSize); // 6-8 size of color table
4623a5750359cb0362d01d394032944854fdde4069eSam Judd        }
4633a5750359cb0362d01d394032944854fdde4069eSam Judd    }
4643a5750359cb0362d01d394032944854fdde4069eSam Judd
4653a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
4663a5750359cb0362d01d394032944854fdde4069eSam Judd     * Writes Logical Screen Descriptor
4673a5750359cb0362d01d394032944854fdde4069eSam Judd     */
4682c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private void writeLSD() throws IOException {
4693a5750359cb0362d01d394032944854fdde4069eSam Judd        // logical screen size
4703a5750359cb0362d01d394032944854fdde4069eSam Judd        writeShort(width);
4713a5750359cb0362d01d394032944854fdde4069eSam Judd        writeShort(height);
4723a5750359cb0362d01d394032944854fdde4069eSam Judd        // packed fields
4733a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write((0x80 | // 1 : global color table flag = 1 (gct used)
4743a5750359cb0362d01d394032944854fdde4069eSam Judd                0x70 | // 2-4 : color resolution = 7
4753a5750359cb0362d01d394032944854fdde4069eSam Judd                0x00 | // 5 : gct sort flag = 0
4763a5750359cb0362d01d394032944854fdde4069eSam Judd                palSize)); // 6-8 : gct size
4773a5750359cb0362d01d394032944854fdde4069eSam Judd
4783a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(0); // background color index
4793a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(0); // pixel aspect ratio - assume 1:1
4803a5750359cb0362d01d394032944854fdde4069eSam Judd    }
4813a5750359cb0362d01d394032944854fdde4069eSam Judd
4823a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
4833a5750359cb0362d01d394032944854fdde4069eSam Judd     * Writes Netscape application extension to define repeat count.
4843a5750359cb0362d01d394032944854fdde4069eSam Judd     */
4852c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private void writeNetscapeExt() throws IOException {
4863a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(0x21); // extension introducer
4873a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(0xff); // app extension label
4883a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(11); // block size
4893a5750359cb0362d01d394032944854fdde4069eSam Judd        writeString("NETSCAPE" + "2.0"); // app id + auth code
4903a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(3); // sub-block size
4913a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(1); // loop sub-block id
4923a5750359cb0362d01d394032944854fdde4069eSam Judd        writeShort(repeat); // loop count (extra iterations, 0=repeat forever)
4933a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(0); // block terminator
4943a5750359cb0362d01d394032944854fdde4069eSam Judd    }
4953a5750359cb0362d01d394032944854fdde4069eSam Judd
4963a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
4973a5750359cb0362d01d394032944854fdde4069eSam Judd     * Writes color table
4983a5750359cb0362d01d394032944854fdde4069eSam Judd     */
4992c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private void writePalette() throws IOException {
5003a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(colorTab, 0, colorTab.length);
5013a5750359cb0362d01d394032944854fdde4069eSam Judd        int n = (3 * 256) - colorTab.length;
5023a5750359cb0362d01d394032944854fdde4069eSam Judd        for (int i = 0; i < n; i++) {
5033a5750359cb0362d01d394032944854fdde4069eSam Judd            out.write(0);
5043a5750359cb0362d01d394032944854fdde4069eSam Judd        }
5053a5750359cb0362d01d394032944854fdde4069eSam Judd    }
5063a5750359cb0362d01d394032944854fdde4069eSam Judd
5073a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
5083a5750359cb0362d01d394032944854fdde4069eSam Judd     * Encodes and writes pixel data
5093a5750359cb0362d01d394032944854fdde4069eSam Judd     */
5102c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private void writePixels() throws IOException {
5113a5750359cb0362d01d394032944854fdde4069eSam Judd        LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
5123a5750359cb0362d01d394032944854fdde4069eSam Judd        encoder.encode(out);
5133a5750359cb0362d01d394032944854fdde4069eSam Judd    }
5143a5750359cb0362d01d394032944854fdde4069eSam Judd
5153a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
5163a5750359cb0362d01d394032944854fdde4069eSam Judd     * Write 16-bit value to output stream, LSB first
5173a5750359cb0362d01d394032944854fdde4069eSam Judd     */
5182c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private void writeShort(int value) throws IOException {
5193a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write(value & 0xff);
5203a5750359cb0362d01d394032944854fdde4069eSam Judd        out.write((value >> 8) & 0xff);
5213a5750359cb0362d01d394032944854fdde4069eSam Judd    }
5223a5750359cb0362d01d394032944854fdde4069eSam Judd
5233a5750359cb0362d01d394032944854fdde4069eSam Judd    /**
5243a5750359cb0362d01d394032944854fdde4069eSam Judd     * Writes string to output stream
5253a5750359cb0362d01d394032944854fdde4069eSam Judd     */
5262c259f532bee14a4f3f6be419bcfb58ef5e22ff5Sam Judd    private void writeString(String s) throws IOException {
5273a5750359cb0362d01d394032944854fdde4069eSam Judd        for (int i = 0; i < s.length(); i++) {
5283a5750359cb0362d01d394032944854fdde4069eSam Judd            out.write((byte) s.charAt(i));
5293a5750359cb0362d01d394032944854fdde4069eSam Judd        }
5303a5750359cb0362d01d394032944854fdde4069eSam Judd    }
5313a5750359cb0362d01d394032944854fdde4069eSam Judd}
532