在 Java 中读取渐进式编码的 9000x9000 JPEG 需要 1 分钟

Reading a progressively encoded 9000x9000 JPEG in Java takes 1 minute

当使用 javax.imageio.ImageIO 从磁盘加载大分辨率 (9000x9000) JPEG 时,我的 Scala 应用程序需要 1 分钟多的时间。我尝试创建一个仅 Java 的项目,但它仍然需要很长时间 - 大约 30 秒。

这是我加载图片的方式:

File file = new File("/Users/the21st/slow2.jpg");
BufferedImage image = ImageIO.read(file);

有什么方法可以提高在 Java 中读取渐进式编码的大分辨率 JPEG 的性能?

有问题的图像是 this one(版主,请不要再次上传到其他托管站点,以免编码/质量发生变化)

ImageIO 的一个更快的替代方法是 ImageMagick,有多种包装器可以通过 Java 连接 ImageMagick,例如 JMagick

要从 JMagick 获取 BufferedImage,您必须首先获取 MagickImage 的实例,可以这样做:

ImageInfo info = new ImageInfo(pathToImage);
MagickImage image = new MagickImage(info);

现在可以使用我们自己提供的方法了Jacob Nordfalk 8 years ago to read the image into a BufferedImage here

public static BufferedImage magickImageToBufferedImage(MagickImage magickImage) throws Exception
{
    Dimension  dim = magickImage.getDimension();
    int size = dim.width * dim.height;
    byte[] pixels = new byte[size * 3];

    magickImage.dispatchImage(0, 0, dim.width, dim.height, "RGB", pixels);

    BufferedImage bimage = createInterleavedRGBImage(dim.width, dim.height, pixels);

    ColorModel cm = bimage.getColorModel();
    Raster raster = bimage.getData();
    WritableRaster writableRaster = null;

    writableRaster = (raster instanceof WritableRaster) ? (WritableRaster) raster : raster.createCompatibleWritableRaster();

    BufferedImage bufferedImage = new BufferedImage(cm, writableRaster, false, null);

    return bufferedImage;
}

然后createInterleavedRGBImage方法:

public static BufferedImage createInterleavedRGBImage(int imageWidth, int imageHeight, byte data[])
{
    int[] numBits = new int[3];
    int[] bandoffsets = new int[3];

    for (int i = 0; i < 3; i++) {
        numBits[i] = 8;
        bandoffsets[i] = i;
    }

    ComponentColorModel ccm = new ComponentColorModel(
        ColorSpace.getInstance(ColorSpace.CS_sRGB),
        numBits,
        false,
        false, //Alpha pre-multiplied
        Transparency.OPAQUE,
        DataBuffer.TYPE_BYTE
    );

    PixelInterleavedSampleModel csm = new PixelInterleavedSampleModel(
        DataBuffer.TYPE_BYTE,
        imageWidth,
        imageHeight,
        3, //Pixel stride
        imageWidth * 3, // Scanline stride
        bandoffsets
    );

    DataBuffer dataBuf = new DataBufferByte(data, imageWidth * imageHeight * 3);
    WritableRaster wr = Raster.createWritableRaster(csm, dataBuf, new Point(0, 0));
    return new BufferedImage(ccm, wr, false, null);
}

好的,这是我到目前为止的发现(老实说,他们有点担心...)。

使用与 Oracle JRE 捆绑在一起的 ImageIO 标准 JPEG 插件:

BufferedImage image = ImageIO.read(file); 

在我的计算机(MacBookPro/2.8GHz i7)上大约 18 秒 读取图像。

使用我的 JPEG plugin for ImageIO,它使用稍微不同的代码路径(即,您可以通过获取 ImageReader 并调用 readRaster() 方法获得相同的结果,然后创建一个 BufferedImage 从那。代码是不平凡的,所以如果你想看代码,请参考项目页面):

BufferedImage image = ImageIO.read(file); 

在我的计算机上大约 8 秒 读取图像。

使用我的 BufferedImageFactory class 和 AWT Toolkit:

BufferedImage image = new BufferedImageFactory(Toolkit.getDefaultToolkit().createImage(file.getAbsolutePat‌​h())).getBufferedImage();

在我的电脑上 ~2.5 秒 读取图像。

使用 sun.awt.codec 中已弃用的 JPEGImageDecoder class:

BufferedImage image = new JPEGImageDecoderImpl(new FileInputStream(file)).decodeAsBufferedImage();

在我的电脑上 ~1.7 秒 读取图像。

因此,这意味着我们应该能够在不到 2 秒的时间内阅读此图像,即使在 Java 中也是如此。 JPEGImageReader 的表现在这种情况下简直荒谬,我真的很想知道为什么。前面已经说了,好像要配合progressive decoding,不过应该比这个好吧

更新:

为了好玩,我创建了一个由 LibJPEG-Turbo Java API 支持的快速 PoC ImageReader 插件。它还不是很复杂,但它允许这样的代码:

BufferedImage image = ImageIO.read(file); 

在我的电脑上 < 1.5 秒 读取图像。

PS:I used to maintain ImageIO wrappers for JMagick(类似于@Jordan Doyle 提到的代码,但它允许您针对 ImageIO API 进行编程),但是我按原样停止了太多的工作。也许我必须重新考虑...如果您不介意依赖 JNI/native 代码安装,至少也值得检查一下他的解决方案。