【问题标题】:Limit rendered image size in icepdf限制icepdf中的渲染图像大小
【发布时间】:2016-12-09 12:10:14
【问题描述】:

在将一堆 PDF 渲染为图像时,icepdf 似乎随机出现 OutOfMemoryError。试图追查这一点,我发现了两件事:

  1. 接近 OOM 时它呈现 A0 页面或类似的大型文档页面
  2. 使用 eclipse 内存分析器,我发现内存中有 1/2GB 的图像。

这建议将输出图像大小限制在可管理的范围内。我想知道最简单的方法是什么?

我查看了 icepdf 的 Page 对象,但强烈建议始终使用 Page.BOUNDARY_CROPBOX 并且 Javadoc 中似乎没有记录其他用途。

如何限制Document.getPageImage 的输出图像大小或我可以使用什么其他措施来防止OOM(除了增加Xmx,我不能这样做)。降低图像质量是一种选择。但它应该只适用于“超大”图像,而不是全部。

我已经尝试使用 Document.paintPage() 使用预定义的图像,但这还不够。

调试终于让我放大了一个有问题的文档。我得到这样的日志:

2016-12-09T14:23:35Z    DEBUG   class org.icepdf.core.pobjects.Document 1       MEMFREE: 712484296 of 838860800
2016-12-09T14:23:35Z    DEBUG   class org.icepdf.core.pobjects.Document 1       LOADING: ..../F1-2.pdf
2016-12-09T14:23:37Z    WARN    class org.icepdf.core.pobjects.graphics.ScaledImageReference    1       Error loading image: 9 0 R Image stream= {Type=XObject, Length=8 0 R, Filter=FlateDecode, ColorSpace=DeviceGray, Decode=[1, 0], Height=18676, Width=13248, Subtype=Image, BitsPerComponent=1, Name=Im1}  9 0 R

所以这将是高度 = 18676,宽度 = 13248,这真的很大。

我猜想在加载图像的过程中已经发生了 OOM,所以以后的缩放没有帮助。此外,org.icepdf.core.imageReference=scaled 属性似乎还不够早。

对我来说,忽略这样的超大图像就可以了。有机会吗?

【问题讨论】:

    标签: icepdf


    【解决方案1】:

    在解码 PDF 内容时,图像加载是迄今为止最耗费内存的内存任务。目前还没有一种简单的方法可以为非常大的图像关闭图像加载,但是如果你想自己实现它,我会给你一些代码提示。

    ImageReferenceFactory.java 类是系统属性org.icepdf.core.imageReference 后面的工厂,您会看到getImageReferenced() 的默认值是ImageStreamReference。您可以像这样创建一个新的 ImageReference 类型:

    public static org.icepdf.core.pobjects.graphics.ImageReference
    getImageReference(ImageStream imageStream, Resources resources, GraphicsState graphicsState,
                      Integer imageIndex, Page page) {
        switch (scaleType) {
            case SCALED:
                return new ScaledImageReference(imageStream, graphicsState, resources, imageIndex, page);
            case SMOOTH_SCALED:
                return new SmoothScaledImageReference(imageStream, graphicsState, resources, imageIndex, page);
            case MIP_MAP:
                return new MipMappedImageReference(imageStream, graphicsState, resources, imageIndex, page);
            case SKIP_LARGE:
                return new SkipLargeImageReference(imageStream, graphicsState, resources, imageIndex, page);
            default:
                return new ImageStreamReference(imageStream, graphicsState, resources, imageIndex, page);
        }
    }
    

    接下来,您可以使用新的 SkipLargeImageReference 类扩展 ImageStreamReference 类。然后按如下方式覆盖 call() 方法,它将跳过加载超过定义的 MAX_SIZE 的任何图像。

    public BufferedImage call() {
        BufferedImage image = null;
        if (imageStream.getWidth() < MAX_SIZE && imageStream.getHeight() < MAX_SIZE){
            long start = System.nanoTime();
            try {
                image = imageStream.getImage(graphicsState, resources);
            } catch (Throwable e) {
                logger.log(Level.WARNING, "Error loading image: " + imageStream.getPObjectReference() +
                        " " + imageStream.toString(), e);
            }
            long end = System.nanoTime();
            notifyImagePageEvents((end - start));
            return image;
        }
        return null;
    }
    

    附带说明:要尽量减少解码图像所需的内存量,请确保您使用的是org.icepdf.core.imageReference=default,因为这只会对图像进行一次解码。 org.icepdf.core.imageReference=scaled 实际上会以全尺寸解码图像,然后进行缩放,这会产生非常大的内存峰值。我们正在试验 NIO 的直接 ByteBuffers,它看起来很有希望将解码内存使用从堆中移出,所以希望这在未来会变得更好。

    【讨论】:

    • 这是我大致猜到的:图像在加载时会变得非常大。我尝试了您实施 SkipImageStreamReference 的建议。对于 Xmx=900m(故意小一点),它仍然会在小于 1500x1500 的图像上以 OOM 轰炸。我们有很多大小从 10000^2 到 20000^2 的结构示意图,所以如果线性缩放,我们需要 90GB 以上的 RAM。
    • 内存映射字节缓冲区将防止 OOM,但它们仍然需要大量 RAM 或巨大的交换空间或页面文件,这会降低性能。我不知道 PDF 渲染是如何工作的,但我的直觉是应该有一种方法可以让初始渲染将分辨率降低到请求的大小。
    猜你喜欢
    • 1970-01-01
    • 2012-10-20
    • 1970-01-01
    • 1970-01-01
    • 2019-03-17
    • 2018-02-07
    • 2013-04-05
    • 2016-03-10
    • 1970-01-01
    相关资源
    最近更新 更多