Konwertuj każdą animowaną klatkę GIF na osobną ramkę BufferedImage

Chcę mieć możliwość wzięcia animowanego GIFA jako wejścia, policzenia klatek (i być może innych metadanych) i przekonwertowania każdej z nich na BufferedImage. Jak mogę to zrobić?

Author: Francesco Menzani, 2012-01-20

7 answers

Jeśli chcesz, aby wszystkie ramki były tego samego rozmiaru (dla zoptymalizowanych GIFów), spróbuj czegoś takiego:

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();
}
 13
Author: Runtherisc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-10 21:32:24

Żadna z odpowiedzi tutaj nie jest poprawna i nadaje się do animacji. W każdym rozwiązaniu jest wiele problemów, więc napisałem coś, co faktycznie działa ze wszystkimi plikami gif. Na przykład bierze to pod uwagę rzeczywistą szerokość i wysokość obrazu, zamiast brać szerokość i wysokość pierwszej klatki, zakładając, że wypełni ona całe płótno, nie, niestety nie jest to takie proste. Po drugie, to nie pozostawia żadnych przezroczystych ogórków. Po trzecie, uwzględnia to metody usuwania. Po czwarte, daje to opóźnienia między klatkami (*10, jeśli chcesz go użyć w wątku.sen()).

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

I klasa 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;
    }
}
 7
Author: Alex Orzechowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-19 12:09:10

Racja, nigdy wcześniej nie robiłem czegoś takiego, ale trochę googlowania i majstrowania w Javie dało mi to:

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: Zobacz modyfikację Ansela Zandegrana do mojej odpowiedzi.

 7
Author: c24w,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:17

Aby podzielić animowany GIF na oddzielne klatki 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
}
 7
Author: Francesco Menzani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-04 17:43:40

Odpowiedź Alexa obejmuje większość przypadków, ale ma kilka problemów. Nie obsługuje poprawnie przezroczystości (przynajmniej zgodnie z powszechną konwencją) i stosuje metodę usuwania bieżącej ramki do poprzedniej Ramki, co jest niepoprawne. Oto wersja, która poprawnie obsługuje te przypadki:

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

Jest dobry opis działania animacji GIF w Ten samouczek ImageMagick .

 4
Author: SteveH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-25 05:10:58

Używając rozwiązania c24w , zastąp:

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

Z:

frames.add(ir.read(i));
 2
Author: Ansel Zandegran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:06

Sam napisałem dekoder obrazu GIF i wydałem go na licencji Apache 2.0 na Githubie. Możesz go pobrać tutaj: https://github.com/DhyanB/Open-Imaging. przykładowe użycie:

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

Dekoder obsługuje GIF87a, GIF89a, animację, przezroczystość i przeplot. Ramki będą miały szerokość i wysokość samego obrazu i zostaną umieszczone we właściwej pozycji na płótnie. Szanuje przejrzystość ramek i metody usuwania. Sprawdź opis projektu, aby uzyskać więcej informacji szczegóły, takie jak obsługa kolorów tła.

Dodatkowo dekoder nie cierpi na ten błąd ImageIO: ArrayIndexOutOfBoundsException: 4096 podczas odczytu pliku gif .

Będę szczęśliwy, aby uzyskać kilka opinii. Testowałem z represyjnym zestawem obrazów, jednak niektóre prawdziwe testy w terenie byłyby dobre.
 1
Author: Jack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:30