1

I'm trying to display an animated gif inside a java application. So far it displays and animates, but where it's supposed to be either entirely black or entirely white it appears to become transparent and I can see the colour of the rectangle drawn underneath. For example, here's one of the gifs I'm using:

Original gif

On a black background it's not noticeable, however when I change it to cyan:

Gif on top of a cyan background

What's really strange is that it seems like it's displaying correctly for the very first frame and then only messes up afterwards. This is the method I'm using to get and draw the image:

//Draw title
img = new ImageIcon(Display.class.getResource(titlePath)).getImage();
g.drawImage(img, 404, 430, this);

I'm overriding paintcomponent in a class that extends JPanel, and I'm only calling repaint on that class once.

What could be causing this, is it a known issue or am I making a mistake in the way I'm going about actually drawing the image? I haven't been able to find any sort of documentation about this issue.

Thanks in advance!

Here are two of the original gif files.

EDIT: Here's a sample of the code I wrote. I removed any unnecessary code to keep it short but kept everything related to the issue. It also may be worth noting that when I draw a png file (which is one of the frames used to create the gif) with the same method there's no issue. The gif was made on http://gifmaker.me/ in case it's relevant.

public class Display extends JPanel implements Constants{
    private GameData GD;
    private String currentImagePath;

    public Display(GameData gd){
        GD = gd;
        this.setPreferredSize(screenDimension);

        this.repaint();
    }

    public void draw(Location loc){
        currentImagePath = loc.getCurrentImagePath();

        this.repaint();
    }

    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        g.setColor(CYAN);
        g.fillRect(0, 0, screenW, screenH);

        Image img;

        if(OtherWorlds.isOnTitle == true){ //Title screen
            //Draw image
            img = new ImageIcon(Display.class.getResource(currentImagePath)).getImage();
            g.drawImage(img, 10, 10, this);

            //Draw other stuff

        {
        else{ //Regular location

            //Draw image
            img = new ImageIcon(Display.class.getResource(currentImagePath)).getImage();
            g.drawImage(img, 10, 10, this);

            //Other images and stuff in different areas
            //Drawn using the exact same method as above

            }

            //Random other lines and stuff
        }
    }
}
  • Interesting, but I can't reproduce it. It would be helpful to edit your question and include a [minimal, complete example](http://stackoverflow.com/help/mcve) so we can see the rest of what you're doing and compile/run it if need be. – Radiodef Apr 17 '16 at 02:20
  • @Radiodef Thanks for the suggestion, I've edited the question with what I hope is enough of an example to provide some insight. – sirrandalot Apr 17 '16 at 02:49
  • Do you have a link to the original image? – MadProgrammer Apr 17 '16 at 05:11
  • I did not notice your question today, but I really feel with you :-): http://stackoverflow.com/questions/36682135/java-animated-gifs-go-automatically-partially-transparent – Quark Apr 17 '16 at 21:49
  • @Tron Yeah! I just saw yours as well. Weird that we're both having the same issue even though you're using an icon on a JLabel and I'm drawing straight to the component. – sirrandalot Apr 17 '16 at 21:52
  • And I just posted an answer for Tron's question, I'm now moving onto yours – MadProgrammer Apr 17 '16 at 22:32

1 Answers1

1

When ever you have these types of problems, you want to start playing around with the disposalMethod of the frames.

I ran your gif through some inspection code and found the disposalMethod to be set to RESTORE_TO_BACKGROUND

So, basically, I took your gif and ran it through the following code, which created a new gif with the disposalMethod of none

Logo01Logo02

So your original image is on top and the "fixed" image is on the bottom. The background is colored red to highlight the difference

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class MirrorImage {

    public static void main(String[] args) {
        new MirrorImage();
    }

    public MirrorImage() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private ImageIcon orig;
        private ImageIcon mirror;

        public TestPane() {
            mirror(new File("Qzlxj.gif"), new File("Test.gif"));
            orig = new ImageIcon("Qzlxj.gif");
            mirror = new ImageIcon("Test.gif");
        }

        @Override
        public Dimension getPreferredSize() {
            return mirror == null ? new Dimension(200, 200) : new Dimension(orig.getIconWidth(), orig.getIconHeight() * 2);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (orig != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                int x = (getWidth() - orig.getIconWidth()) / 2;
                int y = (getHeight() - (orig.getIconHeight() * 2)) / 2;
                g2d.drawImage(orig.getImage(), x, y, this);

//                AffineTransform at = new AffineTransform();
//                at.setToScale(1, -1);
//                at.translate(0, -mirror.getIconHeight());
//                g2d.setTransform(at);
                g2d.drawImage(mirror.getImage(), x, y + mirror.getIconHeight(), this);
                g2d.dispose();
            }
        }
    }

    public static void mirror(File source, File dest) {

        List<BufferedImage> images = new ArrayList<>(25);
        List<Integer> delays = new ArrayList<>(25);
        int delay = 0;

        ImageOutputStream output = null;
        GifSequenceWriter writer = null;

        try {

            String[] imageatt = new String[]{
                "imageLeftPosition",
                "imageTopPosition",
                "imageWidth",
                "imageHeight"
            };

            ImageReader reader = (ImageReader) ImageIO.getImageReadersByFormatName("gif").next();
            ImageInputStream ciis = ImageIO.createImageInputStream(source);
            reader.setInput(ciis, false);
            int noi = reader.getNumImages(true);
            BufferedImage master = null;

            for (int i = 0; i < noi; i++) {

                BufferedImage image = reader.read(i);
                IIOMetadata metadata = reader.getImageMetadata(i);

                Node tree = metadata.getAsTree("javax_imageio_gif_image_1.0");
                NodeList children = tree.getChildNodes();
                for (int j = 0; j < children.getLength(); j++) {
                    Node nodeItem = children.item(j);
                    System.out.println(nodeItem.getNodeName());
                    if (nodeItem.getNodeName().equals("ImageDescriptor")) {
                        Map<String, Integer> imageAttr = new HashMap<String, Integer>();
                        NamedNodeMap attr = nodeItem.getAttributes();
//                        for (int index = 0; index < attr.getLength(); index++) {
//                            Node node = attr.item(index);
//                            System.out.println("----> " + node.getNodeName() + "=" + node.getNodeValue());
//                        }
                        for (int k = 0; k < imageatt.length; k++) {
                            Node attnode = attr.getNamedItem(imageatt[k]);
                            imageAttr.put(imageatt[k], Integer.valueOf(attnode.getNodeValue()));
                        }

                        if (master == null) {
                            master = new BufferedImage(imageAttr.get("imageWidth"), imageAttr.get("imageHeight"), BufferedImage.TYPE_INT_ARGB);
                        }

                        Graphics2D g2d = master.createGraphics();
                        g2d.drawImage(image, imageAttr.get("imageLeftPosition"), imageAttr.get("imageTopPosition"), null);
                        g2d.dispose();

//                        BufferedImage frame = mirror(copyImage(master));
                        BufferedImage frame = copyImage(master);
                        ImageIO.write(frame, "png", new File("img" + i + ".png"));
                        images.add(frame);

                    } else if (nodeItem.getNodeName().equals("GraphicControlExtension")) {
                        NamedNodeMap attr = nodeItem.getAttributes();
                        Node delayNode = attr.getNamedItem("delayTime");
                        if (delayNode != null) {
                            delay = Math.max(delay, Integer.valueOf(delayNode.getNodeValue()));
                            delays.add(delay);
                        }
                    }
                }

            }

            output = new FileImageOutputStream(dest);
            writer = new GifSequenceWriter(output, images.get(0).getType(), delay * 10, true);

            for (int i = 0; i < images.size(); i++) {
                BufferedImage nextImage = images.get(i);
                writer.writeToSequence(nextImage);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                writer.close();
            } catch (Exception e) {
            }
            try {
                output.close();
            } catch (Exception e) {
            }
        }
    }

    public static BufferedImage mirror(BufferedImage img) {

        BufferedImage mirror = createCompatibleImage(img);
        Graphics2D g2d = mirror.createGraphics();
        AffineTransform at = new AffineTransform();
        at.setToScale(1, -1);
        at.translate(0, -img.getHeight());
        g2d.setTransform(at);
        g2d.drawImage(img, 0, 0, null);
        g2d.dispose();

        return mirror;

    }

    public static BufferedImage copyImage(BufferedImage img) {
        int width = img.getWidth();
        int height = img.getHeight();

        BufferedImage newImage = createCompatibleImage(img);
        Graphics graphics = newImage.createGraphics();

        int x = (width - img.getWidth()) / 2;
        int y = (height - img.getHeight()) / 2;

        graphics.drawImage(img, x, y, img.getWidth(), img.getHeight(), null);
        graphics.dispose();

        return newImage;
    }

    public static BufferedImage createCompatibleImage(BufferedImage image) {
        return getGraphicsConfiguration().createCompatibleImage(image.getWidth(), image.getHeight(), image.getTransparency());
    }

    public static GraphicsConfiguration getGraphicsConfiguration() {
        return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
    }

    public static class GifSequenceWriter {

        protected ImageWriter gifWriter;
        protected ImageWriteParam imageWriteParam;
        protected IIOMetadata imageMetaData;

        /**
         * Creates a new GifSequenceWriter
         *
         * @param outputStream the ImageOutputStream to be written to
         * @param imageType one of the imageTypes specified in BufferedImage
         * @param timeBetweenFramesMS the time between frames in miliseconds
         * @param loopContinuously wether the gif should loop repeatedly
         * @throws IIOException if no gif ImageWriters are found
         *
         * @author Elliot Kroo (elliot[at]kroo[dot]net)
         */
        public GifSequenceWriter(
                ImageOutputStream outputStream,
                int imageType,
                int timeBetweenFramesMS,
                boolean loopContinuously) throws IIOException, IOException {
            // my method to create a writer
            gifWriter = getWriter();
            imageWriteParam = gifWriter.getDefaultWriteParam();
            ImageTypeSpecifier imageTypeSpecifier
                    = ImageTypeSpecifier.createFromBufferedImageType(imageType);

            imageMetaData
                    = gifWriter.getDefaultImageMetadata(imageTypeSpecifier,
                            imageWriteParam);

            String metaFormatName = imageMetaData.getNativeMetadataFormatName();

            IIOMetadataNode root = (IIOMetadataNode) imageMetaData.getAsTree(metaFormatName);

            IIOMetadataNode graphicsControlExtensionNode = getNode(
                    root,
                    "GraphicControlExtension");

            //restoreToBackgroundColor
            //restoreToPrevious
            graphicsControlExtensionNode.setAttribute("disposalMethod", "none");
            graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE");
            graphicsControlExtensionNode.setAttribute(
                    "transparentColorFlag",
                    "FALSE");
            graphicsControlExtensionNode.setAttribute(
                    "delayTime",
                    Integer.toString(timeBetweenFramesMS / 10));
            graphicsControlExtensionNode.setAttribute(
                    "transparentColorIndex",
                    "0");

            IIOMetadataNode commentsNode = getNode(root, "CommentExtensions");
            commentsNode.setAttribute("CommentExtension", "Created by MAH");

            IIOMetadataNode appEntensionsNode = getNode(
                    root,
                    "ApplicationExtensions");

            IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension");

            child.setAttribute("applicationID", "NETSCAPE");
            child.setAttribute("authenticationCode", "2.0");

            int loop = loopContinuously ? 0 : 1;

            child.setUserObject(new byte[]{0x1, (byte) (loop & 0xFF), (byte) ((loop >> 8) & 0xFF)});
            appEntensionsNode.appendChild(child);

            imageMetaData.setFromTree(metaFormatName, root);

            gifWriter.setOutput(outputStream);

            gifWriter.prepareWriteSequence(null);
        }

        public void writeToSequence(RenderedImage img) throws IOException {
            gifWriter.writeToSequence(
                    new IIOImage(
                            img,
                            null,
                            imageMetaData),
                    imageWriteParam);
        }

        /**
         * Close this GifSequenceWriter object. This does not close the
         * underlying stream, just finishes off the GIF.
         */
        public void close() throws IOException {
            gifWriter.endWriteSequence();
        }

        /**
         * Returns the first available GIF ImageWriter using
         * ImageIO.getImageWritersBySuffix("gif").
         *
         * @return a GIF ImageWriter object
         * @throws IIOException if no GIF image writers are returned
         */
        private static ImageWriter getWriter() throws IIOException {
            Iterator<ImageWriter> iter = ImageIO.getImageWritersBySuffix("gif");
            if (!iter.hasNext()) {
                throw new IIOException("No GIF Image Writers Exist");
            } else {
                return iter.next();
            }
        }

        /**
         * Returns an existing child node, or creates and returns a new child
         * node (if the requested node does not exist).
         *
         * @param rootNode the <tt>IIOMetadataNode</tt> to search for the child
         * node.
         * @param nodeName the name of the child node.
         *
         * @return the child node, if found or a new node created with the given
         * name.
         */
        private static IIOMetadataNode getNode(
                IIOMetadataNode rootNode,
                String nodeName) {
            int nNodes = rootNode.getLength();
            for (int i = 0; i < nNodes; i++) {
                if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName)
                        == 0) {
                    return ((IIOMetadataNode) rootNode.item(i));
                }
            }
            IIOMetadataNode node = new IIOMetadataNode(nodeName);
            rootNode.appendChild(node);
            return (node);
        }
    }
}

And finally, the "fixed" gif

Fixed01Fixed02

The above is based on the investigations from Mirroring animated gif on load in Java - ImageIcon

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thanks a ton! I'll try integrating something similar into my code as soon as I can and let you know if there are any issues! – sirrandalot Apr 17 '16 at 22:54
  • Alright so I've read over the code and I'm a little confused as to how much of it is necessary. I'm assuming that there's no easy way to change the `disposalMethod`? – sirrandalot Apr 20 '16 at 01:13
  • The code I've posted was used to "fix" the `disposalMethod` and display the results. So if you don't need to modify the `disposalMethod`, you won't need it, but I've provided it so you can play around with it. Deep down in the bowels of the code you will find `graphicsControlExtensionNode.setAttribute("disposalMethod", "none");`, which used to modify the `disposalMethod`, should you need it. You should be able to just download the two last gifs, as they are the result of the code – MadProgrammer Apr 20 '16 at 01:17
  • Fantastic, thanks! Marking your answer as correct, although in the future I think I'll be more cautious about the settings when creating gifs in the first place and make sure the disposal method is correct right off the bat. – sirrandalot Apr 20 '16 at 01:43