1

In trying to make an animated GIF from an image with transparency this code is producing a ghosting effect from the earlier image frames.

enter image description here Image of 3rd frame, which shows the first two frames beneath the final frame

How can the code be altered to produce a 'clean' animated image for all frames?

Note: The code uses two methods, written by GeoffTitmus & Maxideon, that were formerly on the Oracle forums site. The links I first saw referenced in the code (in the JavaDocs section) have long since disappeared, so were removed. Other than that, only a few JavaDoc warnings were corrected, otherwise they are exactly as found from around the internet or as used in my older code projects.

import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.io.*;
import javax.imageio.*;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.stream.*;
import javax.imageio.metadata.*;
import org.w3c.dom.Node;
import java.net.URL;

public class AnimatedGifWithTransparency {

    private JComponent ui = null;
    public final String PATH = "https://i.stack.imgur.com/P59NF.png";
    private BufferedImage image;

    AnimatedGifWithTransparency() {
        try {
            initUI();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * @author GeoffTitmus
     * @param out OutputStream A stream in which to store the animation.
     * @param frames BufferedImage[] Array of BufferedImages, the frames of the
     * animation.
     * @param delayTimes String[] Array of Strings, representing the frame delay
     * times.
     * @throws Exception
     */
    public static void saveAnimate(OutputStream out, BufferedImage[] frames, String[] delayTimes) throws Exception {

        ImageWriter iw = ImageIO.getImageWritersByFormatName("gif").next();

        ImageOutputStream ios = ImageIO.createImageOutputStream(out);
        iw.setOutput(ios);
        iw.prepareWriteSequence(null);

        for (int i = 0; i < frames.length; i++) {
            BufferedImage src = frames[i];
            ImageWriteParam iwp = iw.getDefaultWriteParam();
            IIOMetadata metadata = iw.getDefaultImageMetadata(new ImageTypeSpecifier(src), iwp);

            configure(metadata, delayTimes[i], i);

            IIOImage ii = new IIOImage(src, null, metadata);
            iw.writeToSequence(ii, null);
        }

        iw.endWriteSequence();
        ios.close();
    }

    /**
     * @author Maxideon
     * @param meta
     * @param delayTime String Frame delay for this frame.
     * @param imageIndex
     */
    public static void configure(IIOMetadata meta,
            String delayTime,
            int imageIndex) {

        String metaFormat = meta.getNativeMetadataFormatName();

        if (!"javax_imageio_gif_image_1.0".equals(metaFormat)) {
            throw new IllegalArgumentException(
                    "Unfamiliar gif metadata format: " + metaFormat);
        }

        Node root = meta.getAsTree(metaFormat);

        //find the GraphicControlExtension node
        Node child = root.getFirstChild();
        while (child != null) {
            if ("GraphicControlExtension".equals(child.getNodeName())) {
                break;
            }
            child = child.getNextSibling();
        }

        IIOMetadataNode gce = (IIOMetadataNode) child;
        gce.setAttribute("userDelay", "FALSE");
        gce.setAttribute("delayTime", delayTime);

        //only the first node needs the ApplicationExtensions node
        if (imageIndex == 0) {
            IIOMetadataNode aes
                    = new IIOMetadataNode("ApplicationExtensions");
            IIOMetadataNode ae
                    = new IIOMetadataNode("ApplicationExtension");
            ae.setAttribute("applicationID", "NETSCAPE");
            ae.setAttribute("authenticationCode", "2.0");
            byte[] uo = new byte[]{
                //last two bytes is an unsigned short (little endian) that
                //indicates the the number of times to loop.
                //0 means loop forever.
                0x1, 0x0, 0x0
            };
            ae.setUserObject(uo);
            aes.appendChild(ae);
            root.appendChild(aes);
        }

        try {
            meta.setFromTree(metaFormat, root);
        } catch (IIOInvalidTreeException e) {
            //shouldn't happen
            throw new Error(e);
        }
    }

    private BufferedImage getShiftedImage(int frame) {
        int w = image.getWidth();
        int h = image.getHeight();
        BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        Graphics g = bi.getGraphics();

        int xOffPos = frame*w/3;
        int xOffNeg = xOffPos-w;
        g.drawImage(image, xOffNeg, 0, ui);
        g.drawImage(image, xOffPos, 0, ui);

        g.dispose();
        return bi;
    }

    public final void initUI() throws IOException {
        if (ui != null) {
            return;
        }
        image = ImageIO.read(new URL(PATH));

        ui = new JPanel(new BorderLayout(4, 4));
        ui.setBorder(new EmptyBorder(4, 4, 4, 4));

        BufferedImage[] frames = {
            getShiftedImage(0),
            getShiftedImage(1),
            getShiftedImage(2)
        };

        String[] delayTimes = {
            "50","50","50"
        };
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            saveAnimate(baos, frames, delayTimes);
        } catch (Exception ex) {
            ex.printStackTrace();
            return;
        }
        byte[] bytes = baos.toByteArray();
        ui.add(new JLabel(new ImageIcon(bytes)));
    }

    public JComponent getUI() {
        return ui;
    }

    public static void main(String[] args) {
        Runnable r = () -> {
            AnimatedGifWithTransparency o = new AnimatedGifWithTransparency();

            JFrame f = new JFrame(o.getClass().getSimpleName());
            f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            f.setLocationByPlatform(true);

            f.setContentPane(o.getUI());
            f.pack();
            f.setMinimumSize(f.getSize());

            f.setVisible(true);
        };
        SwingUtilities.invokeLater(r);
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • 1
    I suspect you need to change the "dispose method" for each frame - which is demonstrated [here](https://stackoverflow.com/questions/26330550/java-gif-animation-not-repainting-correctly/26331052#26331052) and [here](https://stackoverflow.com/questions/18117283/mirroring-animated-gif-on-load-in-java-imageicon/18117326#18117326) – MadProgrammer Mar 19 '18 at 23:23
  • 1
    and [here](https://stackoverflow.com/questions/36682135/java-animated-gifs-go-automatically-partially-transparent/36682907#36682907) – MadProgrammer Mar 19 '18 at 23:29
  • Thanks @MadProgrammer. So far I've failed to adapt in the changes that will work for this code, but I'll keep slogging on & see if I can make it work. – Andrew Thompson Mar 20 '18 at 02:04
  • [See also](https://stackoverflow.com/questions/51918486/the-part-frames-of-a-gif-image-which-is-created-by-using-animatedgifencoder-clas) .. – Andrew Thompson Aug 19 '18 at 15:37

0 Answers0