001: /*
002: * $RCSfile: ImageMIPMap.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:09 $
010: * $State: Exp $
011: */
012: package javax.media.jai;
013:
014: import java.awt.Image;
015: import java.awt.geom.AffineTransform;
016: import java.awt.image.RenderedImage;
017: import java.awt.image.renderable.ParameterBlock;
018: import java.awt.image.renderable.RenderableImage;
019: import java.beans.PropertyChangeListener;
020: import java.util.Vector;
021: import com.sun.media.jai.util.PropertyUtil;
022:
023: /**
024: * A class implementing the "MIP map" operation on a
025: * <code>RenderedImage</code>. Given a <code>RenderedImage</code>,
026: * which represents the image at the highest resolution level, the
027: * image at each lower resolution level may be derived by performing a
028: * specific chain of operations to down sample the image at the next
029: * higher resolution level repeatedly. The highest resolution level is
030: * defined as level 0.
031: *
032: * <p> The <code>downSampler</code> is a chain of operations that is
033: * used to derive the image at the next lower resolution level from
034: * the image at the current resolution level. That is, given an image
035: * at resolution level <code>i</code>, the <code>downSampler</code> is
036: * used to obtain the image at resolution level <code>i+1</code>.
037: * The chain may contain one or more operation nodes; however, each
038: * node must be a <code>RenderedOp</code>. The parameter points to the
039: * last node in the chain. The very first node in the chain must be
040: * a <code>RenderedOp</code> that takes one <code>RenderedImage</code>
041: * as its source. All other nodes may have multiple sources. When
042: * traversing back up the chain, if a node has more than one source,
043: * the first source, <code>source0</code>, is used to move up the
044: * chain. This parameter is saved by reference.
045: *
046: * @see ImagePyramid
047: *
048: */
049: public class ImageMIPMap implements ImageJAI {
050:
051: /** The image with the highest resolution. */
052: protected RenderedImage highestImage;
053:
054: /** The image at the current resolution level. */
055: protected RenderedImage currentImage;
056:
057: /** The current resolution level. */
058: protected int currentLevel = 0;
059:
060: /** The operation chain used to derive the lower resolution images. */
061: protected RenderedOp downSampler;
062:
063: /**
064: * A helper object to manage firing events.
065: *
066: * @since JAI 1.1
067: */
068: protected PropertyChangeSupportJAI eventManager = null;
069:
070: /**
071: * A helper object to manage the image properties.
072: *
073: * @since JAI 1.1
074: */
075: protected WritablePropertySourceImpl properties = null;
076:
077: /** The default constructor. */
078: protected ImageMIPMap() {
079: eventManager = new PropertyChangeSupportJAI(this );
080: properties = new WritablePropertySourceImpl(null, null,
081: eventManager);
082: }
083:
084: /**
085: * Constructor. The down sampler is an "affine" operation that
086: * uses the supplied <code>AffineTransform</code> and
087: * <code>Interpolation</code> objects.
088: * All input parameters are saved by reference.
089: *
090: * @param image The image with the highest resolution.
091: * @param transform An affine matrix used with an "affine" operation
092: * to derive the lower resolution images.
093: * @param interpolation The interpolation method for the "affine"
094: * operation. It may be <code>null</code>, in which case the
095: * default "nearest neighbor" interpolation method is used.
096: *
097: * @throws IllegalArgumentException if <code>image</code> is
098: * <code>null</code>.
099: * @throws IllegalArgumentException if <code>transform</code> is
100: * <code>null</code>.
101: */
102: public ImageMIPMap(RenderedImage image, AffineTransform transform,
103: Interpolation interpolation) {
104: this ();
105:
106: if (image == null || transform == null) {
107: throw new IllegalArgumentException(JaiI18N
108: .getString("Generic0"));
109: }
110:
111: ParameterBlock pb = new ParameterBlock();
112: pb.addSource(image);
113: pb.add(transform);
114: pb.add(interpolation);
115:
116: downSampler = JAI.create("affine", pb);
117: downSampler.removeSources();
118:
119: highestImage = image;
120: currentImage = highestImage;
121: }
122:
123: /**
124: * Constructor. The <code>downSampler</code> points to the last
125: * operation node in the <code>RenderedOp</code> chain. The very
126: * first operation in the chain must not have any source images
127: * specified; that is, its number of sources must be 0. All input
128: * parameters are saved by reference.
129: *
130: * @param image The image with the highest resolution.
131: * @param downSampler The operation chain used to derive the lower
132: * resolution images. No validation is done on the first
133: * operation in the chain.
134: *
135: * @throws IllegalArgumentException if <code>image</code> is <code>null</code>.
136: * @throws IllegalArgumentException if <code>downSampler</code> is
137: * <code>null</code>.
138: */
139: public ImageMIPMap(RenderedImage image, RenderedOp downSampler) {
140: this ();
141: if (image == null || downSampler == null) {
142: throw new IllegalArgumentException(JaiI18N
143: .getString("Generic0"));
144: }
145:
146: highestImage = image;
147: currentImage = highestImage;
148: this .downSampler = downSampler;
149: }
150:
151: /**
152: * Constructs a new <code>ImageMIPMap</code> from a
153: * <code>RenderedOp</code> chain. The <code>downSampler</code>
154: * points to the last operation node in the
155: * <code>RenderedOp</code> chain. The source image is determined
156: * by traversing up the chain: starting at the bottom node, given by
157: * the <code>downSample</code> parameter, we move to the first
158: * source of the node and repeat until we find either a sourceless
159: * <code>RenderedOp</code> or any other type of
160: * <code>RenderedImage</code>.
161: *
162: * The <code>downSampler</code> parameter is saved by reference
163: * and should not be modified during the lifetime of any
164: * <code>ImageMIPMap</code> referring to it.
165: *
166: * @param downSampler The operation chain used to derive the lower
167: * resolution images. The source of the first node in this
168: * chain is taken as the image with the highest resolution.
169: *
170: * @throws IllegalArgumentException if <code>downSampler</code> is
171: * <code>null</code>.
172: * @throws IllegalArgumentException if <code>downSampler</code>
173: * has no sources.
174: * @throws IllegalArgumentException if an object other than a
175: * <code>RenderedImage</code> is found in the
176: * <code>downSampler</code> chain.
177: */
178: public ImageMIPMap(RenderedOp downSampler) {
179: this ();
180:
181: if (downSampler == null) {
182: throw new IllegalArgumentException(JaiI18N
183: .getString("Generic0"));
184: }
185:
186: if (downSampler.getNumSources() == 0) {
187: throw new IllegalArgumentException(JaiI18N
188: .getString("ImageMIPMap0"));
189: }
190:
191: // Find the highest resolution image from the chain.
192: RenderedOp op = downSampler;
193: while (true) {
194: Object src = op.getNodeSource(0);
195:
196: if (src instanceof RenderedOp) {
197: RenderedOp srcOp = (RenderedOp) src;
198:
199: if (srcOp.getNumSources() == 0) {
200: highestImage = srcOp;
201: op.removeSources();
202: break;
203: } else {
204: op = srcOp;
205: }
206: } else if (src instanceof RenderedImage) {
207: highestImage = (RenderedImage) src;
208: op.removeSources();
209: break;
210: } else {
211: throw new IllegalArgumentException(JaiI18N
212: .getString("ImageMIPMap1"));
213: }
214: }
215:
216: currentImage = highestImage;
217: this .downSampler = downSampler;
218: }
219:
220: /**
221: * Returns an array of <code>String</code>s recognized as names by
222: * this property source. If no property names match,
223: * <code>null</code> will be returned.
224: *
225: * <p> The default implementation returns <code>null</code>, i.e.,
226: * no property names are recognized.
227: *
228: * @return An array of <code>String</code>s giving the valid
229: * property names.
230: */
231: public String[] getPropertyNames() {
232: return properties.getPropertyNames();
233: }
234:
235: /**
236: * Returns an array of <code>String</code>s recognized as names by
237: * this property source that begin with the supplied prefix. If
238: * no property names are recognized, or no property names match,
239: * <code>null</code> will be returned.
240: * The comparison is done in a case-independent manner.
241: *
242: * @return An array of <code>String</code>s giving the valid
243: * property names.
244: *
245: * @param prefix the supplied prefix for the property source.
246: *
247: * @throws IllegalArgumentException if <code>prefix</code> is
248: * <code>null</code>.
249: */
250: public String[] getPropertyNames(String prefix) {
251: return properties.getPropertyNames(prefix);
252: }
253:
254: /**
255: * Returns the class expected to be returned by a request for
256: * the property with the specified name. If this information
257: * is unavailable, <code>null</code> will be returned.
258: *
259: * @return The <code>Class</code> expected to be return by a
260: * request for the value of this property or <code>null</code>.
261: *
262: * @exception IllegalArgumentException if <code>name</code>
263: * is <code>null</code>.
264: *
265: * @since JAI 1.1
266: */
267: public Class getPropertyClass(String name) {
268: return properties.getPropertyClass(name);
269: }
270:
271: /**
272: * Returns the specified property. The default implementation
273: * returns <code>java.awt.Image.UndefinedProperty</code>.
274: *
275: * @param name The name of the property.
276: *
277: * @return The value of the property, as an Object.
278: *
279: * @exception IllegalArgumentException if <code>name</code>
280: * is <code>null</code>.
281: */
282: public Object getProperty(String name) {
283: return properties.getProperty(name);
284: }
285:
286: /**
287: * Sets a property on a <code>ImageMIPMap</code>.
288: *
289: * @param name a <code>String</code> containing the property's name.
290: * @param value the property, as a general <code>Object</code>.
291: *
292: * @throws IllegalArgumentException If <code>name</code> or
293: * <code>value</code> is <code>null</code>.
294: *
295: * @since JAI 1.1
296: */
297: public void setProperty(String name, Object value) {
298: properties.setProperty(name, value);
299: }
300:
301: /**
302: * Removes the named property from the <code>ImageMIPMap</code>.
303: *
304: * @exception IllegalArgumentException if <code>name</code>
305: * is <code>null</code>.
306: *
307: * @since JAI 1.1
308: */
309: public void removeProperty(String name) {
310: properties.removeProperty(name);
311: }
312:
313: /**
314: * Add a PropertyChangeListener to the listener list. The
315: * listener is registered for all properties.
316: *
317: * @since JAI 1.1
318: */
319: public void addPropertyChangeListener(
320: PropertyChangeListener listener) {
321: eventManager.addPropertyChangeListener(listener);
322: }
323:
324: /**
325: * Add a PropertyChangeListener for a specific property. The
326: * listener will be invoked only when a call on
327: * firePropertyChange names that specific property. The case of
328: * the name is ignored.
329: *
330: * @since JAI 1.1
331: */
332: public void addPropertyChangeListener(String propertyName,
333: PropertyChangeListener listener) {
334: eventManager.addPropertyChangeListener(propertyName, listener);
335: }
336:
337: /**
338: * Remove a PropertyChangeListener from the listener list. This
339: * removes a PropertyChangeListener that was registered for all
340: * properties.
341: *
342: * @since JAI 1.1
343: */
344: public void removePropertyChangeListener(
345: PropertyChangeListener listener) {
346: eventManager.removePropertyChangeListener(listener);
347: }
348:
349: /**
350: * Remove a PropertyChangeListener for a specific property. The case
351: * of the name is ignored.
352: *
353: * @since JAI 1.1
354: */
355: public void removePropertyChangeListener(String propertyName,
356: PropertyChangeListener listener) {
357: eventManager.removePropertyChangeListener(propertyName,
358: listener);
359: }
360:
361: /**
362: * Returns the current resolution level. The highest resolution
363: * level is defined as level 0.
364: */
365: public int getCurrentLevel() {
366: return currentLevel;
367: }
368:
369: /** Returns the image at the current resolution level. */
370: public RenderedImage getCurrentImage() {
371: return currentImage;
372: }
373:
374: /**
375: * Returns the image at the specified resolution level. The
376: * requested level must be greater than or equal to 0 or
377: * <code>null</code> will be returned.
378: *
379: * @param level The specified level of resolution
380: */
381: public RenderedImage getImage(int level) {
382: if (level < 0) {
383: return null;
384: }
385:
386: if (level < currentLevel) { // restart from the highest image
387: currentImage = highestImage;
388: currentLevel = 0;
389: }
390:
391: while (currentLevel < level) {
392: getDownImage();
393: }
394: return currentImage;
395: }
396:
397: /**
398: * Returns the image at the next lower resolution level,
399: * obtained by applying the <code>downSampler</code> on the
400: * image at the current resolution level.
401: */
402: public RenderedImage getDownImage() {
403: currentLevel++;
404:
405: /* Duplicate the downSampler op chain. */
406: RenderedOp op = duplicate(downSampler, vectorize(currentImage));
407: currentImage = op.getRendering();
408: return currentImage;
409: }
410:
411: /**
412: * Duplicates a <code>RenderedOp</code> chain. Each node in the
413: * chain must be a <code>RenderedOp</code>. The <code>op</code>
414: * parameter points to the last <code>RenderedOp</code> in the chain.
415: * The very first op in the chain must have no sources and its source
416: * will be set to the supplied image vector. When traversing up the
417: * chain, if any node has more than one source, the first source will
418: * be used. The first source of each node is duplicated; all other
419: * sources are copied by reference.
420: *
421: * @param op RenderedOp chain
422: * @param vector of source images
423: *
424: * @throws IllegalArgumentException if <code>op</code> is <code>null</code>.
425: * @throws IllegalArgumentException if <code>images</code> is <code>null</code>.
426: */
427: protected RenderedOp duplicate(RenderedOp op, Vector images) {
428: if (images == null) {
429: throw new IllegalArgumentException(JaiI18N
430: .getString("Generic0"));
431: }
432:
433: //
434: // Duplicates a RenderedOp with the original OperationRegistry,
435: // OperationName, ParameterBlock, and RenderingHints copied over
436: // by reference. No property information is copied.
437: //
438: op = new RenderedOp(op.getRegistry(), op.getOperationName(), op
439: .getParameterBlock(), op.getRenderingHints());
440:
441: ParameterBlock pb = new ParameterBlock();
442: pb.setParameters(op.getParameters());
443:
444: Vector srcs = op.getSources();
445: int numSrcs = srcs.size();
446:
447: if (numSrcs == 0) { // first op in the chain
448: pb.setSources(images);
449:
450: } else { // recursively duplicate source0
451: pb.addSource(duplicate((RenderedOp) srcs.elementAt(0),
452: images));
453:
454: for (int i = 1; i < numSrcs; i++) {
455: pb.addSource(srcs.elementAt(i));
456: }
457: }
458:
459: op.setParameterBlock(pb);
460: return op;
461: }
462:
463: /**
464: * Returns the current image as a <code>RenderableImage</code>.
465: * This method returns a <code>MultiResolutionRenderableImage</code>.
466: * The <code>numImages</code> parameter indicates the number of
467: * <code>RenderedImage</code>s used to construct the
468: * <code>MultiResolutionRenderableImage</code>. Starting with the
469: * current image, the images are obtained by finding the necessary
470: * number of lower resolution images using the <code>downSampler</code>.
471: * The current level and current image will not be changed.
472: * If the width or height reaches 1, the downsampling will stop
473: * and return the renderable image.
474: *
475: * <p> The <code>numImages</code> should be greater than or equal to 1.
476: * If a value of less than 1 is specified, this method uses 1 image,
477: * which is the current image.
478: *
479: * @param numImages The number of lower resolution images.
480: * @param minX The minimum X coordinate of the Renderable, as a float.
481: * @param minY The minimum Y coordinate of the Renderable, as a float.
482: * @param height The height of the Renderable, as a float.
483: *
484: * @throws IllegalArgumentException if <code>height</code> is less than 0.
485: *
486: * @see MultiResolutionRenderableImage
487: */
488: public RenderableImage getAsRenderable(int numImages, float minX,
489: float minY, float height) {
490: Vector v = new Vector();
491: v.add(currentImage);
492:
493: RenderedImage image = currentImage;
494: for (int i = 1; i < numImages; i++) {
495: RenderedOp op = duplicate(downSampler, vectorize(image));
496: image = op.getRendering();
497:
498: if (image.getWidth() <= 1 || image.getHeight() <= 1) {
499: break;
500: }
501:
502: v.add(image);
503: }
504:
505: return new MultiResolutionRenderableImage(v, minX, minY, height);
506: }
507:
508: /**
509: * Returns the current image as a <code>RenderableImage</code>.
510: * This method returns a <code>MultiResolutionRenderableImage</code>
511: * with the current image as the only source image, minX and minY
512: * set to 0.0, and height set to 1.0.
513: *
514: * @see MultiResolutionRenderableImage
515: */
516: public RenderableImage getAsRenderable() {
517: return getAsRenderable(1, 0.0F, 0.0F, 1.0F);
518: }
519:
520: // XXX - see OpImage vectorize and consolidate?
521: // could be public static in PlanarImage
522: /**
523: * Creates and returns a <code>Vector</code> containing a single
524: * element equal to the supplied <code>RenderedImage</code>.
525: *
526: *
527: * @since JAI 1.1
528: */
529: protected final Vector vectorize(RenderedImage image) {
530: Vector v = new Vector(1);
531: v.add(image);
532: return v;
533: }
534:
535: /**
536: * Creates and returns a <code>Vector</code> containing two
537: * elements equal to the supplied <code>RenderedImage</code>s
538: * in the order given.
539: *
540: *
541: * @since JAI 1.1
542: */
543: protected final Vector vectorize(RenderedImage im1,
544: RenderedImage im2) {
545: Vector v = new Vector(2);
546: v.add(im1);
547: v.add(im2);
548: return v;
549: }
550: }
|