On the Fly Thumbnail Creation
By meverett | April 9, 2009
One of the features we added into LimeWire 5 was a thumbnail view for images which solves a lot of problems. In older versions, images appeared as a list of filenames. There are few problems with this.
1) Most images are poorly named. Digital cameras have not helped this at all; it’s impossible to remember that the photo from last night of you and your friend is named IMG_00101.
2) This can result in inadvertent file sharing which is something we tried to prevent in the newest LimeWire. Try remembering if you wanted to share IMG_00101 or was it IMG_001101.
3) This is just a bad user experience. Images are meant to be seen.
There have been some very good articles and books that discuss the proper way to load and scale images. They especially point out the fact that you should never use Image.getScaledInstance(). So I will reiterate that point, don’t use Image.getScaledInstance(). For details on why and the alternative methods follow one of the links above which give very good explanations and alternatives for scaling images.
For the thumbnails in LimeWire 5, we use a modified version of the getFasterScaledInstance() in the book, Filthy Rich Clients. This creates high quality thumbnails in a relatively short period of time. I was occasionally running into three different problems,
1) the thumbnails were being created relatively fast but not quite fast enough. This became more noticeable as the file size of the image increased (the solution was going to be write the thumbnails to disk for future use).
2) large images were occasionally failing to load. This was occurring very randomly where the same set of images would fail 20% of the time, and
3) when creating large numbers of thumbnails Out Of Memory exceptions would start happening.
After reading many articles and forums, I came to a solution that solved all three of these problems. The solution we use is to perform subsampling on large images. Subsampling is a process of reading every nth pixel in every nth line. The technique alone is a bit crude for creating thumbnails. All you can specify is the number of lines to skip in an image. So a subsampling factor of 1 will read every line, a subSampling factor of 2 will read every other line, etc. Alone this technique doesn’t produce very nice results, however used in combination with the getFasterScaledInstance(), it creates high quality thumbnails very fast and all the failed image loads and OOM exceptions nicely disappeared.
Below is the code that we use to read an image into a BufferedImage prior to calling getFasterScaledInstance() to create the final thumbnail.
private static final float subSamplingFactor = ThumbnailManager.WIDTH * 4.20f;
/**
* Loads a file into a BufferedImage. If the image is longer than a subSamplingFactor,
* rows are sampled out to reduce the size of the BufferedImage. This can go a long way
* towards reducing OutOfMemoryExceptions when loading large compressed images.
*/
private BufferedImage getSubSampleImage(File file) throws FileNotFoundException, IOException {
BufferedImage image;
ImageInputStream imageInputStream = null;
try {
final ImageReader imgReader = ImageIO.getImageReadersBySuffix(FileUtils.getFileExtension(file)).next();
imageInputStream = ImageIO.createImageInputStream(new BufferedInputStream(new FileInputStream(file)));
imgReader.setInput(ImageInputStream);
int longEdge = (Math.max(imgReader.getHeight(0), imgReader.getWidth(0)));
int subSample = (int)(longEdge/subSamplingFactor);
final ImageReadParam readParam = imgReader.getDefaultReadParam();
// subSample the image
if(subSample > 1) {
readParam.setSourceSubsampling(subSample, subSample, 0, 0);
}
image = imgReader.read(0, readParam);
} finally {
if(imageInputStream != null)
imageInputStream.close();
}
return image;
}
Using this technique, we ensure that all images we read and pass to the getFasterScaledInstance() will always be a reasonable size. Our subSamplingFactor works on a sliding scale so as the image gets larger, more and more lines are skipped when first being read. Using this two step process allows enough information to be left in the original BufferedImage for getFasterScaledInstance() to produce a crisp image.

Comments and Trackbacks
Java desktop links of the week, April 13 | Jonathan Giles Says:
April 12th, 2009 at 5:40 pm |
Permalink
[…] Limewire blog has a post on how to create thumbnails of images faster than the generally recommended getFasterScaledInstance() a… by firstly sub-sampling the image to strip out some data. As they state, subsampling is a process […]
John Taylor Says:
April 23rd, 2009 at 6:17 pm |
Permalink
What an excellent blog, I’ve added your feed to my RSS reader. :-)