M I P A
M I P A

Reputation: 95

Prevent loading a big image into `BufferedImage` when only the size of its compressed version is known

I have a service that receives URLs of already uploaded images: its task is to resize them and save them compressed, loading them like this:

BufferedImage src = ImageIO.read(imgURL);

There might be an issue, though... to compress those images I need to load them into memory first, and I can't judge how much RAM they'll take just by looking at their reported sizes: an 80 KB PNG could be 5000px * 5000px, and the same could happen with a 300 KB JPEG! My resources are quite limited, so is there a way I can find out how much I'm gonna need, so I can discard the images that could be too big in memory? A 4.5 MB JPEG image could take nearly 4 GB in memory...

I kinda wish ImageIO.read(...) had an option so I could tell it "if at some point you see it's gonna take more than X bytes, just throw everything away come back to me." How can I tackle this issue?

Edit: I forgot there are some ways to work with streams in this library. I'll check if it allows me to get the resolution before I download the image into memory.

Upvotes: 0

Views: 304

Answers (2)

Samuel Marchant
Samuel Marchant

Reputation: 320

Not completely as an answer, but each Red Green Blue is stored as an int (4 bytes each int) , if there is Alpha channel for each pixel it's also 4 bytes float. 5000*5000 25,000,000 * 4 100,000,000 100mb Then there are the class footprint instances for holding onto and operating on it, maybe 50mb. So it maybe another 250mb used to complete the op. Java imaging is recommended to often call System.gc() to notify clearing garbage after each "operation" on an image e.g. immediately after loading(opening) the image, then each general operation on it. It is also extremely important to set up a garbage collector such as the G1 gc on the startup commandline or the env variable properties for startup commands in servers e.g. example only: /bin/setenv.sh and to set the memory for stack 1mb heap and min max memory on its commandline or alike in the server

javax.imageio.ImageReader has getWidth(int IMG index) , note. jpeg and tiff carry extra images inside sometimes. I don't say you need do anything except get the reader associate the image file to obtain the width and height, generally image[0] is always the main image

Make a small test class and use ioimage classes, load and read an image and re-obtain the reader class when the data and reader have been set to obtain the info by association in the main object instance , then get width and height, BUT use the java visual VM in the JDK /bin directory to monitor memory before and after loading (switch on the visual VM just before running the test loader small commandline class) Of image[0] in jpeg and tiff formats, I have had good image programs ask, "do you want to store the original image data with the edited version?" that would make image[1] and a thumbnail would be image[2] although a thumb image may be image[1] of which java does not like much above 100 * 100 thumb size (it fails!). Various companies such as Sony or Fuji have their own jpeg multi image specs for these.

Upvotes: 0

M I P A
M I P A

Reputation: 95

Ok I figured it out, but it might not work for every set up: what we can do is take the needed information without loading the entire image, something I didn't know forgot it was possible. We gotta start with ImageIO.createImageInputStream(...). As its documentation says, it accepts any Object and might return null if it can't generate a stream from it. ImageIO was designed with 3rd party plugins in mind, so you should know if it'd be wise to do a null check.

// in my case imgURL is a java.net.URL instance
ImageInputStream imgStream = ImageIO.createImageInputStream(imgURL.openStream());

Then we can get an ImageReader for that type of image (ImageIO.getImageReaders(imgStream).next();), use it for our own image, and ask it to tell us the width and height of our image:

try (imgStream) {
    ImageReader reader = ImageIO.getImageReaders(imgStream).next();
    reader.setInput(imgStream);

    int width = reader.getWidth(0);
    int height = reader.getWidth(0);

    if (width * height > 3_000_000) {
        System.err.println("Too big!");
        return;
    }
}

This is not a perfect solution: there are several things that might fail depending on your plugins for ImageIO, but it's a perfectly viable solution for many cases, I think, including mine. I'm just going to assume every plug in implementation I use checks if the reported dimensions in the metadata don't match the described pixels in the rest of the file...

Upvotes: 1

Related Questions