Thursday, May 1, 2008

Image thumbnail in an optimized way for J2Me platform

Trying to show thumbnail view after taking a big snapshot ? Trying to show a list view of images ? Or in any other case you try to resize or scale an image ?

I was searching for an optimized code for thumbnail creation. The main issues here are

- You need to be careful about the memory you use. After taking a big image in memory you may have a little left for allocation.
- You do not have floating point in most of the mobiles. So your image may not keep the image ratio right after scaling.
- Your need very time efficient code , you can keep the image quality apart ..

I did some google. And found the following discussions useful.

http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=41&t=002988
http://developer.sonyericsson.com/thread/27493

I tested some of those examples. Some of them cause OutOfMemory Exception. Some take a long time to resize image. But I thought I could do better. Finally I wrote the following code.


/**
* Gets the thumbnail that fit with given screen width, height and padding ..
*
* @param image The source image
* @param padding padding to the screen
* @return scaled image
*/
private final Image getThumbnailWrapper(Image image, int expectedWidth, int expectedHeight, int padding) {
final int sourceWidth = image.getWidth();
final int sourceHeight = image.getHeight();
int thumbWidth = -1;
int thumbHeight = -1;

// big width
if(sourceWidth >= sourceHeight) {
thumbWidth = expectedWidth - padding;
thumbHeight = thumbWidth * sourceHeight / sourceWidth;
// fits to height ?
if(thumbHeight > (expectedHeight - padding)) {
thumbHeight = expectedHeight - padding;
thumbWidth = thumbHeight * sourceWidth / sourceHeight;
}
} else {
// big height
thumbHeight = expectedHeight - padding;
thumbWidth = thumbHeight * sourceWidth / sourceHeight;
// fits to width ?
if(thumbWidth > (expectedWidth - padding)) {
thumbWidth = expectedWidth - padding;
thumbHeight = thumbWidth * sourceHeight / sourceWidth;
}
}

// XXX As we do not have floating point, sometimes the thumbnail resolution gets bigger ...
// we are trying hard to avoid that ..
thumbHeight = (sourceHeight < thumbHeight) ? sourceHeight : thumbHeight;
thumbWidth = (sourceWidth < thumbWidth) ? sourceWidth : thumbWidth;

return getThumbnail(image, thumbWidth, thumbHeight);
}

/**
* Gets thumbnail with a height and width specified ..
* @param image
* @param thumbWidth
* @param thumbHeight
* @return scaled image
*/
private final Image getThumbnail(Image image, int thumbWidth, int thumbHeight) {
int x, y, pos, tmp, z = 0;
final int sourceWidth = image.getWidth();
final int sourceHeight = image.getHeight();

// integer ratio ..
final int ratio = sourceWidth / thumbWidth;

// buffer where we read in data from image source
final int[] in = new int[sourceWidth];

// buffer of output thumbnail image
final int[] out = new int[thumbWidth*thumbHeight];

final int[] cols = new int[thumbWidth];

// pre-calculate columns we need to access from source image
for (x = 0,pos = 0; x < thumbWidth; x++) {
cols[x] = pos;

// increase the value without fraction calculation
pos += ratio;
tmp = pos + (thumbWidth - x) * ratio;
if(tmp > sourceWidth) {
pos--;
} else if(tmp < sourceWidth - ratio) {
pos++;
}
}

// read through the rows ..
for (y = 0, pos = 0, z = 0; y < thumbHeight; y++) {

// read a single row ..
image.getRGB(in, 0, sourceWidth, 0, pos, sourceWidth, 1);

for (x = 0; x < thumbWidth; x++, z++) {
// write this row to thumbnail
out[z] = in[cols[x]];
}

pos += ratio;
tmp = pos + (thumbHeight - y) * ratio;
if(tmp > sourceHeight) {
pos--;
} else if(tmp < sourceHeight - ratio) {
pos++;
}
}
return Image.createRGBImage(out, thumbWidth, thumbHeight, false);
}


This code has low memory need as it only reads one row from the source image each time. And it is super fast (I mean unbelievable). If anyone argue at this point or think there is a way to make it more optimized then welcome :) ..

6 comments:

Anonymous said...

Hi, I just needed to do the same thing and my code is very similar to yours :-) Only I have a limitation for buffer size and if the image data is smaller than this limit, I don't get bytes line per line.
However, the main problem for me is not to resize the image, but to create Image instance from byte data/stream without memory exception... I have Nokia N95 for testing, but it always ends with memory exception when loading high quality snapshots (width 2500px).

Anonymous said...

Hi.
I've implemented your code in a Nokia 6120 classic to resize camera image to fit the screen. It works fast, but I've noticed that the image is distorted, i.e. if you take a snapshot of a straight line like an edge of a paper - the resized image is warped. Do you know why?

Unknown said...

@madyar I think you need to keep the ratio right ..

Anonymous said...

the algorithm doesn't work for upscaling and throws and exception

Brad said...
This comment has been removed by the author.
Anonymous said...

Your algorithm is very fast but incorrect. Madyar pointed it out first, the image becomes distorted, not sure why yet.