001: /*
002: * $RCSfile: ImageReadCRIF.java,v $
003: *
004: *
005: * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * - Redistribution of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * - Redistribution in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * Neither the name of Sun Microsystems, Inc. or the names of
020: * contributors may be used to endorse or promote products derived
021: * from this software without specific prior written permission.
022: *
023: * This software is provided "AS IS," without a warranty of any
024: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
025: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
026: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027: * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
028: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
029: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
031: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035: * POSSIBILITY OF SUCH DAMAGES.
036: *
037: * You acknowledge that this software is not designed or intended for
038: * use in the design, construction, operation or maintenance of any
039: * nuclear facility.
040: *
041: * $Revision: 1.2 $
042: * $Date: 2005/12/01 00:39:04 $
043: * $State: Exp $
044: */
045: package com.sun.media.jai.imageioimpl;
046:
047: import java.awt.Dimension;
048: import java.awt.RenderingHints;
049: import java.awt.geom.Rectangle2D;
050: import java.awt.image.RenderedImage;
051: import java.awt.image.renderable.ParameterBlock;
052: import java.awt.image.renderable.RenderableImage;
053: import java.awt.image.renderable.RenderContext;
054: import java.io.FileInputStream;
055: import java.io.InputStream;
056: import java.io.IOException;
057: import java.io.RandomAccessFile;
058: import java.net.Socket;
059: import java.net.URL;
060: import java.nio.channels.Channel;
061: import java.nio.channels.Channels;
062: import java.util.Collection;
063: import java.util.Comparator;
064: import java.util.EventListener;
065: import java.util.Iterator;
066: import java.util.Locale;
067: import java.util.TreeMap;
068: import java.util.Vector;
069: import javax.imageio.ImageIO;
070: import javax.imageio.ImageReader;
071: import javax.imageio.ImageReadParam;
072: import javax.imageio.event.IIOReadProgressListener;
073: import javax.imageio.event.IIOReadUpdateListener;
074: import javax.imageio.event.IIOReadWarningListener;
075: import javax.imageio.spi.ImageReaderSpi;
076: import javax.imageio.stream.ImageInputStream;
077: import javax.media.jai.CRIFImpl;
078: import javax.media.jai.ImageLayout;
079: import javax.media.jai.JAI;
080: import javax.media.jai.MultiResolutionRenderableImage;
081: import javax.media.jai.PropertySource;
082: import javax.media.jai.WritablePropertySource;
083: import com.sun.media.jai.operator.ImageReadDescriptor;
084:
085: public final class ImageReadCRIF extends CRIFImpl {
086: public ImageReadCRIF() {
087: super (); // Pass up the name?
088: }
089:
090: /**
091: * Attempt to create an {@link ImageInputStream} for the supplied
092: * input. The following sequence is effected:
093: * <ol>
094: * <li><ul>
095: * <li>If <code>input</code> is an <code>ImageInputStream</code> it
096: * is cast and returned.</li>
097: * <li>If <code>input</code> is a <code>String</code> it is converted
098: * to a read-only <code>RandomAccessFile</code>.</li>
099: * <li>If conversion to a <code>RandomAccessFile</code> fails, the
100: * <code>String</code> <code>input</code> is converted to an
101: * <code>InputStream</code> by accessing it as a resource bundled
102: * in a JAR file.</li>
103: * <li>If <code>input</code> is a <code>URL</code> it is converted
104: * to an <code>InputStream</code>.</li>
105: * <li>If <code>input</code> is a <code>Socket</code> it is converted
106: * to an <code>InputStream</code>.</li>
107: * </ul></li>
108: * <li><code>ImageIO.createImageInputStream()</code> is invoked
109: * with parameter set to the (possibly converted) input and the
110: * value it returns (which could be <code>null</code>) is returned
111: * to the caller.</li>
112: * </ol>
113: *
114: * @param input An <code>Object</code> to be used as the source,
115: * such as a <code>String</code>, <code>URL</code>, <code>File</code>,
116: * readable <code>RandomAccessFile</code>, <code>InputStream</code>,
117: * readable <code>Socket</code>, or readable <code>Channel</code>.
118: *
119: * @return An <code>ImageInputStream</code> or <code>null</code>.
120: */
121: private static ImageInputStream getImageInputStream(Object input) {
122: // The value to be returned.
123: ImageInputStream stream = null;
124:
125: // If already an ImageInputStream cast and return.
126: if (input instanceof ImageInputStream) {
127: stream = (ImageInputStream) input;
128: } else {
129: // If the input is a String replace it with a RandomAccessFile.
130: if (input instanceof String) {
131: try {
132: // 'input' is conditionally checked for readability
133: // in the OperationDescriptor.
134: input = new RandomAccessFile((String) input, "r");
135: } catch (Exception e) {
136: // Try to get the file as an InputStream resource. This
137: // would happen when the application and image file are
138: // packaged in a JAR file
139: input = ImageReadCRIF.class.getClassLoader()
140: .getResourceAsStream((String) input);
141: if (input == null)
142: throw new RuntimeException(I18N
143: .getString("ImageReadCRIF0")
144: + " " + input);
145: }
146: } else if (input instanceof URL) {
147: // If the input is a URL replace it with an InputStream.
148: try {
149: input = ((URL) input).openStream();
150: } catch (Exception e) {
151: throw new RuntimeException(I18N
152: .getString("ImageReadCRIF1")
153: + " " + input);
154: }
155: } else if (input instanceof Socket) {
156: // If output is a Socket replace it with an InputStream.
157: try {
158: Socket socket = (Socket) input;
159: // XXX check binding, connection, closed, shutdown
160: // as these could have changed.
161: input = socket.getInputStream();
162: } catch (Exception e) {
163: throw new RuntimeException(I18N
164: .getString("ImageReadCRIF2")
165: + " " + input);
166: }
167: }
168: }
169:
170: // Create the ImageInputStream.
171: try {
172: stream = ImageIO.createImageInputStream(input);
173: } catch (Exception e) {
174: throw new RuntimeException(e);
175: }
176:
177: return stream;
178: }
179:
180: /**
181: * Get the <code>ImageReader</code> and set its input and metadata flag.
182: * The input set on the reader might not be the same object as the input
183: * passed in if the latter was replaced by getImageInputStream().
184: */
185: static ImageReader getImageReader(ParameterBlock pb) {
186: // Get the input.
187: Object input = pb.getObjectParameter(0);
188:
189: // Get the reader parameter.
190: ImageReader reader = (ImageReader) pb.getObjectParameter(8);
191:
192: // Attempt to create an ImageInputStream from the input.
193: ImageInputStream stream = getImageInputStream(input);
194:
195: // If no reader passed in, try to find one.
196: if (reader == null) {
197: // Get all compatible readers.
198: Iterator readers = ImageIO
199: .getImageReaders(stream != null ? stream : input);
200:
201: // If any readers, take the first one whose originating
202: // service provider indicates that it can decode the input.
203: if (readers != null && readers.hasNext()) {
204: do {
205: ImageReader tmpReader = (ImageReader) readers
206: .next();
207: ImageReaderSpi readerSpi = tmpReader
208: .getOriginatingProvider();
209: try {
210: if (readerSpi
211: .canDecodeInput(stream != null ? stream
212: : input)) {
213: reader = tmpReader;
214: }
215: } catch (IOException ioe) {
216: // XXX Ignore it?
217: }
218: } while (reader == null && readers.hasNext());
219: }
220: }
221:
222: // If reader found, set its input and metadata flag.
223: if (reader != null) {
224: // Get the locale parameter and set on the reader.
225: Locale locale = (Locale) pb.getObjectParameter(6);
226: if (locale != null) {
227: reader.setLocale(locale);
228: }
229:
230: // Get the listeners parameter and set on the reader.
231: EventListener[] listeners = (EventListener[]) pb
232: .getObjectParameter(5);
233: if (listeners != null) {
234: for (int i = 0; i < listeners.length; i++) {
235: EventListener listener = listeners[i];
236: if (listener instanceof IIOReadProgressListener) {
237: reader
238: .addIIOReadProgressListener((IIOReadProgressListener) listener);
239: }
240: if (listener instanceof IIOReadUpdateListener) {
241: reader
242: .addIIOReadUpdateListener((IIOReadUpdateListener) listener);
243: }
244: if (listener instanceof IIOReadWarningListener) {
245: reader
246: .addIIOReadWarningListener((IIOReadWarningListener) listener);
247: }
248: }
249: }
250:
251: // Get the metadata reading flag.
252: boolean readMetadata = ((Boolean) pb.getObjectParameter(2))
253: .booleanValue();
254:
255: // Set the input and indicate metadata reading state.
256: reader.setInput(stream != null ? stream : input, false, // seekForwardOnly
257: !readMetadata); // ignoreMetadata
258: }
259:
260: return reader;
261: }
262:
263: static void copyProperty(PropertySource ps,
264: WritablePropertySource wps, String propertyName) {
265: Object propertyValue = ps.getProperty(propertyName);
266:
267: if (propertyValue != null
268: && !propertyValue
269: .equals(java.awt.Image.UndefinedProperty)) {
270: wps.setProperty(propertyName, propertyValue);
271:
272: }
273: }
274:
275: public RenderedImage create(ParameterBlock pb, RenderingHints rh) {
276:
277: // Value to be returned.
278: RenderedImage image = null;
279:
280: // Get the reader.
281: ImageReader reader = getImageReader(pb);
282:
283: // Proceed if a compatible reader was found.
284: if (reader != null) {
285: // Get the remaining parameters required.
286: int imageIndex = pb.getIntParameter(1);
287: ImageReadParam param = (ImageReadParam) pb
288: .getObjectParameter(7);
289: boolean readThumbnails = ((Boolean) pb
290: .getObjectParameter(3)).booleanValue();
291:
292: // Initialize the layout.
293: ImageLayout layout = (rh != null && rh
294: .containsKey(JAI.KEY_IMAGE_LAYOUT)) ? (ImageLayout) rh
295: .get(JAI.KEY_IMAGE_LAYOUT)
296: : new ImageLayout();
297:
298: try {
299: // Get the parameter input.
300: Object paramInput = pb.getObjectParameter(0);
301:
302: // Get the reader input.
303: Object readerInput = reader.getInput();
304:
305: // Set the stream to close when the OpImage is disposed.
306: ImageInputStream streamToClose = null;
307: if (readerInput != paramInput
308: && readerInput instanceof ImageInputStream) {
309: streamToClose = (ImageInputStream) readerInput;
310: }
311:
312: // Create the rendering.
313: image = new ImageReadOpImage(layout, rh, param, reader,
314: imageIndex, readThumbnails, streamToClose);
315: } catch (Exception e) {
316: throw new RuntimeException(e);
317: }
318: }
319:
320: return image;
321: }
322:
323: // XXX This implementation of renderable mode is incredibly lame
324: // but the architecture and implementation allow for nothing else.
325: // It would be better if the CRIFs had some kind of state that
326: // could be associated with them. As it standards getBounds2D()
327: // will create a new MultiResolutionRenderableImage and so will
328: // the second create() below. Actually what is needed is a
329: // RenderableImageFactory definition.
330: // XXX There is also a problem with multiple invocations of the
331: // rendered mode case. Without saving and seeking back to the
332: // same offset it appears to have problems. Should ImageReadOpImage
333: // save the initial position and always seek back to it?
334:
335: public RenderableImage createRenderable(ParameterBlock pb,
336: RenderingHints rh) {
337:
338: // Read the collection.
339: Collection sequence = ImageReadCIF.createStatic(pb, rh);
340:
341: // Create a SortedMap which sorts on the basis of inverse area.
342: // The keys will be Dimensions and the objects RenderedImages.
343: TreeMap sourceMap = new TreeMap(new Comparator() {
344: public int compare(Object o1, Object o2) {
345: Dimension d1 = (Dimension) o1;
346: Dimension d2 = (Dimension) o2;
347:
348: int area1 = d1.width * d1.height;
349: int area2 = d2.width * d2.height;
350:
351: double inverse1 = area1 == 0 ? Double.MAX_VALUE
352: : 1.0 / area1;
353: double inverse2 = area2 == 0 ? Double.MAX_VALUE
354: : 1.0 / area2;
355:
356: if (inverse1 < inverse2) {
357: return -1;
358: } else if (inverse1 > inverse2) {
359: return 1;
360: } else {
361: return 0;
362: }
363: }
364:
365: public boolean equals(Object o1, Object o2) {
366: return o1.equals(o2);
367: }
368: });
369:
370: Iterator images = sequence.iterator();
371: while (images.hasNext()) {
372: RenderedImage image = (RenderedImage) images.next();
373: sourceMap.put(new Dimension(image.getWidth(), image
374: .getHeight()), image);
375: }
376:
377: // Create the rendered source list sorted by inverse area.
378: Vector renderedSources = new Vector(sourceMap.size());
379: Iterator keys = sourceMap.keySet().iterator();
380: while (keys.hasNext()) {
381: renderedSources.add(sourceMap.get(keys.next()));
382: }
383:
384: // Create the RenderableImage from the sorted RenderedImages.
385: MultiResolutionRenderableImage renderableImage = new MultiResolutionRenderableImage(
386: renderedSources, 0.0F, 0.0F, 1.0F);
387:
388: // Set properties from those of the first rendered source.
389: PropertySource firstSource = (PropertySource) renderedSources
390: .get(0);
391: copyProperty(firstSource, renderableImage,
392: ImageReadDescriptor.PROPERTY_NAME_IMAGE_READ_PARAM);
393: copyProperty(firstSource, renderableImage,
394: ImageReadDescriptor.PROPERTY_NAME_IMAGE_READER);
395: copyProperty(firstSource, renderableImage,
396: ImageReadDescriptor.PROPERTY_NAME_METADATA_STREAM);
397: copyProperty(firstSource, renderableImage,
398: ImageReadDescriptor.PROPERTY_NAME_METADATA_IMAGE);
399:
400: // Return the RenderableImage.
401: return renderableImage;
402: }
403:
404: public RenderedImage create(RenderContext rc, ParameterBlock pb) {
405:
406: RenderableImage renderableImage = createRenderable(pb, rc
407: .getRenderingHints());
408:
409: RenderedImage renderedImage = renderableImage
410: .createRendering(rc);
411:
412: ((WritablePropertySource) renderedImage).setProperty(
413: ImageReadDescriptor.PROPERTY_NAME_RENDERABLE_INPUT,
414: (PropertySource) renderableImage);
415:
416: return renderedImage;
417: }
418:
419: public Rectangle2D getBounds2D(ParameterBlock pb) {
420: // XXX Should just get the aspect ratio of the first image and use it.
421: // Otherwise this will be very inefficient.
422: RenderableImage renderable = createRenderable(pb, null);
423:
424: return new Rectangle2D.Float(renderable.getMinX(), renderable
425: .getMinY(), renderable.getWidth(), renderable
426: .getHeight());
427: }
428: }
|