1 /* 2 * $RCSfile: RMIImageImpl.java,v $ 3 * 4 * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved. 5 * 6 * Use is subject to license terms. 7 * 8 * $Revision: 1.1 $ 9 * $Date: 2005/02/11 04:56:52 $ 10 * $State: Exp $ 11 */ 12 package com.lightcrafts.media.jai.rmi; 13 14 import java.awt.Rectangle; 15 import java.awt.RenderingHints; 16 import java.awt.image.RenderedImage; 17 import java.awt.image.renderable.RenderContext; 18 import java.io.Serializable; 19 import java.net.InetAddress; 20 import java.rmi.Naming; 21 import java.rmi.RemoteException; 22 import java.rmi.RMISecurityManager; 23 import java.rmi.server.UnicastRemoteObject; 24 import java.util.Hashtable; 25 import java.util.Vector; 26 import com.lightcrafts.mediax.jai.PlanarImage; 27 import com.lightcrafts.mediax.jai.PropertySource; 28 import com.lightcrafts.mediax.jai.RenderableOp; 29 import com.lightcrafts.mediax.jai.RenderedOp; 30 import com.lightcrafts.mediax.jai.remote.SerializableRenderedImage; 31 import com.lightcrafts.mediax.jai.remote.RemoteImagingException; 32 import com.lightcrafts.mediax.jai.util.ImagingListener; 33 import com.lightcrafts.media.jai.util.ImageUtil; 34 35 /* A singleton class representing the serializable version of a null 36 property. This required because java.awt.Image.UndefinedProperty 37 is not serializable. */ 38 class NullPropertyTag implements Serializable { NullPropertyTag()39 NullPropertyTag() {} 40 } 41 42 /** 43 * The server-side implementation of the RMIImage interface. A 44 * RMIImageImpl has a RenderedImage source, acquired via one of three 45 * setSource() methods. The first takes a RenderedImage directly as 46 * its parameter; this image is simply copied over the network using 47 * the normal RMI mechanisms. Note that not every image can be 48 * transferred in this way -- for example, attempting to pass an 49 * OpImage that uses native code or that depends on the availability 50 * of a class not resident on the server as a parameter will cause an 51 * exception to be thrown. 52 * 53 * <p> The second and third ways of setting sources make use of the 54 * RenderedOp and RenderableOp classes to send a high-level 55 * description of an image chain based on operation names. This 56 * chain will be copied over to the server using RMI, where it will be 57 * expanded into an OpImage chain using the server's registry. This 58 * is the preferred method since it requires less data transfer and 59 * offers a better chance of success. It may still fail if the 60 * sources or parameters of any operation in the chain are not 61 * serializable. 62 * 63 * <p> RMI requires all remote methods to declare `throws 64 * RemoteException' in their signatures. It is up to the client to 65 * deal with errors. A simple implementation of error handling may be 66 * found in the RemoteRenderedImage class. 67 * 68 * <p> This class contains a main() method that should be run on the 69 * server after starting the RMI registry. The registry will then 70 * construct new instances of RMIImageImpl on demand. 71 * 72 * @see RMIImage 73 * @see RemoteImage 74 * @see RenderedOp 75 * 76 * @since EA3 77 * 78 */ 79 public class RMIImageImpl implements RMIImage { 80 /** Tag to represent a null property. */ 81 public static final Object NULL_PROPERTY = new NullPropertyTag(); 82 83 /** Identifier counter for the remote images. */ 84 private static long idCounter = 0; 85 86 /** 87 * The RenderedImage sources hashed by an ID string which must be unique 88 * across all possible clients of this object. 89 */ 90 private static Hashtable sources = null; 91 92 /** 93 * The PropertySources hashed by an ID string which must be unique 94 * across all possible clients of this object. 95 */ 96 private static Hashtable propertySources = null; 97 98 /** 99 * Adds a RenderedImage source to the Hashtable of sources. 100 * 101 * @param id A unique ID for the source. 102 * @param source The source RenderedImage. 103 * @param ps The PropertySource. 104 */ addSource(Long id, RenderedImage source, PropertySource ps)105 private static synchronized void addSource(Long id, 106 RenderedImage source, 107 PropertySource ps) { 108 // Create the Hashtables "just in time". 109 if(sources == null) { 110 sources = new Hashtable(); 111 propertySources = new Hashtable(); 112 } 113 114 // Add the source and PropertySource. 115 sources.put(id, source); 116 propertySources.put(id, ps); 117 } 118 119 /** 120 * Retrieve a PlanarImage source from the Hashtable of sources. 121 * 122 * @param id The unique ID of the source. 123 * @return The source. 124 */ getSource(Long id)125 private static PlanarImage getSource(Long id) throws RemoteException { 126 Object obj = null; 127 if(sources == null || 128 (obj = sources.get(id)) == null) { 129 throw new RemoteException(JaiI18N.getString("RMIImageImpl2")); 130 } 131 132 return (PlanarImage)obj; 133 } 134 135 /** 136 * Retrieve a PropertySource from the Hashtable of PropertySources. 137 * 138 * @param id The unique ID of the source. 139 * @return The PropertySource. 140 */ getPropertySource(Long id)141 private static PropertySource getPropertySource(Long id) 142 throws RemoteException { 143 Object obj = null; 144 if(propertySources == null || 145 (obj = propertySources.get(id)) == null) { 146 throw new RemoteException(JaiI18N.getString("RMIImageImpl2")); 147 } 148 149 return (PropertySource)obj; 150 } 151 152 /** 153 * Constructs a RMIImageImpl with a source to be specified 154 * later. 155 */ RMIImageImpl()156 public RMIImageImpl() throws RemoteException { 157 super(); 158 try { 159 UnicastRemoteObject.exportObject(this); 160 } catch(RemoteException e) { 161 ImagingListener listener = 162 ImageUtil.getImagingListener((RenderingHints)null); 163 String message = JaiI18N.getString("RMIImageImpl0"); 164 listener.errorOccurred(message, 165 new RemoteImagingException(message, e), 166 this, false); 167 /* 168 e.printStackTrace(); 169 throw new RuntimeException(JaiI18N.getString("RMIImageImpl0") + 170 e.getMessage()); 171 */ 172 } 173 } 174 175 /** 176 * Returns the identifier of the remote image. This method should be 177 * called to return an identifier before any other methods are invoked. 178 * The same ID must be used in all subsequent references to the remote 179 * image. 180 */ getRemoteID()181 public synchronized Long getRemoteID() throws RemoteException { 182 return new Long(++idCounter); 183 } 184 185 /** 186 * Sets the source of the image on the server side. This source 187 * should ideally be a lightweight reference to an image available 188 * locally on the server or over a further network link (for 189 * example, an IIPOpImage that contains a URL but not actual image 190 * data). 191 * 192 * <p> Although it is legal to use any RenderedImage, one should be 193 * aware that a deep copy might be made and transmitted to the server. 194 * 195 * @param id An ID for the source which must be unique across all clients. 196 * @param source a RenderedImage source. 197 */ setSource(Long id, RenderedImage source)198 public void setSource(Long id, RenderedImage source) 199 throws RemoteException { 200 PlanarImage pi = PlanarImage.wrapRenderedImage(source); 201 addSource(id, pi, pi); 202 } 203 204 /** 205 * Sets the source to a RenderedOp (i.e., an imaging DAG). 206 * This DAG will be copied over to the server where it will be 207 * transformed into an OpImage chain using the server's local 208 * OperationRegistry and available RenderedImageFactory objects. 209 * 210 * @param id An ID for the source which must be unique across all clients. 211 * @param source a RenderedOp source. 212 */ setSource(Long id, RenderedOp source)213 public void setSource(Long id, 214 RenderedOp source) 215 throws RemoteException { 216 addSource(id, source.getRendering(), source); 217 } 218 219 /** 220 * Sets the source to a RenderableOp defined by a renderable imaging 221 * DAG and a rendering context. The entire RenderableImage 222 * DAG will be copied over to the server. 223 */ setSource(Long id, RenderableOp source, RenderContextProxy renderContextProxy)224 public void setSource(Long id, 225 RenderableOp source, 226 RenderContextProxy renderContextProxy) 227 throws RemoteException { 228 RenderContext renderContext = 229 renderContextProxy.getRenderContext(); 230 RenderedImage r = source.createRendering(renderContext); 231 PlanarImage pi = PlanarImage.wrapRenderedImage(r); 232 addSource(id, pi, pi); 233 } 234 235 /** 236 * Disposes of any resouces allocated to the client object with 237 * the specified ID. 238 */ dispose(Long id)239 public void dispose(Long id) throws RemoteException { 240 if(sources != null) { 241 sources.remove(id); 242 propertySources.remove(id); 243 } 244 } 245 246 /** Gets a property from the property set of this image. If the 247 property is undefined the constant NULL_PROPERTY is returned. */ getProperty(Long id, String name)248 public Object getProperty(Long id, String name) throws RemoteException { 249 PropertySource ps = getPropertySource(id); 250 Object property = ps.getProperty(name); 251 if(property == null || 252 property.equals(java.awt.Image.UndefinedProperty)) { 253 property = NULL_PROPERTY; 254 } 255 return property; 256 } 257 258 /** 259 * Returns a list of names recognized by getProperty(). 260 * 261 * @return an array of Strings representing proeprty names. 262 */ getPropertyNames(Long id)263 public String[] getPropertyNames(Long id) throws RemoteException { 264 PropertySource ps = getPropertySource(id); 265 return ps.getPropertyNames(); 266 } 267 268 /** Returns the minimum X coordinate of the RMIImage. */ getMinX(Long id)269 public int getMinX(Long id) throws RemoteException { 270 return getSource(id).getMinX(); 271 } 272 273 /** Returns the smallest X coordinate to the right of the RMIImage. */ getMaxX(Long id)274 public int getMaxX(Long id) throws RemoteException { 275 return getSource(id).getMaxX(); 276 } 277 278 /** Returns the minimum Y coordinate of the RMIImage. */ getMinY(Long id)279 public int getMinY(Long id) throws RemoteException { 280 return getSource(id).getMinY(); 281 } 282 283 /** Returns the smallest Y coordinate below the RMIImage. */ getMaxY(Long id)284 public int getMaxY(Long id) throws RemoteException { 285 return getSource(id).getMaxY(); 286 } 287 288 /** Returns the width of the RMIImage. */ getWidth(Long id)289 public int getWidth(Long id) throws RemoteException { 290 return getSource(id).getWidth(); 291 } 292 293 /** Returns the height of the RMIImage. */ getHeight(Long id)294 public int getHeight(Long id) throws RemoteException { 295 return getSource(id).getHeight(); 296 } 297 298 /** Returns the width of a tile in pixels. */ getTileWidth(Long id)299 public int getTileWidth(Long id) throws RemoteException { 300 return getSource(id).getTileWidth(); 301 } 302 303 /** Returns the height of a tile in pixels. */ getTileHeight(Long id)304 public int getTileHeight(Long id) throws RemoteException { 305 return getSource(id).getTileHeight(); 306 } 307 308 /** 309 * Returns the X coordinate of the upper-left pixel of tile (0, 0). 310 */ getTileGridXOffset(Long id)311 public int getTileGridXOffset(Long id) throws RemoteException { 312 return getSource(id).getTileGridXOffset(); 313 } 314 315 /** 316 * Returns the Y coordinate of the upper-left pixel of tile (0, 0). 317 */ getTileGridYOffset(Long id)318 public int getTileGridYOffset(Long id) throws RemoteException { 319 return getSource(id).getTileGridYOffset(); 320 } 321 322 /** Returns the index of the leftmost column of tiles. */ getMinTileX(Long id)323 public int getMinTileX(Long id) throws RemoteException { 324 return getSource(id).getMinTileX(); 325 } 326 327 /** 328 * Returns the number of tiles along the tile grid in the horizontal 329 * direction. 330 */ getNumXTiles(Long id)331 public int getNumXTiles(Long id) throws RemoteException { 332 return getSource(id).getNumXTiles(); 333 } 334 335 /** Returns the index of the uppermost row of tiles. */ getMinTileY(Long id)336 public int getMinTileY(Long id) throws RemoteException { 337 return getSource(id).getMinTileY(); 338 } 339 340 /** 341 * Returns the number of tiles along the tile grid in the vertical 342 * direction. 343 */ getNumYTiles(Long id)344 public int getNumYTiles(Long id) throws RemoteException { 345 return getSource(id).getNumYTiles(); 346 } 347 348 /** Returns the index of the rightmost column of tiles. */ getMaxTileX(Long id)349 public int getMaxTileX(Long id) throws RemoteException { 350 return getSource(id).getMaxTileX(); 351 } 352 353 /** Returns the index of the bottom row of tiles. */ getMaxTileY(Long id)354 public int getMaxTileY(Long id) throws RemoteException { 355 return getSource(id).getMaxTileY(); 356 } 357 358 /** Returns the SampleModel associated with this image. */ getSampleModel(Long id)359 public SampleModelProxy getSampleModel(Long id) throws RemoteException { 360 return new SampleModelProxy(getSource(id).getSampleModel()); 361 } 362 363 /** Returns the ColorModel associated with this image. */ getColorModel(Long id)364 public ColorModelProxy getColorModel(Long id) throws RemoteException { 365 return new ColorModelProxy(getSource(id).getColorModel()); 366 367 } 368 369 /** 370 * Returns a vector of RenderedImages that are the sources of 371 * image data for this RMIImage. Note that this method 372 * will often return an empty vector. 373 */ getSources(Long id)374 public Vector getSources(Long id) throws RemoteException { 375 Vector sourceVector = getSource(id).getSources(); 376 int size = sourceVector.size(); 377 boolean isCloned = false; 378 for(int i = 0; i < size; i++) { 379 RenderedImage img = (RenderedImage)sourceVector.get(i); 380 if(!(img instanceof Serializable)) { 381 if(!isCloned) { 382 sourceVector = (Vector)sourceVector.clone(); 383 } 384 sourceVector.set(i, new SerializableRenderedImage(img, false)); 385 } 386 } 387 return sourceVector; 388 } 389 390 /** Returns a Rectangle indicating the image bounds. */ getBounds(Long id)391 public Rectangle getBounds(Long id) throws RemoteException { 392 return getSource(id).getBounds(); 393 } 394 395 /** 396 * Returns tile (x, y). Note that x and y are indices into the 397 * tile array, not pixel locations. Unlike in the true RenderedImage 398 * interface, the Raster that is returned should be considered a copy. 399 * 400 * @param id An ID for the source which must be unique across all clients. 401 * @param tileX the X index of the requested tile in the tile array. 402 * @param tileY the Y index of the requested tile in the tile array. 403 * @return the tile as a Raster. 404 */ getTile(Long id, int tileX, int tileY)405 public RasterProxy getTile(Long id, int tileX, int tileY) 406 throws RemoteException { 407 return new RasterProxy(getSource(id).getTile(tileX, tileY)); 408 } 409 410 /** 411 * Returns the entire image as a single Raster. 412 * 413 * @return a Raster containing a copy of this image's data. 414 */ getData(Long id)415 public RasterProxy getData(Long id) throws RemoteException { 416 return new RasterProxy(getSource(id).getData()); 417 } 418 419 /** 420 * Returns an arbitrary rectangular region of the RenderedImage 421 * in a Raster. The rectangle of interest will be clipped against 422 * the image bounds. 423 * 424 * @param id An ID for the source which must be unique across all clients. 425 * @param rect the region of the RenderedImage to be returned. 426 * @return a Raster containing a copy of the desired data. 427 */ getData(Long id, Rectangle bounds)428 public RasterProxy getData(Long id, Rectangle bounds) 429 throws RemoteException { 430 RasterProxy rp = null; 431 if(bounds == null) { 432 rp = getData(id); 433 } else { 434 bounds = bounds.intersection(getBounds(id)); 435 rp = new RasterProxy(getSource(id).getData(bounds)); 436 } 437 return rp; 438 } 439 440 /** 441 * Returns the same result as getData(Rectangle) would for the 442 * same rectangular region. 443 */ copyData(Long id, Rectangle bounds)444 public RasterProxy copyData(Long id, Rectangle bounds) 445 throws RemoteException { 446 return getData(id, bounds); 447 } 448 449 /** 450 * Starts a server on a given port. The RMI registry must be running 451 * on the server host. 452 * 453 * <p> The usage of this class is 454 * 455 * <pre> 456 * java -Djava.rmi.server.codebase=file:$JAI/lib/jai.jar \ 457 * -Djava.rmi.server.useCodebaseOnly=false \ 458 * -Djava.security.policy=\ 459 * file:`pwd`/policy com.lightcrafts.media.jai.rmi.RMIImageImpl \ 460 * [-host hostName] [-port portNumber] 461 * </pre> 462 * 463 * The default host is the local host and the default port is 1099. 464 * 465 * @param args the port number as a command-line argument. 466 */ main(String [] args)467 public static void main(String [] args) { 468 // Set the security manager. 469 if(System.getSecurityManager() == null) { 470 System.setSecurityManager(new RMISecurityManager()); 471 } 472 473 // Set the host name and port number. 474 String host = null; 475 int port = 1099; // default port is 1099 476 for(int i = 0; i < args.length; i++) { 477 if(args[i].equalsIgnoreCase("-host")) { 478 host = args[++i]; 479 } else if(args[i].equalsIgnoreCase("-port")) { 480 port = Integer.parseInt(args[++i]); 481 } 482 } 483 484 // Default to the local host if the host was not specified. 485 if(host == null) { 486 try { 487 host = InetAddress.getLocalHost().getHostAddress(); 488 } catch(java.net.UnknownHostException e) { 489 System.err.println(JaiI18N.getString("RMIImageImpl1") + 490 e.getMessage()); 491 e.printStackTrace(); 492 } 493 } 494 495 System.out.println(JaiI18N.getString("RMIImageImpl3")+" "+ 496 host+":"+port); 497 498 try { 499 RMIImageImpl im = new RMIImageImpl(); 500 String serverName = 501 new String("rmi://" + 502 host + ":" + port + "/" + 503 RMIImage.RMI_IMAGE_SERVER_NAME); 504 System.out.println(JaiI18N.getString("RMIImageImpl4")+" \""+ 505 serverName+"\"."); 506 Naming.rebind(serverName, im); 507 System.out.println(JaiI18N.getString("RMIImageImpl5")); 508 } catch (Exception e) { 509 System.err.println(JaiI18N.getString("RMIImageImpl0") + 510 e.getMessage()); 511 e.printStackTrace(); 512 } 513 } 514 } 515