2012-01-19 2 views
16

Я хочу, чтобы иметь возможность анимировать GIF в качестве входных данных, подсчитывать кадры (и, возможно, другие метаданные), и преобразовывать их в BufferedImage. Как я могу это сделать?Преобразуйте каждый анимированный GIF-кадр в отдельный BufferedImage

+0

См: http://stackoverflow.com/questions/777947/creating-animated-gif-with-imageio – beerbajay

+1

Эта библиотека может также помочь: https://github.com/dragon66/icafe/wiki – dragon66

ответ

10

Если вы хотите, чтобы все кадры быть одинакового размера (для оптимизации GIFs) попробовать что-то вроде этого:

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

Право, я никогда не делал ничего, даже немного, как это раньше, но немного Googling и теребил в Java получил меня это:

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

Edit: см Ansel Zandegran's modification мой ответ.

2

Использование c24w's solution заменить:

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

С:

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

Ни один из ответов здесь не подходит и подходит для анимации. В каждом решении много проблем, поэтому я написал что-то, что действительно работает со всеми файлами gif. Например, это учитывает фактическую ширину и высоту изображения вместо того, чтобы брать ширину и высоту первого кадра, предполагая, что он заполнит весь холст, нет, к сожалению, это не так просто. Во-вторых, это не оставляет прозрачных соленья. В-третьих, это учитывает методы утилизации. В-четвертых, это дает вам задержки между кадрами (* 10, если вы хотите использовать его в 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()]); 
} 

А класс 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; 
    } 
} 
+0

Другие методы, полученные с небольшим повреждением, отлично работали. – cen

+0

Это было из нескольких лет назад ничего себе. Во всяком случае, я написал полную реализацию gif в javascript. Если у вас все еще есть проблемы, я могу передать их вам, и это гарантировано. Я заметил, что встроенный ImageReader для gifs иногда имеет некоторые проблемы с декодированием фактических данных пикселов, ничто не может исправить это, за исключением повторной записи. –

+0

Мне пришлось объединить несколько фрагментов кода SO, чтобы реализовать масштабирование и обрезку в анимированных GIF-файлах. Я получил это, чтобы отлично работать с этим образцом. Сначала я попробовал метод Франческо, но первые несколько кадров выхода имели некоторый шум. Я брошу свое решение на github в ближайшие несколько дней, если какая-нибудь другая потерянная душа попытается сделать что-то подобное. Java и GIF - это дикий запад. – cen

4

ответ Алекс охватывает большинство случаев, но у него есть несколько проблем. Он неправильно обрабатывает прозрачность (по крайней мере, согласно общему соглашению) и применяет метод удаления текущего кадра к предыдущему фрейму, который является неправильным. Вот вариант, который делает обрабатывать эти случаи правильно:

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

Существует хорошее описание того, как GIF анимации работать в this ImageMagick tutorial.

+0

, вам необходимо применить некоторые изменения к этому коду. Если задержка равна нулю, установите ее равной 100, или если она меньше 30 мс, а затем умножьте ее на 10. Это сработало для меня. – Soley

+0

это soution отлично сработало для меня – user3896501

+0

Это работает в отличие от большинства других здесь, что создавало проблему так или иначе. –

1

Я написал декодер изображений GIF самостоятельно и выпустил его под лицензией Apache 2.0 на GitHub. Вы можете скачать его здесь: 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, анимацию, прозрачность и переплетения. Фреймы будут иметь ширину и высоту самого изображения и помещаться в правильное положение на холсте. Он уважает прозрачность и методы удаления рамок. Оформить описание проекта для получения более подробной информации, например, об обработке цветов фона.

Кроме того, декодер не страдает от этой ошибки ImageIO: ArrayIndexOutOfBoundsException: 4096 while reading gif file.

Буду рад получить обратную связь. Я тестировал репрезентативный набор изображений, однако некоторые реальные полевые испытания были бы хорошими.

5

Чтобы разделить анимированный 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 
}