Home > Articles > Programming > Java

  • Print
  • + Share This
This chapter is from the book

Writing to Pixels

In our examples so far, we have used only planar images. As stated earlier, PlanarImage is read-only. To write pixels, you need to use the TiledImage class, which extends PlanarImage and implements WritableRenderedImage. As we saw in Chapter 10, the WritableRenderedImage interface handles the writing of pixel data to tiles.

To write pixels, you need to obtain a WritableRaster object for the desired rectangular region. You cannot write to this instance of WritableRaster immediately because unlike BufferedImage, TiledImage does not have all its data resident in the memory. So you need to get all the tiles that cover the rectangular region. Recall from Chapter 10 that the WritableRenderedImage interface specifies methods for checking out, writing, and releasing tiles. The TiledImage class implements those methods.

Constructing a Tiled Image

First let's see how to construct a TiledImage object. The TiledImage class has three public constructors:

  1. public TiledImage(int minX,
                      int minY,
                      int width,
                      int height,
                      int tileGridXOffset,
                      int tileGridYOffset,
                      java.awt.image.SampleModel sampleModel,
                      java.awt.image.ColorModel colorModel)
  2. public TiledImage(Point origin, SampleModel sampleModel,
                      int tileWidth,
                      int tileHeight)
  3. public TiledImage(SampleModel sampleModel, 
                      int tileWidth,
                      int tileHeight)

These constructors take attributes of the tiled image as input parameters. For a detailed explanation of the parameters, see Tables 10.1 and 10.2. When parameters are not provided, default values are assumed.

Notice that the TiledImage constructors don't have an argument for data. These methods construct a skeleton image. You can set the data later, either by calling the setData() method or by drawing over it using its graphical context. In this respect, a tiled image is just like a buffered image.

One way to create a tiled image is to start from scratch—that is, create an instance of SampleModel and of ColorModel and pass them to the TiledImage constructor along with other parameters. You can also use the factory methods that will be described shortly to create a tiled image.

More often, however, it is necessary to construct a tiled image from a planar image. To do so, use the existing PlanarImage object to get the attributes and then copy the PlanarImage data into the TiledImage object.

Here's an example of code that creates a tiled image from a planar image:

  public static TiledImage createDisplayImage(PlanarImage image){
   SampleModel sampleModel = image.getSampleModel();
   ColorModel colorModel = image.getColorModel();

   TiledImage ti = new TiledImage(image.getMinX(), image.getMinY(),
                  image.getWidth(), image.getHeight(),
                  image.getTileGridXOffset(),
                  image.getTileGridYOffset(),
                  sampleModel, colorModel);
   ti.setData(image.copyData());
   return ti;
  }

In addition to its constructors, the TiledImage class has two factory methods for creating an instance of TiledImage:

  1. public static TiledImage createBanded(int minX,
                                          int minY,
                                          int width,
                                          int height,
                                          int dataType,
                                          int tileWidth,
                                          int tileHeight,
                                          int[] bankIndices,
                                          int[] bandOffsets)
  2. public static TiledImage createInterleaved(int minX,
                                               int minY,
                                               int width,
                                               int height,
                                               int numBands,
                                               int dataType,
                                               int tileWidth,
                                               int tileHeight,
                                               int[] bandOffsets)

The first static method creates an instance of TiledImage with the band-interleaved sample model, and the second method creates the pixel-interleaved sample model. See Chapter 8 for a detailed explanation of these sample models.

The dataType argument indicates the data type. The Java 2D API supports only int, byte, short, and unsigned short types. The JAI APIs support float and double types in addition to the basic types. The DataBuffer class, however, can take any constant representing any type.

As stated earlier, the constructors don't have image data as one of the input parameters. To set the data, the TiledImage class provides different flavors of setXXX() methods. Using these methods, you can set an individual pixel or pixels covering a rectangular region.

Setting the Pixel Value

The TiledImage class does not have a setPixel() method per se. However, it has three flavors of setSample(), which set the value of a sample. Recall from Chapter 8 that a sample is a component of a pixel; that is, a pixel in an RGB image has three samples—one each for red, green, and blue. Unlike a Java 2D image, a JAI image sample can assume float or double values. Accordingly, the setSample() method has the following three flavors for setting int, float, and double values:

  1. public void setSample(int x, int y, int b, int s)

  2. public void setSample(int x, int y, int b, float s)

  3. public void setSample(int x, int y, int b, double s)

The x and y parameters represent the coordinates of a pixel, b represents the band index, and s represents the sample value. Corresponding to the setSample() methods, three methods of the TiledImage class are available for retrieving an individual sample value:

  1. public int getSample(int x, int y, int b)

  2. public float getSampleFloat(int x, int y, int b)

  3. public double getSampleDouble(int x, int y, int b)

There is another way to set a pixel value: by using the WritableRenderedImage methods. We explained these methods in Chapter 8. To write a pixel, first get the index of the tile in which the pixel is located, then check out that tile for writing. Here's an example:

  public static void writePixel(int x, int y, short[] pixelValue TiledImage image) {
   Int xIndex = image.XtoTileX(x);
   int yIndex = image.YtoTileY(y);
   WritableRaster tileRaster = image.getWritableTile (xIndex, yIndex);
   If (tileRaster != null) tileRaster.setPixel(x,y, pixelValue);
   ReleaseWritableTile(xIndex, yIndex);
 }

Setting a Rectangular Region

There are two ways to write to a rectangular region of an image:

  1. Writing tile by tile. You can use the WritableRenderedImage interface methods to write to a rectangular region.

  2. Writing to a rectangular region directly.

Here are the methods:

  • public void set(java.awt.image.RenderedImage im)

  • public void set(java.awt.image.RenderedImage im, ROI roi)

Both of these methods copy the specified RenderedImage data onto a TiledImage object. The sample model of the source image should be compatible with that of the target image. The position and size of the overlapping area are determined from the source image attributes. If the source image area does not overlap the target image, these methods have no effect.

In the case of the latter method, which has an ROI parameter, the ROI overlaps the target image. The overlapping area and its position are computed from the corresponding ROI attributes.

The following two methods use the Raster parameter to pass the image data:

  1. public void setData(java.awt.image.Raster r)

  2. public void setData(java.awt.image.Raster r,ROI roi)

The Raster data provided by the input parameter is written over the target image. The overlapping area and position are computed from the coordinates of the input raster. These methods use the WritableRenderedImage methods to write to tiles. When the data is being written, the affected tiles are checked out and locked.

Creating a Subimage

Just as in BufferedImage, the TiledImage methods create a subimage from a tiled image:

  • public TiledImage getSubImage(int x, int y, int w, int h)

  • public TiledImage getSubImage(int[] bandSelect)

  • public TiledImage getSubImage(int x, int y, int w, int h,
                                  int[] bandSelect)

The subimage follows the same coordinate system as the source image. The x and y parameters represent the upper left-hand corner coordinates of the subimage. The w and h parameters represent the width and height of the subimage, respectively. The bandSelect parameter is an array of indices representing the band indices in the source images. Using this parameter, you can create a subimage containing only the selected bands. The subimage() methods are useful in creating cropped images.

Obtaining an Off-Screen Graphical Context

Again like BufferedImage, the TiledImage class has methods for drawing graphical objects over it. The method signatures are the same as for the BufferedImage methods:

  • public java.awt.Graphics getGraphics()

  • public java.awt.Graphics2D createGraphics()

The first method returns the AWT graphical context, and the second returns the Java 2D graphical context.

NOTE

The code for aggregating images is available on the book's Web page in the directory src/jaiutil/JAIUtil.java.

  • + Share This
  • 🔖 Save To Your Account