Java GIF 动画未正确重绘

Posted

技术标签:

【中文标题】Java GIF 动画未正确重绘【英文标题】:Java GIF animation not repainting correctly 【发布时间】:2014-12-07 11:03:33 【问题描述】:

我正在尝试为 GIF 图像制作动画。动画效果很好,但画得不好。

显示如下(非动画截图):

在图片中,尾巴像这样摇摆:

如您所见,图像重绘效果不佳。我不想使用 JLabels 但它不能正常工作,所以我关注了this question when my image did not animate。

我的代码是这样的:

public void draw(JPanel canvas, Graphics2D g2d, int x, int y) 
    getFrontImage().paintIcon(canvas, g2d, x, y);

图像的检索和保存位置如下:

ImageIcon gif = new ImageIcon(getClass().getResource(filename));

在 JPanel 画布中,我创建了一个绘制方法和一个每 10 毫秒重新绘制一次的计时器线程。这适用于除 GIF 之外的所有内容。谁能帮我解决这个问题?

--- 编辑

对于造成的误解,我深表歉意,我已将图像更新为我实际使用的图像。我希望得到正确答案不会太麻烦......

【问题讨论】:

如果没有更多的conetxt,我会说你已经破坏了油漆链并且你得到了没有被清除的“旧”帧。考虑提供一个runnable example 来证明您的问题。这将导致更少的混乱和更好的响应 其实玩了一下,好像dipsoseMethod没有设置成clear “非动画截图”看起来所有动画帧都相互堆叠(看看移动最多的耳朵和尾巴是如何变得更大的)。鼠兔! 另见working example of loading/using an animated GIF。 我无法正确描述发生的事情,感谢 user1803551 击中了它的头:-) 【参考方案1】:

好的,经过一番折腾,我终于可以将框架的处理方法更改为restoreToBackgroundColor。基本上,这意味着动画不是增量变化,而是完整的帧替换……

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class AnimatedGifTest1 

    public static void main(String[] args) 
        new AnimatedGifTest1();
    

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

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new PaintPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            
        );
    

    public class PaintPane extends JPanel 

        private ImageIcon image;

        public PaintPane() 
            image = new ImageIcon(getClass().getResource("/ertcM02.gif"));
            Timer timer = new Timer(40, new ActionListener() 
                @Override
                public void actionPerformed(ActionEvent e) 
                    repaint();
                
            );
            timer.start();
        

        @Override
        public Dimension getPreferredSize() 
            return image == null ? new Dimension(200, 200) : new Dimension(image.getIconWidth(), image.getIconHeight());
        

        @Override
        protected void paintComponent(Graphics g) 
            super.paintComponent(g); // This is very important!
            int x = (getWidth() - image.getIconWidth()) / 2;
            int y = (getHeight() - image.getIconHeight()) / 2;
            image.paintIcon(this, g, x, y);
        

    




更新...

所以,我终于可以看一下你正在使用的gif的处理方法,设置为restoreToPrevious,根据GRAPHICS INTERCHANGE FORMAT Version 89a的意思是:

恢复到以前。解码器需要将被图形覆盖的区域恢复为渲染图形之前的区域。

我上面提供的图片使用restoreToBackgroundColor,根据GRAPHICS INTERCHANGE FORMAT Version 89a的意思是:

恢复为背景颜色。图形使用的区域必须恢复为背景色

您可以使用以下代码自行检查...

public static class AnimatedGif 

    public enum DisposalMethod 

        RESTORE_TO_BACKGROUND,
        RESTORE_TO_PREVIOUS,
        DO_NOT_DISPOSE,
        UNSPECIFIED;

        public static DisposalMethod find(String text) 

            DisposalMethod dm = UNSPECIFIED;
            System.out.println(text);

            switch (text) 
                case "restoreToBackgroundColor":
                    dm = RESTORE_TO_BACKGROUND;
                    break;
                case "restoreToPrevious":
                    dm = RESTORE_TO_PREVIOUS;
                    break;
            

            return dm;

        
    

    private List<ImageFrame> frames;
    private int frame;

    public AnimatedGif(JComponent player, URL url) throws IOException 
        frames = new ArrayList<>(25);
        try (InputStream is = url.openStream(); ImageInputStream stream = ImageIO.createImageInputStream(is)) 
            Iterator readers = ImageIO.getImageReaders(stream);
            if (!readers.hasNext()) 
                throw new RuntimeException("no image reader found");
            
            ImageReader reader = (ImageReader) readers.next();
            reader.setInput(stream);            // don't omit this line!
            int n = reader.getNumImages(true);  // don't use false!
            System.out.println("numImages = " + n);
            for (int i = 0; i < n; i++) 
                BufferedImage image = reader.read(i);
                ImageFrame imageFrame = new ImageFrame(image);

                IIOMetadata imd = reader.getImageMetadata(i);
                Node tree = imd.getAsTree("javax_imageio_gif_image_1.0");
                NodeList children = tree.getChildNodes();

                for (int j = 0; j < children.getLength(); j++) 
                    Node nodeItem = children.item(j);
                    NamedNodeMap attr = nodeItem.getAttributes();
                    switch (nodeItem.getNodeName()) 
                        case "ImageDescriptor":
                            ImageDescriptor id = new ImageDescriptor(
                                            getIntValue(attr.getNamedItem("imageLeftPosition")),
                                            getIntValue(attr.getNamedItem("imageTopPosition")),
                                            getIntValue(attr.getNamedItem("imageWidth")),
                                            getIntValue(attr.getNamedItem("imageHeight")),
                                            getBooleanValue(attr.getNamedItem("interlaceFlag")));
                            imageFrame.setImageDescriptor(id);
                            break;
                        case "GraphicControlExtension":
                            GraphicControlExtension gc = new GraphicControlExtension(
                                            DisposalMethod.find(getNodeValue(attr.getNamedItem("disposalMethod"))),
                                            getBooleanValue(attr.getNamedItem("userInputFlag")),
                                            getBooleanValue(attr.getNamedItem("transparentColorFlag")),
                                            getIntValue(attr.getNamedItem("delayTime")) * 10,
                                            getIntValue(attr.getNamedItem("transparentColorIndex")));
                            imageFrame.setGraphicControlExtension(gc);
                            break;
                    
                
                frames.add(imageFrame);
            
         finally 
        
    

    protected String getNodeValue(Node node) 
        return node == null ? null : node.getNodeValue();
    

    protected int getIntValue(Node node) 
        return node == null ? 0 : getIntValue(node.getNodeValue());
    

    protected boolean getBooleanValue(Node node) 
        return node == null ? false : getBooleanValue(node.getNodeValue());
    

    protected int getIntValue(String value) 
        return value == null ? 0 : Integer.parseInt(value);
    

    protected boolean getBooleanValue(String value) 
        return value == null ? false : Boolean.parseBoolean(value);
    

    public class ImageFrame 

        private BufferedImage image;
        private ImageDescriptor imageDescriptor;
        private GraphicControlExtension graphicControlExtension;

        public ImageFrame(BufferedImage image) 
            this.image = image;
        

        protected void setImageDescriptor(ImageDescriptor imageDescriptor) 
            this.imageDescriptor = imageDescriptor;
        

        protected void setGraphicControlExtension(GraphicControlExtension graphicControlExtension) 
            this.graphicControlExtension = graphicControlExtension;
            System.out.println(graphicControlExtension.getDisposalMethod());
        

        public GraphicControlExtension getGraphicControlExtension() 
            return graphicControlExtension;
        

        public BufferedImage getImage() 
            return image;
        

        public ImageDescriptor getImageDescriptor() 
            return imageDescriptor;
        

    

    public class GraphicControlExtension 

        private DisposalMethod disposalMethod;
        private boolean userInputFlag;
        private boolean transparentColorFlag;
        private int delayTime;
        private int transparentColorIndex;

        public GraphicControlExtension(DisposalMethod disposalMethod, boolean userInputFlag, boolean transparentColorFlag, int delayTime, int transparentColorIndex) 
            this.disposalMethod = disposalMethod;
            this.userInputFlag = userInputFlag;
            this.transparentColorFlag = transparentColorFlag;
            this.delayTime = delayTime;
            this.transparentColorIndex = transparentColorIndex;
        

        public int getDelayTime() 
            return delayTime;
        

        public DisposalMethod getDisposalMethod() 
            return disposalMethod;
        

        public int getTransparentColorIndex() 
            return transparentColorIndex;
        

        public boolean isTransparentColorFlag() 
            return transparentColorFlag;
        

        public boolean isUserInputFlag() 
            return userInputFlag;
        

    

    public class ImageDescriptor 

        private int imageLeftPosition;
        private int imageTopPosition;
        private int imageHeight;
        private int imageWeight;
        private boolean interlaced;

        public ImageDescriptor(int imageLeftPosition, int imageTopPosition, int imageHeight, int imageWeight, boolean interlaced) 
            this.imageLeftPosition = imageLeftPosition;
            this.imageTopPosition = imageTopPosition;
            this.imageHeight = imageHeight;
            this.imageWeight = imageWeight;
            this.interlaced = interlaced;
        

        public int getImageHeight() 
            return imageHeight;
        

        public int getImageLeftPosition() 
            return imageLeftPosition;
        

        public int getImageTopPosition() 
            return imageTopPosition;
        

        public int getImageWeight() 
            return imageWeight;
        

        public boolean isInterlaced() 
            return interlaced;
        

    


此代码来自.gif image doesn't moves on adding it to the JTabbed pane

【讨论】:

只有当 GIF 包含任何透明度时才需要这样做吗? (我猜“是”,但它可能取决于整个渲染管道——例如,双缓冲也会破坏非透明动画。) 我会说它总是很重要。恢复背景基本会清除当前帧并绘制下一帧,否则它将逐步组合每个帧,这就是您所看到的。如果每一帧都是完整的、独立的图像,那么你需要使用“dispose”,如果下一帧“添加”到最后一帧,那么你就不要... 我对“将框架的处理方法更改为restoreToBackgroundColor”感到困惑。不就是打电话super.paint吗? 不,这是 GIF 格式的基本要求,你有两种基本形式的 GIF,你有一种“渐进式”风格,每一帧都连接到最后一帧来构建动画,这是高度优化,因为只包括帧之间的变化,或者在你的情况下,每个帧都是一个独立的单元格/帧(很像电影中的帧)...... 在摆弄了你的代码之后,我发现我已经做得很好了。我只是不知道有不同种类的 GIF。我给出的问题图像不是我使用的图像。我从互联网上得到了一个,以为它们会是一样的。对不起,我浪费了您的时间,但我添加了我使用的图像。

以上是关于Java GIF 动画未正确重绘的主要内容,如果未能解决你的问题,请参考以下文章

Java 重绘未正确显示组件

vue基本知识点

单击链接 href 后,pie.htc 样式的 Div 未正确重绘

随着窗口变小,OSX 自动布局无法重绘某些控件

UIPickerView 不会立即重绘未选中的行

将 Python 海龟图形保存为动画 .gif?