001: /*
002: * $RCSfile: SnapshotImage.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.2 $
009: * $Date: 2005/05/12 18:24:34 $
010: * $State: Exp $
011: */
012: package javax.media.jai;
013:
014: import java.awt.Point;
015: import java.awt.image.Raster;
016: import java.awt.image.TileObserver;
017: import java.awt.image.WritableRaster;
018: import java.awt.image.WritableRenderedImage;
019: import java.util.Enumeration;
020: import java.util.HashSet;
021: import java.util.Hashtable;
022: import java.util.Iterator;
023:
024: /**
025: * A (Raster, X, Y) tuple.
026: */
027: final class TileCopy {
028:
029: /** The tile's <code>Raster</code> data. */
030: Raster tile;
031:
032: /** The tile's column within the image tile grid. */
033: int tileX;
034:
035: /** The tile's row within the image tile grid. */
036: int tileY;
037:
038: /**
039: * Constructs a TileCopy object given the tile's <code>Raster</code> data
040: * and its location in the tile grid.
041: *
042: * @param tile the <code>Raster</code> containing the tile's data.
043: * @param tileX the tile's X position in the tile grid.
044: * @param tileY the tile's X position in the tile grid.
045: */
046: TileCopy(Raster tile, int tileX, int tileY) {
047: this .tile = tile;
048: this .tileX = tileX;
049: this .tileY = tileY;
050: }
051: }
052:
053: /**
054: * A proxy for <code>Snapshot</code> that calls
055: * <code>Snapshot.dispose()</code> when finalized.
056: * No references to a SnapshotProxy are held internally, only user
057: * references. Thus it will be garbage collected when the last user
058: * reference is relinquished. The <code>Snapshot</code>'s
059: * <code>dispose()</code> method
060: * is called from <code>SnapshotProxy.finalize()</code>, ensuring that all
061: * of the resources held by the <code>Snapshot</code> will become collectable.
062: */
063: final class SnapshotProxy extends PlanarImage {
064: /**
065: * The parent <code>Snapshot</code> to which we forward
066: * <code>getTile()</code> calls.
067: */
068: Snapshot parent;
069:
070: /**
071: * Construct a new proxy for a given <code>Snapshot</code>.
072: *
073: * @param parent the <code>Snapshot</code> to which method calls will
074: * be forwarded.
075: */
076: SnapshotProxy(Snapshot parent) {
077: super (new ImageLayout(parent), null, null);
078: this .parent = parent;
079: }
080:
081: /**
082: * Forwards a tile request to the parent <code>Snapshot</code>.
083: *
084: * @param tileX the X index of the tile.
085: * @param tileY the Y index of the tile.
086: * @return the tile as a <code>Raster</code>.
087: */
088: public Raster getTile(int tileX, int tileY) {
089: return parent.getTile(tileX, tileY);
090: }
091:
092: /** Disposes of resources held by this proxy. */
093: public void dispose() {
094: parent.dispose();
095: }
096: }
097:
098: /**
099: * A non-public class that holds a portion of the state associated
100: * with a <code>SnapShotImage</code>. A <code>Snapshot</code> provides the
101: * appearance of a <code>PlanarImage</code> with fixed contents. In order to
102: * provide this illusion, however, the <code>Snapshot</code> relies on the
103: * fact that it belongs to a linked list of <code>Snapshot</code>s rooted in a
104: * particular <code>SnapShotImage</code>; it cannot function independently.
105: *
106: */
107: final class Snapshot extends PlanarImage {
108:
109: /** The creator of this image. */
110: SnapshotImage parent;
111:
112: /** The next <code>Snapshot</code> in a doubly-linked list. */
113: Snapshot next;
114:
115: /** The previous <code>Snapshot</code> in a doubly-linked list. */
116: Snapshot prev;
117:
118: /** A set of cached TileCopy elements. */
119: Hashtable tiles = new Hashtable();
120:
121: /** True if <code>dispose()</code> has been called. */
122: boolean disposed = false;
123:
124: /**
125: * Constructs a <code>Snapshot</code> that will provide a synchronous
126: * view of a <code>SnapshotImage</code> at a particular moment in time.
127: *
128: * @param parent a <code>SnapshotImage</code> this image will be viewing.
129: */
130: Snapshot(SnapshotImage parent) {
131: super (new ImageLayout(parent), null, null);
132: this .parent = parent;
133: }
134:
135: /**
136: * Returns the version of a tile "seen" by this <code>Snapshot</code>.
137: * The tile "seen" is the oldest copy of the tile made after
138: * the creation of this <code>Snapshot</code>; it may be held in the
139: * tiles <code>Hashtable</code> of this <code>Snapshot</code> or one of
140: * its successors. If no later <code>Snapshot</code> holds a copy of
141: * the tile, the current version of the tile from the source image is
142: * returned.
143: *
144: * <p> <code>getTile()</code> is synchronized in order to prevent calls to
145: * <code>dispose()</code>, which will cause the list of
146: * <code>Snapshot</code>s to change, from occurring at the same time as
147: * the walking of the list.
148: *
149: * @param tileX the X index of the tile.
150: * @param tileY the Y index of the tile.
151: * @return the tile as a <code>Raster</code>.
152: */
153: public Raster getTile(int tileX, int tileY) {
154: // Make sure dispose() and getTile() are mutually exclusive
155: synchronized (parent) {
156: // Check local set of tile copies, if not there move
157: // forward to the next <code>Snapshot</code>, if last image
158: // get the tile from the real source image.
159:
160: TileCopy tc = (TileCopy) tiles.get(new Point(tileX, tileY));
161: if (tc != null) {
162: return tc.tile;
163: } else if (next != null) {
164: return next.getTile(tileX, tileY);
165: } else {
166: return parent.getTrueSource().getTile(tileX, tileY);
167: }
168: }
169: }
170:
171: /**
172: * Sets the next <code>Snapshot</code> in the list to a given
173: * <code>Snapshot</code>.
174: *
175: * @param next the next <code>Snapshot</code> in the list.
176: */
177: void setNext(Snapshot next) {
178: this .next = next;
179: }
180:
181: /**
182: * Sets the previous <code>Snapshot</code> in the list to a given
183: * <code>Snapshot</code>.
184: *
185: * @param prev the previous <code>Snapshot</code> in the list.
186: */
187: void setPrev(Snapshot prev) {
188: this .prev = prev;
189: }
190:
191: /**
192: * Returns true if this <code>Snapshot</code> already stores a version
193: * of a specified tile.
194: *
195: * @param tileX the X index of the tile.
196: * @param tileY the Y index of the tile.
197: * @return true if this <code>Snapshot</code> holds a copy of the tile.
198: */
199: boolean hasTile(int tileX, int tileY) {
200: TileCopy tc = (TileCopy) tiles.get(new Point(tileX, tileY));
201: return tc != null;
202: }
203:
204: /**
205: * Stores a given tile in this <code>Snapshot</code>. The caller should
206: * not attempt to store more than one version of a given tile.
207: *
208: * @param tile a <code>Raster</code> containing the tile data.
209: * @param tileX the tile's column within the image tile grid.
210: * @param tileY the tile's row within the image tile grid.
211: */
212: void addTile(Raster tile, int tileX, int tileY) {
213: TileCopy tc = new TileCopy(tile, tileX, tileY);
214: tiles.put(new Point(tileX, tileY), tc);
215: }
216:
217: /** This image will no longer be referenced by the user. */
218: public void dispose() {
219: // Make sure dispose() and getTile() are mutually exclusive
220: synchronized (parent) {
221: // Make it idempotent
222: if (disposed) {
223: return;
224: }
225: disposed = true;
226:
227: // If this is the last Snapshot, inform the parent
228: if (parent.getTail() == this ) {
229: parent.setTail(prev);
230: }
231:
232: // Remove 'this' from the chain
233: if (prev != null) {
234: prev.setNext(next);
235: }
236: if (next != null) {
237: next.setPrev(prev);
238: }
239:
240: // If there is a previous node, push tiles back to it
241: if (prev != null) {
242: // Push tiles back to the previous Snapshot
243: Enumeration enumeration = tiles.elements();
244: while (enumeration.hasMoreElements()) {
245: TileCopy tc = (TileCopy) enumeration.nextElement();
246: if (!prev.hasTile(tc.tileX, tc.tileY)) {
247: prev.addTile(tc.tile, tc.tileX, tc.tileY);
248: }
249: }
250: }
251:
252: // Null out links to help the GC
253: parent = null;
254: next = prev = null;
255: tiles = null;
256: }
257: }
258: }
259:
260: /**
261: * A class providing an arbitrary number of synchronous views of a
262: * possibly changing <code>WritableRenderedImage</code>.
263: * <code>SnapshotImage</code> is responsible for stabilizing changing sources
264: * in order to allow deferred execution of operations dependent on such
265: * sources.
266: *
267: * <p> Any <code>RenderedImage</code> may be used as the source of a
268: * <code>SnapshotImage</code>; if it is a <code>WritableRenderedImage</code>,
269: * the <code>SnapshotImage</code> will register itself as a
270: * <code>TileObserver</code> and make copies of tiles that are about to change.
271: * Multiple versions of each tile are maintained internally, as long as they
272: * are in demand. <code>SnapshotImage</code> is able to track demand and
273: * should be able to simply forward requests for tiles to the source most
274: * of the time, without the need to make a copy.
275: *
276: * <p> When used as a source, calls to getTile will simply be passed
277: * along to the source. In other words, <code>SnapshotImage</code> is
278: * completely transparent. However, by calling <code>createSnapshot()</code>
279: * an instance of a non-public <code>PlanarImage</code> subclass (called
280: * <code>Snapshot</code> in this implementation) will be created and returned.
281: * This image will always return tile data with contents as of the time of its
282: * construction.
283: *
284: * <p> When a particular <code>Snapshot</code> is no longer needed, its
285: * <code>dispose()</code> method may be called. The <code>dispose()</code>
286: * method will be called automatically when the <code>Snapshot</code> is
287: * finalized by the garbage collector. Disposing of the <code>Snapshot</code>
288: * allows tile data held by the <code>Snapshot</code> that is not needed by
289: * any other <code>Snapshot</code> to be disposed of as well.
290: *
291: * <p> This implementation of <code>SnapshotImage</code> makes use of a
292: * doubly-linked list of <code>Snapshot</code> objects. A new
293: * <code>Snapshot</code> is added to the tail of the list whenever
294: * <code>createSnapshot()</code> is called. Each <code>Snapshot</code>
295: * has a cache containing copies of any tiles that were writable at the
296: * time of its construction, as well as any tiles that become writable
297: * between the time of its construction and the construction of the next
298: * <code>Snapshot</code>.
299: *
300: * <p> When asked for a tile, a <code>Snapshot</code> checks its local cache
301: * and returns its version of the tile if one is found. Otherwise, it
302: * forwards the request onto its successor. This process continues
303: * until the latest <code>Snapshot</code> is reached; if it does not contain
304: * a copy of the tile, the tile is requested from the real source image.
305: *
306: * <p> When a <code>Snapshot</code> is no longer needed, its
307: * <code>dispose()</code> method attempts to push the contents of its tile
308: * cache back to the previous <code>Snapshot</code> in the linked list. If
309: * that image possesses a version of the same tile, the tile is not pushed
310: * back and may be discarded.
311: *
312: * @see java.awt.image.RenderedImage
313: * @see java.awt.image.TileObserver
314: * @see java.awt.image.WritableRenderedImage
315: * @see PlanarImage
316: *
317: */
318: public class SnapshotImage extends PlanarImage implements TileObserver {
319:
320: /** The real image source. */
321: private PlanarImage source;
322:
323: /** The last entry in the list of <code>Snapshot</code>, initially null. */
324: private Snapshot tail = null;
325:
326: /** The set of active tiles, represented as a HashSet of Points. */
327: private HashSet activeTiles = new HashSet();
328:
329: /**
330: * Constructs a <code>SnapshotImage</code> from a <code>PlanarImage</code>
331: * source.
332: *
333: * @param source a <code>PlanarImage</code> source.
334: * @throws IllegalArgumentException if source is null.
335: */
336: public SnapshotImage(PlanarImage source) {
337: super (new ImageLayout(source), null, null);
338:
339: // Record the source image
340: this .source = source;
341: // Set image parameters to match the source
342:
343: // Determine which tiles of the source image are writable
344: if (source instanceof WritableRenderedImage) {
345: WritableRenderedImage wri = (WritableRenderedImage) source;
346: wri.addTileObserver(this );
347:
348: Point[] pts = wri.getWritableTileIndices();
349: if (pts != null) {
350: int num = pts.length;
351: for (int i = 0; i < num; i++) {
352: // Add these tiles to the active list
353: Point p = pts[i];
354: activeTiles.add(new Point(p.x, p.y));
355: }
356: }
357: }
358: }
359:
360: /**
361: * Returns the <code>PlanarImage</code> source of this
362: * <code>SnapshotImage</code>.
363: *
364: * @return a <code>PlanarImage</code> that is the source of data for this
365: * image.
366: */
367: protected PlanarImage getTrueSource() {
368: return source;
369: }
370:
371: /**
372: * Sets the reference to the most current <code>Snapshot</code> to a given
373: * <code>Snapshot</code>.
374: *
375: * @param tail a reference to the new most current <code>Snapshot</code>.
376: */
377: void setTail(Snapshot tail) {
378: this .tail = tail;
379: }
380:
381: /**
382: * Returns a reference to the most current <code>Snapshot</code>.
383: *
384: * @return the <code>Snapshot</code> at the tail end of the list.
385: */
386: Snapshot getTail() {
387: return tail;
388: }
389:
390: /**
391: * Creates and returns a <code>Raster</code> copy of a given source tile.
392: *
393: * @param tileX the X index of the tile.
394: * @param tileY the Y index of the tile.
395: * @return a newly-constructed <code>Raster</code> containing a copy
396: * of the tile data.
397: */
398: private Raster createTileCopy(int tileX, int tileY) {
399: int x = tileXToX(tileX);
400: int y = tileYToY(tileY);
401: Point p = new Point(x, y);
402:
403: WritableRaster tile = RasterFactory.createWritableRaster(
404: sampleModel, p);
405: source.copyData(tile);
406: return tile;
407: }
408:
409: /**
410: * Creates a snapshot of this image. This snapshot may be used
411: * indefinitely, and will always appear to have the pixel data that
412: * this image has currently. The snapshot is semantically a copy
413: * of this image but may be implemented in a more efficient manner.
414: * Multiple snapshots taken at different times may share tiles that
415: * have not changed, and tiles that are currently static in this
416: * image's source do not need to be copied at all.
417: *
418: * @return a <code>PlanarImage</code> snapshot.
419: */
420: public PlanarImage createSnapshot() {
421: if (source instanceof WritableRenderedImage) {
422: // Create a new Snapshot
423: Snapshot snap = new Snapshot(this );
424:
425: // For each active tile:
426: Iterator iter = activeTiles.iterator();
427: while (iter.hasNext()) {
428: Point p = (Point) iter.next();
429:
430: // Make a copy and store it in the Snapshot
431: Raster tile = createTileCopy(p.x, p.y);
432: snap.addTile(tile, p.x, p.y);
433: }
434:
435: // Add the new Snapshot to the list of snapshots
436: if (tail == null) {
437: tail = snap;
438: } else {
439: tail.setNext(snap);
440: snap.setPrev(tail);
441: tail = snap;
442: }
443:
444: // Create a proxy and return it
445: return new SnapshotProxy(snap);
446: } else {
447: return source;
448: }
449: }
450:
451: /**
452: * Receives the information that a tile is either about to become
453: * writable, or is about to become no longer writable.
454: *
455: * @param source the <code>WritableRenderedImage</code> for which we
456: * are an observer.
457: * @param tileX the X index of the tile.
458: * @param tileY the Y index of the tile.
459: * @param willBeWritable true if the tile is becoming writable.
460: */
461: public void tileUpdate(WritableRenderedImage source, int tileX,
462: int tileY, boolean willBeWritable) {
463: if (willBeWritable) {
464: // If the last Snapshot doesn't have the tile, copy it
465: if ((tail != null) && (!tail.hasTile(tileX, tileY))) {
466: tail
467: .addTile(createTileCopy(tileX, tileY), tileX,
468: tileY);
469: }
470: // Add the tile to the active list
471: activeTiles.add(new Point(tileX, tileY));
472: } else {
473: // Remove the tile from the active list
474: activeTiles.remove(new Point(tileX, tileY));
475: }
476: }
477:
478: /**
479: * Returns a non-snapshotted tile from the source.
480: *
481: * @param tileX the X index of the tile.
482: * @param tileY the Y index of the tile.
483: * @return the tile as a <code>Raster</code>.
484: */
485: public Raster getTile(int tileX, int tileY) {
486: // Return the current source tile (X, Y)
487: return source.getTile(tileX, tileY);
488: }
489: }
|