【问题标题】:A program that cuts GIFs into frames and converts them into pixels is using too much memory将 GIF 切割成帧并将其转换为像素的程序占用了太多内存
【发布时间】:2021-11-28 22:21:58
【问题描述】:

我编写了一个将 gif 图像拆分为帧的代码。 然后,它们被缩小成更小的尺寸,分辨率为 128 * 128 像素。然后他们将全帧与特定分辨率组合在一起。

getFrames(String path) 方法从 jar 中获取沿路径的 GIF 文件并创建图像帧。

getResizedimages (...) 方法将帧切割成更小的 128 x 128 图像。

getPixels (Bufferedlmage image) 方法从图像中获取像素数组。

问题在于,在执行这段代码期间(通过测试方法),即使在执行 getFrames (...) 方法或 getFramesRaw (...) 方法的阶段,也会使用非常大量的内存。

import com.sun.imageio.plugins.gif.GIFImageReader;
import com.sun.imageio.plugins.gif.GIFImageReaderSpi;
import org.apache.commons.io.FileUtils;
import org.bukkit.map.MapPalette;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class Gif {

static void test(String path, int height, int width) {
    List<BufferedImage> images = getFrames(path);
    for (int x = 0; x < width; x ++) for (int y = 0; y < height; y ++)
        getResizedFrames(images, width, height, x, y).forEach(image -> getPixels(image));
}

@SuppressWarnings("deprecation")
public static byte[] getPixels(BufferedImage image) {
    int pixelCount = image.getWidth() * image.getHeight();
    int[] pixels = new int[pixelCount];
    image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
    byte[] colors = new byte[pixelCount];
    for (int i = 0; i < pixelCount; i++)
        colors[i] = MapPalette.matchColor(new Color(pixels[i], true));
    return colors;
}

public static BufferedImage getScaledImage(BufferedImage image) {
    BufferedImage newImg = new BufferedImage(128, 128, 3);
    Graphics2D g2 = newImg.createGraphics();
    g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g2.drawImage(image, 0, 0, 128, 128, null);
    g2.dispose();
    return newImg;
}

public static BufferedImage cropImage(BufferedImage completeImg, int width, int height, int xCoord, int yCoord) {
    int coordWidth = completeImg.getWidth() / width;
    int coordHeight = completeImg.getHeight() / height;
    BufferedImage croppedImg = new BufferedImage(completeImg.getWidth(), completeImg.getHeight(), 1);
    Graphics2D g2 = (Graphics2D)croppedImg.getGraphics();
    g2.drawImage(completeImg, 0, 0, completeImg.getWidth(), completeImg.getHeight(), null);
    g2.dispose();
    BufferedImage image = croppedImg.getSubimage(coordWidth * xCoord, coordHeight * yCoord, coordWidth, coordHeight);
    return getScaledImage(image);
}

public static java.util.List<BufferedImage> getFramesRaw(String path) {
    java.util.List<BufferedImage> frames = new ArrayList<>();
    try {
        File file = File.createTempFile("data", ".gif");
        InputStream stream = Main.class.getResourceAsStream(path);
        FileUtils.copyInputStreamToFile(stream, file);
        stream.close();

        ImageInputStream inputStream = ImageIO.createImageInputStream(file);
        ImageReader ir = new GIFImageReader(new GIFImageReaderSpi());
        ir.setInput(inputStream);

        int number = ir.getNumImages(true);
        for (int i = 0; i < number; i++) frames.add(ir.read(i));
        inputStream.close();
        System.gc();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return frames;
}

public static java.util.List<BufferedImage> getFrames(String path) {
    java.util.List<BufferedImage> copies = new ArrayList<>();
    java.util.List<BufferedImage> frames = getFramesRaw(path);
    copies.add(frames.remove(0));
    int width = copies.get(0).getWidth(), height = copies.get(0).getHeight();
    for (BufferedImage frame : frames) {
        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = img.getGraphics();
        g.drawImage(copies.get(copies.size()-1),0,0,null);
        g.drawImage(frame,0,0,null);
        copies.add(img);
    }
    return copies;
}

public static java.util.List<BufferedImage> getResizedFrames(java.util.List<BufferedImage> frames, int width, int height, int x, int y) {
    List<BufferedImage> copies = new ArrayList<>();
    for (BufferedImage image : frames)
        copies.add(cropImage(image, width, height, width - 1 - x, height - 1 - y));
    return copies;
}

}

我需要你的帮助。

【问题讨论】:

  • 想想getFrames() 返回的内容:它是原始大小的每个帧的未压缩内存表示。 TYPE_INT_RGB 每个像素 IIRC 需要 3 个字节,所以这是 width * height * 3 * numberOfFrames 字节至少。一个 1000 帧的中型 GIF(不太可能)很快就会变得非常大。
  • @JoachimSauer 好点,我编辑了我的答案以澄清这一点。

标签: java image memory gif pixel


【解决方案1】:

您的程序使用大量内存的原因是您的代码在每一步(在许多小循环中)在所有帧上运行。所有帧的所有解码图像数据将同时保存在内存中,导致不必要的内存使用。虽然这听起来合乎逻辑,但肯定效率不高。

相反,创建一个循环,为一个单个帧执行所有需要的操作,然后再继续下一帧。

类似这样的:

public static void test(String path) throws IOException {
    List<BufferedImage> thumbnails = new ArrayList<>();

    readFrames(path, image -> thumbnails.add(getScaledImage(image)));
}

private static void readFrames(String path, Consumer<BufferedImage> forEachFrame) throws IOException {
    try (ImageInputStream inputStream = ImageIO.createImageInputStream(GifSplitter.class.getResourceAsStream(path))) {
        Iterator<ImageReader> readers = ImageIO.getImageReaders(inputStream); // ImageIO detects format, no need to hardcode GIF plugin
        if (!readers.hasNext()) {
            return;
        }

        ImageReader ir = readers.next();
        ir.setInput(inputStream);

        // For GIF format (which does not contain frame count in header),
        // it's more efficient to just read each frame until IOOBE occurs,
        // instead of invoking getNumImages(true)
        int i = 0;
        while (i >= 0) {
            try {
                BufferedImage image = ir.read(i++);

                // crop, scale, etc, for a SINGLE image
                forEachFrame.accept(image);
            }
            catch (IndexOutOfBoundsException endOfGif) {
                // No more frames
                break;
            }
        }

        ir.dispose();
    }
}

【讨论】:

  • 您的方法输出正常的第一帧,但其余的问题
  • 你能详细说明“剩下的问题”吗?我的代码需要更少的堆内存,因为它一次运行一帧。只有缩略图保存在内存中(但那是来自您的原始代码,也许您也不需要它?)。另外,请确保您测量正确使用的内存。
猜你喜欢
  • 1970-01-01
  • 2011-09-20
  • 2012-06-22
  • 1970-01-01
  • 2011-11-21
  • 2014-02-19
  • 2021-02-01
  • 2012-05-03
  • 2011-06-21
相关资源
最近更新 更多