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 动画未正确重绘的主要内容,如果未能解决你的问题,请参考以下文章