When using javax.imageio.ImageIO
to load a large-resolution (9000x9000) JPEG from disk, it takes more than 1 minute in my scala application. I tried creating a Java-only project, but it still takes too long - around 30 seconds.
This is how I load the image:
File file = new File("/Users/the21st/slow2.jpg");
BufferedImage image = ImageIO.read(file);
Is there any way to improve performance on reading progressively encoded large-res JPEGs in Java?
The image in question is this one (moderators, please don't reupload to other hosting site again so that encoding / quality doesn't change)
Select File -> Save for Web & Devices . If it's a progressive jpeg, the Progressive checkbox will be selected. Any browser — Baselines jpegs will load top to bottom, and progressive jpegs will do something else. If the file loads too fast, you may need to add bandwidth throttling.
Interlace PNG & Progressive JPEGs So basically, instead of loading the image from top left to bottom right, it actually loads pixels from the whole view port. Each time it loads a frame of pixels the quality gets improved. This technique gives the user a smoother experience regarding image loading.
Progressive images load immediately on your website at first with a low resolution. Then, they increase their resolution as the website loads completely. You may notice a website uses progressive images when the content looks blurry at first.
One faster alternative to ImageIO
is ImageMagick
, there are various wrappers for interfacing ImageMagick
via Java such as JMagick
To get a BufferedImage
from JMagick you must first get an instance of MagickImage
which can be done like so:
ImageInfo info = new ImageInfo(pathToImage);
MagickImage image = new MagickImage(info);
Now you can use the method provided by our very own 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;
}
Then the createInterleavedRGBImage
method:
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);
}
Ok, here's my findings so far (and to be honest, they are a little worrying...).
Using the standard JPEG plugin for ImageIO bundled with the Oracle JRE:
BufferedImage image = ImageIO.read(file);
Reads the image in roughly 18 seconds on my computer (a MacBookPro/2.8GHz i7).
Using my JPEG plugin for ImageIO, which uses a slightly different code path (i.e., you can probably get the same results by obtaining the ImageReader
and invoking the readRaster()
method, then creating a BufferedImage
from that. The code is non-trivial, so please refer tho the project page if you like to see the code):
BufferedImage image = ImageIO.read(file);
Reads the image in roughly 8 seconds on my computer.
Using my BufferedImageFactory
class and the AWT Toolkit
:
BufferedImage image = new BufferedImageFactory(Toolkit.getDefaultToolkit().createImage(file.getAbsolutePath())).getBufferedImage();
Reads the image in ~2.5 seconds on my computer.
Using the deprecated JPEGImageDecoder
class from sun.awt.codec
:
BufferedImage image = new JPEGImageDecoderImpl(new FileInputStream(file)).decodeAsBufferedImage();
Reads the image in ~1.7 seconds on my computer.
So, this means that we should be able to read this image in less than 2 seconds, even in Java. The performance from the JPEGImageReader
is just ridiculous in this case, and I really like to know why. As already mentioned, it seems to have to with the progressive decoding, but still, it should be better than this.
Update:
Just for the fun of it, I created a quick PoC ImageReader
plugin backed by the LibJPEG-Turbo Java API. It's not very sophisticated yet, but it allows for code like:
BufferedImage image = ImageIO.read(file);
To read the image in < 1.5 seconds on my computer.
PS: I used to maintain ImageIO wrappers for JMagick (similar to the code mentioned by @Jordan Doyle, but it would allow you to program against the ImageIO API), however I stopped as it was too much work. Maybe I have to reconsider... At least it's worth checking out his solution as well, if you don't mind on relying on JNI/native code installation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With