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

时间:2012-01-19 21:52:48

标签: java gif bufferedimage javax.imageio

我希望能够将动画GIF作为输入,计算帧数(可能还有其他元数据),并将每个帧转换为BufferedImage。 我怎么能这样做?

7 个答案:

答案 0 :(得分:15)

如果您希望所有帧的大小相同(对于优化的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<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();
}

答案 1 :(得分:10)

这里的答案都不正确,适合动画。每个解决方案都有很多问题,所以我写了一些实际上适用于所有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;
    }
}

答案 2 :(得分:8)

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

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

修改:请参阅Ansel Zandegran's modification我的回答。

答案 3 :(得分:7)

将动画GIF拆分为单独的FileHandle file = Gdx.files.absolute(Gdx.files.getLocalStoragePath().concat("bin/world1.json")); JsonValue jsonValue = new JsonReader().parse(file /*Base64Coder.decodeString(file.readString())*/); JsonValue data = jsonValue.get("data"); JsonValue allTheData = data.get("AllTheData"); int a = allTheData.get("a").get("value").asInt(); int va = allTheData.get("item").get("va").get("value").asInt(); int vb = allTheData.get("item").get("vb").get("value").asInt(); int vc = allTheData.get("item").get("vc").get("value").asInt(); String s = allTheData.get("s").get("value").asString(); 帧:

BufferedImage

答案 4 :(得分: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中的工作原理。

答案 5 :(得分:3)

使用c24w's solution,替换:

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

使用:

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

答案 6 :(得分:1)

我自己编写了一个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,动画,透明度和隔行扫描。框架将具有图像本身的宽度和高度,并放置在画布上的正确位置。它尊重框架透明度和处理方法。查看项目描述以获取更多细节,例如背景颜色的处理。

此外,解码器不会受到ImageIO错误的影响:ArrayIndexOutOfBoundsException: 4096 while reading gif file

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