Contents Previous Next

Programming in Java Advanced Imaging


C H A P T E R12

Client-Server Imaging




THIS chapter describes JAI's client-server imaging system.

12.1 Introduction

Client-server imaging provides the ability to distribute computation between a set of processing nodes. For example, it is possible to set up a large, powerful server that provides image processing services to several thin clients. With JAI, it is possible for a client to set up a complex imaging chain on a server, including references to source images on other network hosts, and to request rendered output from the server.

JAI uses Java Remote Method Invocation (RMI) to implement client-server imaging. To communicate using Remote Method Invocation, both the client and server must be running Java. A stub object is instantiated on the client. The stub object forwards its method calls to a corresponding server object. Method call arguments and return values are transmitted between the client and server by means of the Java Development Environment's serialization capability.

The hostname and port depend on the local setup. The host must be running an RMI registry process and have a RemoteImageServer listening at the desired port.

This call will result in the creation of a server-side RMIImageImpl object and a client-side stub object. The client stub serializes its method arguments and transfers them to the server over a socket; the server serializes its return values and returns them in the same manner.

12.2 Server Name and Port Number

The RemoteImage constructor requires a serverName parameter that consists of a host name and port number, in the following format:

     host:port
For example:

     camus.eng.sun.com:1099
The port number is optional and need be supplied only if the host name is supplied. If the serverName parameter is null, the default is to search for the RMIImage service on the local host at the default rmiregistry port (1099.


API: javax.media.jai.RemoteImage

constructs a RemoteImage from a RenderedImage.

Parameters:

serverName

The name of the server in the appropriate format.

source

A RenderedImage source.

constructs a RemoteImage from a RenderedOp, i.e., an imaging DAG (directed acyclic graph). Note that the properties of the RemoteImage will be those of the RenderedOp node and not of its rendering.

constructs a RemoteImage from a RenderableOp and RenderContext. The entire RenderableOp DAG will be copied over to the server. Note that the properties of the RemoteImage will be those of the RenderableOp node and not of its rendering.

12.3 Setting the Timeout Period and Number of Retries

A network error or a delay caused by the server failing to respond to the request for an image is dealt with through retries. If, on the first attempt, the server fails to respond, the program will wait a specified amount of time and then make another request for the image. When the limit of retries is exceeded, a null Raster may be returned.

The amount of time to wait between retries defaults to 1 second (1000 milliseconds). The getTimeout method is used to get the amount of time between retries, in milliseconds. The setTimeout method is used to set the amount of time between retries.

The number of times the program will attempt to read the remote image may be read with the getNumRetries method. The setNumRetries method is used to set the maximum number of retries.


API: javax.media.jai.RemoteImage

sets the amount of time between retries.

Parameter:

timeout

The time interval between retries in milliseconds.

returns the amount of time between retries.

sets the number of retries.

Parameter:

numRetries

The maximum number of retries. If this is a negative value, the number of retries is unchanged.

12.4 Remote Imaging Test Example

This section contains two examples of remote imaging programs.

12.4.1 Simple Remote Imaging Example

Listing 12-1 shows a complete code example of a RemoteImaging test. This example displays a 2 x 2 grid of ScrollingImagePanels, with each window displaying the sum of two byte images that were rescaled to the range [0,127] prior to addition. The panels display the following specific results:

The lower right image is a dithered version of the sum image passed through a color cube lookup table and may appear slightly different from the other three images, which should be identical.

Listing 12-1 Remote Imaging Example Program


     import java.awt.*;
     import java.awt.event.WindowEvent;
     import java.awt.geom.*;
     import java.awt.image.*;
     import java.awt.image.renderable.*;
     import java.util.*;
     import javax.media.jai.*;
     import javax.media.jai.operator.*;
     import javax.media.jai.widget.*;
     public class RemoteImagingTest extends WindowContainer {
     /** Default remote server. */
     private static final String DEFAULT_SERVER =
                              "camus.eng.sun.com:1099";
     /** Tile dimensions. */
     private static final int TILE_WIDTH = 256;
     private static final int TILE_HEIGHT = 256;
     public static void main(String args[]) {
     String fileName1 = null;
     String fileName2 = null;
     // Check args.
         if(!(args.length >= 0 && args.length <= 3)) {
            System.out.println("\nUsage: java RemoteImagingTest "+
                           "[[[serverName] | [fileName1 fileName2]] | "+
                           "[serverName fileName1 fileName2]]"+"\n");
            System.exit(1);
         }
     // Set the server name.
     String serverName = null;
        if(args.length == 0 || args.length == 2) {
            serverName = DEFAULT_SERVER;
            System.out.println("\nUsing default server '"+
                               DEFAULT_SERVER+"'\n");
        } else {
            serverName = args[0];
        }
     // Set the file names.
        if(args.length == 2) {
            fileName1 = args[0];
            fileName2 = args[1];
        } else if(args.length == 3) {
            fileName1 = args[1];
            fileName2 = args[2];
        } else {
      fileName1 = "/import/jai/JAI_RP/test/images/Boat_At_Dock.tif";
      fileName2 = "/import/jai/JAI_RP/test/images/FarmHouse.tif";
            System.out.println("\nUsing default images '"+
                               fileName1 + "' and '" + fileName2 + "'\n");
          }
     RemoteImagingTest riTest =
            new RemoteImagingTest(serverName, fileName1, fileName2);
         }
     /**
     * Run a remote imaging test.
     *
     * @param serverName The name of the remote server to use.
     * @param fileName1 The first addend image file to use.
     * @param fileName2 The second addend image file to use.
     */
     RemoteImagingTest(String serverName, String fileName1, String
                       fileName2) {
     // Create the operations to load the images from files.
     RenderedOp src1 = JAI.create("fileload", fileName1);
     RenderedOp src2 = JAI.create("fileload", fileName2);
     // Render the sources without freezing the nodes.
     PlanarImage ren1 = src1.createInstance();
     PlanarImage ren2 = src2.createInstance();
     // Create TiledImages with the file images as their sources
     // thereby ensuring that the serialized images are truly tiled.
        SampleModel sampleModel1 =
     ren1.getSampleModel().createCompatibleSampleModel(TILE_WIDTH,
                                                       TILE_HEIGHT);
     TiledImage ti1 = new TiledImage(ren1.getMinX(), ren1.getMinY(),
                                     ren1.getWidth(), ren1.getHeight(),
                                     ren1.getTileGridXOffset(),
                                     ren1.getTileGridYOffset(),
                                     sampleModel1, ren1.getColorModel());
     ti1.set(src1);
     SampleModel sampleModel2 =
       ren2.getSampleModel().createCompatibleSampleModel(TILE_WIDTH,
                                                         TILE_HEIGHT);
      TiledImage ti2 = new TiledImage(ren2.getMinX(), ren2.getMinY(),
                                      ren2.getWidth(), ren2.getHeight(),
                                      ren2.getTileGridXOffset(),
                                      ren2.getTileGridYOffset(),
                                    sampleModel2, ren2.getColorModel());
       ti2.set(src2);
     // Create a hint to specify the tile dimensions.
     ImageLayout layout = new ImageLayout();
     layout.setTileWidth(TILE_WIDTH).setTileHeight(TILE_HEIGHT);
          RenderingHints rh = new
              RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);
     // Rescale the images to the range [0, 127].
     ParameterBlock pb = (new ParameterBlock());
     pb.addSource(ti1);
     pb.add(new double[] {0.5}).add(new double[] {0.0});
     RenderedOp addend1 = JAI.create("rescale", pb, rh);
     pb = (new ParameterBlock());
     pb.addSource(ti2);
     pb.add(new double[] {0.5}).add(new double[] {0.0});
     RenderedOp addend2 = JAI.create("rescale", pb, rh);
     // Add the rescaled images.
     pb = (new
        ParameterBlock()).addSource(addend1).addSource(addend2);
             RenderedOp sum = JAI.create("add", pb, rh);
             // Dither the sum of the rescaled images.
             pb = (new ParameterBlock()).addSource(sum);
             
pb.add(ColorCube.BYTE_496).add(KernelJAI.DITHER_MASK_443);
             RenderedOp dithered = JAI.create("ordereddither", pb, rh);
     // Construct a RemoteImage from the RenderedOp chain.
     RemoteImage remoteImage = new RemoteImage(serverName, sum);
     // Set the display title and window layout.
     setTitle(getClass().getName());
     setLayout(new GridLayout(2, 2));
     // Local rendering.
     add(new ScrollingImagePanel(sum,
                                 sum.getWidth(),
                                 sum.getHeight()));
     // RenderedOp remote rendering.
     add(new ScrollingImagePanel(remoteImage,
                                 remoteImage.getWidth(),
                                 remoteImage.getHeight()));
     // RenderedImage remote rendering
     PlanarImage sumImage = sum.getRendering();
     remoteImage = new RemoteImage(serverName, sumImage);
     add(new ScrollingImagePanel(remoteImage,
                                 remoteImage.getWidth(),
                                 remoteImage.getHeight()));
     // RenderableOp remote rendering.
     pb = new ParameterBlock();
     pb.addSource(dithered);
     RenderableOp absImage = JAI.createRenderable("absolute", pb);
     pb = new ParameterBlock();
     pb.addSource(absImage).add(ColorCube.BYTE_496);
     RenderableOp lutImage = JAI.createRenderable("lookup", pb);
     AffineTransform tf =
            AffineTransform.getScaleInstance(384/dithered.getWidth(),
                                             256/dithered.getHeight());
     Rectangle aoi = new Rectangle(128, 128, 384, 256);
     RenderContext rc = new RenderContext(tf, aoi, rh);
     remoteImage = new RemoteImage(serverName, lutImage, rc);
     add(new ScrollingImagePanel(remoteImage,
                                 remoteImage.getWidth(),
                                 remoteImage.getHeight()));
     // Finally display everything
             pack();
             show();
         }
     }

12.4.2 RemoteImaging Example Across Two Nodes

Listing 12-2 shows an example of a RemoteImaging chain spread across two remote nodes, and displays the results locally.

Listing 12-2 RemoteImaging Example Program Using Two Nodes


     import java.awt.image.*;
     import java.awt.image.renderable.ParameterBlock;
     import javax.media.jai.*;
     import javax.media.jai.widget.*;
     /**
      * This test creates an imaging chain spread across two remote 
      * nodes and displays the result locally.
      */
     public class MultiNodeTest extends WindowContainer {
         public static void main(String[] args) {
             if(args.length != 3) {
               throw new RuntimeException("Usage: java MultiNodeTest "+
                                            "file node1 node2");
             }
             new MultiNodeTest(args[0], args[1], args[2]);
         }
     public MultiNodeTest(String fileName, String node1, String
                          node2) {
     // Create a chain on node 1.
     System.out.println("Creating dst1 = log(invert(fileload("+
                                fileName+"))) on "+node1);
             RenderedOp src = JAI.create("fileload", fileName);
             RenderedOp op1 = JAI.create("invert", src);
             RenderedOp op2 = JAI.create("log", op1);
             RemoteImage rmt1 = new RemoteImage(node1, op2);
     // Create a chain on node 2.
     System.out.println("Creating dst2 = not(exp(dst1)) on "+node2);
             RenderedOp op3 = JAI.create("exp", rmt1);
             RenderedOp op4 = JAI.create("not", op3);
             RemoteImage rmt2 = new RemoteImage(node2, op4);
     // Display the result of node 2.
     System.out.println("Displaying results");
     setTitle(getClass().getName()+" "+fileName);
     add(new ScrollingImagePanel(rmt2, rmt2.getWidth(),
                                 rmt2.getHeight()));
             pack();
             show();
         }
     }


API: javax.media.jai.RemoteImage

returns the width of the RemoteImage.

returns the height of the RemoteImage.

returns the entire image as one large tile.

returns an arbitrary rectangular region of the RemoteImage.

Parameters:

rect

The region of the RemoteImage to be returned.

returns an arbitrary rectangular region of the RemoteImage in a user-supplied WritableRaster. The rectangular region is the entire image if the argument is null or the intersection of the argument bounds with the image bounds if the region is non-null. If the argument is non-null but has bounds that have an empty intersection with the image bounds, the return value will be null. The return value may also be null if the argument is non-null but is incompatible with the Raster returned from the remote image..

Parameters:

raster

A WritableRaster to hold the returned portion of the image.

If the raster argument is null, the entire image will be copied into a newly-created WritableRaster with a SampleModel that is compatible with that of the image.

returns a tile (x, y). Note that x and y are indices into the tile array, not pixel locations. Unlike in the true RenderedImage interface, the Raster that is returned should be considered a copy.

Parameters:

x

The x index of the requested tile in the tile array

y

The y index of the requested tile in the tile array

12.5 Running Remote Imaging

To run remote imaging in JAI, you have to do the following:

These four steps are explained in more detail in the following sections.

12.5.1 Step 1: Create a Security Policy File

The default RMI security policy implementation is specified within one or more policy configuration files. These configuration files specify what permissions are allowed for code from various sources. There is a default system-wide policy file and a single user policy file. For more information on policy files and permissions, see:

     http://java.sun.com/products/jdk/1.2/docs/guide/security/PolicyFiles.html
     http://java.sun.com/products/jdk/1.2/docs/guide/security/permissions.html
The policy file is located in the base directory where Java Advanced Imaging is installed. If $JAI is the base directory where Java Advanced Imaging is installed, use any simple text editor to create a text file named $JAI/policy containing the following:

     grant {
     // Allow everything for now
        permission java.security.AllPermission;
     };
Note that this policy file is for testing purposes only.

12.5.2 Step 2: Start the RMI Registry

The RMI registry is a simple server-side name server that allows remote clients to get a reference to a remote object. Typically, the registry is used only to locate the first remote object an application needs to talk to. Then that object in turn provides application-specific support for finding other objects.


Note: Before starting the rmiregistry, make sure that the shell or window in which you will run the registry either has no CLASSPATH set or has a CLASSPATH that does not include the path to any classes you want downloaded to your client, including the stubs for your remote object implementation classes.
To start the registry on the server, log in to the remote system where the image server will be running and execute the rmiregistry command.

For example, in the Solaris operating environment using a Bourne-compatible shell (e.g., /bin/sh):

     $ unset CLASSPATH
     $ rmiregistry &
Note that the CLASSPATH environment variable is deliberately not set.

For example, on on Windows 95 or Windows NT:

     start rmiregistry
If the start commend is not available, use javaw.

12.5.3 Step 3: Start the Remote Image Server

While still logged in to the remote server system, set the CLASSPATH and LD_LIBRARY_PATH environment variables as required for JAI (see the INSTALL file) and start the remote imaging server. For example:

     $ CLASSPATH=$JAI/lib/jai.jar:\
                 $JAI/lib/mlibwrapper_jai.jar
     $ export CLASSPATH
     $ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JAI/lib
     $ export LD_LIBRARY_PATH
     $ java \
     -Djava.rmi.server.codebase=\
          file:$JAI/lib/jai.jar \
     -Djava.rmi.server.useCodebaseOnly=false \
     -Djava.security.policy=file:$JAI/policy \
           com.sun.media.jai.rmi.RMIImageImpl
For example, when the above steps are executed on a machine with IP address 123.456.78.90 the following is printed:

     Server: using host 123.456.78.90 port 1099
     Registering image server as
       "rmi://123.456.78.90:1099/RemoteImageServer".
     Server: Bound RemoteImageServer into
       the registry.

12.5.4 Step 4: Run the Local Application

After completing steps 1 through 3, you are ready to run the local application. When running the local application, make sure that the serverName parameter of any RemoteImage constructors corresponds to the machine on which the remote image server is running. For example, if the machine with IP address 123.456.78.90 above is named myserver, the serverName parameter of any RemoteImage() constructors should be "myserver".



Contents Previous Next

Programming in Java Advanced Imaging


Copyright © 1999, Sun Microsystems, Inc. All rights reserved.

Casa de Bender