The Source for Java Technology Collaboration
User: Password:



Start New Message Delete Post a Reply

Article: 
 The Perils of Image.getScaledInstance()
Subject:  Antialiasing and scaling down JPEGs quickly
Date:  2008-06-06 06:03:46
From:  cook_cl


Thanks for the great article.

2 Points:

1) ANTI-ALIASING.
I get much better results using ANTI-ALIASING which creates a slight blur when downsizing the image. Add this line:
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

2) REDUCING LARGE JPG SIZE QUICKLY
I've been struggling to reduce large JPEGs down to good quality samples quickly - I find that the performance is too slow with BILINEAR interpolation, taking over 1 minute to reduce an 8MB image to approx 300 pixels wide.

I tried using subsampling but this was still quite slow (approx 30 secs) before I started losing too much quality.

After a lot of testing I think I have found a good compromise. I am doing the following:

1) Quickly scale to 4x desired size using NEAREST_NEIGHBOUR.
2) Scale from 4x to 2x using BILINEAR.
3) Scale again from 2x to 1x using BILINEAR.

This can scale a 5MB file in under 1 second. I find that the results (if you use ANTI-ALIAS) are almost as good as using all BILINEAR sampling.

Code:
int type = (img.getTransparency() == Transparency.OPAQUE) ?
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
int w = img.getWidth();
int h = img.getHeight();
if (w < targetWidth && h < targetHeight) {
// Only scale down - not up
return img;
}

// First pass - quickly reduce to 4x desired size
BufferedImage firstPassImg = null;
if (w > (targetWidth * 4)) { // Image more than double required size - scale to double size first
logger.debug("Reducing image phase 1. Width:" + w + " Height:" + h + " Scale to: W:" + (targetWidth * 4) + " H:" + (targetHeight * 4));
firstPassImg = new BufferedImage(targetWidth * 4, targetHeight * 4, type);
Graphics2D g2 = firstPassImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g2.drawImage(img, 0, 0, targetWidth * 4, targetHeight * 4, null);
g2.dispose();
} else {
logger.debug("Phase 1 not required. Width:" + w + " Height:" + h);
firstPassImg = img;
}

// Second pass - quality reduce to 2x desired size
BufferedImage secondPassImg = null;
if (w > (targetWidth * 2)) { // Image more than double required size - scale to double size first
logger.debug("Reducing image phase 2. Width:" + w + " Height:" + h + " Scale to: W:" + (targetWidth * 2) + " H:" + (targetHeight * 2));
secondPassImg = new BufferedImage(targetWidth * 2, targetHeight * 2, type);
Graphics2D g2 = secondPassImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(firstPassImg, 0, 0, targetWidth * 2, targetHeight * 2, null);
g2.dispose();
} else {
logger.debug("Phase 2 not required. Width:" + w + " Height:" + h);
secondPassImg = firstPassImg;
}

// Final pass - quality reduce to final size
logger.debug("Reducing image phase 3. Width:" + targetWidth + " Height:" + targetHeight);
BufferedImage finalImg = new BufferedImage(targetWidth, targetHeight, type);
Graphics2D g2 = finalImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(secondPassImg, 0, 0, targetWidth, targetHeight, null);
g2.dispose();

return finalImg;

Hope you find this useful.

 Feed java.net RSS Feeds