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

孤街醉人 提交于 2021-02-06 19:16:22

问题


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)


回答1:


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.




回答2:


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);
}


来源:https://stackoverflow.com/questions/29705050/reading-a-progressively-encoded-9000x9000-jpeg-in-java-takes-1-minute

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!