将每个动画GIF帧转换为单独的BufferedImage

我希望能够将动画GIF作为输入,计算帧(以及可能的其他元数据),并将每个转换为BufferedImage 。 我怎样才能做到这一点?

如果您希望所有帧都具有相同的大小(对于优化的GIF),请尝试以下方法:

 try { String[] imageatt = new String[]{ "imageLeftPosition", "imageTopPosition", "imageWidth", "imageHeight" }; ImageReader reader = (ImageReader)ImageIO.getImageReadersByFormatName("gif").next(); ImageInputStream ciis = ImageIO.createImageInputStream(new File("house2.gif")); 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); if(nodeItem.getNodeName().equals("ImageDescriptor")){ Map imageAttr = new HashMap(); for (int k = 0; k < imageatt.length; k++) { NamedNodeMap attr = nodeItem.getAttributes(); Node attnode = attr.getNamedItem(imageatt[k]); imageAttr.put(imageatt[k], Integer.valueOf(attnode.getNodeValue())); } if(i==0){ master = new BufferedImage(imageAttr.get("imageWidth"), imageAttr.get("imageHeight"), BufferedImage.TYPE_INT_ARGB); } master.getGraphics().drawImage(image, imageAttr.get("imageLeftPosition"), imageAttr.get("imageTopPosition"), null); } } ImageIO.write(master, "GIF", new File( i + ".gif")); } } catch (IOException e) { e.printStackTrace(); } 

这里没有一个答案是正确的,适合动画。 每个解决方案都有很多问题,所以我写了一些实际上适用于所有gif文件的东西。 例如,这考虑了图像的实际宽度和高度,而不是考虑第一帧的宽度和高度,假设它将填满整个canvas,不,不幸的是,它不是那么简单。 其次,这不会留下任何透明的泡菜。 第三,这考虑了处理方法。 第四,这给你帧之间的延迟(* 10如果你想在Thread.sleep()中使用它)。

 private ImageFrame[] readGif(InputStream stream) throws IOException{ ArrayList frames = new ArrayList(2); ImageReader reader = (ImageReader) ImageIO.getImageReadersByFormatName("gif").next(); reader.setInput(ImageIO.createImageInputStream(stream)); int lastx = 0; int lasty = 0; int width = -1; int height = -1; IIOMetadata metadata = reader.getStreamMetadata(); Color backgroundColor = null; if(metadata != null) { IIOMetadataNode globalRoot = (IIOMetadataNode) metadata.getAsTree(metadata.getNativeMetadataFormatName()); NodeList globalColorTable = globalRoot.getElementsByTagName("GlobalColorTable"); NodeList globalScreeDescriptor = globalRoot.getElementsByTagName("LogicalScreenDescriptor"); if (globalScreeDescriptor != null && globalScreeDescriptor.getLength() > 0){ IIOMetadataNode screenDescriptor = (IIOMetadataNode) globalScreeDescriptor.item(0); if (screenDescriptor != null){ width = Integer.parseInt(screenDescriptor.getAttribute("logicalScreenWidth")); height = Integer.parseInt(screenDescriptor.getAttribute("logicalScreenHeight")); } } if (globalColorTable != null && globalColorTable.getLength() > 0){ IIOMetadataNode colorTable = (IIOMetadataNode) globalColorTable.item(0); if (colorTable != null) { String bgIndex = colorTable.getAttribute("backgroundColorIndex"); IIOMetadataNode colorEntry = (IIOMetadataNode) colorTable.getFirstChild(); while (colorEntry != null) { if (colorEntry.getAttribute("index").equals(bgIndex)) { int red = Integer.parseInt(colorEntry.getAttribute("red")); int green = Integer.parseInt(colorEntry.getAttribute("green")); int blue = Integer.parseInt(colorEntry.getAttribute("blue")); backgroundColor = new Color(red, green, blue); break; } colorEntry = (IIOMetadataNode) colorEntry.getNextSibling(); } } } } BufferedImage master = null; boolean hasBackround = false; for (int frameIndex = 0;; frameIndex++) { BufferedImage image; try{ image = reader.read(frameIndex); }catch (IndexOutOfBoundsException io){ break; } if (width == -1 || height == -1){ width = image.getWidth(); height = image.getHeight(); } IIOMetadataNode root = (IIOMetadataNode) reader.getImageMetadata(frameIndex).getAsTree("javax_imageio_gif_image_1.0"); IIOMetadataNode gce = (IIOMetadataNode) root.getElementsByTagName("GraphicControlExtension").item(0); NodeList children = root.getChildNodes(); int delay = Integer.valueOf(gce.getAttribute("delayTime")); String disposal = gce.getAttribute("disposalMethod"); if (master == null){ master = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); master.createGraphics().setColor(backgroundColor); master.createGraphics().fillRect(0, 0, master.getWidth(), master.getHeight()); hasBackround = image.getWidth() == width && image.getHeight() == height; master.createGraphics().drawImage(image, 0, 0, null); }else{ int x = 0; int y = 0; for (int nodeIndex = 0; nodeIndex < children.getLength(); nodeIndex++){ Node nodeItem = children.item(nodeIndex); if (nodeItem.getNodeName().equals("ImageDescriptor")){ NamedNodeMap map = nodeItem.getAttributes(); x = Integer.valueOf(map.getNamedItem("imageLeftPosition").getNodeValue()); y = Integer.valueOf(map.getNamedItem("imageTopPosition").getNodeValue()); } } if (disposal.equals("restoreToPrevious")){ BufferedImage from = null; for (int i = frameIndex - 1; i >= 0; i--){ if (!frames.get(i).getDisposal().equals("restoreToPrevious") || frameIndex == 0){ from = frames.get(i).getImage(); break; } } { ColorModel model = from.getColorModel(); boolean alpha = from.isAlphaPremultiplied(); WritableRaster raster = from.copyData(null); master = new BufferedImage(model, raster, alpha, null); } }else if (disposal.equals("restoreToBackgroundColor") && backgroundColor != null){ if (!hasBackround || frameIndex > 1){ master.createGraphics().fillRect(lastx, lasty, frames.get(frameIndex - 1).getWidth(), frames.get(frameIndex - 1).getHeight()); } } master.createGraphics().drawImage(image, x, y, null); lastx = x; lasty = y; } { BufferedImage copy; { ColorModel model = master.getColorModel(); boolean alpha = master.isAlphaPremultiplied(); WritableRaster raster = master.copyData(null); copy = new BufferedImage(model, raster, alpha, null); } frames.add(new ImageFrame(copy, delay, disposal, image.getWidth(), image.getHeight())); } master.flush(); } reader.dispose(); return frames.toArray(new ImageFrame[frames.size()]); } 

和ImageFrame类:

 import java.awt.image.BufferedImage; public class ImageFrame { private final int delay; private final BufferedImage image; private final String disposal; private final int width, height; public ImageFrame (BufferedImage image, int delay, String disposal, int width, int height){ this.image = image; this.delay = delay; this.disposal = disposal; this.width = width; this.height = height; } public ImageFrame (BufferedImage image){ this.image = image; this.delay = -1; this.disposal = null; this.width = -1; this.height = -1; } public BufferedImage getImage() { return image; } public int getDelay() { return delay; } public String getDisposal() { return disposal; } public int getWidth() { return width; } public int getHeight() { return height; } } 

是的,我之前从未做过任何有点像这样的事情,但谷歌的一些谷歌搜索和摆弄让我这样:

 public ArrayList getFrames(File gif) throws IOException{ ArrayList frames = new ArrayList(); ImageReader ir = new GIFImageReader(new GIFImageReaderSpi()); ir.setInput(ImageIO.createImageInputStream(gif)); for(int i = 0; i < ir.getNumImages(true); i++) frames.add(ir.getRawImageType(i).createBufferedImage(ir.getWidth(i), ir.getHeight(i))); return frames; } 

编辑:请参阅Ansel Zandegran对我的回答的修改 。

要将动画GIF拆分为单独的BufferedImage帧:

 try { ImageReader reader = ImageIO.getImageReadersByFormatName("gif").next(); File input = new File("input.gif"); ImageInputStream stream = ImageIO.createImageInputStream(input); reader.setInput(stream); int count = reader.getNumImages(true); for (int index = 0; index < count; index++) { BufferedImage frame = reader.read(index); // Here you go } } catch (IOException ex) { // An I/O problem has occurred } 

亚历克斯的答案涵盖了大多数情况,但确实存在一些问题。 它没有正确处理透明度(至少根据普通惯例)并且它正在将当前帧的处理方法应用于前一帧,这是不正确的。 这是一个正确处理这些案例的版本:

 private ImageFrame[] readGIF(ImageReader reader) throws IOException { ArrayList frames = new ArrayList(2); int width = -1; int height = -1; IIOMetadata metadata = reader.getStreamMetadata(); if (metadata != null) { IIOMetadataNode globalRoot = (IIOMetadataNode) metadata.getAsTree(metadata.getNativeMetadataFormatName()); NodeList globalScreenDescriptor = globalRoot.getElementsByTagName("LogicalScreenDescriptor"); if (globalScreenDescriptor != null && globalScreenDescriptor.getLength() > 0) { IIOMetadataNode screenDescriptor = (IIOMetadataNode) globalScreenDescriptor.item(0); if (screenDescriptor != null) { width = Integer.parseInt(screenDescriptor.getAttribute("logicalScreenWidth")); height = Integer.parseInt(screenDescriptor.getAttribute("logicalScreenHeight")); } } } BufferedImage master = null; Graphics2D masterGraphics = null; for (int frameIndex = 0;; frameIndex++) { BufferedImage image; try { image = reader.read(frameIndex); } catch (IndexOutOfBoundsException io) { break; } if (width == -1 || height == -1) { width = image.getWidth(); height = image.getHeight(); } IIOMetadataNode root = (IIOMetadataNode) reader.getImageMetadata(frameIndex).getAsTree("javax_imageio_gif_image_1.0"); IIOMetadataNode gce = (IIOMetadataNode) root.getElementsByTagName("GraphicControlExtension").item(0); int delay = Integer.valueOf(gce.getAttribute("delayTime")); String disposal = gce.getAttribute("disposalMethod"); int x = 0; int y = 0; if (master == null) { master = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); masterGraphics = master.createGraphics(); masterGraphics.setBackground(new Color(0, 0, 0, 0)); } else { NodeList children = root.getChildNodes(); for (int nodeIndex = 0; nodeIndex < children.getLength(); nodeIndex++) { Node nodeItem = children.item(nodeIndex); if (nodeItem.getNodeName().equals("ImageDescriptor")) { NamedNodeMap map = nodeItem.getAttributes(); x = Integer.valueOf(map.getNamedItem("imageLeftPosition").getNodeValue()); y = Integer.valueOf(map.getNamedItem("imageTopPosition").getNodeValue()); } } } masterGraphics.drawImage(image, x, y, null); BufferedImage copy = new BufferedImage(master.getColorModel(), master.copyData(null), master.isAlphaPremultiplied(), null); frames.add(new ImageFrame(copy, delay, disposal)); if (disposal.equals("restoreToPrevious")) { BufferedImage from = null; for (int i = frameIndex - 1; i >= 0; i--) { if (!frames.get(i).getDisposal().equals("restoreToPrevious") || frameIndex == 0) { from = frames.get(i).getImage(); break; } } master = new BufferedImage(from.getColorModel(), from.copyData(null), from.isAlphaPremultiplied(), null); masterGraphics = master.createGraphics(); masterGraphics.setBackground(new Color(0, 0, 0, 0)); } else if (disposal.equals("restoreToBackgroundColor")) { masterGraphics.clearRect(x, y, image.getWidth(), image.getHeight()); } } reader.dispose(); return frames.toArray(new ImageFrame[frames.size()]); } private class ImageFrame { private final int delay; private final BufferedImage image; private final String disposal; public ImageFrame(BufferedImage image, int delay, String disposal) { this.image = image; this.delay = delay; this.disposal = disposal; } public BufferedImage getImage() { return image; } public int getDelay() { return delay; } public String getDisposal() { return disposal; } } 

在这个ImageMagick教程中有一个很好的描述GIF动画的工作原理。

使用c24w的解决方案 ,替换:

 frames.add(ir.getRawImageType(i).createBufferedImage(ir.getWidth(i), ir.getHeight(i))); 

附:

 frames.add(ir.read(i)); 

我自己编写了一个GIF图像解码器,并在GitHub上的Apache License 2.0下发布了它。 你可以在这里下载: https : //github.com/DhyanB/Open-Imaging 。 用法示例:

 void example(final byte[] data) throws Exception { final GifImage gif = GifDecoder .read(data); final int width = gif.getWidth(); final int height = gif.getHeight(); final int background = gif.getBackgroundColor(); final int frameCount = gif.getFrameCount(); for (int i = 0; i < frameCount; i++) { final BufferedImage img = gif.getFrame(i); final int delay = gif.getDelay(i); ImageIO.write(img, "png", new File(OUTPATH + "frame_" + i + ".png")); } } 

解码器支持GIF87a,GIF89a,动画,透明度和隔行扫描。 框架将具有图像本身的宽度和高度,并放置在canvas上的正确位置。 它尊重框架透明度和处理方法。 查看项目描述以获取更多详细信息,例如背景颜色的处理。

此外,解码器不会遇到此ImageIO错误: ArrayIndexOutOfBoundsException:4096在读取gif文件时 。

我很乐意得到一些反馈。 我一直在测试一组具有代表性的图像,但是,一些真实的现场测试会很好。