Tuesday, November 11, 2008

Image Processing in Java. Dim, Regions, Thumbnails, etc.

Using Java APIs for getting dim, selecting region, thumbnails, etc..

The Java 2D API treats all the images as a rectangular 2D array of pixels, where a pixel is nothing but the representation of the color at that position in the image and evidently dimension is the horizontal (width) and vertical (height) extent of the image.

The API uses representation of images as objects of the class java.awt.image.BufferedImage class and once you have created an object of this class for an image, you can play with any of the available APIs (which support quite a few image processing capabilities) to have a programmatic image processing experience. These objects can either be created directly or by providing an external image of any of the supported image types (almost all popular types are supported including GIF, PNG, JPEG, BMP, and WBMP). The Image I/O API is bundled in the javax.imageio package. Image I/O is extensible as well, which means plug-ins for non-supported file formats can be used easily. TIFF and JPEG 2000 plug-ins are separately available and are the widely used plug-ins with Image I/O.

The APIs not only allow you to draw different objects (lines, texts, or even images) over the image objects, gather info about the images like width, height, etc., but also allow you to blur or sharpen the images. That means you will virtually have almost everything you need to develop a image processing tool like Picasa :-)

java.awt.image.BufferedImage which is a sub-class of java.awt.Image class manages the image in memory and this class has methods for retrieving or manipulating pixel data of the image.

Reading/Writing an image

BufferedImage image = null;
File imageFile = new File("C:\images\geek.jpeg") //image file path


image = ImageIO.read(imageFile);

}catch(IOExcetion ioe){

System.err.println("ERROR: image '" + imageFile.getName() + "' " + "couldn't be read!");

The read method has several formats, each of which support reading an image in a different way. For example: we can read image directly by specifying the image file path or if we are reading an image in an applet then we can simply provide the URL of the image obtained from the Applet Codebase.

Similarly we can use the write method to write a supported image file. A sample code snippet can be like this:

BufferedImage image = null;
File imageFile = new File("C:\images\geek.jpeg") //image file path


ImageIO.write(image, "jpeg", imageFile);

}catch(IOExcetion ioe){

System.err.println("ERROR: image '" + imageFile.getName() + "' " + "couldn't be written!");

How do the APIs identify a supported image file format?

The file formats are identified automatically by reading the "magic number" of the image file which almost every image contains in their first few bytes. If no such magic number is found or if the number is nor recognizable then the API fails and we may require some sort of extra code to make the APIs work for such image files or we may require to install separately available plug-ins to support them.

How to get the currently supported formats for reading/writing?

This can be obtained pro grammatically by calling the APIs ImageIO.getReaderFormatNames() and ImageIO.getWriterFormatNames(), respectively. You may like to extract the image file extension and check if it's one of the supported formats or not and this may allow your application to handle any image file format (supported or unsupported) gracefully.

Using ImageReader.read instead of ImageIO.read

Instead of using ImageIO.read() method to decode the entire image , an application may use the ImageReader (obtained from the ImageIO class) to have a better control on image decoding and reading.

These readers can be obtained either by specifying the image file type or file suffix, MIME type, or it may even be based on the file contents. As we know that a "gif" file is actually a series of files which are repainted at a certain time interval to give the feel of animation. In such cases, an ImageReader can be used to read all the images contained in a single "gif" image. We use ImageIndex for that. The index starts from "0" and obviously we would require to provide only "0" for those image file types which contain only one image. Find below a sample code snippet:

Iterator imageReaders = ImageIO.getImageReaders("gif");
ImageReader imageReader = (ImageReader)imageReaders.next();

Object imageSource; // it can be either File or InputStream
ImageInputStream iis = ImageIO.createImageInputStream(imageSource);

//...attaching the image source to the reader
imageReader.setInput(iis, true);

//...reading first image
BufferedImage image1 = reader.read(0);

Obtaining info like height, width from an image

Height or Widhth can be obtained once you've obtained a ImageReader and attached it to the image source you're interested to find the info about. ImageReader not only allows you to have these info, but also goes a step further and allows you to sample the image by specifying the region of the image you're interested into. This avoids reading the entire image, say for example if you're interested only in the top left quadrant. Obviously a performance boost.

Image Width and Height can be obtained by calling the obvious-looking APIs named getImageWidth(imageIndex) and getImageHeight(imageIndex). The APIs return an 'int' value which can be manipulated to specify a selected region of the image using the corresponding co-ordinates. We just need to pass an object of type ImageReadParam in this case which encapsulates all the parameters. Find below a sample code snippet for better understanding:

int imageIndex = 0;

//...obtaining height and width
int width = imageReader.getImageWidth(imageIndex);
int height = imageReader.getImageHeight(imageIndex);

//... creating a region for the upper-left quadrant
Rectangle rectangle = new Rectangle(0, 0, width/2, height/2);

//... getting the default ImageReadParam and setting the parameters
ImageReadParam imageReadParam = imageReader.getDefaultReadParam();

//... now we can read the upper-left quadrant by passing the ImageReadParam object
BufferredImage upperLeftQuad = imageReader.read(imageIndex, imageReadParam);

Similarly we can call any of the available APIs to set the corresponding parameters in the ImageReadParam object and once done with that then we simply need to pass that to the read method of the ImageReader class and we're done!

Reading/Accessing Thumbnail images

Thumbnails are a small preview (or maybe multiple depending on the particular image format) associated with the main image. Accessing thumbnails is evidently much faster as they are of smaller size and hence they can be decoded faster. ImageReader provides API to find out the number of thumbnails available with a particular image. In case the image has multiple images (as is the case with GIF images) then each of these images can have their own thumbnails. Find below a sample code snippet illustrating how easily can you use these APIs:

int numOfThumbnails = 0;
numOfThumbnails = imageReader.getNumThumbnails(imageIndex);

for(int i = 0; i < thumbnailimage =" imageReader.readThumbnail(imageIndex,">

This sort of completes our discussion of the ImageReader class and its possible uses. ImageWriter can also be used in a very similar way and I hope it would not be a tough task using the APIs belonging to that class assuming that you now have a fair understanding of the image processing in Java. Do let me know in case any of the APIs of the ImageWrite class need any further discussion. I've removed try-catch from some of the code-snippets just to avoid cluttering and to maintain teh focus on the actual APIs. These code-snippets are just to give you a feel as how easily you can use the APIs. Use them as per your application requirements. See the Sun Java documentation of the classes for the entire list of APIs available. Some of them are: ImageIO, ImageReader, ImageWriter, ImageReadParam.


1 comment:

Anonymous said...

Nice One.....