java gif bufferedimage javax.imageio

java - Convierta cada cuadro GIF animado en una imagen almacenada por separado.



bufferedimage javax.imageio (7)

Quiero poder tomar un GIF animado como entrada, contar los fotogramas (y quizás otros metadatos) y convertir cada uno a un BufferedImage . ¿Cómo puedo hacer esto?


Claro, nunca antes había hecho algo así, pero un poco de googlear y manipular Java me dio esto:

public ArrayList<BufferedImage> getFrames(File gif) throws IOException{ ArrayList<BufferedImage> frames = new ArrayList<BufferedImage>(); 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; }

Edición: ver la modificación de Ansel Zandegran a mi respuesta.


Escribí un decodificador de imagen GIF por mi cuenta y lo publiqué bajo la Licencia Apache 2.0 en GitHub. Puede descargarlo aquí: https://github.com/DhyanB/Open-Imaging . Ejemplo de uso:

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

El decodificador es compatible con GIF87a, GIF89a, animación, transparencia y entrelazado. Los marcos tendrán el ancho y el alto de la imagen y se colocarán en la posición correcta en el lienzo. Respeta la transparencia del marco y los métodos de eliminación. Consulte la descripción del proyecto para obtener más detalles, como el manejo de los colores de fondo.

Además, el decodificador no sufre de este error ImageIO: ArrayIndexOutOfBoundsException: 4096 al leer un archivo gif .

Me encantaría recibir algunos comentarios. He estado probando con un conjunto representativo de imágenes, sin embargo, algunas pruebas de campo reales serían buenas.


La respuesta de Alex cubre la mayoría de los casos, pero tiene un par de problemas. No maneja la transparencia correctamente (al menos de acuerdo con la convención común) y está aplicando el método de eliminación del cuadro actual al cuadro anterior que es incorrecto. Aquí hay una versión que maneja esos casos correctamente:

private ImageFrame[] readGIF(ImageReader reader) throws IOException { ArrayList<ImageFrame> frames = new ArrayList<ImageFrame>(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; } }

Hay una buena descripción de cómo funcionan las animaciones GIF en este tutorial de ImageMagick .


Ninguna de las respuestas aquí son correctas y adecuadas para la animación. Hay muchos problemas en cada solución, así que escribí algo que realmente funciona con todos los archivos gif. Por ejemplo, esto toma en cuenta el ancho y el alto reales de la imagen en lugar de tomar el ancho y el alto del primer cuadro asumiendo que llenará todo el lienzo, no, desafortunadamente no es tan simple. En segundo lugar, esto no deja ningún pepinillo transparente. En tercer lugar, esto tiene en cuenta los métodos de eliminación. Cuarto, esto le da demoras entre los cuadros (* 10 si desea usarlo en Thread.sleep ()).

private ImageFrame[] readGif(InputStream stream) throws IOException{ ArrayList<ImageFrame> frames = new ArrayList<ImageFrame>(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()]); }

Y la clase 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; } }


Para dividir un GIF animado en marcos separados de 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 }


Si desea que todos los marcos sean del mismo tamaño (para GIF optimizados) intente algo como esto:

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<String, Integer> imageAttr = new HashMap<String, Integer>(); 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(); }


Usando share , reemplace:

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

Con:

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