'Efficient way to read an image file using Java

I am using javax.imageio.ImageIO.read() which almost takes 9 seconds to read an image having size 5 mb and located at windows temp location, PFB the screenshot of Jprofiler. i want a more efficient way which can decrease the time to at least 2-3 seconds.

enter image description here

The file is coming as a org.springframework.web.multipart.MultipartFile request through rest endpoint and then getting copied to the windows temp location for further proccessing. The complete code block:

String fileName = StringUtils.cleanPath(multipartFile.getOriginalFilename());
Path destinationPath = Paths.get(System.getProperty("java.io.tmpdir"));
String tempPath = destinationPath.resolve(fileName).toString();
File uploadedImageFile = new File(tempPath);
File originalFileInTempLocation = file.transferTo(uploadedImageFile);
BufferedImage originalImage = ImageIO.read(originalFileInTempLocation);

While googling i found a 3rd party library which claims to be more efficient than javax.imageio.ImageIO but its a paid one: https://files.idrsolutions.com/maven/site/jdeli/apidocs/com/idrsolutions/image/JDeli.html#read(java.io.File).

Can anyone suggest a better and efficient way to read the image file.



Solution 1:[1]

Your time of 9 seconds looks bad - I wonder if you've included all code and timing of just ImageIO.read(File)?

However I found that ImageIO.read(File) quite slow on non-SSD drives (ie 500ms to 1 second). When testing NAS and HDD drives on my PC 4-6MB some image reads used InputStream.read() up to 1000 times. I found that that elapsed time for loads are reduced if entire image was read into memory before calling ImageIO.read:

static BufferedImage load(File f) throws IOException
{
    byte[] bytes = Files.readAllBytes(f.toPath());
    try (InputStream is = new ByteArrayInputStream(bytes))
    {
        return ImageIO.read(is);
    }
}

This solution can slow down access time on SSD drives a little (which was not an issue for me at ~ 20ms), but the margin of gain on NAS/network drive was 50-250ms on my PC.

Obviously the timings you observe would depend on your hardware, image file size and types but it may be worth trying in your particular case.

If you need to load several images you can split the file I/O and ImageIO calls to separate threads for a small extra gain, but at cost of extra complexity and memory footprint.

Solution 2:[2]

First of all, I have to say that 9 seconds for a 5Mb image seem too much for modern hardware. Sure, complex images or sophisticated formats (i.e. multi-page tiff) can take longer but my low-end PC needs 1.3 seconds for a 5mb JPG. I would definitely try to identify hardware and/or OS level issues.

Having that said, here is my proposed solution for faster image loading since the use case is to generate thumbnails. The key point here is that we can instruct the image reader to only read part of the image from disk, since we need less image data to generate a thumbnail. This technique (sub-sampling) can be realized for JPG as:

Iterator<ImageReader> imageReadersByFormatName = ImageIO.getImageReadersByFormatName("jpg");
ImageReader ir = imageReadersByFormatName.next(); //There should be at least the default com.sun.imageio.plugins.jpeg.JPEGImageReader installed
ImageReadParam defaultReadParam = ir.getDefaultReadParam();
defaultReadParam.setSourceSubsampling(8, 8, 0, 0); //Configure the reader to read every 8th row / 8th column of the image
ir.setInput(ImageIO.createImageInputStream(new FileInputStream(IMAGE_PATH)), true);
BufferedImage image = ir.read(0, defaultReadParam);

You will have to determine the optimal values for X-axis and Y-axis sub-sampling (first two parameters to setSourceSubsampling()). This will be a speed/image quality trade-off.

There are more configuration options for ImageReadParam.

Bonus material: Since you are using AWS, you might consider this approach in order to delegate this task to a lambda function (i.e. if this task can be done asynchronously).

Solution 3:[3]

I would recommend trying to use classes from the javax.imageio.stream package:

  • if you want to keep your solution with the temp file:
FileInputStream fileInputStream = new FileInputStream(uploadedImageFile);
FileCacheImageInputStream fileCache = new FileCacheImageInputStream?(fileInputStream, new File(tempPath));
BufferedImage bufferedImage =  ImageIO.read(fileCache);
  • if you have enough memory you can use solution with the memory cache:
FileInputStream fileInputStream = new FileInputStream(uploadedImageFile);
MemoryCacheImageInputStream memoryCache = new MemoryCacheImageInputStream(fileInputStream);
BufferedImage bufferedImage =  ImageIO.read(memoryCache);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Ilya Lapitan