High-Quality Image Resize with Java
Image resize with java isn't a new topic. What I want to explain here is a technique to create small high-quality images using the Java2D API, since I couldn't find any reasonable solution on the Internet. The are some solutions available - like this one - but the final quality is low.
You can use this approach for different purposes, but the best example I can think of is the implementation of small avatars on web applications (see Nabble, DZone, etc.) or desktop software (e.g., Skype, MSN, etc.).
In order to show the contribution of this technique, I have to explain image resize from the beginning. We will improve the algorithm as we go and I also assume you have some background with Java2D.
Reading and Writing the Image
Since this is a simple example, I will read an image from the file system, resize it and save it back. The image I am using is below. Note that I am using a JPG file, which is the common format for pictures.

To read this image, we can use the ImageIO class:
First Attempt
Now we have to find a way to resize this image. Our first attempt is to use the drawImage() method of the Graphics interface:
g.drawImage(image, 0, 0, width, height, null);
g.dispose();
return resizedImage;
With this method, we can create a 24x24 avatar by calling:
The last step is to save the image so that we can see the results. The ImageIO class can do this job:
Note that the second parameter is the format of the saved image. I used "png" because it is simpler. If you want to save it as JPG, you will have to use the JPEGImageEncoder class (and the result is the same, trust me).
The result of this method is a low-quality image:

Pay attention to the details of the image (look closer). There is no anti-alias and the face is distorted. Would you add such algorithm to your web application? I don't think so. Users could get scared by looking at those images.
Second Attempt
We noticed that the main problem of the resize() method above is the fact that it doesn't have anti-aliasing. So we could use the Rendering Hints of the Java2D API as an attempt to solve that problem:
BufferedImage resizedImage = new BufferedImage(width, height, type);
Graphics2D g = resizedImage.createGraphics();
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
g.setRenderingHint(RenderingHints.KEY_RENDERING,
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
g.drawImage(image, 0, 0, width, height, null);
g.dispose();
return resizedImage;
The result is below (right image). If you pay attention to the details, it is somewhat better, but still a crap:


The Trick
Now I will explain how to improve the quality of this image. Basically, the trick is to blur the image before resizing. The code that blurs an image in java is:
float[] blurKernel = {
ninth, ninth, ninth,
ninth, ninth, ninth
Map
map.put(RenderingHints.KEY_INTERPOLATION,
map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
RenderingHints hints = new RenderingHints(map);
BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, blurKernel), ConvolveOp.EDGE_NO_OP, hints);
return op.filter(image, null);
Since the original image is in the JPG format, we can't blur it directly. We have to create a compatible image first:
int w = image.getWidth();
int h = image.getHeight();
BufferedImage result = gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT);
Graphics2D g2 = result.createGraphics();
g2.drawRenderedImage(image, null);
g2.dispose();
return result;
Blurring the image before resizing it isn't the only trick. You have to do this when the width or height of the image is 100px. Since small avatars are usually a square (width = height), you can resize it to 100x100, call the blur() method and resize again to 24x24:
image = resize(image, 100, 100);
image = blurImage(image);
image = resize(image, width, height);
return image;
(the resize() method is the one described in the second attempt)
Now you can compare the three results:



You have to look closer, but the last result is much better than the others. Now you can let users upload avatars on your java applications!
Conclusion
This was my contribution to the tough problem of shrinking images using the Java2D API. I would like to remember that this isn't the ultimate algorithm to achieve that goal, but it is good enough considering its simplicity. If you have comments or feedback, I would like to hear them.
Comments
| 1 | 2 | Next |
Last |
http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html
Hi Stefan, thanks for commenting.
This approach is good because it is simple.
I don't use any fancy algorithm that could impact on performance (like the one you suggested, which seems to be the right direction).
For web servers that handle millions of requests every day, a simple approach like this might fit well.
I am aware that this isn't a perfect solution, but the performance is good and the final image is nice for most cases.
So why not consider it?
Regards,
Hugo Teixeira
I won't go further into details but I highly recommend you to read Chris Campbell's article on the matter (that Stefan already posted): http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html
The algorithm proposed in Chris' article is *not* fancy at all. And it's pretty fast. Chet and I timed the various approaches and discussed them at length in our book Filthy Rich Client and in a couple of JavaOne presentations and the progressive bilinear approach is very very fast.
Hi Romain, thanks for your nice feedback.
I agree with your comments and your expertise is very important at this moment.
I just want to clarify some points that might have been misunderstood:
1 - The rendering hints in the resize() method aren't that useless. They do produce better results (compared to the same method without them), although not as good as a high-quality resize (the hints in the blur() method might be useless though).
2 - You are right in the sense that a blur requires more operations than a bilinear resize, but I don't run the blur in the original image. First I reduce the size to height/width = 100 and then I run the blur. This is a completely different situation and creates a whole new scenario for performance analysis. So the question is: Is a blur in a small image faster than having a bilinear resize run several times? The fact is, we don't know the answer and it depends on the situation. So "flawed" might be a strong word in this sense.
Anyway, I am not fully concerned about the fate of this approach since we have a correct way of doing things. But I have to admit that it was nice to develop it and funny at the same time due to the unusual implementation.
Best regards,
Hugo Teixeira
the best time i've got is 31ms for an 5k image, it
is pretty good for a dev machine
(pentuim IV 2.4Ghz 1.5G ram Tomcat Eclipse Postgres launched)
Regards
FM
Thanks a ton for this article. I've been wrestling with image quality for resizing JPEG's in Java for a bit now, and your trick helped a ton.
I documented my results here:
http://www.byfolio.com/roller/blog/entry/hugo_saves_the_day_for
Thanks!
Cory
http://www.comesolvego.com/2008/04/29/resize-images-with-java-high-quality-and-working-solution/
thx a lot
I test the giving code but it give a bad quality, i founded an other java code can give you a more better quality
package com.diwansoft.resizer;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import javax.swing.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.Kernel;
import java.awt.image.ConvolveOp;
public class ImageUtil {
public static void resize(File originalFile, File resizedFile, int newWidth, float quality) throws IOException {
if (quality 1) {
throw new IllegalArgumentException("Quality has to be between 0 and 1");
}
ImageIcon ii = new ImageIcon(originalFile.getCanonicalPath());
Image i = ii.getImage();
Image resizedImage = null;
int iWidth = i.getWidth(null);
int iHeight = i.getHeight(null);
if (iWidth > iHeight) {
resizedImage = i.getScaledInstance(newWidth, (newWidth * iHeight) / iWidth, Image.SCALE_SMOOTH);
} else {
resizedImage = i.getScaledInstance((newWidth * iWidth) / iHeight, newWidth, Image.SCALE_SMOOTH);
}
// This code ensures that all the pixels in the image are loaded.
Image temp = new ImageIcon(resizedImage).getImage();
// Create the buffered image.
BufferedImage bufferedImage = new BufferedImage(temp.getWidth(null), temp.getHeight(null),
BufferedImage.TYPE_INT_RGB);
// Copy image to buffered image.
Graphics g = bufferedImage.createGraphics();
// Clear background and paint the image.
g.setColor(Color.white);
g.fillRect(0, 0, temp.getWidth(null), temp.getHeight(null));
g.drawImage(temp, 0, 0, null);
g.dispose();
// Soften.
float softenFactor = 0.05f;
float[] softenArray = {0, softenFactor, 0, softenFactor, 1-(softenFactor*4), softenFactor, 0, softenFactor, 0};
Kernel kernel = new Kernel(3, 3, softenArray);
ConvolveOp cOp = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
bufferedImage = cOp.filter(bufferedImage, null);
// Write the jpeg to a file.
FileOutputStream out = new FileOutputStream(resizedFile);
// Encodes image as a JPEG data stream
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bufferedImage);
param.setQuality(quality, true);
encoder.setJPEGEncodeParam(param);
encoder.encode(bufferedImage);
}
// Example usage
public static void main(String[] args) throws IOException {
File originalImage = new File("C:\Documents and Settings\Kamal Haddad\workspace\Flex\WebContent\projects\test\snapshots\fullsize\1.jpeg");
resize(originalImage, new File("C:\Documents and Settings\Kamal Haddad\workspace\Flex\WebContent\projects\test\snapshots\fullsize\1-7.jpeg"), 400, 0.7f);
resize(originalImage, new File("C:\Documents and Settings\Kamal Haddad\workspace\Flex\WebContent\projects\test\snapshots\fullsize\1-1.jpeg"), 400, 1f);
}
}
Can you not simply use the 'getScaledInstance' method of the Image (extended to BufferedImage) class. You can select different quality levels.. e.g. Image.SCALE_SMOOTH. I don't think it's the fastest but I read somewhere it is high quality output? I'd be interested in what you think about this method.
Hi Michael,
The Image.getScaledInstance is VERY slow.. I was using that initially and was disappointed. The quality is good, but because it's done all in software it is very slow.
I finally decided upon:
org.jdesktop.swingx.graphics.GraphicUtilities
and the GraphicUtilities.createThumbnail() method.
It is part of the SwingX download from http://swinglabs.org/
It uses multiple intermediate bi-linear resizes from a managed bufferedimage's drawImage to resize in steps to preserve image quality.
See the following bug report with good comments about the getScaledImage() method http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6196792
For more information and a trial download, visit http://www.devella.net/thumbmaster
thank you
| 1 | 2 | Next |
Last |

Stumble it!
Dzone.com
Digg.com
Reddit.com
Del.icio.us
