0001: /*
0002: * $RCSfile: TiledImage.java,v $
0003: *
0004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * Use is subject to license terms.
0007: *
0008: * $Revision: 1.2 $
0009: * $Date: 2007/08/31 16:25:50 $
0010: * $State: Exp $
0011: */
0012: package javax.media.jai;
0013:
0014: import java.awt.Graphics;
0015: import java.awt.Graphics2D;
0016: import java.awt.Point;
0017: import java.awt.Rectangle;
0018: import java.awt.Shape;
0019: import java.awt.geom.AffineTransform;
0020: import java.awt.geom.Area;
0021: import java.awt.geom.PathIterator;
0022: import java.awt.image.BandedSampleModel;
0023: import java.awt.image.ColorModel;
0024: import java.awt.image.ComponentSampleModel;
0025: import java.awt.image.DataBuffer;
0026: import java.awt.image.Raster;
0027: import java.awt.image.RenderedImage;
0028: import java.awt.image.SampleModel;
0029: import java.awt.image.TileObserver;
0030: import java.awt.image.WritableRaster;
0031: import java.awt.image.WritableRenderedImage;
0032: import java.awt.image.renderable.ParameterBlock;
0033: import java.beans.PropertyChangeEvent;
0034: import java.beans.PropertyChangeListener;
0035: import java.util.Enumeration;
0036: import java.util.Iterator;
0037: import java.util.LinkedList;
0038: import java.util.Vector;
0039: import com.sun.media.jai.util.JDKWorkarounds;
0040:
0041: /**
0042: * A concrete implementation of <code>WritableRenderedImage</code>.
0043: *
0044: * <p> <code>TiledImage</code> is the main class for writable images
0045: * in JAI. <code>TiledImage</code> provides a straightforward
0046: * implementation of the <code>WritableRenderedImage</code> interface,
0047: * taking advantage of that interface's ability to describe images
0048: * with multiple tiles. The tiles of a
0049: * <code>WritableRenderedImage</code> must share a
0050: * <code>SampleModel</code>, which determines their width, height, and
0051: * pixel format. The tiles form a regular grid, which may occupy any
0052: * rectangular region of the plane. Tile pixels the locations of which
0053: * lie outside the stated image bounds have undefined values.
0054: *
0055: * <p> The contents of a <code>TiledImage</code> are defined by a
0056: * single <code>RenderedImage</code> source provided by means of one of
0057: * the <code>set()</code> methods or to a constructor which accepts a
0058: * <code>RenderedImage</code>. The <code>set()</code> methods provide
0059: * a way to selectively overwrite a portion of a <code>TiledImage</code>,
0060: * possibly using a region of interest (ROI).
0061: *
0062: * <p> <code>TiledImage</code> also supports direct manipulation of
0063: * pixels by means of the <code>getWritableTile()</code> method. This
0064: * method returns a <code>WritableRaster</code> that can be modified directly.
0065: * Such changes become visible to readers according to the regular
0066: * thread synchronization rules of the Java virtual machine; JAI makes
0067: * no additional guarantees. When a writer is finished modifying a
0068: * tile, it should call <code>releaseWritableTile()</code>. A
0069: * shortcut is to call <code>setData()</code>, which copies a rectangular
0070: * region or an area specified by a <code>ROI</code> from a supplied
0071: * <code>Raster</code> directly into the <code>TiledImage</code>.
0072: *
0073: * <p> A final way to modify the contents of a <code>TiledImage</code>
0074: * is through calls to the object returned by <code>createGraphics()</code>.
0075: * This returns a <code>Graphics2D</code> object that can be used to draw
0076: * line art, text, and images in the usual Abstract Window Toolkit (AWT)
0077: * manner.
0078: *
0079: * <p> A <code>TiledImage</code> does not attempt to maintain
0080: * synchronous state on its own. That task is left to
0081: * <code>SnapshotImage</code>. If a synchronous (unchangeable) view
0082: * of a <code>TiledImage</code> is desired, its
0083: * <code>createSnapshot()</code> method must be used. Otherwise,
0084: * changes due to calls to set() or direct writing of tiles by objects
0085: * that call <code>getWritableTile()</code> will be visible.
0086: *
0087: * <p> <code>TiledImage</code> does not actually cause its tiles to be
0088: * copied from the specified source until their contents are demanded.
0089: * Once a tile has been computed, its contents may be discarded if it can
0090: * be determined that it can be recomputed identically from the source.
0091: * The <code>lockTile()</code> method forces a tile to be computed and
0092: * maintained for the lifetime of the <code>TiledImage</code>.
0093: *
0094: * @see SnapshotImage
0095: * @see java.awt.image.RenderedImage
0096: * @see java.awt.image.WritableRenderedImage
0097: *
0098: */
0099: public class TiledImage extends PlanarImage implements
0100: WritableRenderedImage, PropertyChangeListener {
0101:
0102: /** The number of tiles in the X direction. */
0103: protected int tilesX;
0104:
0105: /** The number of tiles in the Y direction. */
0106: protected int tilesY;
0107:
0108: /** The index of the leftmost column of tiles. */
0109: protected int minTileX;
0110:
0111: /** The index of the uppermost row of tiles. */
0112: protected int minTileY;
0113:
0114: /** The tile array. */
0115: protected WritableRaster[][] tiles;
0116:
0117: /** The number of writers of each tile; -1 indicates a locked tile. */
0118: protected int[][] writers;
0119:
0120: /** The current set of TileObservers. */
0121: protected Vector tileObservers = null;
0122:
0123: /** Whether DataBuffers are shared with the source image. */
0124: private boolean areBuffersShared = false;
0125:
0126: /** The parent TiledImage if this one was created using getSubImage(). */
0127: private TiledImage parent = null;
0128:
0129: /** The SampleModel of the TiledImage ancestor. */
0130: private SampleModel ancestorSampleModel = null;
0131:
0132: /** The sub-banding list with respect to the ancestor. */
0133: private int[] bandList = null;
0134:
0135: /** The number of writable tiles; shared with all ancestors. */
0136: private int[] numWritableTiles = null;
0137:
0138: /** The ROI to be used with the source image of uncomputed tiles. */
0139: private ROI srcROI = null;
0140:
0141: /** The bounds of the intersection of the source image bounds with
0142: those of this image and with the source ROI if present. */
0143: private Rectangle overlapBounds = null;
0144:
0145: /**
0146: * Derives a <code>SampleModel</code> with the specified dimensions
0147: * from the input <code>SampleModel</code>. If the input
0148: * <code>SampleModel</code> already has these dimensions, it is
0149: * used directly; otherwise a new <code>SampleModel</code> of the
0150: * required dimensions is derived and returned.
0151: */
0152: private static SampleModel coerceSampleModel(
0153: SampleModel sampleModel, int sampleModelWidth,
0154: int sampleModelHeight) {
0155: return (sampleModel.getWidth() == sampleModelWidth && sampleModel
0156: .getHeight() == sampleModelHeight) ? sampleModel
0157: : sampleModel.createCompatibleSampleModel(
0158: sampleModelWidth, sampleModelHeight);
0159: }
0160:
0161: /*
0162: * Derives the values of minTileX, minTileY, tilesX, and tilesY
0163: * from minX, minY, width, height, tileGridXOffset, tileGridYOffset,
0164: * tileWidth, and tileHeight. If the image has a parent, its tile
0165: * grid minima are set to those of the parent.
0166: *
0167: * @param parent The parent TiledImage.
0168: */
0169: private void initTileGrid(TiledImage parent) {
0170: if (parent != null) {
0171: this .minTileX = parent.minTileX;
0172: this .minTileY = parent.minTileY;
0173: } else {
0174: this .minTileX = getMinTileX();
0175: this .minTileY = getMinTileY();
0176: }
0177:
0178: int maxTileX = getMaxTileX();
0179: int maxTileY = getMaxTileY();
0180:
0181: this .tilesX = maxTileX - minTileX + 1;
0182: this .tilesY = maxTileY - minTileY + 1;
0183: }
0184:
0185: /**
0186: * Constructs a <code>TiledImage</code> with a given layout,
0187: * <code>SampleModel</code>, and <code>ColorModel</code>. The
0188: * width and height of the image tiles will be respectively equal
0189: * to the width and height of the <code>SampleModel</code>. The
0190: * <code>tileFactory</code> instance variable will be set to the
0191: * value of the <code>JAI.KEY_TILE_FACTORY</code> hint set on
0192: * the default instance of <code>JAI</code>.
0193: *
0194: * @param minX The X coordinate of the upper-left pixel
0195: * @param minY The Y coordinate of the upper-left pixel.
0196: * @param width The width of the image.
0197: * @param height The height of the image.
0198: * @param tileGridXOffset The X coordinate of the upper-left
0199: * pixel of tile (0, 0).
0200: * @param tileGridYOffset The Y coordinate of the upper-left
0201: * pixel of tile (0, 0).
0202: * @param tileSampleModel A <code>SampleModel</code> with which to be
0203: * compatible.
0204: * @param colorModel A <code>ColorModel</code> to associate with the
0205: * image.
0206: */
0207: public TiledImage(int minX, int minY, int width, int height,
0208: int tileGridXOffset, int tileGridYOffset,
0209: SampleModel tileSampleModel, ColorModel colorModel) {
0210: this (null, minX, minY, width, height, tileGridXOffset,
0211: tileGridYOffset, tileSampleModel, colorModel);
0212: }
0213:
0214: /**
0215: * Constructs a child TiledImage. If the parent is null it is a root
0216: * TiledImage and all instance variables will be allocated.
0217: */
0218: private TiledImage(TiledImage parent, int minX, int minY,
0219: int width, int height, int tileGridXOffset,
0220: int tileGridYOffset, SampleModel sampleModel,
0221: ColorModel colorModel) {
0222: super (new ImageLayout(minX, minY, width, height,
0223: tileGridXOffset, tileGridYOffset, sampleModel
0224: .getWidth(), sampleModel.getHeight(),
0225: sampleModel, colorModel), null, null);
0226: initTileGrid(parent);
0227:
0228: if (parent == null) {
0229: this .tiles = new WritableRaster[tilesX][tilesY];
0230: this .writers = new int[tilesX][tilesY];
0231: tileObservers = new Vector();
0232: numWritableTiles = new int[1];
0233: numWritableTiles[0] = 0;
0234: ancestorSampleModel = sampleModel;
0235: } else {
0236: this .parent = parent;
0237: this .tiles = parent.tiles;
0238: this .writers = parent.writers;
0239: tileObservers = parent.tileObservers;
0240: numWritableTiles = parent.numWritableTiles;
0241: ancestorSampleModel = parent.ancestorSampleModel;
0242: }
0243:
0244: tileFactory = (TileFactory) JAI.getDefaultInstance()
0245: .getRenderingHint(JAI.KEY_TILE_FACTORY);
0246: }
0247:
0248: /**
0249: * Constructs a <code>TiledImage</code> with a
0250: * <code>SampleModel</code> that is compatible with a given
0251: * <code>SampleModel</code>, and given tile dimensions. The width
0252: * and height are taken from the <code>SampleModel</code>, and the
0253: * image begins at a specified point. The <code>ColorModel</code>
0254: * will be derived from the <code>SampleModel</code> using the
0255: * <code>createColorModel</code> method of <code>PlanarImage</code>.
0256: * Note that this implies that the <code>ColorModel</code> could be
0257: * <code>null</code>.
0258: *
0259: * @param origin A <code>Point</code> indicating the image's upper
0260: * left corner.
0261: * @param sampleModel A <code>SampleModel</code> with which to be
0262: * compatible.
0263: * @param tileWidth The desired tile width.
0264: * @param tileHeight The desired tile height.
0265: *
0266: * @deprecated as of JAI 1.1.
0267: */
0268: public TiledImage(Point origin, SampleModel sampleModel,
0269: int tileWidth, int tileHeight) {
0270: this (origin.x, origin.y, sampleModel.getWidth(), sampleModel
0271: .getHeight(), origin.x, origin.y, coerceSampleModel(
0272: sampleModel, tileWidth, tileHeight), PlanarImage
0273: .createColorModel(sampleModel));
0274: }
0275:
0276: /**
0277: * Constructs a <code>TiledImage</code> starting at the global
0278: * coordinate origin. The <code>ColorModel</code>
0279: * will be derived from the <code>SampleModel</code> using the
0280: * <code>createColorModel</code> method of <code>PlanarImage</code>.
0281: * Note that this implies that the <code>ColorModel</code> could be
0282: * <code>null</code>.
0283: *
0284: * @param sampleModel A <code>SampleModel</code> with which to be
0285: * compatible.
0286: * @param tileWidth The desired tile width.
0287: * @param tileHeight The desired tile height.
0288: *
0289: * @deprecated as of JAI 1.1.
0290: */
0291: public TiledImage(SampleModel sampleModel, int tileWidth,
0292: int tileHeight) {
0293: this (0, 0, sampleModel.getWidth(), sampleModel.getHeight(), 0,
0294: 0,
0295: coerceSampleModel(sampleModel, tileWidth, tileHeight),
0296: PlanarImage.createColorModel(sampleModel));
0297: }
0298:
0299: /**
0300: * Constructs a <code>TiledImage</code> equivalent to a given
0301: * <code>RenderedImage</code> but with specific tile dimensions.
0302: * Actual copying of the pixel data from the <code>RenderedImage</code>
0303: * will be deferred until the first time they are requested from the
0304: * <code>TiledImage</code>.
0305: *
0306: * @param source The source <code>RenderedImage</code>.
0307: * @param tileWidth The desired tile width.
0308: * @param tileHeight The desired tile height.
0309: *
0310: * @since JAI 1.1
0311: */
0312: public TiledImage(RenderedImage source, int tileWidth,
0313: int tileHeight) {
0314: this (source.getMinX(), source.getMinY(), source.getWidth(),
0315: source.getHeight(), source.getTileGridXOffset(), source
0316: .getTileGridYOffset(), coerceSampleModel(source
0317: .getSampleModel(), tileWidth, tileHeight),
0318: source.getColorModel());
0319:
0320: set(source);
0321: }
0322:
0323: /**
0324: * Constructs a <code>TiledImage</code> equivalent to a given
0325: * <code>RenderedImage</code>. Actual copying of the pixel data from
0326: * the <code>RenderedImage</code> will be deferred until the first time
0327: * they are requested from the <code>TiledImage</code>. The tiles of
0328: * the <code>TiledImage</code> may optionally share
0329: * <code>DataBuffer</code>s with the tiles of the source image but it
0330: * should be realized in this case that data written into the
0331: * <code>TiledImage</code> will be visible in the source image.
0332: *
0333: * @param source The source <code>RenderedImage</code>.
0334: * @param areBuffersShared Whether the tile <code>DataBuffer</code>s
0335: * of the source are re-used in the tiles of
0336: * this image. If <code>false</code> new
0337: * <code>WritableRaster</code>s will be
0338: * created.
0339: *
0340: * @since JAI 1.1
0341: */
0342: public TiledImage(RenderedImage source, boolean areBuffersShared) {
0343: this (source, source.getTileWidth(), source.getTileHeight());
0344:
0345: // Do not re-use source tiles if the source does not compute unique tiles.
0346: RenderedImage sourceRendering = source instanceof RenderedOp ? ((RenderedOp) source)
0347: .getRendering()
0348: : source;
0349: boolean suppressBufferSharing = sourceRendering instanceof OpImage
0350: && !((OpImage) sourceRendering).computesUniqueTiles();
0351:
0352: this .areBuffersShared = areBuffersShared
0353: && !suppressBufferSharing;
0354: }
0355:
0356: /**
0357: * Returns a <code>TiledImage</code> making use of an
0358: * interleaved <code>SampleModel</code> with a given layout,
0359: * number of bands, and data type. The <code>ColorModel</code>
0360: * will be derived from the <code>SampleModel</code> using the
0361: * <code>createColorModel</code> method of <code>PlanarImage</code>.
0362: * Note that this implies that the <code>ColorModel</code> could be
0363: * <code>null</code>.
0364: *
0365: * @param minX The X coordinate of the upper-left pixel
0366: * @param minY The Y coordinate of the upper-left pixel.
0367: * @param width The width of the image.
0368: * @param height The height of the image.
0369: * @param numBands The number of bands in the image.
0370: * @param dataType The data type, from among the constants
0371: * <code>DataBuffer.TYPE_*</code>.
0372: * @param tileWidth The tile width.
0373: * @param tileHeight The tile height.
0374: * @param bandOffsets An array of non-duplicated integers between 0 and
0375: * <code>numBands - 1</code> of length <code>numBands</code>
0376: * indicating the relative offset of each band.
0377: *
0378: * @deprecated as of JAI 1.1.
0379: */
0380: public static TiledImage createInterleaved(int minX, int minY,
0381: int width, int height, int numBands, int dataType,
0382: int tileWidth, int tileHeight, int[] bandOffsets) {
0383: SampleModel sm = RasterFactory
0384: .createPixelInterleavedSampleModel(dataType, tileWidth,
0385: tileHeight, numBands, numBands * tileWidth,
0386: bandOffsets);
0387: return new TiledImage(minX, minY, width, height, minX, minY,
0388: sm, PlanarImage.createColorModel(sm));
0389: }
0390:
0391: /**
0392: * Returns a <code>TiledImage</code> making use of an
0393: * banded <code>SampleModel</code> with a given layout,
0394: * number of bands, and data type. The <code>ColorModel</code>
0395: * will be derived from the <code>SampleModel</code> using the
0396: * <code>createColorModel</code> method of <code>PlanarImage</code>.
0397: * Note that this implies that the <code>ColorModel</code> could be
0398: * <code>null</code>.
0399: *
0400: * @param minX The X coordinate of the upper-left pixel
0401: * @param minY The Y coordinate of the upper-left pixel.
0402: * @param width The width of the image.
0403: * @param height The height of the image.
0404: * @param dataType The data type, from among the constants
0405: * <code>DataBuffer.TYPE_*</code>.
0406: * @param tileWidth The tile width.
0407: * @param tileHeight The tile height.
0408: * @param bankIndices An array of <code>int</code>s indicating the
0409: * index of the bank to use for each band. Bank indices
0410: * may be duplicated.
0411: * @param bandOffsets An array of integers indicating the starting
0412: * offset of each band within its bank. Bands stored in
0413: * the same bank must have sufficiently different offsets
0414: * so as not to overlap.
0415: *
0416: * @deprecated as of JAI 1.1.
0417: */
0418: public static TiledImage createBanded(int minX, int minY,
0419: int width, int height, int dataType, int tileWidth,
0420: int tileHeight, int[] bankIndices, int[] bandOffsets) {
0421: SampleModel sm = new BandedSampleModel(dataType, tileWidth,
0422: tileHeight, tileWidth, bankIndices, bandOffsets);
0423: return new TiledImage(minX, minY, width, height, minX, minY,
0424: sm, PlanarImage.createColorModel(sm));
0425: }
0426:
0427: /**
0428: * Overlays a rectangular area of pixels from an image onto a tile.
0429: *
0430: * @param tile
0431: * @param im
0432: * @param rect
0433: */
0434: private void overlayPixels(WritableRaster tile, RenderedImage im,
0435: Rectangle rect) {
0436: // Create a child of tile occupying the intersection area
0437: WritableRaster child = tile.createWritableChild(rect.x, rect.y,
0438: rect.width, rect.height, rect.x, rect.y, bandList);
0439:
0440: im.copyData(child);
0441: }
0442:
0443: /**
0444: * Overlays a set of pixels described by an Area from an image
0445: * onto a tile.
0446: *
0447: * @param tile
0448: * @param im
0449: * @param a
0450: */
0451: private void overlayPixels(WritableRaster tile, RenderedImage im,
0452: Area a) {
0453: ROIShape rs = new ROIShape(a);
0454: Rectangle bounds = rs.getBounds();
0455: LinkedList rectList = rs.getAsRectangleList(bounds.x, bounds.y,
0456: bounds.width, bounds.height);
0457: int numRects = rectList.size();
0458: for (int i = 0; i < numRects; i++) {
0459: Rectangle rect = (Rectangle) rectList.get(i);
0460: WritableRaster child = tile.createWritableChild(rect.x,
0461: rect.y, rect.width, rect.height, rect.x, rect.y,
0462: bandList);
0463: im.copyData(child);
0464: }
0465: }
0466:
0467: /**
0468: * Overlays a set of pixels described by a bitmask
0469: * onto a tile.
0470: */
0471: private void overlayPixels(WritableRaster tile, RenderedImage im,
0472: Rectangle rect, int[][] bitmask) {
0473: Raster r = im.getData(rect);
0474:
0475: // If this is sub-banded child image, create a child of the
0476: // tile into which to write.
0477: if (bandList != null) {
0478: tile = tile.createWritableChild(rect.x, rect.y, rect.width,
0479: rect.height, rect.x, rect.y, bandList);
0480: }
0481:
0482: // Create a buffer suitable for transferring pixels
0483: Object data = r.getDataElements(rect.x, rect.y, null);
0484:
0485: // The bitmask passed in might have undefined values outside
0486: // the specified rect - therefore make sure that those bits
0487: // are ignored.
0488: int leftover = rect.width % 32;
0489: int bitWidth = ((rect.width + 31) / 32)
0490: - (leftover > 0 ? 1 : 0);
0491: int y = rect.y;
0492:
0493: for (int j = 0; j < rect.height; j++, y++) {
0494: int[] rowMask = bitmask[j];
0495: int i, x = rect.x;
0496:
0497: for (i = 0; i < bitWidth; i++) {
0498: int mask32 = rowMask[i];
0499: int bit = 0x80000000;
0500:
0501: for (int b = 0; b < 32; b++, x++) {
0502: if ((mask32 & bit) != 0) {
0503: r.getDataElements(x, y, data);
0504: tile.setDataElements(x, y, data);
0505: }
0506: bit >>>= 1;
0507: }
0508: }
0509:
0510: if (leftover > 0) {
0511: int mask32 = rowMask[i];
0512: int bit = 0x80000000;
0513:
0514: for (int b = 0; b < leftover; b++, x++) {
0515: if ((mask32 & bit) != 0) {
0516: r.getDataElements(x, y, data);
0517: tile.setDataElements(x, y, data);
0518: }
0519: bit >>>= 1;
0520: }
0521: }
0522: }
0523: }
0524:
0525: /**
0526: * Overlays a given <code>RenderedImage</code> on top of the
0527: * current contents of the <code>TiledImage</code>. The source
0528: * image must have a <code>SampleModel</code> compatible with that
0529: * of this image. If the source image does not overlap this image
0530: * then invoking this method will have no effect.
0531: *
0532: * <p> The source image is added as a fallback <code>PropertySource</code>
0533: * for the <code>TiledImage</code>: if a given property is not set directly
0534: * on the <code>TiledImage</code> an attempt will be made to obtain its
0535: * value from the source image.
0536: *
0537: * @param im A <code>RenderedImage</code> source to overlay.
0538: *
0539: * @throws <code>IllegalArgumentException</code> if <code>im</code> is
0540: * <code>null</code>.
0541: */
0542: public void set(RenderedImage im) {
0543:
0544: if (im == null) {
0545: throw new IllegalArgumentException(JaiI18N
0546: .getString("Generic0"));
0547: }
0548:
0549: // Same source: do nothing.
0550: if (getNumSources() > 0 && im == getSourceImage(0)) {
0551: return;
0552: }
0553:
0554: Rectangle imRect = new Rectangle(im.getMinX(), im.getMinY(), im
0555: .getWidth(), im.getHeight());
0556:
0557: // Return if the source image does not overlap this image.
0558: if ((imRect = imRect.intersection(getBounds())).isEmpty()) {
0559: return;
0560: }
0561:
0562: // Unset buffer sharing flag.
0563: areBuffersShared = false;
0564:
0565: // Set tile index limits.
0566: int txMin = XToTileX(imRect.x);
0567: int tyMin = YToTileY(imRect.y);
0568: int txMax = XToTileX(imRect.x + imRect.width - 1);
0569: int tyMax = YToTileY(imRect.y + imRect.height - 1);
0570:
0571: // Loop over all in-bound tiles, find ones that have been computed
0572: for (int j = tyMin; j <= tyMax; j++) {
0573: for (int i = txMin; i <= txMax; i++) {
0574: WritableRaster t;
0575: if ((t = tiles[i - minTileX][j - minTileY]) != null
0576: && !isTileLocked(i, j)) {
0577: Rectangle tileRect = getTileRect(i, j);
0578: tileRect = tileRect.intersection(imRect);
0579: if (!tileRect.isEmpty()) {
0580: overlayPixels(t, im, tileRect);
0581: }
0582: }
0583: }
0584: }
0585:
0586: // Cache the (wrapped) source image and clear the source ROI.
0587: PlanarImage src = PlanarImage.wrapRenderedImage(im);
0588: if (getNumSources() == 0) {
0589: addSource(src);
0590: } else {
0591: setSource(src, 0);
0592: }
0593: srcROI = null;
0594: overlapBounds = imRect;
0595:
0596: // Add the source as fallback PropertySource.
0597: properties.addProperties(src);
0598: }
0599:
0600: /**
0601: * Overlays a given <code>RenderedImage</code> on top of the
0602: * current contents of the <code>TiledImage</code> and its
0603: * intersection with the supplied ROI. The source
0604: * image must have a <code>SampleModel</code> compatible with that
0605: * of this image. If the source image and the region of interest
0606: * do not both overlap this image then invoking this method will
0607: * have no effect.
0608: *
0609: * <p> The source image is added as a fallback <code>PropertySource</code>
0610: * for the <code>TiledImage</code>: if a given property is not set directly
0611: * on the <code>TiledImage</code> an attempt will be made to obtain its
0612: * value from the source image.
0613: *
0614: * @param im A <code>RenderedImage</code> source to overlay.
0615: * @param roi The region of interest.
0616: *
0617: * @throws <code>IllegalArgumentException</code> either parameter is
0618: * <code>null</code>.
0619: */
0620: public void set(RenderedImage im, ROI roi) {
0621:
0622: if (im == null) {
0623: throw new IllegalArgumentException(JaiI18N
0624: .getString("Generic0"));
0625: }
0626:
0627: // Same source: do nothing.
0628: if (getNumSources() > 0 && im == getSourceImage(0)) {
0629: return;
0630: }
0631:
0632: Rectangle imRect = new Rectangle(im.getMinX(), im.getMinY(), im
0633: .getWidth(), im.getHeight());
0634:
0635: // Return if there is not a common intersection among this
0636: // image and the source image and the region of interest.
0637: Rectangle overlap = imRect.intersection(roi.getBounds());
0638: if (overlap.isEmpty()
0639: || (overlap = overlap.intersection(getBounds()))
0640: .isEmpty()) {
0641: return;
0642: }
0643:
0644: // Unset buffer sharing flag.
0645: areBuffersShared = false;
0646:
0647: // Set tile index limits.
0648: int txMin = XToTileX(overlap.x);
0649: int tyMin = YToTileY(overlap.y);
0650: int txMax = XToTileX(overlap.x + overlap.width - 1);
0651: int tyMax = YToTileY(overlap.y + overlap.height - 1);
0652:
0653: Shape roiShape = roi.getAsShape();
0654: Area roiArea = null;
0655: if (roiShape != null) {
0656: roiArea = new Area(roiShape);
0657: }
0658:
0659: // Loop over all in-bound tiles, find ones that have been computed
0660: for (int j = tyMin; j <= tyMax; j++) {
0661: for (int i = txMin; i <= txMax; i++) {
0662: WritableRaster t;
0663: if ((t = tiles[i - minTileX][j - minTileY]) != null
0664: && !isTileLocked(i, j)) {
0665: Rectangle rect = getTileRect(i, j).intersection(
0666: overlap);
0667: if (!rect.isEmpty()) {
0668: if (roiShape != null) {
0669: Area a = new Area(rect);
0670: a.intersect(roiArea);
0671:
0672: if (!a.isEmpty()) {
0673: overlayPixels(t, im, a);
0674: }
0675: } else {
0676: int[][] bitmask = roi.getAsBitmask(rect.x,
0677: rect.y, rect.width, rect.height,
0678: null);
0679:
0680: if (bitmask != null && bitmask.length > 0) {
0681: overlayPixels(t, im, rect, bitmask);
0682: }
0683: }
0684: }
0685: }
0686: }
0687: }
0688:
0689: // Cache the (wrapped) source image and the source ROI.
0690: PlanarImage src = PlanarImage.wrapRenderedImage(im);
0691: if (getNumSources() == 0) {
0692: addSource(src);
0693: } else {
0694: setSource(src, 0);
0695: }
0696: srcROI = roi;
0697: overlapBounds = overlap;
0698:
0699: // Add the source as fallback PropertySource.
0700: properties.addProperties(src);
0701: }
0702:
0703: /**
0704: * Creates a <code>Graphics</code> object that can be used to
0705: * paint text and graphics onto the <code>TiledImage</code>.
0706: * The <code>TiledImage</code> must be of integral data type
0707: * or an <code>UnsupportedOperationException</code> will be thrown.
0708: *
0709: * @deprecated as of JAI 1.1.
0710: */
0711: public Graphics getGraphics() {
0712: return createGraphics();
0713: }
0714:
0715: /**
0716: * Creates a <code>Graphics2D</code> object that can be used to
0717: * paint text and graphics onto the <code>TiledImage</code>.
0718: * The <code>TiledImage</code> must be of integral data type
0719: * or an <code>UnsupportedOperationException</code> will be thrown.
0720: */
0721: public Graphics2D createGraphics() {
0722: int dataType = sampleModel.getDataType();
0723: if (dataType != DataBuffer.TYPE_BYTE
0724: && dataType != DataBuffer.TYPE_SHORT
0725: && dataType != DataBuffer.TYPE_USHORT
0726: && dataType != DataBuffer.TYPE_INT) {
0727: throw new UnsupportedOperationException(JaiI18N
0728: .getString("TiledImage0"));
0729: }
0730: return new TiledImageGraphics(this );
0731: }
0732:
0733: /**
0734: * Returns a <code>TiledImage</code> that shares the tile
0735: * <code>Raster</code>s of this image. The returned image
0736: * occupies a sub-area of the parent image, and possesses a
0737: * possibly permuted subset of the parent's bands. The two images
0738: * share a common coordinate system.
0739: *
0740: * <p> The image bounds are clipped against the bounds of the
0741: * parent image.
0742: *
0743: * <p> If the specified <code>ColorModel</code> is <code>null</code>
0744: * then the <code>ColorModel</code> of the sub-image will be set to
0745: * <code>null</code> unless <code>bandSelect</code> is either
0746: * <code>null</code> or equal in length to the number of bands in the
0747: * image in which cases the sub-image <code>ColorModel</code> will be
0748: * set to that of the current image.
0749: *
0750: * @param x the minimum X coordinate of the subimage.
0751: * @param y the minimum Y coordinate of the subimage.
0752: * @param w the width of the subimage.
0753: * @param h the height of the subimage.
0754: * @param bandSelect an array of band indices; if null,
0755: * all bands are selected.
0756: * @param cm the <code>ColorModel</code> of the sub-image.
0757: *
0758: * @return The requested sub-image or <code>null</code> if either
0759: * the specified rectangular area or its intersection with
0760: * the current image is empty.
0761: *
0762: * @since JAI 1.1
0763: */
0764: public TiledImage getSubImage(int x, int y, int w, int h,
0765: int[] bandSelect, ColorModel cm) {
0766: // Check for empty overlap.
0767: Rectangle subImageBounds = new Rectangle(x, y, w, h);
0768: if (subImageBounds.isEmpty()) {
0769: return null;
0770: }
0771: Rectangle overlap = subImageBounds.intersection(getBounds());
0772: if (overlap.isEmpty()) {
0773: return null;
0774: }
0775:
0776: // Use the original SampleModel or create a subset of it.
0777: SampleModel sm = bandSelect != null ? getSampleModel()
0778: .createSubsetSampleModel(bandSelect) : getSampleModel();
0779:
0780: // Set the ColorModel.
0781: if (cm == null
0782: && (bandSelect == null || bandSelect.length == getSampleModel()
0783: .getNumBands())) {
0784: cm = getColorModel();
0785: }
0786:
0787: // Create the sub-image.
0788: TiledImage subImage = new TiledImage(this , overlap.x,
0789: overlap.y, overlap.width, overlap.height,
0790: getTileGridXOffset(), getTileGridYOffset(), sm, cm);
0791:
0792: // Derive sub-image sub-band list with respect to the ancestor
0793: // TiledImage. It is possible here that an
0794: // ArrayIndexOutOfBoundsException could be thrown if the user
0795: // is not careful.
0796: int[] subBandList = null;
0797: if (bandSelect != null) { // "this" is being sub-banded.
0798: if (bandList != null) { //"this" is a sub-band child.
0799: // Derive the sub-band list using the sub-band list of
0800: // "this" and the list passed in.
0801: subBandList = new int[bandSelect.length];
0802: for (int band = 0; band < bandSelect.length; band++) {
0803: subBandList[band] = bandList[bandSelect[band]];
0804: }
0805: } else { // "this" is not a sub-band child.
0806: // Set the sub-band list to the list passed in.
0807: subBandList = bandSelect;
0808: }
0809: } else { // "this" is not being sub-banded.
0810: // Pass on the sub-band list of "this".
0811: subBandList = bandList;
0812: }
0813:
0814: // Set the sub-band list of the newly created sub-image.
0815: subImage.bandList = subBandList;
0816:
0817: return subImage;
0818: }
0819:
0820: /**
0821: * Returns a <code>TiledImage</code> that shares the tile
0822: * <code>Raster</code>s of this image. The returned image
0823: * occupies a sub-area of the parent image, and possesses a
0824: * possibly permuted subset of the parent's bands. The two images
0825: * share a common coordinate system. The <code>ColorModel</code>
0826: * will be derived from the sub-image <code>SampleModel</code> using the
0827: * <code>createColorModel</code> method of <code>PlanarImage</code>.
0828: * Note that this implies that the <code>ColorModel</code> could be
0829: * <code>null</code>.
0830: *
0831: * <p> The image bounds are clipped against the bounds of the
0832: * parent image.
0833: *
0834: * @param x the minimum X coordinate of the subimage.
0835: * @param y the minimum Y coordinate of the subimage.
0836: * @param w the width of the subimage.
0837: * @param h the height of the subimage.
0838: * @param bandSelect an array of band indices; if null,
0839: * all bands are selected.
0840: *
0841: * @return The requested sub-image or <code>null</code> if either
0842: * the specified rectangular area or its intersection with
0843: * the current image is empty.
0844: *
0845: * @deprecated as of JAI 1.1.
0846: */
0847: public TiledImage getSubImage(int x, int y, int w, int h,
0848: int[] bandSelect) {
0849: SampleModel sm = bandSelect != null ? getSampleModel()
0850: .createSubsetSampleModel(bandSelect) : getSampleModel();
0851: return getSubImage(x, y, w, h, bandSelect, createColorModel(sm));
0852: }
0853:
0854: /**
0855: * Returns a <code>TiledImage</code> that shares the tile
0856: * <code>Raster</code>s of this image. The returned image
0857: * occupies a subarea of the parent image. The two images share a
0858: * common coordinate system.
0859: *
0860: * <p> The image bounds are clipped against the bounds of the
0861: * parent image.
0862: *
0863: * @param x the minimum X coordinate of the subimage.
0864: * @param y the minimum Y coordinate of the subimage.
0865: * @param w the width of the subimage.
0866: * @param h the height of the subimage.
0867: */
0868: public TiledImage getSubImage(int x, int y, int w, int h) {
0869: return getSubImage(x, y, w, h, null, null);
0870: }
0871:
0872: /**
0873: * Returns a <code>TiledImage</code> that shares the tile
0874: * <code>Raster</code>s of this image.
0875: *
0876: * <p> If the specified <code>ColorModel</code> is <code>null</code>
0877: * then the <code>ColorModel</code> of the sub-image will be set to
0878: * <code>null</code> unless <code>bandSelect</code> is equal in length
0879: * to the number of bands in the image in which cases the sub-image
0880: * <code>ColorModel</code> will be set to that of the current image.
0881: *
0882: * @param bandSelect an array of band indices.
0883: * @param cm the <code>ColorModel</code> of the sub-image.
0884: *
0885: * @throws <code>IllegalArgumentException</code> is <code>bandSelect</code>
0886: * is <code>null</code>.
0887: *
0888: * @since JAI 1.1
0889: */
0890: public TiledImage getSubImage(int[] bandSelect, ColorModel cm) {
0891: if (bandSelect == null) {
0892: throw new IllegalArgumentException(JaiI18N
0893: .getString("Generic0"));
0894: }
0895: return getSubImage(getMinX(), getMinY(), getWidth(),
0896: getHeight(), bandSelect, cm);
0897: }
0898:
0899: /**
0900: * Returns a <code>TiledImage</code> that shares the tile
0901: * <code>Raster</code>s of this image. The returned image
0902: * occupies the same area as the parent image, and possesses a
0903: * possibly permuted subset of the parent's bands. The
0904: * <code>ColorModel</code> will be derived from the sub-image
0905: * <code>SampleModel</code> using the <code>createColorModel</code>
0906: * method of <code>PlanarImage</code>. Note that this implies that
0907: * the <code>ColorModel</code> could be <code>null</code>.
0908: *
0909: * @param bandSelect an array of band indices.
0910: *
0911: * @deprecated as of JAI 1.1.
0912: */
0913: public TiledImage getSubImage(int[] bandSelect) {
0914: if (bandSelect == null) {
0915: throw new IllegalArgumentException(JaiI18N
0916: .getString("Generic0"));
0917: }
0918: return getSubImage(getMinX(), getMinY(), getWidth(),
0919: getHeight(), bandSelect); // Deliberately using 5-param version.
0920: }
0921:
0922: /**
0923: * Forces the requested tile to be computed if has not already been so
0924: * and if a source is available.
0925: *
0926: * @throws ArrayIndexOutOfBoundsException if at least one of the supplied
0927: * tile indices is out of the image.
0928: */
0929: private void createTile(int tileX, int tileY) {
0930: PlanarImage src = getNumSources() > 0 ? getSourceImage(0)
0931: : null;
0932:
0933: // If this is a child image with no source for uncomputed tiles
0934: // forward the call to the parent.
0935: if (src == null && parent != null) {
0936: parent.createTile(tileX, tileY);
0937: return;
0938: }
0939:
0940: synchronized (tiles) {
0941: // Do nothing if tile is non-null, i.e., already computed.
0942: if (tiles[tileX - minTileX][tileY - minTileY] == null) {
0943: // If sharing buffers, do so.
0944: if (areBuffersShared) {
0945: Raster srcTile = src.getTile(tileX, tileY);
0946: if (srcTile instanceof WritableRaster) {
0947: tiles[tileX - minTileX][tileY - minTileY] = (WritableRaster) srcTile;
0948: } else {
0949: Point location = new Point(srcTile.getMinX(),
0950: srcTile.getMinY());
0951: tiles[tileX - minTileX][tileY - minTileY] = Raster
0952: .createWritableRaster(sampleModel,
0953: srcTile.getDataBuffer(),
0954: location);
0955: }
0956: return;
0957: }
0958:
0959: // Create the tile using the ancestor SampleModel.
0960: tiles[tileX - minTileX][tileY - minTileY] = createWritableRaster(
0961: ancestorSampleModel, new Point(tileXToX(tileX),
0962: tileYToY(tileY)));
0963: WritableRaster tile = tiles[tileX - minTileX][tileY
0964: - minTileY];
0965:
0966: // If a source is available try to set the tile's data.
0967: if (src != null) {
0968: // Get the bounds of the tile's support.
0969: Rectangle tileRect = getTileRect(tileX, tileY);
0970:
0971: // Determine the intersection of the tile and the overlap.
0972: Rectangle rect = overlapBounds
0973: .intersection(tileRect);
0974:
0975: // Bail if this doesn't intersect the effective overlap.
0976: if (rect.isEmpty()) {
0977: return;
0978: }
0979:
0980: // If a source ROI is present, use it.
0981: if (srcROI != null) {
0982: // Attempt to get the ROI as a Shape.
0983: Shape roiShape = srcROI.getAsShape();
0984:
0985: if (roiShape != null) {
0986: // Determine the area of overlap.
0987: Area a = new Area(rect);
0988: a.intersect(new Area(roiShape));
0989:
0990: if (!a.isEmpty()) {
0991: // If the area is non-empty overlay the pixels.
0992: overlayPixels(tile, src, a);
0993: }
0994: } else {
0995: int[][] bitmask = srcROI.getAsBitmask(
0996: rect.x, rect.y, rect.width,
0997: rect.height, null);
0998:
0999: overlayPixels(tile, src, rect, bitmask);
1000: }
1001: } else {
1002: // If the intersection equals the tile area, copy data into
1003: // the entire tile. If the tile straddles the edge of the
1004: // source, copy only into the intersection.
1005: if (!rect.isEmpty()) {
1006: if (bandList == null
1007: && rect.equals(tileRect)) {
1008: // The current image has the same bands in the
1009: // same order as its ancestor TiledImage and
1010: // the requested tile is completely within "src".
1011: if (tileRect.equals(tile.getBounds()))
1012: src.copyData(tile);
1013: else
1014: src.copyData(tile
1015: .createWritableChild(
1016: rect.x, rect.y,
1017: rect.width,
1018: rect.height,
1019: rect.x, rect.y,
1020: null));
1021: } else {
1022: overlayPixels(tile, src, rect);
1023: }
1024: }
1025: }
1026: }
1027: }
1028: }
1029: }
1030:
1031: /**
1032: * Retrieves a particular tile from the image for reading only.
1033: * The tile will be computed if it hasn't been previously.
1034: * Any attempt to write to the tile will produce undefined results.
1035: *
1036: * @param tileX the X index of the tile.
1037: * @param tileY the Y index of the tile.
1038: */
1039: public Raster getTile(int tileX, int tileY) {
1040: if (tileX < minTileX || tileY < minTileY
1041: || tileX > getMaxTileX() || tileY > getMaxTileY()) {
1042: return null;
1043: }
1044:
1045: createTile(tileX, tileY);
1046:
1047: // For non-sub-banded image return the tile directly.
1048: if (bandList == null) {
1049: return (Raster) tiles[tileX - minTileX][tileY - minTileY];
1050: }
1051:
1052: // For sub-banded image return appropriate band subset.
1053: Raster r = (Raster) tiles[tileX - minTileX][tileY - minTileY];
1054:
1055: return r.createChild(r.getMinX(), r.getMinY(), r.getWidth(), r
1056: .getHeight(), r.getMinX(), r.getMinY(), bandList);
1057: }
1058:
1059: /**
1060: * Retrieves a particular tile from the image for reading and writing.
1061: * If the tile is locked, null will be returned. Otherwise, the tile
1062: * will be computed if it hasn't been previously. Updates of the tile
1063: * will become visible to readers of this image as they occur.
1064: *
1065: * @param tileX the X index of the tile.
1066: * @param tileY the Y index of the tile.
1067: * @return The requested tile or null if the tile is locked.
1068: */
1069: public WritableRaster getWritableTile(int tileX, int tileY) {
1070: if (tileX < minTileX || tileY < minTileY
1071: || tileX > getMaxTileX() || tileY > getMaxTileY()) {
1072: return null;
1073: }
1074:
1075: if (isTileLocked(tileX, tileY)) {
1076: return null;
1077: }
1078:
1079: createTile(tileX, tileY);
1080: ++writers[tileX - minTileX][tileY - minTileY];
1081:
1082: if (writers[tileX - minTileX][tileY - minTileY] == 1) {
1083: numWritableTiles[0]++;
1084:
1085: Enumeration e = tileObservers.elements();
1086: while (e.hasMoreElements()) {
1087: TileObserver t = (TileObserver) e.nextElement();
1088: t.tileUpdate(this , tileX, tileY, true);
1089: }
1090: }
1091:
1092: // For non-sub-banded image return the tile directly.
1093: if (bandList == null) {
1094: return tiles[tileX - minTileX][tileY - minTileY];
1095: }
1096:
1097: // For sub-banded image return appropriate band subset.
1098: WritableRaster wr = tiles[tileX - minTileX][tileY - minTileY];
1099:
1100: return wr.createWritableChild(wr.getMinX(), wr.getMinY(), wr
1101: .getWidth(), wr.getHeight(), wr.getMinX(),
1102: wr.getMinY(), bandList);
1103: }
1104:
1105: /**
1106: * Indicates that a writer is done updating a tile.
1107: * The effects of attempting to release a tile that has not been
1108: * grabbed, or releasing a tile more than once are undefined.
1109: *
1110: * @param tileX the X index of the tile.
1111: * @param tileY the Y index of the tile.
1112: */
1113: public void releaseWritableTile(int tileX, int tileY) {
1114: if (isTileLocked(tileX, tileY)) {
1115: return;
1116: }
1117:
1118: --writers[tileX - minTileX][tileY - minTileY];
1119:
1120: if (writers[tileX - minTileX][tileY - minTileY] < 0) {
1121: throw new RuntimeException(JaiI18N.getString("TiledImage1"));
1122: }
1123:
1124: if (writers[tileX - minTileX][tileY - minTileY] == 0) {
1125: numWritableTiles[0]--;
1126:
1127: Enumeration e = tileObservers.elements();
1128: while (e.hasMoreElements()) {
1129: TileObserver t = (TileObserver) e.nextElement();
1130: t.tileUpdate(this , tileX, tileY, false);
1131: }
1132: }
1133: }
1134:
1135: /**
1136: * Forces a tile to be computed, and its contents stored
1137: * indefinitely. A tile may not be locked if it is currently
1138: * writable. This method should only be used within JAI, in
1139: * order to optimize memory allocation.
1140: *
1141: * @param tileX the X index of the tile.
1142: * @param tileY the Y index of the tile.
1143: * @return Whether the tile was successfully locked.
1144: */
1145: protected boolean lockTile(int tileX, int tileY) {
1146: if (tileX < minTileX || tileY < minTileY
1147: || tileX > getMaxTileX() || tileY > getMaxTileY()) {
1148: return false;
1149: }
1150:
1151: // Return false if the tile is writable.
1152: if (isTileWritable(tileX, tileY)) {
1153: return false;
1154: }
1155:
1156: // Force the tile to be computed if it has not yet been.
1157: createTile(tileX, tileY);
1158:
1159: // Set the corresponding writers count to -1.
1160: writers[tileX - minTileX][tileY - minTileY] = -1;
1161:
1162: return true;
1163: }
1164:
1165: /**
1166: * Returns <code>true</code> if a tile is locked.
1167: *
1168: * @param tileX the X index of the tile.
1169: * @param tileY the Y index of the tile.
1170: * @return Whether the tile is locked.
1171: */
1172: protected boolean isTileLocked(int tileX, int tileY) {
1173: return writers[tileX - minTileX][tileY - minTileY] < 0;
1174: }
1175:
1176: /**
1177: * Sets a region of a <code>TiledImage</code> to be a copy of a
1178: * supplied <code>Raster</code>. The <code>Raster</code>'s
1179: * coordinate system is used to position it within the image.
1180: * The computation of all overlapping tiles will be forced prior
1181: * to modification of the data of the affected area.
1182: *
1183: * @param r a <code>Raster</code> containing pixels to be copied
1184: * into the <code>TiledImage</code>.
1185: */
1186: public void setData(Raster r) {
1187: // Return if the intersection of the image and Raster bounds is empty.
1188: Rectangle rBounds = r.getBounds();
1189: if ((rBounds = rBounds.intersection(getBounds())).isEmpty()) {
1190: return;
1191: }
1192:
1193: // Set tile index limits.
1194: int txMin = XToTileX(rBounds.x);
1195: int tyMin = YToTileY(rBounds.y);
1196: int txMax = XToTileX(rBounds.x + rBounds.width - 1);
1197: int tyMax = YToTileY(rBounds.y + rBounds.height - 1);
1198:
1199: for (int ty = tyMin; ty <= tyMax; ty++) {
1200: for (int tx = txMin; tx <= txMax; tx++) {
1201: WritableRaster wr = getWritableTile(tx, ty);
1202: if (wr != null) {
1203: // XXX bpb 02/04/1999
1204: // All this checking shouldn't be necessary except
1205: // that it doesn't look as if WritableRaster.setRect()
1206: // correctly accounts for cases wherein the parameter
1207: // Raster is not contained in the target WritableRaster.
1208: Rectangle tileRect = getTileRect(tx, ty);
1209: if (tileRect.contains(rBounds)) {
1210: JDKWorkarounds.setRect(wr, r, 0, 0);
1211: } else {
1212: Rectangle xsect = rBounds
1213: .intersection(tileRect);
1214: Raster rChild = r.createChild(xsect.x, xsect.y,
1215: xsect.width, xsect.height, xsect.x,
1216: xsect.y, null);
1217: WritableRaster wChild = wr.createWritableChild(
1218: xsect.x, xsect.y, xsect.width,
1219: xsect.height, xsect.x, xsect.y, null);
1220: JDKWorkarounds.setRect(wChild, rChild, 0, 0);
1221: }
1222: releaseWritableTile(tx, ty);
1223: }
1224: }
1225: }
1226: }
1227:
1228: /**
1229: * Sets a region of a <code>TiledImage</code> to be a copy of a
1230: * supplied <code>Raster</code>. The <code>Raster</code>'s
1231: * coordinate system is used to position it within the image.
1232: * The computation of all overlapping tiles will be forced prior
1233: * to modification of the data of the affected area.
1234: *
1235: * @param r a <code>Raster</code> containing pixels to be copied
1236: * into the <code>TiledImage</code>.
1237: * @param roi The region of interest.
1238: */
1239: public void setData(Raster r, ROI roi) {
1240: // Return if the intersection of the image bounds, the Raster,
1241: // and the ROI bounds is empty.
1242: Rectangle rBounds = r.getBounds();
1243: if ((rBounds = rBounds.intersection(getBounds())).isEmpty()
1244: || (rBounds = rBounds.intersection(roi.getBounds()))
1245: .isEmpty()) {
1246: return;
1247: }
1248:
1249: // Get the Rectangle list representation of the ROI.
1250: LinkedList rectList = roi.getAsRectangleList(rBounds.x,
1251: rBounds.y, rBounds.width, rBounds.height);
1252:
1253: // Set tile index limits.
1254: int txMin = XToTileX(rBounds.x);
1255: int tyMin = YToTileY(rBounds.y);
1256: int txMax = XToTileX(rBounds.x + rBounds.width - 1);
1257: int tyMax = YToTileY(rBounds.y + rBounds.height - 1);
1258:
1259: int numRects = rectList.size();
1260:
1261: for (int ty = tyMin; ty <= tyMax; ty++) {
1262: for (int tx = txMin; tx <= txMax; tx++) {
1263: WritableRaster wr = getWritableTile(tx, ty);
1264: if (wr != null) {
1265: Rectangle tileRect = getTileRect(tx, ty);
1266: for (int i = 0; i < numRects; i++) {
1267: Rectangle rect = (Rectangle) rectList.get(i);
1268: rect = rect.intersection(tileRect);
1269: // XXX: Should the if-block below be split as in
1270: // set(RenderedImage, ROI) above?
1271: if (!rect.isEmpty()) {
1272: Raster rChild = r.createChild(rect.x,
1273: rect.y, rect.width, rect.height,
1274: rect.x, rect.y, null);
1275: WritableRaster wChild = wr
1276: .createWritableChild(rect.x,
1277: rect.y, rect.width,
1278: rect.height, rect.x,
1279: rect.y, null);
1280: JDKWorkarounds
1281: .setRect(wChild, rChild, 0, 0);
1282: }
1283: }
1284: releaseWritableTile(tx, ty);
1285: }
1286: }
1287: }
1288: }
1289:
1290: /**
1291: * Informs this <code>TiledImage</code> that another object is
1292: * interested in being notified whenever any tile becomes writable
1293: * or ceases to be writable. A tile becomes writable when it is
1294: * not currently writable and <code>getWritableTile()</code> is
1295: * called. A tile ceases to be writable when
1296: * <code>releaseTile()</code> is called and the number of calls to
1297: * <code>getWritableTile()</code> and
1298: * <code>releaseWritableTile()</code> are identical.
1299: *
1300: * <p> It is the responsibility of the <code>TiledImage</code> to
1301: * inform all registered <code>TileObserver</code> objects of such
1302: * changes in tile writability before the writer has a chance to
1303: * make any modifications.
1304: *
1305: * @param observer An object implementing the
1306: * <code>TileObserver</code> interface.
1307: */
1308: public void addTileObserver(TileObserver observer) {
1309: tileObservers.addElement(observer);
1310: }
1311:
1312: /**
1313: * Informs this <code>TiledImage</code> that a particular TileObserver no
1314: * longer wishes to receive updates on tile writability status.
1315: * The result of attempting to remove a listener that is not
1316: * registered is undefined.
1317: *
1318: * @param observer An object implementing the
1319: * <code>TileObserver</code> interface.
1320: */
1321: public void removeTileObserver(TileObserver observer) {
1322: tileObservers.removeElement(observer);
1323: }
1324:
1325: /**
1326: * Returns a list of tiles that are currently held by one or more
1327: * writers or <code>null</code> of no tiles are so held.
1328: *
1329: * @return An array of <code>Point</code>s representing tile indices
1330: * or <code>null</code>.
1331: */
1332: public Point[] getWritableTileIndices() {
1333: Point[] indices = null;
1334:
1335: if (hasTileWriters()) {
1336: Vector v = new Vector();
1337: int count = 0;
1338:
1339: for (int j = 0; j < tilesY; j++) {
1340: for (int i = 0; i < tilesX; i++) {
1341: if (writers[i][j] > 0) {
1342: v.addElement(new Point(i + minTileX, j
1343: + minTileY));
1344: ++count;
1345: }
1346: }
1347: }
1348:
1349: indices = new Point[count];
1350: for (int k = 0; k < count; k++) {
1351: indices[k] = (Point) v.elementAt(k);
1352: }
1353: }
1354:
1355: return indices;
1356: }
1357:
1358: /**
1359: * Returns <code>true</code> if any tile is being held by a
1360: * writer, <code>false</code> otherwise. This provides a quick
1361: * way to check whether it is necessary to make copies of tiles --
1362: * if there are no writers, it is safe to use the tiles directly,
1363: * while registering to learn of future writers.
1364: */
1365: public boolean hasTileWriters() {
1366: return numWritableTiles[0] > 0;
1367: }
1368:
1369: /**
1370: * Returns <code>true</code> if a tile has writers.
1371: *
1372: * @param tileX the X index of the tile.
1373: * @param tileY the Y index of the tile.
1374: */
1375: public boolean isTileWritable(int tileX, int tileY) {
1376: return writers[tileX - minTileX][tileY - minTileY] > 0;
1377: }
1378:
1379: /**
1380: * Sets the <code>tiles</code> array to <code>null</code> so that
1381: * the image may be used again.
1382: *
1383: * @throws IllegalStateException if <code>hasTileWriters()</code>
1384: * returns <code>true</code>.
1385: *
1386: * @since JAI 1.1.2
1387: */
1388: public void clearTiles() {
1389: if (hasTileWriters()) {
1390: throw new IllegalStateException(JaiI18N
1391: .getString("TiledImage2"));
1392: }
1393: tiles = null;
1394: }
1395:
1396: /*
1397: private int[] XToTileXArray = null;
1398: private int[] YToTileYArray = null;
1399:
1400: private void initTileArrays() {
1401: if (XTileTileXArray == null) {
1402: XToTileXArray = new int[width];
1403: YToTileYArray = new int[height];
1404:
1405: for (int i = 0; i < width; i++) {
1406: XToTileXArray[i] = XToTileX(minX + i);
1407: }
1408:
1409: for (int j = 0; j < height; j++) {
1410: YToTileYArray[j] = YToTileY(minY + j);
1411: }
1412: }
1413: }
1414: */
1415:
1416: /**
1417: * Sets a sample of a pixel to a given <code>int</code> value.
1418: *
1419: * @param x The X coordinate of the pixel.
1420: * @param y The Y coordinate of the pixel.
1421: * @param b The band of the sample within the pixel.
1422: * @param s The value to which to set the sample.
1423: */
1424: public void setSample(int x, int y, int b, int s) {
1425: int tileX = XToTileX(x);
1426: int tileY = YToTileY(y);
1427: WritableRaster t = getWritableTile(tileX, tileY);
1428: if (t != null) {
1429: t.setSample(x, y, b, s);
1430: }
1431: releaseWritableTile(tileX, tileY);
1432: }
1433:
1434: /**
1435: * Returns the value of a given sample of a pixel as an <code>int</code>.
1436: *
1437: * @param x The X coordinate of the pixel.
1438: * @param y The Y coordinate of the pixel.
1439: * @param b The band of the sample within the pixel.
1440: */
1441: public int getSample(int x, int y, int b) {
1442: int tileX = XToTileX(x);
1443: int tileY = YToTileY(y);
1444: Raster t = getTile(tileX, tileY);
1445: return t.getSample(x, y, b);
1446: }
1447:
1448: /**
1449: * Sets a sample of a pixel to a given <code>float</code> value.
1450: *
1451: * @param x The X coordinate of the pixel.
1452: * @param y The Y coordinate of the pixel.
1453: * @param b The band of the sample within the pixel.
1454: * @param s The value to which to set the sample.
1455: */
1456: public void setSample(int x, int y, int b, float s) {
1457: int tileX = XToTileX(x);
1458: int tileY = YToTileY(y);
1459: WritableRaster t = getWritableTile(tileX, tileY);
1460: if (t != null) {
1461: t.setSample(x, y, b, s);
1462: }
1463: releaseWritableTile(tileX, tileY);
1464: }
1465:
1466: /**
1467: * Returns the value of a given sample of a pixel as a <code>float</code>.
1468: *
1469: * @param x The X coordinate of the pixel.
1470: * @param y The Y coordinate of the pixel.
1471: * @param b The band of the sample within the pixel.
1472: */
1473: public float getSampleFloat(int x, int y, int b) {
1474: int tileX = XToTileX(x);
1475: int tileY = YToTileY(y);
1476: Raster t = getTile(tileX, tileY);
1477: return t.getSampleFloat(x, y, b);
1478: }
1479:
1480: /**
1481: * Sets a sample of a pixel to a given <code>double</code> value.
1482: *
1483: * @param x The X coordinate of the pixel.
1484: * @param y The Y coordinate of the pixel.
1485: * @param b The band of the sample within the pixel.
1486: * @param s The value to which to set the sample.
1487: */
1488: public void setSample(int x, int y, int b, double s) {
1489: int tileX = XToTileX(x);
1490: int tileY = YToTileY(y);
1491: WritableRaster t = getWritableTile(tileX, tileY);
1492: if (t != null) {
1493: t.setSample(x, y, b, s);
1494: }
1495: releaseWritableTile(tileX, tileY);
1496: }
1497:
1498: /**
1499: * Returns the value of a given sample of a pixel as a <code>double</code>.
1500: *
1501: * @param x The X coordinate of the pixel.
1502: * @param y The Y coordinate of the pixel.
1503: * @param b The band of the sample within the pixel.
1504: */
1505: public double getSampleDouble(int x, int y, int b) {
1506: int tileX = XToTileX(x);
1507: int tileY = YToTileY(y);
1508: Raster t = getTile(tileX, tileY);
1509: return t.getSampleDouble(x, y, b);
1510: }
1511:
1512: /**
1513: * Implementation of <code>PropertyChangeListener</code>.
1514: *
1515: * <p> When invoked with an event emitted by the source image specified
1516: * for this <code>TiledImage</code> and the event is either a
1517: * <code>PropertyChangeEventJAI</code> named "InvalidRegion"
1518: * (case-insensitive) or a <code>RenderingChangeEvent</code>, then
1519: * all tiles which overlap the intersection of the invalid region and the
1520: * region of interest specified for this image (if any) will
1521: * be cleared. If the event is a <code>RenderingChangeEvent</code> then
1522: * the invalid region will be obtained from the <code>getInvalidRegion</code>
1523: * method of the event object; if a <code>PropertyChangeEventJAI</code>
1524: * it will be obtained from the <code>getNewValue()</code> method.
1525: * In either case, a new <code>PropertyChangeEventJAI</code> will be
1526: * fired to all registered listeners of the property name
1527: * "InvalidRegion" and to all known sinks which are
1528: * <code>PropertyChangeListener</code>s. Its old and new values will
1529: * contain the previous and current invalid regions. This may be used to
1530: * determine which tiles must be re-requested. The
1531: * <code>TiledImage</code> itself will not re-request the data.
1532: *
1533: * @since JAI 1.1
1534: */
1535: public synchronized void propertyChange(PropertyChangeEvent evt) {
1536: PlanarImage src = getNumSources() > 0 ? getSourceImage(0)
1537: : null;
1538:
1539: if (evt.getSource() == src
1540: && (evt instanceof RenderingChangeEvent || (evt instanceof PropertyChangeEventJAI && evt
1541: .getPropertyName().equalsIgnoreCase(
1542: "InvalidRegion")))) {
1543:
1544: // Get the region.
1545: Shape invalidRegion = evt instanceof RenderingChangeEvent ? ((RenderingChangeEvent) evt)
1546: .getInvalidRegion()
1547: : (Shape) evt.getNewValue();
1548:
1549: // If empty, all is valid.
1550: Rectangle invalidBounds = invalidRegion.getBounds();
1551: if (invalidBounds.isEmpty()) {
1552: return;
1553: }
1554:
1555: // Intersect with ROI.
1556: Area invalidArea = new Area(invalidRegion);
1557: if (srcROI != null) {
1558: Shape roiShape = srcROI.getAsShape();
1559: if (roiShape != null) {
1560: invalidArea.intersect(new Area(roiShape));
1561: } else {
1562: LinkedList rectList = srcROI.getAsRectangleList(
1563: invalidBounds.x, invalidBounds.y,
1564: invalidBounds.width, invalidBounds.height);
1565: Iterator it = rectList.iterator();
1566: while (it.hasNext() && !invalidArea.isEmpty()) {
1567: invalidArea.intersect(new Area((Rectangle) it
1568: .next()));
1569: }
1570: }
1571: }
1572:
1573: // If empty, all is valid.
1574: if (invalidArea.isEmpty()) {
1575: return;
1576: }
1577:
1578: // Determine all possible overlapping tiles.
1579: Point[] tileIndices = getTileIndices(invalidArea
1580: .getBounds());
1581: int numIndices = tileIndices.length;
1582:
1583: // Clear any tiles which intersect the invalid area.
1584: for (int i = 0; i < numIndices; i++) {
1585: int tx = tileIndices[i].x;
1586: int ty = tileIndices[i].y;
1587: Raster tile = tiles[tx][ty];
1588: if ((tile != null)
1589: && invalidArea.intersects(tile.getBounds())) {
1590: tiles[tx][ty] = null;
1591: }
1592: }
1593:
1594: if (eventManager.hasListeners("InvalidRegion")) {
1595: // Determine the old invalid region.
1596: Shape oldInvalidRegion = new Rectangle(); // default is empty.
1597:
1598: // If there is a ROI, the old invalid region is the
1599: // complement of the ROI within the image bounds.
1600: if (srcROI != null) {
1601: Area oldInvalidArea = new Area(getBounds());
1602: Shape roiShape = srcROI.getAsShape();
1603: if (roiShape != null) {
1604: oldInvalidArea.subtract(new Area(roiShape));
1605: } else {
1606: Rectangle oldInvalidBounds = oldInvalidArea
1607: .getBounds();
1608: LinkedList rectList = srcROI
1609: .getAsRectangleList(oldInvalidBounds.x,
1610: oldInvalidBounds.y,
1611: oldInvalidBounds.width,
1612: oldInvalidBounds.height);
1613: Iterator it = rectList.iterator();
1614: while (it.hasNext()
1615: && !oldInvalidArea.isEmpty()) {
1616: oldInvalidArea.subtract(new Area(
1617: (Rectangle) it.next()));
1618: }
1619: }
1620: oldInvalidRegion = oldInvalidArea;
1621: }
1622:
1623: // Fire an InvalidRegion event.
1624: PropertyChangeEventJAI irEvt = new PropertyChangeEventJAI(
1625: this , "InvalidRegion", oldInvalidRegion,
1626: invalidRegion);
1627:
1628: // Fire an event to all registered PropertyChangeListeners.
1629: eventManager.firePropertyChange(irEvt);
1630:
1631: // Fire an event to all PropertyChangeListener sinks.
1632: Vector sinks = getSinks();
1633: if (sinks != null) {
1634: int numSinks = sinks.size();
1635: for (int i = 0; i < numSinks; i++) {
1636: Object sink = sinks.get(i);
1637: if (sink instanceof PropertyChangeListener) {
1638: ((PropertyChangeListener) sink)
1639: .propertyChange(irEvt);
1640: }
1641: }
1642: }
1643: }
1644: }
1645: }
1646: }
|