001: /*
002: * $RCSfile: MultiResolutionRenderableImage.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:12 $
010: * $State: Exp $
011: */
012: package javax.media.jai;
013:
014: import java.awt.Image;
015: import java.awt.RenderingHints;
016: import java.awt.geom.AffineTransform;
017: import java.awt.image.RenderedImage;
018: import java.awt.image.renderable.ParameterBlock;
019: import java.awt.image.renderable.RenderableImage;
020: import java.awt.image.renderable.RenderContext;
021: import java.beans.PropertyChangeListener;
022: import java.io.IOException;
023: import java.io.ObjectInputStream;
024: import java.io.ObjectOutputStream;
025: import java.io.Serializable;
026: import java.util.Hashtable;
027: import java.util.Vector;
028: import javax.media.jai.remote.SerializableState;
029: import javax.media.jai.remote.SerializerFactory;
030: import com.sun.media.jai.util.PropertyUtil;
031:
032: /**
033: * A RenderableImage that produces renderings based on a set of
034: * supplied RenderedImages at various resolutions.
035: */
036: public class MultiResolutionRenderableImage implements
037: WritablePropertySource, RenderableImage, Serializable {
038:
039: /** An array of RenderedImage sources. */
040: protected transient RenderedImage[] renderedSource;
041: private int numSources;
042:
043: /** The aspect ratio, derived from the highest-resolution source. */
044: protected float aspect;
045:
046: /** The min X coordinate in Renderable coordinates. */
047: protected float minX;
048:
049: /** The min Y coordinate in Renderable coordinates. */
050: protected float minY;
051:
052: /** The width in Renderable coordinates. */
053: protected float width;
054:
055: /** The height in Renderable coordinates. */
056: protected float height;
057:
058: /**
059: * A helper object to manage firing events.
060: *
061: * @since JAI 1.1
062: */
063: protected PropertyChangeSupportJAI eventManager = null;
064:
065: /**
066: * A helper object to manage the image properties.
067: *
068: * @since JAI 1.1
069: */
070: protected WritablePropertySourceImpl properties = null;
071:
072: private MultiResolutionRenderableImage() {
073: eventManager = new PropertyChangeSupportJAI(this );
074: properties = new WritablePropertySourceImpl(null, null,
075: eventManager);
076: }
077:
078: /**
079: * Constructs a MultiResolutionRenderableImage with
080: * given dimensions from a Vector of progressively
081: * lower resolution versions of a RenderedImage.
082: *
083: * @param renderedSources a Vector of RenderedImages.
084: * @param minX the minimum X coordinate of the Renderable,
085: * as a float.
086: * @param minY the minimum Y coordinate of the Renderable,
087: * as a float.
088: * @param height the height of the Renderable, as a float.
089: * @throws IllegalArgumentException if the supplied height is
090: * non-positive.
091: *
092: */
093: public MultiResolutionRenderableImage(Vector renderedSources,
094: float minX, float minY, float height) {
095: this ();
096:
097: // Check the height
098: if (height <= 0.0F) {
099: throw new IllegalArgumentException(JaiI18N
100: .getString("MultiResolutionRenderableImage0"));
101: }
102:
103: numSources = renderedSources.size();
104: this .renderedSource = new RenderedImage[numSources];
105: for (int i = 0; i < numSources; i++) {
106: this .renderedSource[i] = (RenderedImage) renderedSources
107: .elementAt(i);
108: }
109:
110: int maxResWidth = renderedSource[0].getWidth();
111: int maxResHeight = renderedSource[0].getHeight();
112: aspect = (float) maxResWidth / maxResHeight;
113:
114: this .minX = minX;
115: this .width = height * aspect;
116:
117: this .minY = minY;
118: this .height = height;
119: }
120:
121: /**
122: * Returns an empty Vector, indicating that this RenderableImage
123: * has no Renderable sources.
124: *
125: * @return an empty Vector.
126: */
127: public Vector getSources() {
128: return null;
129: }
130:
131: /**
132: * Returns a list of the properties recognized by this image.
133: * If no properties are recognized by this image, null will be returned.
134: * The default implementation returns <code>null</code>, i.e.,
135: * no property names are recognized.
136: *
137: * @return an array of Strings representing valid property names.
138: *
139: * @since JAI 1.1
140: */
141: public String[] getPropertyNames() {
142: return properties.getPropertyNames();
143: }
144:
145: /**
146: * Returns an array of <code>String</code>s recognized as names by
147: * this property source that begin with the supplied prefix. If
148: * no property names are recognized, or no property names match,
149: * <code>null</code> will be returned.
150: * The comparison is done in a case-independent manner.
151: *
152: * @return An array of <code>String</code>s giving the valid
153: * property names.
154: *
155: * @param prefix the supplied prefix for the property source.
156: *
157: * @throws IllegalArgumentException if <code>prefix</code> is
158: * <code>null</code>.
159: */
160: public String[] getPropertyNames(String prefix) {
161: if (prefix == null) {
162: throw new IllegalArgumentException(JaiI18N
163: .getString("Generic0"));
164: }
165: return properties.getPropertyNames(prefix);
166: }
167:
168: /**
169: * Returns the class expected to be returned by a request for
170: * the property with the specified name. If this information
171: * is unavailable, <code>null</code> will be returned.
172: *
173: * @return The <code>Class</code> expected to be return by a
174: * request for the value of this property or <code>null</code>.
175: *
176: * @exception IllegalArgumentException if <code>name</code>
177: * is <code>null</code>.
178: *
179: * @since JAI 1.1
180: */
181: public Class getPropertyClass(String name) {
182: return properties.getPropertyClass(name);
183: }
184:
185: /**
186: * Gets a property from the property set of this image.
187: * If the property name is not recognized, java.awt.Image.UndefinedProperty
188: * will be returned. The default implementation returns
189: * <code>java.awt.Image.UndefinedProperty</code>.
190: *
191: * @param name the name of the property to get, as a String.
192: * @return a reference to the property Object, or the value
193: * java.awt.Image.UndefinedProperty.
194: *
195: * @exception IllegalArgumentException if <code>name</code>
196: * is <code>null</code>.
197: */
198: public Object getProperty(String name) {
199: return properties.getProperty(name);
200: }
201:
202: /**
203: * Sets a property on a <code>MultiResolutionRenderableImage</code>.
204: *
205: * @param name a <code>String</code> containing the property's name.
206: * @param value the property, as a general <code>Object</code>.
207: *
208: * @throws IllegalArgumentException If <code>name</code> or
209: * <code>value</code> is <code>null</code>.
210: *
211: * @since JAI 1.1
212: */
213: public void setProperty(String name, Object value) {
214: properties.setProperty(name, value);
215: }
216:
217: /**
218: * Removes the named property from the
219: * <code>MultiResolutionRenderableImage</code>.
220: *
221: * @return The value of the property removed or
222: * <code>java.awt.Image.UndefinedProperty</code> if it was
223: * not present in the property set.
224: *
225: * @exception IllegalArgumentException if <code>name</code>
226: * is <code>null</code>.
227: *
228: * @since JAI 1.1
229: */
230: public void removeProperty(String name) {
231: properties.removeProperty(name);
232: }
233:
234: /**
235: * Add a PropertyChangeListener to the listener list. The
236: * listener is registered for all properties.
237: *
238: * @since JAI 1.1
239: */
240: public void addPropertyChangeListener(
241: PropertyChangeListener listener) {
242: eventManager.addPropertyChangeListener(listener);
243: }
244:
245: /**
246: * Add a PropertyChangeListener for a specific property. The
247: * listener will be invoked only when a call on
248: * firePropertyChange names that specific property. The case of
249: * the name is ignored.
250: *
251: * @since JAI 1.1
252: */
253: public void addPropertyChangeListener(String propertyName,
254: PropertyChangeListener listener) {
255: eventManager.addPropertyChangeListener(propertyName, listener);
256: }
257:
258: /**
259: * Remove a PropertyChangeListener from the listener list. This
260: * removes a PropertyChangeListener that was registered for all
261: * properties.
262: *
263: * @since JAI 1.1
264: */
265: public void removePropertyChangeListener(
266: PropertyChangeListener listener) {
267: eventManager.removePropertyChangeListener(listener);
268: }
269:
270: /**
271: * Remove a PropertyChangeListener for a specific property. The case
272: * of the name is ignored.
273: *
274: * @since JAI 1.1
275: */
276: public void removePropertyChangeListener(String propertyName,
277: PropertyChangeListener listener) {
278: eventManager.removePropertyChangeListener(propertyName,
279: listener);
280: }
281:
282: /**
283: * Returns the floating-point width of the RenderableImage.
284: */
285: public float getWidth() {
286: return width;
287: }
288:
289: /**
290: * Returns the floating-point height of the RenderableImage.
291: */
292: public float getHeight() {
293: return height;
294: }
295:
296: /**
297: * Returns the floating-point min X coordinate of the
298: * RenderableImage.
299: */
300: public float getMinX() {
301: return minX;
302: }
303:
304: /**
305: * Returns the floating-point max X coordinate of the
306: * RenderableImage.
307: */
308: public float getMaxX() {
309: return minX + width;
310: }
311:
312: /**
313: * Returns the floating-point min Y coordinate of the
314: * RenderableImage.
315: */
316: public float getMinY() {
317: return minY;
318: }
319:
320: /**
321: * Returns the floating-point max Y coordinate of the
322: * RenderableImage.
323: */
324: public float getMaxY() {
325: return minY + height;
326: }
327:
328: /**
329: * Returns false since successive renderings (that is, calls to
330: * createRendering() or createScaledRendering()) with the same
331: * arguments will never produce different results.
332: */
333: public boolean isDynamic() {
334: return false;
335: }
336:
337: /**
338: * Returns a rendering with a given width, height, and rendering
339: * hints.
340: *
341: * <p> If a JAI rendering hint named
342: * <code>JAI.KEY_INTERPOLATION</code> is provided, its
343: * corresponding <code>Interpolation</code> object is used as an
344: * argument to the JAI operator used to scale the image. If no
345: * such hint is present, an instance of
346: * <code>InterpolationNearest</code> is used.
347: *
348: * @param width the width of the rendering in pixels.
349: * @param height the height of the rendering in pixels.
350: * @param hints a Hashtable of rendering hints.
351: * @throws IllegalArgumentException if width or height are non-positive.
352: */
353: public RenderedImage createScaledRendering(int width, int height,
354: RenderingHints hints) {
355: if (width <= 0 && height <= 0) {
356: throw new IllegalArgumentException(JaiI18N
357: .getString("MultiResolutionRenderableImage1"));
358: }
359:
360: int res = numSources - 1;
361: while (res > 0) {
362: if (height > 0) {
363: int imh = renderedSource[res].getHeight();
364: if (imh >= height) {
365: break;
366: }
367: } else {
368: int imw = renderedSource[res].getWidth();
369: if (imw >= width) {
370: break;
371: }
372: }
373: res--;
374: }
375:
376: RenderedImage source = renderedSource[res];
377: if (width <= 0) {
378: width = (int) Math.round(height * source.getWidth()
379: / source.getHeight());
380: } else if (height <= 0) {
381: height = (int) Math.round(width * source.getHeight()
382: / source.getWidth());
383: }
384: double sx = (double) width / source.getWidth();
385: double sy = (double) height / source.getHeight();
386: double tx = (getMinX() - source.getMinX()) * sx;
387: double ty = (getMinY() - source.getMinY()) * sy;
388:
389: Interpolation interp = Interpolation
390: .getInstance(Interpolation.INTERP_NEAREST);
391: if (hints != null) {
392: Object obj = hints.get(JAI.KEY_INTERPOLATION);
393: if (obj != null) {
394: interp = (Interpolation) obj;
395: }
396: }
397:
398: ParameterBlock pb = new ParameterBlock();
399: pb.addSource(source);
400: pb.add((float) sx);
401: pb.add((float) sy);
402: pb.add((float) tx);
403: pb.add((float) ty);
404: pb.add(interp);
405:
406: return JAI.create("scale", pb, null);
407: }
408:
409: /**
410: * Returns the full resolution source RenderedImage
411: * with no rendering hints.
412: */
413: public RenderedImage createDefaultRendering() {
414: return renderedSource[0];
415: }
416:
417: /**
418: * Returns a rendering based on a RenderContext.
419: *
420: * <p> If a JAI rendering hint named
421: * <code>JAI.KEY_INTERPOLATION</code> is provided, its
422: * corresponding <code>Interpolation</code> object is used as an
423: * argument to the JAI operator used to transform the image. If
424: * no such hint is present, an instance of
425: * <code>InterpolationNearest</code> is used.
426: *
427: * <p> The <code>RenderContext</code> may contain a <code>Shape</code>
428: * that represents the area-of-interest (aoi). If the aoi is specifed,
429: * it is still legal to return an image that's larger than this aoi.
430: * Therefore, by default, the aoi, if specified, is ignored at the
431: * rendering.
432: *
433: * @param renderContext a RenderContext describing the transform
434: * rendering hints.
435: * @throws IllegalArgumentException if renderContext is null.
436: */
437: public RenderedImage createRendering(RenderContext renderContext) {
438: if (renderContext == null) {
439: throw new IllegalArgumentException(JaiI18N
440: .getString("Generic0"));
441: }
442:
443: // Get a clone of the context's transform
444: AffineTransform usr2dev = renderContext.getTransform();
445: RenderingHints hints = renderContext.getRenderingHints();
446:
447: int type = usr2dev.getType();
448: if (type == AffineTransform.TYPE_UNIFORM_SCALE
449: || type == AffineTransform.TYPE_GENERAL_SCALE) {
450: int width = (int) Math.ceil(usr2dev.getScaleX()
451: * getWidth());
452: int height = (int) Math.ceil(usr2dev.getScaleY()
453: * getHeight());
454:
455: return createScaledRendering(width, height, hints);
456: }
457:
458: // Use the square root of the determinant as an estimate of
459: // the single-axis scale factor.
460: int height = (int) Math.ceil(Math
461: .sqrt(usr2dev.getDeterminant())
462: * getHeight());
463: int res = numSources - 1;
464: while (res > 0) {
465: int imh = renderedSource[res].getHeight();
466: if (imh >= height) {
467: break;
468: }
469: res--;
470: }
471:
472: RenderedImage source = renderedSource[res];
473: double sx = (double) getWidth() / source.getWidth();
474: double sy = (double) getHeight() / source.getHeight();
475:
476: AffineTransform transform = new AffineTransform();
477: transform.translate(-source.getMinX(), -source.getMinY());
478: transform.scale(sx, sy);
479: transform.translate(getMinX(), getMinY());
480: transform.preConcatenate(usr2dev);
481:
482: Interpolation interp = Interpolation
483: .getInstance(Interpolation.INTERP_NEAREST);
484: if (hints != null) {
485: Object obj = hints.get(JAI.KEY_INTERPOLATION);
486: if (obj != null) {
487: interp = (Interpolation) obj;
488: }
489: }
490:
491: ParameterBlock pb = new ParameterBlock();
492: pb.addSource(source);
493: pb.add(transform);
494: pb.add(interp);
495:
496: return JAI.create("affine", pb, null);
497: }
498:
499: /**
500: * Serialize the MultiResolutionRenderableImage.
501: *
502: * @param out The stream provided by the VM to which to write the object.
503: */
504: private void writeObject(ObjectOutputStream out) throws IOException {
505: // Create an array for the serializable form of the sources.
506: Object[] sources = new Object[numSources];
507:
508: // Copy each source converting it to a serializable form if necessary.
509: for (int i = 0; i < numSources; i++) {
510: if (renderedSource[i] instanceof Serializable) {
511: // Image is already serializable.
512: sources[i] = renderedSource[i];
513: } else {
514: // Derive a serializable form.
515: sources[i] = SerializerFactory
516: .getState(renderedSource[i]);
517: }
518: }
519:
520: // Write non-transient fields.
521: out.defaultWriteObject();
522:
523: // Write array of serializable sources.
524: out.writeObject(sources);
525: }
526:
527: /**
528: * Deserialize the MultiResolutionRenderableImage.
529: *
530: * @param in The stream provided by the VM from which to read the object.
531: */
532: private void readObject(ObjectInputStream in) throws IOException,
533: ClassNotFoundException {
534: // Read non-transient fields.
535: in.defaultReadObject();
536:
537: // Read array of sources.
538: Object[] source = (Object[]) in.readObject();
539: numSources = source.length;
540: renderedSource = new RenderedImage[numSources];
541: for (int i = 0; i < numSources; i++) {
542: if (source[i] instanceof SerializableState) {
543: SerializableState ss = (SerializableState) source[i];
544: renderedSource[i] = (RenderedImage) ss.getObject();
545: } else
546: renderedSource[i] = (RenderedImage) source[i];
547: }
548: }
549: }
|