Friday, November 16, 2007

Process images in Java with JAI

The Java Advanced Imaging (JAI) API provides a professional approach to the imaging in Java. It offers many advantages to the developer through high-level programming model. The model allows you to create your own image manipulation routines without the additional cost or licensing restrictions associated with commercial image processing software.

You can download JAI directly from Sun Microsystems for Windows, Solaris, and Linux platforms. Apple provides a version of JAI on its Web site for Mac OS X 10.3; Mac OS X 10.4 ships with the API preinstalled.

Platform-specific implementations can either use the pure Java implementation or provide an implementation that takes advantage of native technology on the platform to offer better performance.

Data representation

Images in JAI may be multidimensional (i.e., several values associated to a single pixel) and may have pixels with integer or floating point values. Pixels may be packed in different ways or unpacked in the image data array. Also, different color models can be used.

In order to be able to represent a variety of image data, you must deal with an assortment of classes. Before I present examples of the classes in action, here are descriptions of the basic classes for image data representation.

  • PlanarImage: This is the basic class for image representation in JAI; it allows the representation of images with more flexibility than the Java class BufferedImage. (BufferedImage and PlanarImage use several different classes for flexible image data representation.) Image pixels are stored in an instance of Raster object, which contains a concrete subclass of DataBuffer filled accordingly to the rules described by one of the subclasses of SampleModel. A PlanarImage is read-only (i.e., it may be created), and pixels values may be read in several different ways, but there are no methods that allow the modification of pixels values.
  • TiledImage: This subclass of PlanarImage can be used for reading and writing image data.
  • RenderedOp: This subclass of PlanarImage represents a node in a rendered imaging chain. A rendered imaging chain is a powerful and interesting concept in JAI that allows the processing of an image to be specified as a series of steps (operators and parameters) that are applied to one or more image.

Another interesting concept in JAI is tiled images. Tiles can be considered subsets of the images that may be processed independently. Large images can be processed in JAI with reasonable performance (even through rendered imaging chains), since there is no need to load the whole image data in memory at once. If the image is tiled, all its tiles must have the same width and height.

Figure A shows a simple tiled image, where the origin of the tiles coincides with the origin of the image, but the tiles are extended past the image edges (as is often the case). When a tile extends past the image edges, its contents are undefined.
Figure A

Figure A

Image creation

In order to create an image in the memory, follow these steps:

  1. Create the image data in an array in memory. This array must be a unidimensional array (for simplicity, you can create a multidimensional array and flatten it later).
  2. Create an instance of a concrete subclass of DataBuffer using one of its constructors and the image data array.
  3. Create an instance of SampleModel with the same data type of the DataBuffer and desired dimensions. A factory method of the class RasterFactory may be used for this.
  4. Create an instance of ColorModel compatible with the sample model being used. The static method PlanarImage.createColorModel() may be used for this, using the sample model as an argument.
  5. Create an instance of WritableRaster using the sample model and the image data array. The method RasterFactory.createWritableRaster() can be used for this.
  6. Create a writable image (i.e., an instance of TiledImage) using the sample model, color model, and dimensions.
  7. Associate the instance of Raster with the image using the method setData() of the class TiledImage.
  8. Do something with the instance of TiledImage, such as save it to disk or display or process it.

Below is the Java code listing that creates a floating-point one-banded (grayscale) image.

package test.jai;
import java.awt.Point;
import java.awt.image.*;
public class CreateGrayImage
public static void main(String[] args)
int width = 1024; int height = 1024; // Dimensions of the image.
float[] imageData = new float[width*height]; // Image data array.
int count = 0; // Auxiliary counter.
for(int w=0;w for(int h=0;h imageData[count++] = (float)(Math.sqrt(w+h));
// Create a DataBuffer from the values on the image array. dbuffer =
// Create a float data sample model.
SampleModel sampleModel =
// Create a compatible ColorModel.
ColorModel colorModel = PlanarImage.createColorModel(sampleModel);
// Create a WritableRaster.
Raster raster = RasterFactory.createWritableRaster(sampleModel,dbuffer,
new Point(0,0));
// Create a TiledImage using the float SampleModel.
TiledImage tiledImage = new TiledImage(0,0,width,height,0,0,
// Set the data of the tiled image to be the raster.
// Save the image on a file.

If you want to get information about an existing image, you can use several getXXX() methods from the classes PlanarImage, SampleModel, and ColorModel.

Image operations

The JAI API contains several image operators that you can apply with minimum programming. The operators follow the concept of a rendered imaging chain, where the steps for the image processing are defined but will be carried out only when needed (deferred execution).

Those operations are specified in a simple way: First an instance of ParameterBlock is created (which is basically a vector of data that will be used for the operation), and then the static method create() of the class JAI is executed. This method gets as an argument a name for the operation and the instance of ParameterBlock and returns an instance of RenderedOp, which can be manipulated as a PlanarImage. Alternatively, you can add the original image in the instance of ParameterBlock as a parameter to its addSource() method. Other parameters are added to the ParameterBlock with its add() method. Other forms of the method do not require a ParameterBlock and accept other arguments.

One example of a JAI operator is the filestore operator, which is used in the code listing above to store an instance of PlanarImage (or of a subclass of it) in a file. The call for the JAI.create() method used the following as arguments: the name of the operator, the instance of PlanarImage, a file name, and a string containing the desired image file name (TIFF, JPEG,”PNG, etc.).

Other possible operators include: scale, rotate, and convolve. The convolve operator performs convolution of an image with a kernel, which can be created as an instance of the class KernelJAI. This instance is created with an array that represents the kernel values; then, the instance of KernelJAI may be used even without a ParameterBlock.

The code below shows how you can create a 15 x 15 smoothing kernel and apply it to an input image, resulting in an output image. The kernel values must be normalized (i.e., they must add up to one).

int kernelSize = 15;
float[] kernelMatrix = new float[kernelSize*kernelSize];
for(int k=0;kkernelMatrix[k] = 1.0f/(kernelSize*kernelSize);
KernelJAI kernel = new KernelJAI(kernelSize,kernelSize,kernelMatrix);
PlanarImage output = JAI.create("convolve", input, kernel);


The JAI API offers application developers various advantages in comparison to other imaging solutions. These advantages include the following:

  • It’s a cross-platform imaging platform with the support of distributed calculations.
  • It has an object-oriented API that is extensible and flexible.
  • It supports Java’s Remote Method Invocation and the Internet Imaging Protocol for its network-based imaging; this allows for scalable solutions from PDAs, laptops, desktops, and high-end servers.

Additional resources about the JAI API

No comments: