Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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)

like image 710
the21st Avatar asked Apr 17 '15 16:04

the21st


People also ask

How do I know if my JPEG is progressive?

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.

What is progressive PNG?

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.

What is progressive images?

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.


2 Answers

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);
}
like image 36
Jordan Doyle Avatar answered Sep 28 '22 09:09

Jordan Doyle


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.getAbsolutePat‌​h())).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.

like image 51
Harald K Avatar answered Sep 28 '22 09:09

Harald K