我有java程序从硬盘驱动器读取jpeg文件并将其用作各种其他东西的背景图像。图像本身存储在BufferImage
对象中,如下所示:
BufferedImage background
background = ImageIO.read(file)
这很有效 - 问题在于BufferedImage
对象本身是巨大的。例如,一个215k的jpeg文件成为一个BufferedImag
e对象,它是4兆并且会改变。有问题的应用程序可以加载一些相当大的背景图像,但是jpegs永远不会超过一两或两个,用于存储BufferedImage
的内存可以快速超过100兆字节。
我认为所有这一切都是因为图像作为原始RGB数据存储在ram中,而不是以任何方式压缩或优化。
有没有办法让它以较小的格式将图像存储在ram中?我处在CPU方面比RAM更松弛的情况下,因此将图像对象的大小调回到jpeg压缩的轻微性能影响将非常值得。
答案 0 :(得分:10)
我的一个项目我只是在动态从ImageStream中读取图像时对其进行下采样。下采样将图像的尺寸减小到所需的宽度和宽度。高度虽然不需要昂贵的大小调整计算或修改磁盘上的图像。
由于我将图像下采样到较小的尺寸,因此也显着降低了显示图像所需的处理能力和RAM。为了进行额外的优化,我也在瓦片中渲染缓冲的图像......但这有点超出了本讨论的范围。请尝试以下方法:
public static BufferedImage subsampleImage(
ImageInputStream inputStream,
int x,
int y,
IIOReadProgressListener progressListener) throws IOException {
BufferedImage resampledImage = null;
Iterator<ImageReader> readers = ImageIO.getImageReaders(inputStream);
if(!readers.hasNext()) {
throw new IOException("No reader available for supplied image stream.");
}
ImageReader reader = readers.next();
ImageReadParam imageReaderParams = reader.getDefaultReadParam();
reader.setInput(inputStream);
Dimension d1 = new Dimension(reader.getWidth(0), reader.getHeight(0));
Dimension d2 = new Dimension(x, y);
int subsampling = (int)scaleSubsamplingMaintainAspectRatio(d1, d2);
imageReaderParams.setSourceSubsampling(subsampling, subsampling, 0, 0);
reader.addIIOReadProgressListener(progressListener);
resampledImage = reader.read(0, imageReaderParams);
reader.removeAllIIOReadProgressListeners();
return resampledImage;
}
public static long scaleSubsamplingMaintainAspectRatio(Dimension d1, Dimension d2) {
long subsampling = 1;
if(d1.getWidth() > d2.getWidth()) {
subsampling = Math.round(d1.getWidth() / d2.getWidth());
} else if(d1.getHeight() > d2.getHeight()) {
subsampling = Math.round(d1.getHeight() / d2.getHeight());
}
return subsampling;
}
要从File获取ImageInputStream,请使用:
ImageIO.createImageInputStream(new File("C:\\image.jpeg"));
如您所见,此实现也尊重图像的原始宽高比。您可以选择注册IIOReadProgressListener,以便您可以跟踪到目前为止已读取了多少图像。如果通过网络读取图像,这对于显示进度条非常有用......但不是必需的,您只需指定null。
为什么这与您的情况特别相关?它永远不会将整个图像读入内存,就像您需要它一样,以便它可以以所需的分辨率显示。对于巨大的图像非常有效,即使是磁盘上10英寸的图像也是如此。
答案 1 :(得分:3)
我认为这一切都是因为图像 作为原始RGB存储在ram中 数据,未压缩或优化 无论如何。
确实......假设一个1920x1200的JPG在内存中可以容纳300 KB,在(典型的)RGB + alpha中,每个组件8位(因此每像素32位)它应该占用内存:
1920 x 1200 x 32 / 8 = 9 216 000 bytes
所以你的300 KB文件变成需要近9 MB RAM的图片(请注意,根据您使用的图像类型,取决于JVM和OS,这有时可能是GFX卡RAM)。 / p>
如果您想将图片用作1920x1200桌面的背景,您可能不需要比内存中的图片更大(除非您想要一些特殊效果,例如sub-rgb decimation / color anti -aliasing / etc.)。
所以你必须做出选择:
我通常使用数字2,因为减少硬盘上的文件大小意味着你正在丢失细节(1920x1200图片的细节不如3940x2400的“相同”:你会通过缩减来“丢失信息”)。
现在,Java有点大肆操纵大图片(从性能角度来看,从内存使用的角度来看,从质量的角度来看[*])。回到过去,我会先用Java调用ImageMagick来调整磁盘上的图片大小,然后加载调整大小的图片(比如说我的屏幕大小)。
现在有Java桥/ API直接与ImageMagick接口。
[*] NO WAY 您正在使用Java的内置API缩小图像的速度,并且质量与ImageMagick提供的质量一样好,开始时。
答案 2 :(得分:2)
你必须使用BufferedImage
吗?你能编写自己的Image
实现,将jpg字节存储在内存中,并根据需要转换为BufferedImage,然后丢弃吗?
这适用于一些显示感知逻辑(在将您的字节数组存储为jpg之前使用JAI重新缩放图像),将使其比每次解码大型jpg更快,并且占用的空间比您当前拥有的更小(处理内存)要求除外)。
答案 3 :(得分:2)
使用imgscalr:
http://www.thebuzzmedia.com/software/imgscalr-java-image-scaling-library/
为什么?
代码:
BufferedImage thumbnail = Scalr.resize(image, 150);
or
BufferedImage thumbnail = Scalr.resize(image, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_WIDTH, 150, 100, Scalr.OP_ANTIALIAS);
此外,转换后在较大的图片上使用image.flush()
以帮助提高内存利用率。
答案 4 :(得分:1)
磁盘上JPG的文件大小完全 无关 。
文件的像素尺寸是。如果您的图像是15百万像素,则需要使用垃圾填充RAM来加载原始未压缩版本
将图像尺寸重新调整为您需要的尺寸,这是您可以做的最好的选择,而无需使用不太丰富的色彩空间表示。
答案 5 :(得分:0)
您可以将图像的像素复制到另一个缓冲区,看看它是否比BufferedImage对象占用更少的内存。可能是这样的:
BufferedImage background = new BufferedImage(
width,
height,
BufferedImage.TYPE_INT_RGB
);
int[] pixels = background.getRaster().getPixels(
0,
0,
imageBuffer.getWidth(),
imageBuffer.getHeight(),
(int[]) null
);