001/* 002 * $RCSfile: SimpleRenderedImage.java,v $ 003 * 004 * 005 * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without 008 * modification, are permitted provided that the following conditions 009 * are met: 010 * 011 * - Redistribution of source code must retain the above copyright 012 * notice, this list of conditions and the following disclaimer. 013 * 014 * - Redistribution in binary form must reproduce the above copyright 015 * notice, this list of conditions and the following disclaimer in 016 * the documentation and/or other materials provided with the 017 * distribution. 018 * 019 * Neither the name of Sun Microsystems, Inc. or the names of 020 * contributors may be used to endorse or promote products derived 021 * from this software without specific prior written permission. 022 * 023 * This software is provided "AS IS," without a warranty of any 024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY 027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS 030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, 032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND 033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR 034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE 035 * POSSIBILITY OF SUCH DAMAGES. 036 * 037 * You acknowledge that this software is not designed or intended for 038 * use in the design, construction, operation or maintenance of any 039 * nuclear facility. 040 * 041 * $Revision: 1.1 $ 042 * $Date: 2005/02/11 05:01:23 $ 043 * $State: Exp $ 044 */ 045package com.github.jaiimageio.impl.common; 046 047import java.awt.Point; 048import java.awt.Rectangle; 049import java.awt.image.ColorModel; 050import java.awt.image.Raster; 051import java.awt.image.RenderedImage; 052import java.awt.image.SampleModel; 053import java.awt.image.WritableRaster; 054import java.util.Enumeration; 055import java.util.Hashtable; 056import java.util.Iterator; 057import java.util.Vector; 058 059public abstract class SimpleRenderedImage implements RenderedImage { 060 /** The X coordinate of the image's upper-left pixel. */ 061 protected int minX; 062 063 /** The Y coordinate of the image's upper-left pixel. */ 064 protected int minY; 065 066 /** The image's width in pixels. */ 067 protected int width; 068 069 /** The image's height in pixels. */ 070 protected int height; 071 072 /** The width of a tile. */ 073 protected int tileWidth; 074 075 /** The height of a tile. */ 076 protected int tileHeight; 077 078 /** The X coordinate of the upper-left pixel of tile (0, 0). */ 079 protected int tileGridXOffset = 0; 080 081 /** The Y coordinate of the upper-left pixel of tile (0, 0). */ 082 protected int tileGridYOffset = 0; 083 084 /** The image's SampleModel. */ 085 protected SampleModel sampleModel; 086 087 /** The image's ColorModel. */ 088 protected ColorModel colorModel; 089 090 /** The image's sources, stored in a Vector. */ 091 protected Vector sources = new Vector(); 092 093 /** A Hashtable containing the image properties. */ 094 protected Hashtable properties = new Hashtable(); 095 096 /** Returns the X coordinate of the leftmost column of the image. */ 097 public int getMinX() { 098 return minX; 099 } 100 101 /** 102 * Returns the X coordinate of the column immediatetely to the 103 * right of the rightmost column of the image. getMaxX() is 104 * implemented in terms of getMinX() and getWidth() and so does 105 * not need to be implemented by subclasses. 106 */ 107 public final int getMaxX() { 108 return getMinX() + getWidth(); 109 } 110 111 /** Returns the X coordinate of the uppermost row of the image. */ 112 public int getMinY() { 113 return minY; 114 } 115 116 /** 117 * Returns the Y coordinate of the row immediately below the 118 * bottom row of the image. getMaxY() is implemented in terms of 119 * getMinY() and getHeight() and so does not need to be 120 * implemented by subclasses. 121 */ 122 public final int getMaxY() { 123 return getMinY() + getHeight(); 124 } 125 126 /** Returns the width of the image. */ 127 public int getWidth() { 128 return width; 129 } 130 131 /** Returns the height of the image. */ 132 public int getHeight() { 133 return height; 134 } 135 136 /** Returns a Rectangle indicating the image bounds. */ 137 public Rectangle getBounds() { 138 return new Rectangle(getMinX(), getMinY(), getWidth(), getHeight()); 139 } 140 141 /** Returns the width of a tile. */ 142 public int getTileWidth() { 143 return tileWidth; 144 } 145 146 /** Returns the height of a tile. */ 147 public int getTileHeight() { 148 return tileHeight; 149 } 150 151 /** 152 * Returns the X coordinate of the upper-left pixel of tile (0, 0). 153 */ 154 public int getTileGridXOffset() { 155 return tileGridXOffset; 156 } 157 158 /** 159 * Returns the Y coordinate of the upper-left pixel of tile (0, 0). 160 */ 161 public int getTileGridYOffset() { 162 return tileGridYOffset; 163 } 164 165 /** 166 * Returns the horizontal index of the leftmost column of tiles. 167 * getMinTileX() is implemented in terms of getMinX() 168 * and so does not need to be implemented by subclasses. 169 */ 170 public int getMinTileX() { 171 return XToTileX(getMinX()); 172 } 173 174 /** 175 * Returns the horizontal index of the rightmost column of tiles. 176 * getMaxTileX() is implemented in terms of getMaxX() 177 * and so does not need to be implemented by subclasses. 178 */ 179 public int getMaxTileX() { 180 return XToTileX(getMaxX() - 1); 181 } 182 183 /** 184 * Returns the number of tiles along the tile grid in the 185 * horizontal direction. getNumXTiles() is implemented in terms 186 * of getMinTileX() and getMaxTileX() and so does not need to be 187 * implemented by subclasses. 188 */ 189 public int getNumXTiles() { 190 return getMaxTileX() - getMinTileX() + 1; 191 } 192 193 /** 194 * Returns the vertical index of the uppermost row of tiles. getMinTileY() 195 * is implemented in terms of getMinY() and so does not need to be 196 * implemented by subclasses. 197 */ 198 public int getMinTileY() { 199 return YToTileY(getMinY()); 200 } 201 202 /** 203 * Returns the vertical index of the bottom row of tiles. getMaxTileY() 204 * is implemented in terms of getMaxY() and so does not need to 205 * be implemented by subclasses. 206 */ 207 public int getMaxTileY() { 208 return YToTileY(getMaxY() - 1); 209 } 210 211 /** 212 * Returns the number of tiles along the tile grid in the vertical 213 * direction. getNumYTiles() is implemented in terms 214 * of getMinTileY() and getMaxTileY() and so does not need to be 215 * implemented by subclasses. 216 */ 217 public int getNumYTiles() { 218 return getMaxTileY() - getMinTileY() + 1; 219 } 220 221 /** Returns the SampleModel of the image. */ 222 public SampleModel getSampleModel() { 223 return sampleModel; 224 } 225 226 /** Returns the ColorModel of the image. */ 227 public ColorModel getColorModel() { 228 return colorModel; 229 } 230 231 /** 232 * Gets a property from the property set of this image. If the 233 * property name is not recognized, 234 * <code>java.awt.Image.UndefinedProperty</code> will be returned. 235 * 236 * @param name the name of the property to get, as a 237 * <code>String</code>. @return a reference to the property 238 * <code>Object</code>, or the value 239 * <code>java.awt.Image.UndefinedProperty.</code> 240 */ 241 public Object getProperty(String name) { 242 name = name.toLowerCase(); 243 Object value = properties.get(name); 244 return value != null ? value : java.awt.Image.UndefinedProperty; 245 } 246 247 /** 248 * Returns a list of the properties recognized by this image. If 249 * no properties are available, <code>null</code> will be 250 * returned. 251 * 252 * @return an array of <code>String</code>s representing valid 253 * property names. 254 */ 255 public String[] getPropertyNames() { 256 String[] names = null; 257 258 if(properties.size() > 0) { 259 names = new String[properties.size()]; 260 int index = 0; 261 262 Enumeration e = properties.keys(); 263 while (e.hasMoreElements()) { 264 String name = (String)e.nextElement(); 265 names[index++] = name; 266 } 267 } 268 269 return names; 270 } 271 272 /** 273 * Returns an array of <code>String</code>s recognized as names by 274 * this property source that begin with the supplied prefix. If 275 * no property names match, <code>null</code> will be returned. 276 * The comparison is done in a case-independent manner. 277 * 278 * <p> The default implementation calls 279 * <code>getPropertyNames()</code> and searches the list of names 280 * for matches. 281 * 282 * @return an array of <code>String</code>s giving the valid 283 * property names. 284 */ 285 public String[] getPropertyNames(String prefix) { 286 String propertyNames[] = getPropertyNames(); 287 if (propertyNames == null) { 288 return null; 289 } 290 291 prefix = prefix.toLowerCase(); 292 293 Vector names = new Vector(); 294 for (int i = 0; i < propertyNames.length; i++) { 295 if (propertyNames[i].startsWith(prefix)) { 296 names.addElement(propertyNames[i]); 297 } 298 } 299 300 if (names.size() == 0) { 301 return null; 302 } 303 304 // Copy the strings from the Vector over to a String array. 305 String prefixNames[] = new String[names.size()]; 306 int count = 0; 307 for (Iterator it = names.iterator(); it.hasNext(); ) { 308 prefixNames[count++] = (String)it.next(); 309 } 310 311 return prefixNames; 312 } 313 314 // Utility methods. 315 316 /** 317 * Converts a pixel's X coordinate into a horizontal tile index 318 * relative to a given tile grid layout specified by its X offset 319 * and tile width. 320 */ 321 public static int XToTileX(int x, int tileGridXOffset, int tileWidth) { 322 x -= tileGridXOffset; 323 if (x < 0) { 324 x += 1 - tileWidth; // Force round to -infinity 325 } 326 return x/tileWidth; 327 } 328 329 /** 330 * Converts a pixel's Y coordinate into a vertical tile index 331 * relative to a given tile grid layout specified by its Y offset 332 * and tile height. 333 */ 334 public static int YToTileY(int y, int tileGridYOffset, int tileHeight) { 335 y -= tileGridYOffset; 336 if (y < 0) { 337 y += 1 - tileHeight; // Force round to -infinity 338 } 339 return y/tileHeight; 340 } 341 342 /** 343 * Converts a pixel's X coordinate into a horizontal tile index. 344 * This is a convenience method. No attempt is made to detect 345 * out-of-range coordinates. 346 * 347 * @param x the X coordinate of a pixel. 348 * @return the X index of the tile containing the pixel. 349 */ 350 public int XToTileX(int x) { 351 return XToTileX(x, getTileGridXOffset(), getTileWidth()); 352 } 353 354 /** 355 * Converts a pixel's Y coordinate into a vertical tile index. 356 * This is a convenience method. No attempt is made to detect 357 * out-of-range coordinates. 358 * 359 * @param y the Y coordinate of a pixel. 360 * @return the Y index of the tile containing the pixel. 361 */ 362 public int YToTileY(int y) { 363 return YToTileY(y, getTileGridYOffset(), getTileHeight()); 364 } 365 366 /** 367 * Converts a horizontal tile index into the X coordinate of its 368 * upper left pixel relative to a given tile grid layout specified 369 * by its X offset and tile width. 370 */ 371 public static int tileXToX(int tx, int tileGridXOffset, int tileWidth) { 372 return tx*tileWidth + tileGridXOffset; 373 } 374 375 /** 376 * Converts a vertical tile index into the Y coordinate of 377 * its upper left pixel relative to a given tile grid layout 378 * specified by its Y offset and tile height. 379 */ 380 public static int tileYToY(int ty, int tileGridYOffset, int tileHeight) { 381 return ty*tileHeight + tileGridYOffset; 382 } 383 384 /** 385 * Converts a horizontal tile index into the X coordinate of its 386 * upper left pixel. This is a convenience method. No attempt is made 387 * to detect out-of-range indices. 388 * 389 * @param tx the horizontal index of a tile. 390 * @return the X coordinate of the tile's upper left pixel. 391 */ 392 public int tileXToX(int tx) { 393 return tx*tileWidth + tileGridXOffset; 394 } 395 396 /** 397 * Converts a vertical tile index into the Y coordinate of its 398 * upper left pixel. This is a convenience method. No attempt is made 399 * to detect out-of-range indices. 400 * 401 * @param ty the vertical index of a tile. 402 * @return the Y coordinate of the tile's upper left pixel. 403 */ 404 public int tileYToY(int ty) { 405 return ty*tileHeight + tileGridYOffset; 406 } 407 408 public Vector getSources() { 409 return null; 410 } 411 412 /** 413 * Returns the entire image in a single Raster. For images with 414 * multiple tiles this will require making a copy. 415 * 416 * <p> The returned Raster is semantically a copy. This means 417 * that updates to the source image will not be reflected in the 418 * returned Raster. For non-writable (immutable) source images, 419 * the returned value may be a reference to the image's internal 420 * data. The returned Raster should be considered non-writable; 421 * any attempt to alter its pixel data (such as by casting it to 422 * WritableRaster or obtaining and modifying its DataBuffer) may 423 * result in undefined behavior. The copyData method should be 424 * used if the returned Raster is to be modified. 425 * 426 * @return a Raster containing a copy of this image's data. 427 */ 428 public Raster getData() { 429 Rectangle rect = new Rectangle(getMinX(), getMinY(), 430 getWidth(), getHeight()); 431 return getData(rect); 432 } 433 434 /** 435 * Returns an arbitrary rectangular region of the RenderedImage 436 * in a Raster. The rectangle of interest will be clipped against 437 * the image bounds. 438 * 439 * <p> The returned Raster is semantically a copy. This means 440 * that updates to the source image will not be reflected in the 441 * returned Raster. For non-writable (immutable) source images, 442 * the returned value may be a reference to the image's internal 443 * data. The returned Raster should be considered non-writable; 444 * any attempt to alter its pixel data (such as by casting it to 445 * WritableRaster or obtaining and modifying its DataBuffer) may 446 * result in undefined behavior. The copyData method should be 447 * used if the returned Raster is to be modified. 448 * 449 * @param bounds the region of the RenderedImage to be returned. 450 */ 451 public Raster getData(Rectangle bounds) { 452 // Get the image bounds. 453 Rectangle imageBounds = getBounds(); 454 455 // Check for parameter validity. 456 if(bounds == null) { 457 bounds = imageBounds; 458 } else if(!bounds.intersects(imageBounds)) { 459 throw new IllegalArgumentException(I18N.getString("SimpleRenderedImage0")); 460 } 461 462 // Determine tile limits for the prescribed bounds. 463 int startX = XToTileX(bounds.x); 464 int startY = YToTileY(bounds.y); 465 int endX = XToTileX(bounds.x + bounds.width - 1); 466 int endY = YToTileY(bounds.y + bounds.height - 1); 467 468 // If the bounds are contained in a single tile, return a child 469 // of that tile's Raster. 470 if ((startX == endX) && (startY == endY)) { 471 Raster tile = getTile(startX, startY); 472 return tile.createChild(bounds.x, bounds.y, 473 bounds.width, bounds.height, 474 bounds.x, bounds.y, null); 475 } else { 476 // Recalculate the tile limits if the data bounds are not a 477 // subset of the image bounds. 478 if(!imageBounds.contains(bounds)) { 479 Rectangle xsect = bounds.intersection(imageBounds); 480 startX = XToTileX(xsect.x); 481 startY = YToTileY(xsect.y); 482 endX = XToTileX(xsect.x + xsect.width - 1); 483 endY = YToTileY(xsect.y + xsect.height - 1); 484 } 485 486 // Create a WritableRaster of the desired size 487 SampleModel sm = 488 sampleModel.createCompatibleSampleModel(bounds.width, 489 bounds.height); 490 491 // Translate it 492 WritableRaster dest = 493 Raster.createWritableRaster(sm, bounds.getLocation()); 494 495 // Loop over the tiles in the intersection. 496 for (int j = startY; j <= endY; j++) { 497 for (int i = startX; i <= endX; i++) { 498 // Retrieve the tile. 499 Raster tile = getTile(i, j); 500 501 // Create a child of the tile for the intersection of 502 // the tile bounds and the bounds of the requested area. 503 Rectangle tileRect = tile.getBounds(); 504 Rectangle intersectRect = 505 bounds.intersection(tile.getBounds()); 506 Raster liveRaster = tile.createChild(intersectRect.x, 507 intersectRect.y, 508 intersectRect.width, 509 intersectRect.height, 510 intersectRect.x, 511 intersectRect.y, 512 null); 513 514 // Copy the data from the child. 515 dest.setRect(liveRaster); 516 } 517 } 518 519 return dest; 520 } 521 } 522 523 /** 524 * Copies an arbitrary rectangular region of the RenderedImage 525 * into a caller-supplied WritableRaster. The region to be 526 * computed is determined by clipping the bounds of the supplied 527 * WritableRaster against the bounds of the image. The supplied 528 * WritableRaster must have a SampleModel that is compatible with 529 * that of the image. 530 * 531 * <p> If the raster argument is null, the entire image will 532 * be copied into a newly-created WritableRaster with a SampleModel 533 * that is compatible with that of the image. 534 * 535 * @param dest a WritableRaster to hold the returned portion of 536 * the image. 537 * @return a reference to the supplied WritableRaster, or to a 538 * new WritableRaster if the supplied one was null. 539 */ 540 public WritableRaster copyData(WritableRaster dest) { 541 // Get the image bounds. 542 Rectangle imageBounds = getBounds(); 543 544 Rectangle bounds; 545 if (dest == null) { 546 // Create a WritableRaster for the entire image. 547 bounds = imageBounds; 548 Point p = new Point(minX, minY); 549 SampleModel sm = 550 sampleModel.createCompatibleSampleModel(width, height); 551 dest = Raster.createWritableRaster(sm, p); 552 } else { 553 bounds = dest.getBounds(); 554 } 555 556 // Determine tile limits for the intersection of the prescribed 557 // bounds with the image bounds. 558 Rectangle xsect = imageBounds.contains(bounds) ? 559 bounds : bounds.intersection(imageBounds); 560 int startX = XToTileX(xsect.x); 561 int startY = YToTileY(xsect.y); 562 int endX = XToTileX(xsect.x + xsect.width - 1); 563 int endY = YToTileY(xsect.y + xsect.height - 1); 564 565 // Loop over the tiles in the intersection. 566 for (int j = startY; j <= endY; j++) { 567 for (int i = startX; i <= endX; i++) { 568 // Retrieve the tile. 569 Raster tile = getTile(i, j); 570 571 // Create a child of the tile for the intersection of 572 // the tile bounds and the bounds of the requested area. 573 Rectangle tileRect = tile.getBounds(); 574 Rectangle intersectRect = 575 bounds.intersection(tile.getBounds()); 576 Raster liveRaster = tile.createChild(intersectRect.x, 577 intersectRect.y, 578 intersectRect.width, 579 intersectRect.height, 580 intersectRect.x, 581 intersectRect.y, 582 null); 583 584 // Copy the data from the child. 585 dest.setRect(liveRaster); 586 } 587 } 588 589 return dest; 590 } 591} 592