将 GIF 切割成帧并将其转换为像素的程序占用了太多内存
Posted
技术标签:
【中文标题】将 GIF 切割成帧并将其转换为像素的程序占用了太多内存【英文标题】:A program that cuts GIFs into frames and converts them into pixels is using too much memory 【发布时间】: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 好点,我编辑了我的答案以澄清这一点。
【参考方案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();
【讨论】:
您的方法输出正常的第一帧,但其余的问题 你能详细说明“剩下的问题”吗?我的代码需要更少的堆内存,因为它一次运行一帧。只有缩略图保存在内存中(但那是来自您的原始代码,也许您也不需要它?)。另外,请确保您测量正确使用的内存。以上是关于将 GIF 切割成帧并将其转换为像素的程序占用了太多内存的主要内容,如果未能解决你的问题,请参考以下文章