public boolean addFrame(BufferedImage im) {
        if ((im == null) || !started) {
            return false;
        }
        boolean ok = true;
        try {
            if (!sizeSet) {
                // use first frame's size
                setSize(im.getWidth(), im.getHeight());
            }
            image = im;
            getImagePixels(); // convert to correct format if necessary
            analyzePixels(); // build color table & map pixels
            if (firstFrame) {
                writeLSD(); // logical screen descriptior
                writePalette(); // global color table
                if (repeat >= 0) {
                    // use NS app extension to indicate reps
                    writeNetscapeExt();
                }
            }
            writeGraphicCtrlExt(); // write graphic control extension
            writeImageDesc(); // image descriptor
            if (!firstFrame) {
                writePalette(); // local color table
            }
            writePixels(); // encode and write pixel data
            firstFrame = false;
        } catch (IOException e) {
            ok = false;
        }
        return ok;
    }
Adds next GIF frame.  The frame is not written immediately, but is
actually deferred until the next frame is received so that timing
data can be inserted.  Invoking <code>finish()</code> flushes all
frames.  If <code>setSize</code> was not invoked, the size of the
first image is used for all subsequent frames.

@param im BufferedImage containing frame to write.
@return true if successful.
plugins/base-plugin/src/main/java/com/github/hui/quick/plugin/base/gif/GifEncoder.java