001: /*
002: * $RCSfile: StatisticsOpImage.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.1 $
009: * $Date: 2005/02/11 04:57:21 $
010: * $State: Exp $
011: */
012: package javax.media.jai;
013:
014: import java.awt.Point;
015: import java.awt.Rectangle;
016: import java.awt.image.Raster;
017: import java.awt.image.RenderedImage;
018: import java.util.Vector;
019: import com.sun.media.jai.util.PropertyUtil;
020:
021: /**
022: * An abstract base class representing image operators that compute
023: * statistics on a given region of an image, and with a given sampling
024: * period. Such operators may only have one source image.
025: *
026: * <p> The layout of this image is exactly the same as that of the source
027: * image. Any user supplied layout values via the
028: * <code>RenderingHints</code> are ignored.
029: * The <code>StatisticsOpImage</code> simply passes the pixels of the
030: * source image through unchanged. However, the desired statistics
031: * are computed on demand and made available as a property or set of
032: * properties on the image.
033: *
034: * <p> All instances of <code>StatisticsOpImage</code> make use of a
035: * region of interest, specified as a <code>ROI</code> object. If this
036: * argument is <code>null</code>, the entire source image is used.
037: * Additionally, they may perform spatial subsampling of the region of
038: * interest according to <code>xPeriod</code> and <code>yPeriod</code>
039: * parameters that may vary from 1 (sample every pixel of the region
040: * of interest) upwards. This allows the speed and quality of
041: * statistics gathering to be traded off against one another.
042: *
043: * <p> Subclasses should provide implementations
044: * of the <code>getStatisticsNames</code>, <code>createStatistics</code>,
045: * and <code>accumulateStatistics</code> methods.
046: *
047: * @see OpImage
048: */
049: public abstract class StatisticsOpImage extends OpImage {
050:
051: /**
052: * The region of interest over which to compute the statistics.
053: * If it is <code>null</code>, the entire image is used to compute
054: * the statistics.
055: */
056: protected ROI roi;
057:
058: /** The X coordinate of the initial sample. */
059: protected int xStart;
060:
061: /** The Y coordinate of the initial sample. */
062: protected int yStart;
063:
064: /** The horizontal sampling rate. */
065: protected int xPeriod;
066:
067: /** The vertical sampling rate. */
068: protected int yPeriod;
069:
070: /** Whether to check for skipped tiles. **/
071: private boolean checkForSkippedTiles;
072:
073: /**
074: * Constructor.
075: *
076: * <p> The layout of this image is exactly the same as that of the
077: * source image. Any user supplied layout values via the
078: * <code>RenderingHints</code> are ignored.
079: *
080: * @param source The source image over which the statistics
081: * is accumulated.
082: * @param roi The region of interest that specifies the region of the
083: * source image over which to compute the statistics. If it
084: * is <code>null</code>, the entire source image is used.
085: * @param xStart The initial X sample coordinate.
086: * @param yStart The initial Y sample coordinate.
087: * @param xPeriod The horizontal sampling rate.
088: * @param yPeriod The vertical sampling rate.
089: *
090: * @throws IllegalArgumentException If <code>source</code> is
091: * <code>null</code>.
092: *
093: * @since JAI 1.1
094: */
095: public StatisticsOpImage(RenderedImage source, ROI roi, int xStart,
096: int yStart, int xPeriod, int yPeriod) {
097: super (vectorize(source), // vectorize() checks for null source.
098: new ImageLayout(source), null, // configuration
099: false);
100:
101: this .roi = roi == null ? new ROIShape(getSource(0).getBounds())
102: : roi;
103: this .xStart = xStart;
104: this .yStart = yStart;
105: this .xPeriod = xPeriod;
106: this .yPeriod = yPeriod;
107:
108: this .checkForSkippedTiles = xPeriod > tileWidth
109: || yPeriod > tileHeight;
110: }
111:
112: /**
113: * Returns <code>false</code> as <code>computeTile()</code> invocations
114: * are forwarded to the <code>RenderedImage</code> source and are
115: * therefore not unique objects in the global sense.
116: *
117: * @since JAI 1.1
118: */
119: public boolean computesUniqueTiles() {
120: return false;
121: }
122:
123: /**
124: * Returns a tile of this image as a <code>Raster</code>. If the
125: * requested tile is completely outside of this image's bounds,
126: * this method returns <code>null</code>.
127: *
128: * <p> Statistics operators do not cache their tiles internally.
129: * Rather, the implementation of this method in this class simply
130: * forwards the request to the source image.
131: *
132: * @param tileX The X index of the tile.
133: * @param tileY The Y index of the tile.
134: *
135: * @return The requested tile as a <code>Raster</code> or
136: * <code>null</code>.
137: */
138: public Raster getTile(int tileX, int tileY) {
139: return getSource(0).getTile(tileX, tileY);
140: }
141:
142: /**
143: * Computes the image data of a tile.
144: *
145: * <p> The implementation of this method in this class simply forwards
146: * the request to the source image.
147: *
148: * @param tileX The X index of the tile.
149: * @param tileY The Y index of the tile.
150: *
151: * @since JAI 1.1
152: */
153: public Raster computeTile(int tileX, int tileY) {
154: return getSource(0).getTile(tileX, tileY);
155: }
156:
157: /**
158: * Returns a list of tiles. The request is simply
159: * forwarded to the source image.
160: *
161: * @param tileIndices The indices of the tiles requested.
162: *
163: * @throws IllegalArgumentException If <code>tileIndices</code> is
164: * <code>null</code>.
165: */
166: public Raster[] getTiles(Point[] tileIndices) {
167: if (tileIndices == null) {
168: throw new IllegalArgumentException(JaiI18N
169: .getString("Generic0"));
170: }
171:
172: return getSource(0).getTiles(tileIndices);
173: }
174:
175: /**
176: * Maps the source rectangle into destination space unchanged.
177: *
178: * @param sourceRect the Rectangle in source coordinates.
179: * @param sourceIndex the index of the source image.
180: *
181: * @return A <code>Rectangle</code> indicating the valid destination region.
182: *
183: * @throws IllegalArgumentException If <code>sourceIndex</code>
184: * is not 0.
185: * @throws IllegalArgumentException If <code>sourceRect</code> is
186: * <code>null</code>.
187: */
188: public Rectangle mapSourceRect(Rectangle sourceRect, int sourceIndex) {
189: if (sourceRect == null) {
190: throw new IllegalArgumentException(JaiI18N
191: .getString("Generic0"));
192: }
193:
194: if (sourceIndex != 0) { // there is only 1 source
195: throw new IllegalArgumentException(JaiI18N
196: .getString("Generic1"));
197: }
198: return new Rectangle(sourceRect);
199: }
200:
201: /**
202: * Maps the destination rectangle into source space unchanged.
203: *
204: * @param destRect the Rectangle in destination coordinates.
205: * @param sourceIndex the index of the source image.
206: *
207: * @return A <code>Rectangle</code> indicating the required source region.
208: *
209: * @throws IllegalArgumentException If <code>sourceIndex</code>
210: * is not 0.
211: * @throws IllegalArgumentException If <code>destRect</code> is
212: * <code>null</code>.
213: */
214: public Rectangle mapDestRect(Rectangle destRect, int sourceIndex) {
215: if (destRect == null) {
216: throw new IllegalArgumentException(JaiI18N
217: .getString("Generic0"));
218: }
219:
220: if (sourceIndex != 0) { // there is only 1 source
221: throw new IllegalArgumentException(JaiI18N
222: .getString("Generic1"));
223: }
224: return new Rectangle(destRect);
225: }
226:
227: /**
228: * Returns one of the available statistics as a property. If the
229: * property name is not recognized, this method returns
230: * <code>java.awt.Image.UndefinedProperty</code>.
231: *
232: * @throws IllegalArgumentException If <code>name</code> is
233: * <code>null</code>.
234: */
235: public Object getProperty(String name) {
236: if (name == null) {
237: throw new IllegalArgumentException(JaiI18N
238: .getString("Generic0"));
239: }
240:
241: // Is this property already in the Hashtable?
242: Object stats = super .getProperty(name);
243:
244: if (stats.equals(java.awt.Image.UndefinedProperty)) {
245: // This property has not been generated; generate it.
246: synchronized (this ) { // lock other threads
247: stats = createStatistics(name);
248:
249: if (!stats.equals(java.awt.Image.UndefinedProperty)) {
250: PlanarImage source = getSource(0);
251:
252: // Cycle throw all source tiles.
253: int minTileX = source.getMinTileX();
254: int maxTileX = source.getMaxTileX();
255: int minTileY = source.getMinTileY();
256: int maxTileY = source.getMaxTileY();
257:
258: for (int y = minTileY; y <= maxTileY; y++) {
259: for (int x = minTileX; x <= maxTileX; x++) {
260: // Determine the required region of this tile.
261: // (Note that getTileRect() instersects tile and
262: // image bounds.)
263: Rectangle tileRect = getTileRect(x, y);
264:
265: // Process if and only if within ROI bounds.
266: if (roi.intersects(tileRect)) {
267:
268: // If checking for skipped tiles determine
269: // whether this tile is "hit".
270: if (checkForSkippedTiles
271: && tileRect.x >= xStart
272: && tileRect.y >= yStart) {
273: // Determine the offset within the tile.
274: int offsetX = (xPeriod - ((tileRect.x - xStart) % xPeriod))
275: % xPeriod;
276: int offsetY = (yPeriod - ((tileRect.y - yStart) % yPeriod))
277: % yPeriod;
278:
279: // Continue with next tile if offset
280: // is larger than either tile dimension.
281: if (offsetX >= tileRect.width
282: || offsetY >= tileRect.height) {
283: continue;
284: }
285: }
286:
287: // Accumulate statistics for this tile.
288: accumulateStatistics(name, source
289: .getData(tileRect), stats);
290: }
291: }
292: }
293:
294: // Store the generated property in Hastable.
295: setProperty(name, stats);
296: }
297: }
298: }
299:
300: return stats;
301: }
302:
303: /**
304: * Returns a list of property names that are recognized by this image.
305: *
306: * @return An array of <code>String</code>s containing valid
307: * property names.
308: */
309: public String[] getPropertyNames() {
310: // Get statistics names and names from superclass.
311: String[] statsNames = getStatisticsNames();
312: String[] super Names = super .getPropertyNames();
313:
314: // Return stats names if not superclass names.
315: if (super Names == null) {
316: return statsNames;
317: }
318:
319: // Check for overlap between stats names and superclass names.
320: Vector extraNames = new Vector();
321: for (int i = 0; i < statsNames.length; i++) {
322: String prefix = statsNames[i];
323: String[] names = PropertyUtil.getPropertyNames(super Names,
324: prefix);
325: if (names != null) {
326: for (int j = 0; j < names.length; j++) {
327: if (names[j].equalsIgnoreCase(prefix)) {
328: extraNames.add(prefix);
329: }
330: }
331: }
332: }
333:
334: // If no overlap then return.
335: if (extraNames.size() == 0) {
336: return super Names;
337: }
338:
339: // Combine superclass and extra names.
340: String[] propNames = new String[super Names.length
341: + extraNames.size()];
342: System
343: .arraycopy(super Names, 0, propNames, 0,
344: super Names.length);
345: int offset = super Names.length;
346: for (int i = 0; i < extraNames.size(); i++) {
347: propNames[offset++] = (String) extraNames.get(i);
348: }
349:
350: // Return combined name set.
351: return propNames;
352: }
353:
354: /**
355: * Returns a list of names of statistics understood
356: * by this class.
357: */
358: protected abstract String[] getStatisticsNames();
359:
360: /**
361: * Returns an object that will be used to gather the
362: * named statistic.
363: *
364: * @param name The name of the statistic to be gathered.
365: */
366: protected abstract Object createStatistics(String name);
367:
368: /**
369: * Accumulates statistics on the specified region into
370: * the previously created statistics object. The
371: * region of interest and X and Y sampling rate
372: * should be respected.
373: *
374: * @param name The name of the statistic to be gathered.
375: * @param source A <code>Raster</code> containing source pixels.
376: * The dimensions of the Raster will not
377: * exceed maxWidth x maxHeight.
378: * @param stats A statistics object generated by a previous call
379: * to createStatistics.
380: */
381: protected abstract void accumulateStatistics(String name,
382: Raster source, Object stats);
383: }
|