在 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.getAbsolutePath())).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 代码安装,至少也值得检查一下他的解决方案。
当使用 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.getAbsolutePath())).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 代码安装,至少也值得检查一下他的解决方案。