BufferedImage resizing very slow

Here's the class I use for resizing a BufferedImage:

publicclass ImageResizer

{

private BufferedImage imageToResize;

privatedouble factor;

private GraphicsConfiguration gc;

public ImageResizer(BufferedImage image,double scale)

{

imageToResize = image;

factor = scale;

gc = getDefaultConfiguration();

}

public BufferedImage getResizedImage()

{

return getScaledInstance();

}

public BufferedImage copy(BufferedImage target, Object interpolationHint)

{

Graphics2D g = target.createGraphics();

g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolationHint);

double scalex = (double) target.getWidth() / imageToResize.getWidth();

double scaley = (double) target.getHeight() / imageToResize.getHeight();

AffineTransform at = AffineTransform.getScaleInstance(scalex, scaley);

g.drawRenderedImage(imageToResize, at);

g.dispose();

return target;

}

public BufferedImage getScaledInstance2D(Object interpolationHint)

{

int w = (int) (imageToResize.getWidth() * factor);

int h = (int) (imageToResize.getHeight() * factor);

int transparency = imageToResize.getColorModel().getTransparency();

return copy(gc.createCompatibleImage(w, h, transparency), interpolationHint);

}

public BufferedImage getScaledInstanceAWT(int hint)

{

int w = (int) (imageToResize.getWidth() * factor);

int h = (int) (imageToResize.getHeight() * factor);

Image image = imageToResize.getScaledInstance(w, h, hint);

BufferedImage result =new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);

Graphics2D g = result.createGraphics();

g.drawImage(image, 0, 0,null);

g.dispose();

return result;

}

public BufferedImage getScaledInstance()

{

if (factor >= 1.0)

return getScaledInstance2D(RenderingHints.VALUE_INTERPOLATION_BICUBIC);

else

return getScaledInstanceAWT(Image.SCALE_AREA_AVERAGING);

}

public GraphicsConfiguration getDefaultConfiguration()

{

GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();

GraphicsDevice gd = ge.getDefaultScreenDevice();

return gd.getDefaultConfiguration();

}

}

It simply takes in a BufferedImage and a scale (greater or less than 1 depending on whether the image is shrunk or magniifed) and spits out a resized BufferedImage. If my memory serves me rightly, most of the code (if not all) is culled from here:

http://forum.java.sun.com/thread.jspa?threadID=522483

(thank you Dr Laszlo) and produces very good quality output.

The problem is that shrinking images (ie, this method: getScaledInstanceAWT(int hint) ) is very slow and sometimes takes around 15 seconds to process. (And as I'm sometimes process batches of multiple images, it therefore becomes very tedious).

I've tried passing everything through the getScaledInstance2D(Object interpolationHint) method regardles of the factor and it works much much quicker but with some loss of quality when shrinking images.

How can I speed up the resize on shrinking the BufferedImage without compensating on the resultant image quality?

Can anyone suggest anything? Any help would be greatly appreciated.

Many thanks

[5118 byte] By [damian-milanoa] at [2007-10-2 11:50:52]
# 1

The problem is that shrinking images (ie, this method: getScaledInstanceAWT(int hint) ) is very slow and sometimes takes around 15 seconds to process.

Yes, getScaledInstance() produces good results but is very slow. 15 seconds seems rather high, though. What are the dimensions and bit depths of the images you're scaling from and to?

itchyscratchya at 2007-7-13 6:24:14 > top of Java-index,Security,Cryptography...
# 2

thanks for getting back on this one....

Here is a part of my debug output (where the processing times are in seconds)

Image 1:

srcWidth: 2272

new_dimension: 80

factor: 0.035211267605633804

elapsed time: 20.85

= = = = = = = = = = = =

srcWidth: 2272

new_dimension: 900

factor: 0.3961267605633803

elapsed time: 22.133

= = = = = = = = = = = =

srcWidth: 2272

new_dimension: 380

factor: 0.16725352112676056

elapsed time: 20.343

= = = = = = = = = = = =

__

Image 2:

srcWidth: 640

new_dimension: 80

factor: 0.125

elapsed time: 52.145

= = = = = = = = = = = =

srcWidth: 640

new_dimension: 900

factor: 1.40625

elapsed time: 0.532

= = = = = = = = = = = =

As you'll see, anything over a factor of 1.0 processes relatively quickly, whereas anything else is very slow..... one image even took an incredible 52 seconds to resize.

Nor can I really see much correlation between final dimensions and resulting process times. I'd imagined that shrinking an image to 380 px wide would take longer than shrinking it to 900 wide. Or maybe not?

As far as bit depths go.... I'm afraid I don't know. How do I find that out? The second image I loaded was just 160KB whereas the first was 1.3MB

damian-milanoa at 2007-7-13 6:24:14 > top of Java-index,Security,Cryptography...
# 3

As you'll see, anything over a factor of 1.0 processes relatively quickly, whereas anything else is very slow..... one image even took an incredible 52 seconds to resize.

That sounds very unusual. I would expect an image of ~2000px width (assuming a similar height) to rescale in about 3 seconds.

Something odd is going on there. What VM and platform are you using?

Nor can I really see much correlation between final dimensions and resulting process times. I'd imagined that shrinking an image to 380 px wide would take longer than shrinking it to 900 wide. Or maybe not?

No, shrinking to a larger size will take longer: it involves allocating more memory and perfroming more calculations.

As far as bit depths go.... I'm afraid I don't know. How do I find that out? The second image I loaded was just 160KB whereas the first was 1.3MB

If you load your image into a paint program like Gimp you can find out - but actually that's not really necessary. Take your input image and call getType() on it, and check it against the constants defined by BufferedImage. Also call getColorModel().getTransferType() and check that.

One useful piece of diagnosis would be to drop this in:

public BufferedImage getResizedImage()

{

BufferedImage i = getScaledInstance();

System.out.println("Matching types: " + (this.imageToResize.getColorModel().getTransferType() == i.getColorModel().getTransferType()));

return i;

}

I'm not sure this will tell us much, I think your time differences are too great to be caused by this, but still, it might be a useful sanity check.

itchyscratchya at 2007-7-13 6:24:14 > top of Java-index,Security,Cryptography...
# 4

thanks itchyscratchy....

Doing as you say, adding in that println statement, I get the following:

IN colour type: 0

OUT colour type: 3

Matching types: false

Does that help us in any way?

In either case, adding more dugging lines to my code, I find that it is precisely this line of code which is slowing it down:

g.drawImage(image, 0, 0, null);

On average it takes 15 seconds for this single line of code to execute. I've tried susbstituing it with other drawImage() methods form the Graphics and Graphics2D class, but so far nothing I have done has made much difference.

I'm running Java 5,0 on MacOSX Tiger here in my development evironment. In production, the code runs on Linux via Tomcat-Apache. I haven't made any exact measurements and, although it's perhaps a little quicker, it's certainly not very much so.

Do you have any further ideas?

damian-milanoa at 2007-7-13 6:24:14 > top of Java-index,Security,Cryptography...
# 5
Hm. Change this line,BufferedImage result = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);to the following,BufferedImage result = getDefaultConfiguration().createCompatibleImage(w, h);and see if that fixes it.
itchyscratchya at 2007-7-13 6:24:14 > top of Java-index,Security,Cryptography...
# 6

Mmm, no, not quite.

I tried this too:

int transparency = this.imageToResize.getColorModel().getTransparency();

BufferedImage result = getDefaultConfiguration().createCompatibleImage(w, h, transparency);

and this:

BufferedImage result = new BufferedImage(w, h, this.imageToResize.getType());

but it's still slows down at the line I mentioned before.

Still, I have a hunch you are right in thinking my fundamental mistake lies here.... I'll keep hunting.....

damian-milanoa at 2007-7-13 6:24:14 > top of Java-index,Security,Cryptography...
# 7

Still haven't found a solution to my problem.... can anyone perhaps suggest another way of shrinking a JPEG image?

I need an excellent quality result but (as I'm processing multiple images) I don't want each one to take longer than a maximum of 3 seconds. Ideally, I don't want to use a third party package and ideally I'd like to steer away from using the Sun JPEGEncoder classes. Surely there has to be a way? I tried JAI, but that disn't seem any quicker.

One solution I had in mind was to create a multi-threaded image processor - to use some quick alogorithm just to return a place-holder image while a separate thread got on with producing the high-quality output. But it really seems like overkill to have to resort to that....

Actually, there's another side to all this.... All I am doing is allowing a user to upload multiple images to the server. The upload servlet calls my imageResizer class to create multiple, differently sized copies of the same image (ie: thumb, small, medium, large and jumbo sizes). These images are then accessed by the front end of my web application.

My question is this..... in my jsp page, using normal css tags I can specify the size of the image I want and the browser will (usually, hopefully) do the resizing. So is all this work I am doing on the server unnecessary? (My assumed answer is doing it the way I am presently ensures the highest definition and fastest page download times in my jsp pages (which is ultimately the most important factor for me). But then, I'm not an image expert. Can anyone enlighten me on this?

Many thanks

damian-milanoa at 2007-7-13 6:24:14 > top of Java-index,Security,Cryptography...
# 8

Ideally, I don't want to use a third party package

Mm, last time I did this I ended up using JNI to hook up an IP library we'd licensed for other aspects of the project anyway. So you could JNI it and get hold of an open source JPEG library.

One solution I had in mind was to create a multi-threaded image processor - to use some quick alogorithm just to return a place-holder image while a separate thread got on with producing the high-quality output.

Threads are of debatable use. You don't get anything for free so, overall, it takes longer to batch-process the images. If your resizing is holding up a web response though then clearly you need something fed back to the user as even just a few seconds can look like a dead request.

Multithreading it wouldn't be particularly complex - depends how comfortable you are with threads of course, but they're no big deal if they're dealt with in manageable and isolatable chunks.

So is all this work I am doing on the server unnecessary? (My assumed answer is doing it the way I am presently ensures the highest definition and fastest page download times in my jsp pages (which is ultimately the most important factor for me).

Yes, you're right, taking large images and downscaling them in the browser isn't very satisfactory. Apart from the fact that download times and bandwidth usage are increased, the images will look terrible, since the browsers generally just point-resample the image.

I would suggest though that one option may be to reduce the number of image sizes you produce: for most applications a thumb, a medium and a full size would be sufficient. Your "full size" may just be a copy of the original and involve no resampling. Depends what you're trying to do of course, but moving the goalposts like this may not be wholly inappropriate.

Another approach is to use a signed applet in the "upload" web page itself, so that it can start doing some of the resizing before it even sends the image off. So, for instance, let's say you're not just uploading the image but you're also uploading some data like a title and a description. If this was done in an applet, you force the workflow: request the image first. Then, whilst they're typing in their other information, your applet can be performing resizing in the background on a separate thread. When the user comes to submit, hopefully the work is all done and you can upload all the resized images, otherwise you can either upload just the finished ones and let the server finish, you could wait for the current one to finish and then submit, or you could wait for all of them to be done client side, obviously with some indication to the user.

All this very much depends on your app and the infrastructure etc but it throws some more ideas in...

itchyscratchya at 2007-7-13 6:24:14 > top of Java-index,Security,Cryptography...
# 9

thanks for those ideas itchyscratchy...

I've got plenty to chew on now, but perhaps your suggestion of a third party library via JNI is a good one after all (expecially as I've actually got one to hand that I "borrowed" from my ex-boss). Alternatively, as you say, using an applet in the upload page might work very well indeed as typically, it's quite a large form to fill out.

Thanks again for your help.

damian-milanoa at 2007-7-13 6:24:14 > top of Java-index,Security,Cryptography...