In this blog, I’ll describe 3 different approaches of downscaling images in Java. I have gained my knowledge from the creation of Easizer – a user friendly image resizer I have written in Java. Even though Easizer supports upscaling of images, the normal use case would be downscaling a high-resolution photo to size useable for webpages and emails. Besides rescaling the Image, Easizer also supports adjusting the jpeg quality to a certain file size and copy the EXIF information.
Image.getScaledInstance()
The first and probably the most well known of scaling images in Java. You provide 3 parameters to the method: width, height and hints, where hints can be
- Image.SCALE_AREA_AVERAGING
- Image.SCALE_REPLICATE
- Image.SCALE_SMOOTH – (In Suns JDK 1.6 equals SCALE_AREA_AVERAGING)
- Image.SCALE_FAST – (In Suns JDK 1.6 equals SCALE_REPLICATE)
- Image.SCALE_DEFAULT – (In Suns JDK 1.6 it also equals SCALE_REPLICATE)
The quality of the scaled instance is very good, but the scaling is a heavy in both CPU and memory usage. Another annoying thing is that the result object is never a instance of a BufferedImage. In many cases this means that you have to create BufferedImage and copy the result image to it.
But the reason I choose not to use getScaledInstance in Easizer was that there was simply no way of knowing the state of the operation and it was simply unacceptable to leave the user without a progressbar when they scaled large images.
Multi Step Rescale operation
Another much faster approach is using Graphics2D ability to draw a scaled image. The scaling of the image is uses on of the following algoritms:
- VALUE_INTERPOLATION_NEAREST_NEIGHBOR
- VALUE_INTERPOLATION_BILINEAR
- VALUE_INTERPOLATION_BICUBIC
This technique is describen in Chris Campbell’s blog The Perils of Image.getScaledInstance(). As Chris mentions, when downscaling to something less than factor 0.5, you get the best result by doing multiple downscaling with a minimum factor of 0.5 (in other words: each scaling operation should scale to maximum half the size).
My first version of Easizer used this approach (with bilinear interpolation) and it works very good (low memory and CPU usage). A friend of mine noticed that scaled images turned out to be a bit blurred with was unacceptable (since the original photo was sharp). The bluriness is a side-effect of the interpolation, and there is no way to solve this. Another problem with this algoritm is moiré pattern may appear.
So while this approach worked well for fast on-the-fly scaling, the quality is simple not good enough for high quality photos.
Implementation of Sinc (Lanczos 3) resampling
After a bit of investigation I found out the the open source image editor GIMP used the Sinc (Lanczos 3) as an option for image resizing. I believe its the the exact same algorithm used by Image.getScaledInstance() using the SCALE_AREA_AVERAGING hint.
The algorithm first scale the images vertical and then horizontal. In other words the image is scaled in one dimension at a time.
Since I believe the scaling algorithm is the same and hence has the same complexity as Image.getScaledInstance(), I didn’t expect to make it run faster or make it consume less memory. But I would be able to make the algorithm support a progress listener. This was enough motivation for me to get started.
The first thing I did, was browse the web, to see if anyone had implemented the algorithm in Java. It turned out that there existed a working implementation in the The Java Imaging Utilities, but this implementation was old (created when JDK 1.1 was around), used its own image object and even though a wrapper class existed for a BufferedImage, it was simply too slow to be of any real use.
So I began rewriting the algorithm to work directly on BufferedImage-objects. Improvements/changes compared to the original algorithm were:
- I hardcoded 3 channels (red, green and blue) to make it faster.
- I copied the image to a bytearray using Raster.getDataElements() to improve the speed of pixel lookups.
- To reduce the size of the bytearray mentioned above, I only worked on one row at a time for the horizonally scaling.
- To improve general scaling speed, I added support for multiple threads. On a multicore CPU this will improve the scaling speed.
- Compared to Image.getScaledInstance() my implementation is a bit slower running on a single core CPU and a bit faster running on a dual core CPU. The memory consumption seems to be the same.
Imagescaling API
I have grouped the last two imagescaling algorithms into a small framework. The main class of the framework is the abstract AdvancedResizeOp, that provides a interface for rescaling BufferedImage objects.
Using this abstraction makes it easy for a program to choose between different implementations.
The AdvancedResizeOp extends the BufferedImageOp and is used by invoking the filter(source, destination) method that returns the filtered (resized) image. Usually you should use null as destination parameter, this will create a compatible image with the correct dimension.
The AdvancedResizeOp also supports progress listeners. Simple create a objects that implements the ProgreeListener interface and give it to the resize object using the method addProgressListener().
The class MultiStepRescaleOp inherits from AdvancedResizeOp. It takes a destination with and height in its constructor and also a optional rendering hints (the default value is bilinear). The algorithm uses multistep algorithm described above to resize images. The implementation is thread safe.
The class ResampleOp uses the Sinc (Lanczos 3) to rescale images. The algorithm also takes destination with and height as contructor paramters, but also takes an optional numberOfThreads parameter (The default value uses the Runtime.availableProcessors()). The class supports other algoritms (BellFilter, BoxFilter,
BsplineFilter, HermiteFilter, MitchellFilter and TriangleFilter) by
using the setFilter() method.
Note that this class is not threadsafe.
Example scaling and code
The following shows the scaling results for the different scaling options in the framework.
These are the test-results for scaling a 4272×2848 pixel image into a 106×71 pixel image on my 2.4GHz Intel Core 2 Duo MacBook Pro. The time above is from a single benchmark, and therefor not accurate, but should give you an idea of the performance of the different algorithms.
I have also create a small example program that show how to use the API. The program is a command-line JPEG image scaling program. Please note that the scaling algorithm is quite memory consuming, so you might need to use the VM parameter Xmx to adjust the maximum heap size (E.g. -Xmx512m).
import com.mortennobel.easizer.imageoperation.ResampleOp; import com.mortennobel.easizer.imageoperation.ProgressListener; import javax.imageio.ImageIO; import java.io.File; import java.awt.image.BufferedImage; public class RescaleImage { public static void main(String[] args) { if (args.length!=4){ System.out.println("Usage java RescaleImage [sourcefile] [destfile] [newwidth] [newheight]"); System.exit(1); } try { String sourcefile = args[0]; String destfile = args[1]; int newwidth = Integer.parseInt(args[2]); int newheight = Integer.parseInt(args[3]); BufferedImage sourceImage = ImageIO.read(new File(sourcefile)); ResampleOp resizeOp = new ResampleOp(newwidth, newheight); resizeOp.addProgressListener(new ProgressListener() { public void notifyProgress(float fraction) { System.out.printf("Resizing %f%n",fraction); } }); BufferedImage resizedImage = resizeOp.filter(sourceImage, null); ImageIO.write(resizedImage,"jpeg", new File(destfile)); } catch (Exception e){ e.printStackTrace(); } } }
The sourcecode is available for download here (its a part of the Easizer source code):
http://www.nobel-joergensen.com/java/projects/easizer/
Update 5th January 2009:
The sourcecode is available for download (under LGPL) here:
http://code.google.com/p/java-image-scaling-framework
http://code.google.com/p/java-image-scaling/
Update 22nd March 2009. Also read my blog Improving image resizing quality in Java.
Hi Morten, I found your work yesterday and am really impressed with your understanding of image processing and scaling. Your work with java-image-scaling has helped me cut out memory usage by about 1/3 and helped speed up our process by about 3 times what I was able to do with scaling images in the past. Thank you very much. Also you can link to our site if you would like to show people of another example of a site using your software at http://www.panorak.com
Thanks again!
By: Dustin Hagstrom on July 7, 2011
at 19:28
Thanks for the feedback. It always good to know that the library is being used 🙂
By: Morten Nobel-Jørgensen on July 7, 2011
at 19:45
Hey Morten, thanks again for your awesome library. I’m still having a little trouble though with memory issues (nothing to do with your library, just my implementation) is there a way to stream (or buffer) an image to be resized instead of putting the entire image into memory?
right now I am doing:
BufferedImage sourceImage = ImageIO.read(origImageFile);
To get my buffered image to put in your filter.
You can email me at dustin (at ) panorak.com if you like.
By: Dustin Hagstrom on August 8, 2011
at 19:32
Hi Dustin
The main problem is that ImageIO stores images uncompressed in memory (which is usually good, since it gives the highest performance).
The image scaling library’s ResampleOp also use a large amount of memory. The most memory efficient (and fastest) algorithm is MultiStepRescaleOp.
By: Morten Nobel-Jørgensen on August 8, 2011
at 20:02
Hi,
I just want to share my experience, your library is just what many people awaited and I’d like to thank you for that. I use it to resize images to Image or ImageIcon for my application UI and I have a remark :
the default resample operation ResampleOp shows artifacts with transparent gifs or pngs, I have to use the MultiStepRescaleOp, happy to know it’s more efficient too.
By: Jean-Louis Sénégas on October 21, 2011
at 11:40
Hi Jean. It is a known problem. The issue is described here: http://code.google.com/p/java-image-scaling/issues/detail?id=24 . Unfortunately I haven’t been able to solve the problem.
By: Morten Nobel-Jørgensen on October 21, 2011
at 11:53
Hi Morten,
I have been using your API for scaling images on my site: cheqpost.com.
Everything works fine 95% of the time, but when i try to scale images smaller than 100kb it starts giving problems. The resulting image comes out like a negative.
Below is the code i use:
ResampleOp resampleOp = new ResampleOp (rescaledWidth,rescaledHeight);
resampleOp.setUnsharpenMask(UnsharpenMask.Normal);
BufferedImage rescaledImage = resampleOp.filter(origBufferedImage, null);
Let me know if i can do something about this.
Thanks
Pratap
By: ppnyc on November 15, 2011
at 09:26
Hi Pratab
Please submit a bug report on http://code.google.com/p/java-image-scaling/issues/list (and attach one of the images that gives you problems).
– Morten
By: Morten Nobel-Jørgensen on November 15, 2011
at 09:29
Hi,
Does your image scaling library work for J2ME?
By: John on July 16, 2013
at 14:28
It’s been a while since I have worked with J2ME. The library is not tested with J2ME, and I guess that it is quite likely that it uses one or more methods not available in J2ME. (On the other hand it should be fairly easy to get working on J2ME).
By: Morten Nobel-Jørgensen on July 16, 2013
at 15:20
Hi Morten, just started using your library, thank you for sharing it, it is great!
By: Anna on September 6, 2013
at 20:01
Thanks Bro…Now I can continue my work with Java Based OSs
By: Soumyadeep on October 8, 2013
at 07:43
Just want to say that I’m lovin’ this library! It has sped up the graphics rendering in my games considerably. Thanks again!
By: Ike on February 6, 2014
at 05:47
Thank you for sharing your hard work with the world. I hacked for hours with the Java image API, only to create a very bad reduced size image. With your library, the code is only a few lines, and the result is excellent quality.
By: jmiloslick on January 15, 2016
at 05:01