Java:动画 GIF 会自动部分透明

Posted

技术标签:

【中文标题】Java:动画 GIF 会自动部分透明【英文标题】:Java: Animated GIFs go automatically partially transparent 【发布时间】:2016-08-09 11:48:16 【问题描述】:

我想使用 Java Swing 显示一个带有动画 GIF 的等待对话框,但 GIF 显示不正确。

代码示例:

final JDialog progressDialog = new JDialog(new Frame(), "...", true);

URL url =  getClass().getResource("wait.gif");
Icon icon = new ImageIcon(url);
JLabel label = new JLabel(icon);

progressDialog.getContentPane().setBackground(new Color (0, 200, 200));
progressDialog.getContentPane().add(label);
progressDialog.pack();
progressDialog.setVisible(true);

原始 GIF 在这里:

但 Java 输出如下所示:

有什么想法可能是错的吗?

【问题讨论】:

也发布了here,可能是同样的问题。我无法重现它,但由于我另一台计算机的硬件问题,我现在被困在 Java 6 上。我对另一张海报也说过同样的话,MCVE 非常有帮助。 ***.com/help/mcve 另见例如***.com/a/4694343/2891664。再次,MCVE,非常有帮助。 @Radiodef 感谢您的帖子,我没有注意到它(同样的问题,今天......难以置信)。 @Radiodef ***.com/a/4694343/2891664 是一个有点不同的问题(透明区域是黑色的,它是静态 GIF)。这里的黑色区域是透明的(不仅黑色会受到影响,如果 GIF 是红色的,那么“纯红色”也是透明的)。标准静态 GIF 以及动画 GIF 的第一帧都能正确显示。 是的,这很公平;但是,链接的部分要点是说明 MCVE 中的值。我无法重现这个问题,但我必须编写自己的程序来测试它,也许你和其他发帖人(从未发布过 MCVE)正在做一些不同的事情。如果我仍然无法重现它,MCVE 还可以为任何其他看到您的问题的 Swing 人员节省时间。 【参考方案1】:

当您遇到这些类型的问题时,您想开始使用帧的disposalMethod

我通过一些检查代码运行了您的 gif,发现 disposalMethod 设置为 RESTORE_TO_BACKGROUND

所以,基本上,我拿了你的 gif 并通过以下代码运行它,它使用 disposalMethodnone 创建了一个新的 gif

所以你的原始图像在顶部,“固定”图像在底部

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);
        
    

最后是“固定”的 gif

以上内容来自Mirroring animated gif on load in Java - ImageIcon的调查

【讨论】:

以上是关于Java:动画 GIF 会自动部分透明的主要内容,如果未能解决你的问题,请参考以下文章

如何在java gui上将一张图片的某些部分变为透明的

使用CSS3 animation模拟gif动画,解决部分浏览器gif图卡顿问题

使用imagemagick的动画gif中的多种透明颜色?

Java:在其他动画 gif 上叠加/覆盖动画 gif

如何确定动画 GIF 帧中透明像素的数量?

将裁剪蒙版应用于具有透明背景的动画 gif