- Applying Rendering Hints
- Managing Memory
- Scheduling Tiles
- Reformatting an Image
- Extending the Border
- A Rendering Example
- A Closer Look at the PlanarImage Class
- Using the RenderedOp Class
- Working with Tiles
- A Tiled-Image Viewer
- Writing to Pixels
- Creating an Aggregate Image
- A JAI Image Browser
- The Renderable Layer
- Conclusion
The Renderable Layer
We already described the RenderableImage interface in Chapter 10. Many JAI standard operators take RenderableImage as an input source. The RenderableOp class is the equivalent of RenderedOp for a renderable layer. In other words, RenderableOp represents a node in the renderable chain.
A rendering chain has a source and a sink. The first step in building a renderable chain is to create a renderable sourcethat is, a renderable image.
Even though the renderable image has no size and lacks many other physical attributes, a real image is needed as a source. To construct a renderable chain, a renderable image is derived from this image.
TABLE 11.5 Renderable Operator Parameters
Operator Name |
Parameter |
Type |
Default Value |
Description |
Renderable |
downSampler |
RenderedOp |
null |
Rendered chain that supplies low-resolution images |
maxLowResDim |
float |
64 |
||
minX |
float |
0.0f |
||
minY |
float |
0.0f |
||
height |
int |
1.0f |
An image in Java can be loaded as an Image, BufferedImage, or PlanarImage object. There is no equivalent of PlanarImage for the renderable chain. Instead, JAI provides an operator named Renderable, which converts RenderedImage to RenderableImage. Table 11.5 shows the parameters for this operator.
Obviously the Renderable operator does not take the renderable image as a source. This operator creates a pyramid of low-resolution images. But normally you would want to create a single image. Listing 11.12 shows an example that uses the Renderable operator.
LISTING 11.12 Converting to a renderable node
public static RenderableOp convertToRenderable(PlanarImage image) { ParameterBlock pb = new ParameterBlock(); pb.addSource(image); pb.add(null).add(null).add(null).add(null).add(null); return JAI.createRenderable("renderable", pb); }
To apply standard operators to a renderable image, use the JAI.create Renderable() method, which returns a RenderableOp node.
Creating a Renderable Node
In the renderable chain, the RenderableOp class represents a renderable node. The RenderableOp class implements the RenderableImage interface. Recall from Chapter 10 that the RenderableImage interface has three different methods for creating a rendering: createDefaultRendering(), createScaledRendering(), and createRendering(). Each one returns a RenderedImage object.
To evaluate a renderable node, call one of the three rendering methods. While these methods are being executed, the size is assigned to the image. The RenderedImage object returned by these methods can be rendered through Graphics2D's drawRenderedImage() method. This means that the same components we developed for displaying planar images can be used for rendering a renderable node.
NOTE
The source code for RenderableImageCanvas is available on the book's Web page in the directory src/chapter11/simplerenderable.
However, the Graphics2D class has a method that directly renders a renderable image:
public abstract void drawRenderableImage(RenderableImage img, AffineTransform xform)
Let's use this method to display a renderable image. As stated earlier, a renderable image has no size. This means that it has no width or height properties. However, a renderable image does have an aspect ratio. Because the renderable image has no size, the image viewer will need to provide the input. The options are
To fit the viewport. Regardless of the size of a viewport, the size of the displayed image is increased or decreased to fit the viewport.
Scaled. The size of the image fits the viewport, but the aspect ratio is maintained.
Original size. The size matches the size of the source image from which the renderable image is generated.
Let's first build a canvas to display the renderable image. Listing 11.13 shows the class (RenderableImageCanvas) that can do this. It is similar to JAIImageCanvas, except for the paintComponent() method.
LISTING 11.13 The RenderableImageCanvas class
package com.vistech.jai.render; import java.io.*; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import javax.swing.*; import java.awt.geom.*; import java.awt.image.renderable.*; import javax.media.jai.*; import com.vistech.imageviewer.*; public class RenderableImageCanvas extends JComponent implements ScrollController { public final static int TO_FIT =0; public final static int SCALED = 1; public final static int MAX_SIZE = 4; protected RenderableImage sourceImage; protected AffineTransform atx = new AffineTransform(); protected boolean imageDrawn = false; protected int panX =0, panY =0; private Point scrollAnchor = new Point(0,0); protected boolean scrollOn = true; protected int viewerWidth = 480, viewerHeight = 400; protected Point panOffset = new Point(0,0); protected int displayMode = SCALED; protected int interpolationMode = Interpolation.INTERP_BILINEAR; protected float sourceImageHeight = 1.0f; protected float sourceImageWidth = 1.0f; protected int maxHeight =1024, maxWidth = 1024; public RenderableImageCanvas() {} public RenderableImageCanvas(RenderableImage img){ atx = new AffineTransform(); setImage(img); } public boolean isImageDrawn(){ return imageDrawn;} public void setDisplayMode(int dispMode) { displayMode = dispMode; createScalingTransform(); } public int getDisplayMode(){ return displayMode;} public void setOrigImageSize(Dimension size){ maxWidth = size.width; maxHeight = size.height; } public void setImage(RenderableImage rImg){ sourceImage = rImg; panX =0; panY =0; sourceImageHeight = sourceImage.getHeight(); sourceImageWidth = sourceImage.getWidth(); createScalingTransform(); imageDrawn = false; repaint(); } protected void createScalingTransform(){ int wid = viewerWidth, ht = viewerHeight; double aspectRatio = sourceImageWidth/sourceImageHeight; switch (displayMode) { case SCALED: if(aspectRatio > 1.00) ht = (int)(viewerHeight/aspectRatio); else wid = (int)(viewerWidth/aspectRatio); atx.setToScale(wid/(double)sourceImageWidth,ht*sourceImageHeight); break; case MAX_SIZE: wid = maxWidth; ht = maxHeight; atx.setToScale(maxWidth/(double)sourceImageWidth, maxHeight*sourceImageHeight); break; case TO_FIT: atx.setToScale(viewerWidth/(double)sourceImageWidth, sourceImageHeight*viewerHeight); break; } repaint(); } public void paintComponent(Graphics gc){ Graphics2D g = (Graphics2D)gc; Rectangle rect = this.getBounds(); if((viewerWidth != rect.width) || (viewerHeight != rect.height)){ viewerWidth =rect.width; viewerHeight = rect.height; createScalingTransform(); } g.setColor(Color.black); g.fillRect(0, 0, viewerWidth, viewerHeight); if(sourceImage == null) return; try { Point2D dest = null; dest = atx.inverseTransform(new Point(panX,panY), dest); atx.translate(dest.getX(), dest.getY()); g.drawRenderableImage(sourceImage, atx); }catch (Exception e) {} imageDrawn = true; } public void setPanOffset(Point panOffset){ firePropertyChange("PanOffset",this.panOffset,panOffset); this.panOffset = panOffset; panX = panOffset.x; panY = panOffset.y; } public Point getPanOffset(){ return panOffset;} public void setScrollOn(boolean onOff){ scrollOn = onOff; panX =0; panY =0; } public boolean getScrollOn(){ return scrollOn;} public void startScroll(int x, int y){ scrollAnchor.x = x- panX; scrollAnchor.y = y- panY; repaint(); } public void scroll(int x, int y){ if((x <0 )|| (y<0)) return; panX = x-scrollAnchor.x; panY = y-scrollAnchor.y; repaint(); } public void stopScroll(){ setCursor(Cursor.getDefaultCursor());} public void reset(){ atx = new AffineTransform(); panX =0; panY =0; } }
Just like the JAIImageCanvas class, RenderableImageCanvas implements ScrollController, which allows you to scroll the displayed image. To display a renderable image, the client object calls the setImage() method. This method first obtains the width and height of the renderable image. The height of a renderable image always equals 1.0f, and the width is given by 1/aspect ratio. This means that the rendering operations are performed on an image that is 1 pixel high and (1/aspect ratio) wide.
The setImage() method then sets the scaling factor by calling the createScalingTransform() method, which determines the scaling factor on the basis of the user's choice and sets the atx instance variable. Users have three choices:
SCALED
TO_FIT
ORIG_SIZE
The original size of the RenderableImage cannot be determined from the RenderedImage class, so setOrigImageSize() sets this variable.
The paintComponent() method uses atx (AffineTransform) to
Compute the translation parameters in the RenderableImage space by inverse-transforming the panX and panY variables. This computation is a must because the renderable image is very small compared to the viewport.
Concatenate this translation.
Perform drawRenderable().
Every time the paintComponent() method is called to paint the scrolled image, a lot of computation is involved. You will notice this if you try to scroll a large image.
Next let's build an image viewer using the RenderableImageViewer class, which is shown in Listing 11.14.
LISTING 11.14 The RenderableImageViewer class
public class RenderableImageViewer extends JAIImageViewer{ RenderableImageCanvas viewer; public static void main(String[] args){ RenderableImageViewer ip = new RenderableImageViewer(); if(args.length <1) { ip.createUI(); }else ip.loadAndDisplay(args[0]); } public void displayImage(PlanarImage img) { int wid = img.getWidth(); int ht = img.getHeight(); ParameterBlock pb = new ParameterBlock(); pb.addSource(img); pb.add(null).add(null).add(null).add(null).add(null); RenderableOp op = JAI.createRenderable("renderable", pb); viewer.setImage(op); viewer.setOrigImageSize(new Dimension(wid, ht)); saver.setImage(img); viewer.repaint(); } public void createUI() { Dimension dim= Toolkit.getDefaultToolkit().getScreenSize(); int width = (int)(dim.width *3/4.0); int height = (int)(dim.height*3/4.0); setTitle("Renderable Image Viewer"); viewer = new RenderableImageCanvas(); Dimension d = getViewerSize(width/(double)height); viewer.setPreferredSize(new Dimension(d.width, d.height)); createImageLoaderAndSaver(); loader.addPlanarImageLoadedListener( new PlanarImageLoadedListener() { public void imageLoaded(PlanarImageLoadedEvent e) { PlanarImage image = e.getImage(); if(image == null) return; SwingUtilities.invokeLater(new ImagePaint(image)); } } ); // Layout code not shown. } // DisplayModePanel code not shown. } class ImagePaint implements Runnable { PlanarImage image; boolean firstTime = true; public ImagePaint(PlanarImage image){this.image = image;} public void run() { if(firstTime) { try { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); displayImage(image); firstTime = false; SwingUtilities.invokeLater(this); } catch(Exception e){SwingUtilities.invokeLater(this);} } else { if(!viewer.isImageDrawn()) SwingUtilities.invokeLater(this); else{ setCursor(Cursor.getDefaultCursor()); } } } }
NOTE
The source code for TiledRenderableImageCanvas is available on the book's Web page in the directory src/chapter11/renderable.
The RenderableImageViewer class is a subclass of JAIImageViewer (see Listing 10.3). As in JAIImageViewer, the image is painted in a thread. The ImagePaint thread is invoked when an image is loaded. The run() method of ImagePaint calls the displayImage() method. The displayImage() method first converts PlanarImage to RenderableImage using the Renderable operator and then sends it to Renderable ImageCanvas. Figure 11.9 shows a screen shot of the renderable-image viewer application with a large image scaled to fit the viewport size.
FIGURE 11.9 Scaling a large image to the viewport size
To speed up image manipulation, we can compute tiles and paint only those needed in the paintComponent() method. We did this already in the RenderedImageCanvas class. So to display a renderable image, let's create a canvas that is a subclass of RenderedImageCanvas. We'll call this class TiledRenderable ImageCanvas. There is no need to override the paintComponent() method because we can create a rendered image by calling RenderableOp's createRendering() method. Listing 11.15 shows the code for TiledRenderable ImageCanvas.
LISTING 11.15 The TiledRenderableImageCanvas class
package com.vistech.jai.render; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.io.*; import javax.swing.*; import javax.swing.event.*; import java.awt.geom.*; import java.awt.image.renderable.*; import javax.media.jai.*; public class TiledRenderableImageCanvas extends RenderedImageCanvas { public final static int TO_FIT =0; public final static int SCALED = 1; public final static int ORIG_SIZE = 4; protected RenderableImage sourceImage; protected int interpolationMode = Interpolation.INTERP_BILINEAR; protected int displayMode = ORIG_SIZE; protected float sourceImageHeight = 1.0f; protected float sourceImageWidth = 1.0f; protected int maxHeight =1024, maxWidth = 1024; public TiledRenderableImageCanvas() {} public TiledRenderableImageCanvas(RenderableImage img){ atx = new AffineTransform(); setImage(img); } public void setImage(RenderableImage pixelLessImg){ PlanarImage img = createPlanarImage(pixelLessImg); displayImage = makeTiledImage(img); sampleModel = displayImage.getSampleModel(); colorModel = displayImage.getColorModel(); getTileInfo(displayImage); imageDrawn = false; repaint(); } public void setDisplayMode(int dispMode) { displayMode = dispMode; setImage(sourceImage); } public int getDisplayMode(){ return displayMode;} public void setOrigImageSize(Dimension size){ maxWidth = size.width; maxHeight = size.height; } public void setInterpolationMode(int interpMode) { interpolationMode = interpMode;} public int getInterpolationMode(){ return interpolationMode;} protected PlanarImage createPlanarImage(RenderableImage pixelLessImg){ panX =0; panY =0; sourceImage = pixelLessImg; sourceImageHeight = sourceImage.getHeight(); sourceImageWidth = sourceImage.getWidth(); return createPixelImage(pixelLessImg); } protected PlanarImage createPixelImage(RenderableImage pixelLessImg){ RenderingHints hints = new RenderingHints(JAI.KEY_INTERPOLATION, Interpolation.getInstance(interpolationMode)); int wid = viewerWidth, ht = viewerHeight; double aspectRatio = sourceImageWidth/sourceImageHeight; switch (displayMode) { case SCALED: if(aspectRatio > 1.00) ht = (int)(viewerHeight/aspectRatio); else wid = (int)(viewerWidth*aspectRatio); break; case ORIG_SIZE: ht = maxHeight; wid = maxWidth; break; case TO_FIT: } if(sourceImage == null) return null; return (PlanarImage) sourceImage.createScaledRendering(wid, ht, hints); } public void setTileWidth(int tw){ tileWidth = tw; setImage(sourceImage); repaint(); } public void setTileHeight(int th){ tileHeight = th; setImage(sourceImage); repaint(); } }
The TiledRenderableImageViewer application uses TiledRenderableImage Canvas to display images. We won't show the source code for this application because it is similar to the source code for the RenderableImageViewer application shown in Listing 11.14. Figure 11.10 shows a screen shot of the TiledRenderable ImageViewer application.
FIGURE 11.10 The tiled renderable-image viewer