【问题标题】:How to convert and write Large Images without causing an OOM Error?如何在不导致 OOM 错误的情况下转换和写入大图像?
【发布时间】:2011-11-22 22:48:43
【问题描述】:

我将图像以 ImageIcons 的形式存储在数据库中,我想将这些图像提供给我们的网页,但是对于大图像,我会出现内存不足的异常。

这是我目前的做法,

[编辑]我扩展了我的 ImageUtilities 以提供一个非透明的 BufferedImage 来简化代码,

BufferedImage rgbbi = ImageUtilities.toBufferedImage(icon.getImage());

ServletOutputStream out = null;
try {
    // Get the Servlets output stream.
    out = responseSupplier.get().getOutputStream();

    // write image to our piped stream
    ImageIO.write(rgbbi, "jpg", out);

} catch (IOException e1) {
    logger.severe("Exception writing image: " + e1.getMessage());
} finally {
    try {
        out.close();
    } catch (IOException e) {
        logger.info("Error closing output stream, " + e.getMessage());
    }
}

抛出的异常如下,

Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space
    at java.awt.image.DataBufferInt.<init>(DataBufferInt.java:41)
    at java.awt.image.Raster.createPackedRaster(Raster.java:458)
    at java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1015)
    at sun.awt.image.ImageRepresentation.createBufferedImage(ImageRepresentation.java:230)
    at sun.awt.image.ImageRepresentation.setPixels(ImageRepresentation.java:484)
    at sun.awt.image.ImageDecoder.setPixels(ImageDecoder.java:120)
    at sun.awt.image.JPEGImageDecoder.sendPixels(JPEGImageDecoder.java:97)
at sun.awt.image.JPEGImageDecoder.readImage(Native Method)
at sun.awt.image.JPEGImageDecoder.produceImage(JPEGImageDecoder.java:119)
at sun.awt.image.InputStreamImageSource.doFetch(InputStreamImageSource.java:246)
at sun.awt.image.ImageFetcher.fetchloop(ImageFetcher.java:172)
at sun.awt.image.ImageFetcher.run(ImageFetcher.java:136)
Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space
Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space
Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space
...

有没有办法可以重写它以流式传输ImageIO.write 的输出并以某种方式限制其缓冲区大小?

[编辑] 我也不能只增加堆大小,我需要提供的图像在 10000x7000 像素的范围内,作为一个字节数组,计算出 (10000px x 7000px x 24bits)280MB。我认为为 servlet 中的图像转换分配的堆大小不合理。

示例图片Large

【问题讨论】:

  • 很抱歉问了这么明显的问题,但您是否尝试过增加堆大小或将图像切成更小的块?
  • @kostja 我的图像的一个例子是 10368x6912 像素大,压缩后为 5Mb,未压缩在我认为不到 2Gb 的位置。另外,我怎样才能切一个 ImageIcon?
  • 所以您将从服务器发送 2GB 数据?如果你有这样的带宽容量,你可以负担更多的 RAM 和 Java 堆。
  • @Andrew 我做错了数学:P,280MB 是正确的大小。该大小指的是BufferedImage,需要转换为转换后提供的jpeg,这是一个可管理的大小。
  • 您真的需要在 servlet 中处理这些图像吗?因为这是个糟糕的主意。

标签: java image-processing awt


【解决方案1】:

正如 cmets 中所指出的,将 10000x7000 图像以 ImageIcon 的形式存储在数据库中,并通过 servlet 为它们提供服务,这听起来像是糟糕的设计。 尽管如此,我还是指出了这个PNGJ 库(免责声明:我编写了它),它允许您逐行按顺序读取/写入PNG 图像。当然,这仅在您以该格式存储大图像时才有用。

【讨论】:

  • 感谢相关回复。虽然不是我采用的解决方案,但它回答了我认为最好的问题。没有内置的方法可以做到这一点,所以 3rd 方库或自己做似乎是最好的方法。
  • @leonbloy:你知道用于编写 JPEG 文件的类似库吗?由于典型的 JPEG 块大小为 16x16,因此应该可以一次写入 16 行。无论如何,PNGJ 似乎是一个不错的库。在 2009 年为我的项目编写 PNG 文件时编写了类似的代码。
【解决方案2】:

我假设您的屏幕上没有足够的像素来显示完整的图像。由于您似乎需要在 RAM 中显示它的未压缩版本,因此您将需要与图像大小所暗示的一样多的堆。话虽如此,还有很多更好的方法。

我的学士论文是关于有效地同时显示多个高达 40000x40000 像素的大图像。我们最终实现了具有多级缓存的 LOD。这意味着图像被调整大小并且每个大小被切成方形块,从而产生image pyramid。我们必须进行一些实验才能找到最佳的块大小。它因系统而异,但可以安全地假定为介于 64x64 和 256x256 像素之间。

接下来要实现一个调度算法来上传正确的块,以保持 texel:pixel 的比例为 1:1。为了获得更好的质量,我们在金字塔切片之间使用了三线性插值。

“多级”是指将图像块上传到显卡的显存中,内存作为一级缓存,高清作为二级缓存(前提是图像在网络上),但这种优化可能过度你的情况。

总而言之,当您只是要求内存控制时,需要考虑很多事情。但是,如果这是一个重大项目,那么实施 LOD 是完成这项工作的正确工具。

【讨论】:

  • 大图像正在ImageIO.write 调用中转换为jpg 图像,这会产生合理大小的最终输出。问题在于在两者之间创建的原始图像。
【解决方案3】:

更多内存似乎是转换的唯一答案,而无需我自己编写。

然后我的解决方案是不转换图像并使用this 答案中描述的方法来检索图像 mime 类型以便能够设置标题。

【讨论】:

    【解决方案4】:

    您将无法像您正在使用的那样使用内置类来执行此操作,因为它们旨在批量处理位图。你可能最好通过 Image Magick (或者现在的任何东西)从 java 中运行这些。

    您只需要这样做一次吗?

    您可能不得不自己编写所有这些,加载文件,处理“像素”并将其写出。那将是最好的方法,而不是加载整个东西,转换(即复制)它,然后写出来。我不知道 Image Magick 之类的东西是否适用于流或内存图像。

    AlexR 的附录:

    要正确执行此操作,他需要将文件解码为某种可流式传输的格式。例如,JPEG 将图像分成 8x8 块,单独压缩它们,然后将这些块流出。当它流出块时,块本身被压缩(所以如果你有 10 个黑色块,你会得到 1 个黑色块,计数为 10)。

    原始位图只不过是字节块,对于带有 alpha 的高色彩空间,它是 4 个字节(红色、绿色、蓝色和 Alpha 各一个)。大多数色彩空间转换发生在像素级别。其他更复杂的滤镜适用于像素和周围像素(高斯模糊就是一个简单的例子)。

    为了简单起见,尤其是对于许多不同的格式,将整个图像“加载”到内存中,处理其原始位图,在转换时复制该位图,然后以任何方式将原始图像写回更容易格式(例如,将彩色 JPEG 转换为灰度 PNG)。

    对于大图像,就像这个人正在处理的那样,它恰好在内存方面非常昂贵。

    因此,最理想的是,他会编写特定的代码来分批读取文件,即流式传输,转换每一位,然后再次流式传输。这将占用很少的内存,但他可能不得不自己完成大部分工作。

    所以,是的,他可以“逐字节读取图像”,但处理和算法可能会相当复杂。

    【讨论】:

    • 这不是他需要的,我想。注意`ImageUtilities.toBufferedTransparentImage` - 这是导致图像透明的代码,所以我认为他不能只是从文件中逐字节读取图像并将其写入输出流。
    猜你喜欢
    • 2015-04-10
    • 2019-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多