1 /*
2 * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package javax.imageio;
27
28 import java.awt.image.BufferedImage;
29 import java.awt.image.RenderedImage;
30 import java.io.File;
31 import java.io.FilePermission;
32 import java.io.InputStream;
33 import java.io.IOException;
34 import java.io.OutputStream;
35 import java.lang.reflect.Method;
36 import java.net.URL;
37 import java.security.AccessController;
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import java.util.Iterator;
42 import java.util.NoSuchElementException;
43 import java.util.Set;
44 import javax.imageio.spi.IIORegistry;
45 import javax.imageio.spi.ImageReaderSpi;
46 import javax.imageio.spi.ImageReaderWriterSpi;
47 import javax.imageio.spi.ImageWriterSpi;
48 import javax.imageio.spi.ImageInputStreamSpi;
49 import javax.imageio.spi.ImageOutputStreamSpi;
50 import javax.imageio.spi.ImageTranscoderSpi;
51 import javax.imageio.spi.ServiceRegistry;
52 import javax.imageio.stream.ImageInputStream;
53 import javax.imageio.stream.ImageOutputStream;
54 import sun.awt.AppContext;
55 import sun.security.action.GetPropertyAction;
56
57 /**
58 * A class containing static convenience methods for locating
59 * {@code ImageReader}s and {@code ImageWriter}s, and
60 * performing simple encoding and decoding.
61 *
62 */
63 public final class ImageIO {
64
65 private static final IIORegistry theRegistry =
66 IIORegistry.getDefaultInstance();
67
68 /**
69 * Constructor is private to prevent instantiation.
70 */
71 private ImageIO() {}
72
73 /**
74 * Scans for plug-ins on the application class path,
75 * loads their service provider classes, and registers a service
76 * provider instance for each one found with the
77 * {@code IIORegistry}.
78 *
79 * <p>This method is needed because the application class path can
80 * theoretically change, or additional plug-ins may become available.
81 * Rather than re-scanning the classpath on every invocation of the
82 * API, the class path is scanned automatically only on the first
83 * invocation. Clients can call this method to prompt a re-scan.
84 * Thus this method need only be invoked by sophisticated applications
85 * which dynamically make new plug-ins available at runtime.
86 *
87 * <p> The {@code getResources} method of the context
88 * {@code ClassLoader} is used locate JAR files containing
89 * files named
90 * {@code META-INF/services/javax.imageio.spi.}<i>classname</i>,
91 * where <i>classname</i> is one of {@code ImageReaderSpi},
92 * {@code ImageWriterSpi}, {@code ImageTranscoderSpi},
93 * {@code ImageInputStreamSpi}, or
94 * {@code ImageOutputStreamSpi}, along the application class
95 * path.
96 *
97 * <p> The contents of the located files indicate the names of
98 * actual implementation classes which implement the
99 * aforementioned service provider interfaces; the default class
100 * loader is then used to load each of these classes and to
101 * instantiate an instance of each class, which is then placed
102 * into the registry for later retrieval.
103 *
104 * <p> The exact set of locations searched depends on the
105 * implementation of the Java runtime environment.
106 *
107 * @see ClassLoader#getResources
108 */
109 public static void scanForPlugins() {
110 theRegistry.registerApplicationClasspathSpis();
111 }
112
113 // ImageInputStreams
114
115 /**
116 * A class to hold information about caching. Each
117 * {@code ThreadGroup} will have its own copy
118 * via the {@code AppContext} mechanism.
119 */
120 static class CacheInfo {
121 boolean useCache = true;
122 File cacheDirectory = null;
123 Boolean hasPermission = null;
124
125 public CacheInfo() {}
126
127 public boolean getUseCache() {
128 return useCache;
129 }
130
131 public void setUseCache(boolean useCache) {
132 this.useCache = useCache;
133 }
134
135 public File getCacheDirectory() {
136 return cacheDirectory;
137 }
138
139 public void setCacheDirectory(File cacheDirectory) {
140 this.cacheDirectory = cacheDirectory;
141 }
142
143 public Boolean getHasPermission() {
144 return hasPermission;
145 }
146
147 public void setHasPermission(Boolean hasPermission) {
148 this.hasPermission = hasPermission;
149 }
150 }
151
152 /**
153 * Returns the {@code CacheInfo} object associated with this
154 * {@code ThreadGroup}.
155 */
156 private static synchronized CacheInfo getCacheInfo() {
157 AppContext context = AppContext.getAppContext();
158 CacheInfo info = (CacheInfo)context.get(CacheInfo.class);
159 if (info == null) {
160 info = new CacheInfo();
161 context.put(CacheInfo.class, info);
162 }
163 return info;
164 }
165
166 /**
167 * Returns the default temporary (cache) directory as defined by the
168 * java.io.tmpdir system property.
169 */
170 private static String getTempDir() {
171 GetPropertyAction a = new GetPropertyAction("java.io.tmpdir");
172 return AccessController.doPrivileged(a);
173 }
174
175 /**
176 * Determines whether the caller has write access to the cache
177 * directory, stores the result in the {@code CacheInfo} object,
178 * and returns the decision. This method helps to prevent mysterious
179 * SecurityExceptions to be thrown when this convenience class is used
180 * in an applet, for example.
181 */
182 private static boolean hasCachePermission() {
183 Boolean hasPermission = getCacheInfo().getHasPermission();
184
185 if (hasPermission != null) {
186 return hasPermission.booleanValue();
187 } else {
188 try {
189 SecurityManager security = System.getSecurityManager();
190 if (security != null) {
191 File cachedir = getCacheDirectory();
192 String cachepath;
193
194 if (cachedir != null) {
195 cachepath = cachedir.getPath();
196 } else {
197 cachepath = getTempDir();
198
199 if (cachepath == null || cachepath.isEmpty()) {
200 getCacheInfo().setHasPermission(Boolean.FALSE);
201 return false;
202 }
203 }
204
205 // we have to check whether we can read, write,
206 // and delete cache files.
207 // So, compose cache file path and check it.
208 String filepath = cachepath;
209 if (!filepath.endsWith(File.separator)) {
210 filepath += File.separator;
211 }
212 filepath += "*";
213
214 security.checkPermission(new FilePermission(filepath, "read, write, delete"));
215 }
216 } catch (SecurityException e) {
217 getCacheInfo().setHasPermission(Boolean.FALSE);
218 return false;
219 }
220
221 getCacheInfo().setHasPermission(Boolean.TRUE);
222 return true;
223 }
224 }
225
226 /**
227 * Sets a flag indicating whether a disk-based cache file should
228 * be used when creating {@code ImageInputStream}s and
229 * {@code ImageOutputStream}s.
230 *
231 * <p> When reading from a standard {@code InputStream}, it
232 * may be necessary to save previously read information in a cache
233 * since the underlying stream does not allow data to be re-read.
234 * Similarly, when writing to a standard
235 * {@code OutputStream}, a cache may be used to allow a
236 * previously written value to be changed before flushing it to
237 * the final destination.
238 *
239 * <p> The cache may reside in main memory or on disk. Setting
240 * this flag to {@code false} disallows the use of disk for
241 * future streams, which may be advantageous when working with
242 * small images, as the overhead of creating and destroying files
243 * is removed.
244 *
245 * <p> On startup, the value is set to {@code true}.
246 *
247 * @param useCache a {@code boolean} indicating whether a
248 * cache file should be used, in cases where it is optional.
249 *
250 * @see #getUseCache
251 */
252 public static void setUseCache(boolean useCache) {
253 getCacheInfo().setUseCache(useCache);
254 }
255
256 /**
257 * Returns the current value set by {@code setUseCache}, or
258 * {@code true} if no explicit setting has been made.
259 *
260 * @return true if a disk-based cache may be used for
261 * {@code ImageInputStream}s and
262 * {@code ImageOutputStream}s.
263 *
264 * @see #setUseCache
265 */
266 public static boolean getUseCache() {
267 return getCacheInfo().getUseCache();
268 }
269
270 /**
271 * Sets the directory where cache files are to be created. A
272 * value of {@code null} indicates that the system-dependent
273 * default temporary-file directory is to be used. If
274 * {@code getUseCache} returns false, this value is ignored.
275 *
276 * @param cacheDirectory a {@code File} specifying a directory.
277 *
278 * @see File#createTempFile(String, String, File)
279 *
280 * @exception SecurityException if the security manager denies
281 * access to the directory.
282 * @exception IllegalArgumentException if {@code cacheDir} is
283 * non-{@code null} but is not a directory.
284 *
285 * @see #getCacheDirectory
286 */
287 public static void setCacheDirectory(File cacheDirectory) {
288 if ((cacheDirectory != null) && !(cacheDirectory.isDirectory())) {
289 throw new IllegalArgumentException("Not a directory!");
290 }
291 getCacheInfo().setCacheDirectory(cacheDirectory);
292 getCacheInfo().setHasPermission(null);
293 }
294
295 /**
296 * Returns the current value set by
297 * {@code setCacheDirectory}, or {@code null} if no
298 * explicit setting has been made.
299 *
300 * @return a {@code File} indicating the directory where
301 * cache files will be created, or {@code null} to indicate
302 * the system-dependent default temporary-file directory.
303 *
304 * @see #setCacheDirectory
305 */
306 public static File getCacheDirectory() {
307 return getCacheInfo().getCacheDirectory();
308 }
309
310 /**
311 * Returns an {@code ImageInputStream} that will take its
312 * input from the given {@code Object}. The set of
313 * {@code ImageInputStreamSpi}s registered with the
314 * {@code IIORegistry} class is queried and the first one
315 * that is able to take input from the supplied object is used to
316 * create the returned {@code ImageInputStream}. If no
317 * suitable {@code ImageInputStreamSpi} exists,
318 * {@code null} is returned.
319 *
320 * <p> The current cache settings from {@code getUseCache} and
321 * {@code getCacheDirectory} will be used to control caching.
322 *
323 * @param input an {@code Object} to be used as an input
324 * source, such as a {@code File}, readable
325 * {@code RandomAccessFile}, or {@code InputStream}.
326 *
327 * @return an {@code ImageInputStream}, or {@code null}.
328 *
329 * @exception IllegalArgumentException if {@code input}
330 * is {@code null}.
331 * @exception IOException if a cache file is needed but cannot be
332 * created.
333 *
334 * @see javax.imageio.spi.ImageInputStreamSpi
335 */
336 public static ImageInputStream createImageInputStream(Object input)
337 throws IOException {
338 if (input == null) {
339 throw new IllegalArgumentException("input == null!");
340 }
341
342 Iterator<ImageInputStreamSpi> iter;
343 // Ensure category is present
344 try {
345 iter = theRegistry.getServiceProviders(ImageInputStreamSpi.class,
346 true);
347 } catch (IllegalArgumentException e) {
348 return null;
349 }
350
351 boolean usecache = getUseCache() && hasCachePermission();
352
353 while (iter.hasNext()) {
354 ImageInputStreamSpi spi = iter.next();
355 if (spi.getInputClass().isInstance(input)) {
356 try {
357 return spi.createInputStreamInstance(input,
358 usecache,
359 getCacheDirectory());
360 } catch (IOException e) {
361 throw new IIOException("Can't create cache file!", e);
362 }
363 }
364 }
365
366 return null;
367 }
368
369 // ImageOutputStreams
370
371 /**
372 * Returns an {@code ImageOutputStream} that will send its
373 * output to the given {@code Object}. The set of
374 * {@code ImageOutputStreamSpi}s registered with the
375 * {@code IIORegistry} class is queried and the first one
376 * that is able to send output from the supplied object is used to
377 * create the returned {@code ImageOutputStream}. If no
378 * suitable {@code ImageOutputStreamSpi} exists,
379 * {@code null} is returned.
380 *
381 * <p> The current cache settings from {@code getUseCache} and
382 * {@code getCacheDirectory} will be used to control caching.
383 *
384 * @param output an {@code Object} to be used as an output
385 * destination, such as a {@code File}, writable
386 * {@code RandomAccessFile}, or {@code OutputStream}.
387 *
388 * @return an {@code ImageOutputStream}, or
389 * {@code null}.
390 *
391 * @exception IllegalArgumentException if {@code output} is
392 * {@code null}.
393 * @exception IOException if a cache file is needed but cannot be
394 * created.
395 *
396 * @see javax.imageio.spi.ImageOutputStreamSpi
397 */
398 public static ImageOutputStream createImageOutputStream(Object output)
399 throws IOException {
400 if (output == null) {
401 throw new IllegalArgumentException("output == null!");
402 }
403
404 Iterator<ImageOutputStreamSpi> iter;
405 // Ensure category is present
406 try {
407 iter = theRegistry.getServiceProviders(ImageOutputStreamSpi.class,
408 true);
409 } catch (IllegalArgumentException e) {
410 return null;
411 }
412
413 boolean usecache = getUseCache() && hasCachePermission();
414
415 while (iter.hasNext()) {
416 ImageOutputStreamSpi spi = iter.next();
417 if (spi.getOutputClass().isInstance(output)) {
418 try {
419 return spi.createOutputStreamInstance(output,
420 usecache,
421 getCacheDirectory());
422 } catch (IOException e) {
423 throw new IIOException("Can't create cache file!", e);
424 }
425 }
426 }
427
428 return null;
429 }
430
431 private static enum SpiInfo {
432 FORMAT_NAMES {
433 @Override
434 String[] info(ImageReaderWriterSpi spi) {
435 return spi.getFormatNames();
436 }
437 },
438 MIME_TYPES {
439 @Override
440 String[] info(ImageReaderWriterSpi spi) {
441 return spi.getMIMETypes();
442 }
443 },
444 FILE_SUFFIXES {
445 @Override
446 String[] info(ImageReaderWriterSpi spi) {
447 return spi.getFileSuffixes();
448 }
449 };
450
451 abstract String[] info(ImageReaderWriterSpi spi);
452 }
453
454 private static <S extends ImageReaderWriterSpi>
455 String[] getReaderWriterInfo(Class<S> spiClass, SpiInfo spiInfo)
456 {
457 // Ensure category is present
458 Iterator<S> iter;
459 try {
460 iter = theRegistry.getServiceProviders(spiClass, true);
461 } catch (IllegalArgumentException e) {
462 return new String[0];
463 }
464
465 HashSet<String> s = new HashSet<>();
466 while (iter.hasNext()) {
467 ImageReaderWriterSpi spi = iter.next();
468 String[] info = spiInfo.info(spi);
469 if (info != null) {
470 Collections.addAll(s, info);
471 }
472 }
473
474 return s.toArray(new String[s.size()]);
475 }
476
477 // Readers
478
479 /**
480 * Returns an array of {@code String}s listing all of the
481 * informal format names understood by the current set of registered
482 * readers.
483 *
484 * @return an array of {@code String}s.
485 */
486 public static String[] getReaderFormatNames() {
487 return getReaderWriterInfo(ImageReaderSpi.class,
488 SpiInfo.FORMAT_NAMES);
489 }
490
491 /**
492 * Returns an array of {@code String}s listing all of the
493 * MIME types understood by the current set of registered
494 * readers.
495 *
496 * @return an array of {@code String}s.
497 */
498 public static String[] getReaderMIMETypes() {
499 return getReaderWriterInfo(ImageReaderSpi.class,
500 SpiInfo.MIME_TYPES);
501 }
502
503 /**
504 * Returns an array of {@code String}s listing all of the
505 * file suffixes associated with the formats understood
506 * by the current set of registered readers.
507 *
508 * @return an array of {@code String}s.
509 * @since 1.6
510 */
511 public static String[] getReaderFileSuffixes() {
512 return getReaderWriterInfo(ImageReaderSpi.class,
513 SpiInfo.FILE_SUFFIXES);
514 }
515
516 static class ImageReaderIterator implements Iterator<ImageReader> {
517 // Contains ImageReaderSpis
518 private Iterator<ImageReaderSpi> iter;
519
520 public ImageReaderIterator(Iterator<ImageReaderSpi> iter) {
521 this.iter = iter;
522 }
523
524 public boolean hasNext() {
525 return iter.hasNext();
526 }
527
528 public ImageReader next() {
529 ImageReaderSpi spi = null;
530 try {
531 spi = iter.next();
532 return spi.createReaderInstance();
533 } catch (IOException e) {
534 // Deregister the spi in this case, but only as
535 // an ImageReaderSpi
536 theRegistry.deregisterServiceProvider(spi, ImageReaderSpi.class);
537 }
538 return null;
539 }
540
541 public void remove() {
542 throw new UnsupportedOperationException();
543 }
544 }
545
546 static class CanDecodeInputFilter
547 implements ServiceRegistry.Filter {
548
549 Object input;
550
551 public CanDecodeInputFilter(Object input) {
552 this.input = input;
553 }
554
555 public boolean filter(Object elt) {
556 try {
557 ImageReaderSpi spi = (ImageReaderSpi)elt;
558 ImageInputStream stream = null;
559 if (input instanceof ImageInputStream) {
560 stream = (ImageInputStream)input;
561 }
562
563 // Perform mark/reset as a defensive measure
564 // even though plug-ins are supposed to take
565 // care of it.
566 boolean canDecode = false;
567 if (stream != null) {
568 stream.mark();
569 }
570 try {
571 canDecode = spi.canDecodeInput(input);
572 } finally {
573 if (stream != null) {
574 stream.reset();
575 }
576 }
577
578 return canDecode;
579 } catch (IOException e) {
580 return false;
581 }
582 }
583 }
584
585 static class CanEncodeImageAndFormatFilter
586 implements ServiceRegistry.Filter {
587
588 ImageTypeSpecifier type;
589 String formatName;
590
591 public CanEncodeImageAndFormatFilter(ImageTypeSpecifier type,
592 String formatName) {
593 this.type = type;
594 this.formatName = formatName;
595 }
596
597 public boolean filter(Object elt) {
598 ImageWriterSpi spi = (ImageWriterSpi)elt;
599 return Arrays.asList(spi.getFormatNames()).contains(formatName) &&
600 spi.canEncodeImage(type);
601 }
602 }
603
604 static class ContainsFilter
605 implements ServiceRegistry.Filter {
606
607 Method method;
608 String name;
609
610 // method returns an array of Strings
611 public ContainsFilter(Method method,
612 String name) {
613 this.method = method;
614 this.name = name;
615 }
616
617 public boolean filter(Object elt) {
618 try {
619 return contains((String[])method.invoke(elt), name);
620 } catch (Exception e) {
621 return false;
622 }
623 }
624 }
625
626 /**
627 * Returns an {@code Iterator} containing all currently
628 * registered {@code ImageReader}s that claim to be able to
629 * decode the supplied {@code Object}, typically an
630 * {@code ImageInputStream}.
631 *
632 * <p> The stream position is left at its prior position upon
633 * exit from this method.
634 *
635 * @param input an {@code ImageInputStream} or other
636 * {@code Object} containing encoded image data.
637 *
638 * @return an {@code Iterator} containing {@code ImageReader}s.
639 *
640 * @exception IllegalArgumentException if {@code input} is
641 * {@code null}.
642 *
643 * @see javax.imageio.spi.ImageReaderSpi#canDecodeInput
644 */
645 public static Iterator<ImageReader> getImageReaders(Object input) {
646 if (input == null) {
647 throw new IllegalArgumentException("input == null!");
648 }
649 Iterator<ImageReaderSpi> iter;
650 // Ensure category is present
651 try {
652 iter = theRegistry.getServiceProviders(ImageReaderSpi.class,
653 new CanDecodeInputFilter(input),
654 true);
655 } catch (IllegalArgumentException e) {
656 return Collections.emptyIterator();
657 }
658
659 return new ImageReaderIterator(iter);
660 }
661
662 private static Method readerFormatNamesMethod;
663 private static Method readerFileSuffixesMethod;
664 private static Method readerMIMETypesMethod;
665 private static Method writerFormatNamesMethod;
666 private static Method writerFileSuffixesMethod;
667 private static Method writerMIMETypesMethod;
668
669 static {
670 try {
671 readerFormatNamesMethod =
672 ImageReaderSpi.class.getMethod("getFormatNames");
673 readerFileSuffixesMethod =
674 ImageReaderSpi.class.getMethod("getFileSuffixes");
675 readerMIMETypesMethod =
676 ImageReaderSpi.class.getMethod("getMIMETypes");
677
678 writerFormatNamesMethod =
679 ImageWriterSpi.class.getMethod("getFormatNames");
680 writerFileSuffixesMethod =
681 ImageWriterSpi.class.getMethod("getFileSuffixes");
682 writerMIMETypesMethod =
683 ImageWriterSpi.class.getMethod("getMIMETypes");
684 } catch (NoSuchMethodException e) {
685 e.printStackTrace();
686 }
687 }
688
689 /**
690 * Returns an {@code Iterator} containing all currently
691 * registered {@code ImageReader}s that claim to be able to
692 * decode the named format.
693 *
694 * @param formatName a {@code String} containing the informal
695 * name of a format (<i>e.g.</i>, "jpeg" or "tiff".
696 *
697 * @return an {@code Iterator} containing
698 * {@code ImageReader}s.
699 *
700 * @exception IllegalArgumentException if {@code formatName}
701 * is {@code null}.
702 *
703 * @see javax.imageio.spi.ImageReaderSpi#getFormatNames
704 */
705 public static Iterator<ImageReader>
706 getImageReadersByFormatName(String formatName)
707 {
708 if (formatName == null) {
709 throw new IllegalArgumentException("formatName == null!");
710 }
711 Iterator<ImageReaderSpi> iter;
712 // Ensure category is present
713 try {
714 iter = theRegistry.getServiceProviders(ImageReaderSpi.class,
715 new ContainsFilter(readerFormatNamesMethod,
716 formatName),
717 true);
718 } catch (IllegalArgumentException e) {
719 return Collections.emptyIterator();
720 }
721 return new ImageReaderIterator(iter);
722 }
723
724 /**
725 * Returns an {@code Iterator} containing all currently
726 * registered {@code ImageReader}s that claim to be able to
727 * decode files with the given suffix.
728 *
729 * @param fileSuffix a {@code String} containing a file
730 * suffix (<i>e.g.</i>, "jpg" or "tiff").
731 *
732 * @return an {@code Iterator} containing
733 * {@code ImageReader}s.
734 *
735 * @exception IllegalArgumentException if {@code fileSuffix}
736 * is {@code null}.
737 *
738 * @see javax.imageio.spi.ImageReaderSpi#getFileSuffixes
739 */
740 public static Iterator<ImageReader>
741 getImageReadersBySuffix(String fileSuffix)
742 {
743 if (fileSuffix == null) {
744 throw new IllegalArgumentException("fileSuffix == null!");
745 }
746 // Ensure category is present
747 Iterator<ImageReaderSpi> iter;
748 try {
749 iter = theRegistry.getServiceProviders(ImageReaderSpi.class,
750 new ContainsFilter(readerFileSuffixesMethod,
751 fileSuffix),
752 true);
753 } catch (IllegalArgumentException e) {
754 return Collections.emptyIterator();
755 }
756 return new ImageReaderIterator(iter);
757 }
758
759 /**
760 * Returns an {@code Iterator} containing all currently
761 * registered {@code ImageReader}s that claim to be able to
762 * decode files with the given MIME type.
763 *
764 * @param MIMEType a {@code String} containing a file
765 * suffix (<i>e.g.</i>, "image/jpeg" or "image/x-bmp").
766 *
767 * @return an {@code Iterator} containing
768 * {@code ImageReader}s.
769 *
770 * @exception IllegalArgumentException if {@code MIMEType} is
771 * {@code null}.
772 *
773 * @see javax.imageio.spi.ImageReaderSpi#getMIMETypes
774 */
775 public static Iterator<ImageReader>
776 getImageReadersByMIMEType(String MIMEType)
777 {
778 if (MIMEType == null) {
779 throw new IllegalArgumentException("MIMEType == null!");
780 }
781 // Ensure category is present
782 Iterator<ImageReaderSpi> iter;
783 try {
784 iter = theRegistry.getServiceProviders(ImageReaderSpi.class,
785 new ContainsFilter(readerMIMETypesMethod,
786 MIMEType),
787 true);
788 } catch (IllegalArgumentException e) {
789 return Collections.emptyIterator();
790 }
791 return new ImageReaderIterator(iter);
792 }
793
794 // Writers
795
796 /**
797 * Returns an array of {@code String}s listing all of the
798 * informal format names understood by the current set of registered
799 * writers.
800 *
801 * @return an array of {@code String}s.
802 */
803 public static String[] getWriterFormatNames() {
804 return getReaderWriterInfo(ImageWriterSpi.class,
805 SpiInfo.FORMAT_NAMES);
806 }
807
808 /**
809 * Returns an array of {@code String}s listing all of the
810 * MIME types understood by the current set of registered
811 * writers.
812 *
813 * @return an array of {@code String}s.
814 */
815 public static String[] getWriterMIMETypes() {
816 return getReaderWriterInfo(ImageWriterSpi.class,
817 SpiInfo.MIME_TYPES);
818 }
819
820 /**
821 * Returns an array of {@code String}s listing all of the
822 * file suffixes associated with the formats understood
823 * by the current set of registered writers.
824 *
825 * @return an array of {@code String}s.
826 * @since 1.6
827 */
828 public static String[] getWriterFileSuffixes() {
829 return getReaderWriterInfo(ImageWriterSpi.class,
830 SpiInfo.FILE_SUFFIXES);
831 }
832
833 static class ImageWriterIterator implements Iterator<ImageWriter> {
834 // Contains ImageWriterSpis
835 private Iterator<ImageWriterSpi> iter;
836
837 public ImageWriterIterator(Iterator<ImageWriterSpi> iter) {
838 this.iter = iter;
839 }
840
841 public boolean hasNext() {
842 return iter.hasNext();
843 }
844
845 public ImageWriter next() {
846 ImageWriterSpi spi = null;
847 try {
848 spi = iter.next();
849 return spi.createWriterInstance();
850 } catch (IOException e) {
851 // Deregister the spi in this case, but only as a writerSpi
852 theRegistry.deregisterServiceProvider(spi, ImageWriterSpi.class);
853 }
854 return null;
855 }
856
857 public void remove() {
858 throw new UnsupportedOperationException();
859 }
860 }
861
862 private static boolean contains(String[] names, String name) {
863 for (int i = 0; i < names.length; i++) {
864 if (name.equalsIgnoreCase(names[i])) {
865 return true;
866 }
867 }
868
869 return false;
870 }
871
872 /**
873 * Returns an {@code Iterator} containing all currently
874 * registered {@code ImageWriter}s that claim to be able to
875 * encode the named format.
876 *
877 * @param formatName a {@code String} containing the informal
878 * name of a format (<i>e.g.</i>, "jpeg" or "tiff".
879 *
880 * @return an {@code Iterator} containing
881 * {@code ImageWriter}s.
882 *
883 * @exception IllegalArgumentException if {@code formatName} is
884 * {@code null}.
885 *
886 * @see javax.imageio.spi.ImageWriterSpi#getFormatNames
887 */
888 public static Iterator<ImageWriter>
889 getImageWritersByFormatName(String formatName)
890 {
891 if (formatName == null) {
892 throw new IllegalArgumentException("formatName == null!");
893 }
894 Iterator<ImageWriterSpi> iter;
895 // Ensure category is present
896 try {
897 iter = theRegistry.getServiceProviders(ImageWriterSpi.class,
898 new ContainsFilter(writerFormatNamesMethod,
899 formatName),
900 true);
901 } catch (IllegalArgumentException e) {
902 return Collections.emptyIterator();
903 }
904 return new ImageWriterIterator(iter);
905 }
906
907 /**
908 * Returns an {@code Iterator} containing all currently
909 * registered {@code ImageWriter}s that claim to be able to
910 * encode files with the given suffix.
911 *
912 * @param fileSuffix a {@code String} containing a file
913 * suffix (<i>e.g.</i>, "jpg" or "tiff").
914 *
915 * @return an {@code Iterator} containing {@code ImageWriter}s.
916 *
917 * @exception IllegalArgumentException if {@code fileSuffix} is
918 * {@code null}.
919 *
920 * @see javax.imageio.spi.ImageWriterSpi#getFileSuffixes
921 */
922 public static Iterator<ImageWriter>
923 getImageWritersBySuffix(String fileSuffix)
924 {
925 if (fileSuffix == null) {
926 throw new IllegalArgumentException("fileSuffix == null!");
927 }
928 Iterator<ImageWriterSpi> iter;
929 // Ensure category is present
930 try {
931 iter = theRegistry.getServiceProviders(ImageWriterSpi.class,
932 new ContainsFilter(writerFileSuffixesMethod,
933 fileSuffix),
934 true);
935 } catch (IllegalArgumentException e) {
936 return Collections.emptyIterator();
937 }
938 return new ImageWriterIterator(iter);
939 }
940
941 /**
942 * Returns an {@code Iterator} containing all currently
943 * registered {@code ImageWriter}s that claim to be able to
944 * encode files with the given MIME type.
945 *
946 * @param MIMEType a {@code String} containing a file
947 * suffix (<i>e.g.</i>, "image/jpeg" or "image/x-bmp").
948 *
949 * @return an {@code Iterator} containing {@code ImageWriter}s.
950 *
951 * @exception IllegalArgumentException if {@code MIMEType} is
952 * {@code null}.
953 *
954 * @see javax.imageio.spi.ImageWriterSpi#getMIMETypes
955 */
956 public static Iterator<ImageWriter>
957 getImageWritersByMIMEType(String MIMEType)
958 {
959 if (MIMEType == null) {
960 throw new IllegalArgumentException("MIMEType == null!");
961 }
962 Iterator<ImageWriterSpi> iter;
963 // Ensure category is present
964 try {
965 iter = theRegistry.getServiceProviders(ImageWriterSpi.class,
966 new ContainsFilter(writerMIMETypesMethod,
967 MIMEType),
968 true);
969 } catch (IllegalArgumentException e) {
970 return Collections.emptyIterator();
971 }
972 return new ImageWriterIterator(iter);
973 }
974
975 /**
976 * Returns an {@code ImageWriter} corresponding to the given
977 * {@code ImageReader}, if there is one, or {@code null}
978 * if the plug-in for this {@code ImageReader} does not
979 * specify a corresponding {@code ImageWriter}, or if the
980 * given {@code ImageReader} is not registered. This
981 * mechanism may be used to obtain an {@code ImageWriter}
982 * that will understand the internal structure of non-pixel
983 * metadata (as encoded by {@code IIOMetadata} objects)
984 * generated by the {@code ImageReader}. By obtaining this
985 * data from the {@code ImageReader} and passing it on to the
986 * {@code ImageWriter} obtained with this method, a client
987 * program can read an image, modify it in some way, and write it
988 * back out preserving all metadata, without having to understand
989 * anything about the structure of the metadata, or even about
990 * the image format. Note that this method returns the
991 * "preferred" writer, which is the first in the list returned by
992 * {@code javax.imageio.spi.ImageReaderSpi.getImageWriterSpiNames()}.
993 *
994 * @param reader an instance of a registered {@code ImageReader}.
995 *
996 * @return an {@code ImageWriter}, or null.
997 *
998 * @exception IllegalArgumentException if {@code reader} is
999 * {@code null}.
1000 *
1001 * @see #getImageReader(ImageWriter)
1002 * @see javax.imageio.spi.ImageReaderSpi#getImageWriterSpiNames()
1003 */
1004 public static ImageWriter getImageWriter(ImageReader reader) {
1005 if (reader == null) {
1006 throw new IllegalArgumentException("reader == null!");
1007 }
1008
1009 ImageReaderSpi readerSpi = reader.getOriginatingProvider();
1010 if (readerSpi == null) {
1011 Iterator<ImageReaderSpi> readerSpiIter;
1012 // Ensure category is present
1013 try {
1014 readerSpiIter =
1015 theRegistry.getServiceProviders(ImageReaderSpi.class,
1016 false);
1017 } catch (IllegalArgumentException e) {
1018 return null;
1019 }
1020
1021 while (readerSpiIter.hasNext()) {
1022 ImageReaderSpi temp = readerSpiIter.next();
1023 if (temp.isOwnReader(reader)) {
1024 readerSpi = temp;
1025 break;
1026 }
1027 }
1028 if (readerSpi == null) {
1029 return null;
1030 }
1031 }
1032
1033 String[] writerNames = readerSpi.getImageWriterSpiNames();
1034 if (writerNames == null) {
1035 return null;
1036 }
1037
1038 Class<?> writerSpiClass = null;
1039 try {
1040 writerSpiClass = Class.forName(writerNames[0], true,
1041 ClassLoader.getSystemClassLoader());
1042 } catch (ClassNotFoundException e) {
1043 return null;
1044 }
1045
1046 ImageWriterSpi writerSpi = (ImageWriterSpi)
1047 theRegistry.getServiceProviderByClass(writerSpiClass);
1048 if (writerSpi == null) {
1049 return null;
1050 }
1051
1052 try {
1053 return writerSpi.createWriterInstance();
1054 } catch (IOException e) {
1055 // Deregister the spi in this case, but only as a writerSpi
1056 theRegistry.deregisterServiceProvider(writerSpi,
1057 ImageWriterSpi.class);
1058 return null;
1059 }
1060 }
1061
1062 /**
1063 * Returns an {@code ImageReader} corresponding to the given
1064 * {@code ImageWriter}, if there is one, or {@code null}
1065 * if the plug-in for this {@code ImageWriter} does not
1066 * specify a corresponding {@code ImageReader}, or if the
1067 * given {@code ImageWriter} is not registered. This method
1068 * is provided principally for symmetry with
1069 * {@code getImageWriter(ImageReader)}. Note that this
1070 * method returns the "preferred" reader, which is the first in
1071 * the list returned by
1072 * javax.imageio.spi.ImageWriterSpi.{@code getImageReaderSpiNames()}.
1073 *
1074 * @param writer an instance of a registered {@code ImageWriter}.
1075 *
1076 * @return an {@code ImageReader}, or null.
1077 *
1078 * @exception IllegalArgumentException if {@code writer} is
1079 * {@code null}.
1080 *
1081 * @see #getImageWriter(ImageReader)
1082 * @see javax.imageio.spi.ImageWriterSpi#getImageReaderSpiNames()
1083 */
1084 public static ImageReader getImageReader(ImageWriter writer) {
1085 if (writer == null) {
1086 throw new IllegalArgumentException("writer == null!");
1087 }
1088
1089 ImageWriterSpi writerSpi = writer.getOriginatingProvider();
1090 if (writerSpi == null) {
1091 Iterator<ImageWriterSpi> writerSpiIter;
1092 // Ensure category is present
1093 try {
1094 writerSpiIter =
1095 theRegistry.getServiceProviders(ImageWriterSpi.class,
1096 false);
1097 } catch (IllegalArgumentException e) {
1098 return null;
1099 }
1100
1101 while (writerSpiIter.hasNext()) {
1102 ImageWriterSpi temp = writerSpiIter.next();
1103 if (temp.isOwnWriter(writer)) {
1104 writerSpi = temp;
1105 break;
1106 }
1107 }
1108 if (writerSpi == null) {
1109 return null;
1110 }
1111 }
1112
1113 String[] readerNames = writerSpi.getImageReaderSpiNames();
1114 if (readerNames == null) {
1115 return null;
1116 }
1117
1118 Class<?> readerSpiClass = null;
1119 try {
1120 readerSpiClass = Class.forName(readerNames[0], true,
1121 ClassLoader.getSystemClassLoader());
1122 } catch (ClassNotFoundException e) {
1123 return null;
1124 }
1125
1126 ImageReaderSpi readerSpi = (ImageReaderSpi)
1127 theRegistry.getServiceProviderByClass(readerSpiClass);
1128 if (readerSpi == null) {
1129 return null;
1130 }
1131
1132 try {
1133 return readerSpi.createReaderInstance();
1134 } catch (IOException e) {
1135 // Deregister the spi in this case, but only as a readerSpi
1136 theRegistry.deregisterServiceProvider(readerSpi,
1137 ImageReaderSpi.class);
1138 return null;
1139 }
1140 }
1141
1142 /**
1143 * Returns an {@code Iterator} containing all currently
1144 * registered {@code ImageWriter}s that claim to be able to
1145 * encode images of the given layout (specified using an
1146 * {@code ImageTypeSpecifier}) in the given format.
1147 *
1148 * @param type an {@code ImageTypeSpecifier} indicating the
1149 * layout of the image to be written.
1150 * @param formatName the informal name of the {@code format}.
1151 *
1152 * @return an {@code Iterator} containing {@code ImageWriter}s.
1153 *
1154 * @exception IllegalArgumentException if any parameter is
1155 * {@code null}.
1156 *
1157 * @see javax.imageio.spi.ImageWriterSpi#canEncodeImage(ImageTypeSpecifier)
1158 */
1159 public static Iterator<ImageWriter>
1160 getImageWriters(ImageTypeSpecifier type, String formatName)
1161 {
1162 if (type == null) {
1163 throw new IllegalArgumentException("type == null!");
1164 }
1165 if (formatName == null) {
1166 throw new IllegalArgumentException("formatName == null!");
1167 }
1168
1169 Iterator<ImageWriterSpi> iter;
1170 // Ensure category is present
1171 try {
1172 iter = theRegistry.getServiceProviders(ImageWriterSpi.class,
1173 new CanEncodeImageAndFormatFilter(type,
1174 formatName),
1175 true);
1176 } catch (IllegalArgumentException e) {
1177 return Collections.emptyIterator();
1178 }
1179
1180 return new ImageWriterIterator(iter);
1181 }
1182
1183 static class ImageTranscoderIterator
1184 implements Iterator<ImageTranscoder>
1185 {
1186 // Contains ImageTranscoderSpis
1187 public Iterator<ImageTranscoderSpi> iter;
1188
1189 public ImageTranscoderIterator(Iterator<ImageTranscoderSpi> iter) {
1190 this.iter = iter;
1191 }
1192
1193 public boolean hasNext() {
1194 return iter.hasNext();
1195 }
1196
1197 public ImageTranscoder next() {
1198 ImageTranscoderSpi spi = null;
1199 spi = iter.next();
1200 return spi.createTranscoderInstance();
1201 }
1202
1203 public void remove() {
1204 throw new UnsupportedOperationException();
1205 }
1206 }
1207
1208 static class TranscoderFilter
1209 implements ServiceRegistry.Filter {
1210
1211 String readerSpiName;
1212 String writerSpiName;
1213
1214 public TranscoderFilter(ImageReaderSpi readerSpi,
1215 ImageWriterSpi writerSpi) {
1216 this.readerSpiName = readerSpi.getClass().getName();
1217 this.writerSpiName = writerSpi.getClass().getName();
1218 }
1219
1220 public boolean filter(Object elt) {
1221 ImageTranscoderSpi spi = (ImageTranscoderSpi)elt;
1222 String readerName = spi.getReaderServiceProviderName();
1223 String writerName = spi.getWriterServiceProviderName();
1224 return (readerName.equals(readerSpiName) &&
1225 writerName.equals(writerSpiName));
1226 }
1227 }
1228
1229 /**
1230 * Returns an {@code Iterator} containing all currently
1231 * registered {@code ImageTranscoder}s that claim to be
1232 * able to transcode between the metadata of the given
1233 * {@code ImageReader} and {@code ImageWriter}.
1234 *
1235 * @param reader an {@code ImageReader}.
1236 * @param writer an {@code ImageWriter}.
1237 *
1238 * @return an {@code Iterator} containing
1239 * {@code ImageTranscoder}s.
1240 *
1241 * @exception IllegalArgumentException if {@code reader} or
1242 * {@code writer} is {@code null}.
1243 */
1244 public static Iterator<ImageTranscoder>
1245 getImageTranscoders(ImageReader reader, ImageWriter writer)
1246 {
1247 if (reader == null) {
1248 throw new IllegalArgumentException("reader == null!");
1249 }
1250 if (writer == null) {
1251 throw new IllegalArgumentException("writer == null!");
1252 }
1253 ImageReaderSpi readerSpi = reader.getOriginatingProvider();
1254 ImageWriterSpi writerSpi = writer.getOriginatingProvider();
1255 ServiceRegistry.Filter filter =
1256 new TranscoderFilter(readerSpi, writerSpi);
1257
1258 Iterator<ImageTranscoderSpi> iter;
1259 // Ensure category is present
1260 try {
1261 iter = theRegistry.getServiceProviders(ImageTranscoderSpi.class,
1262 filter, true);
1263 } catch (IllegalArgumentException e) {
1264 return Collections.emptyIterator();
1265 }
1266 return new ImageTranscoderIterator(iter);
1267 }
1268
1269 // All-in-one methods
1270
1271 /**
1272 * Returns a {@code BufferedImage} as the result of decoding
1273 * a supplied {@code File} with an {@code ImageReader}
1274 * chosen automatically from among those currently registered.
1275 * The {@code File} is wrapped in an
1276 * {@code ImageInputStream}. If no registered
1277 * {@code ImageReader} claims to be able to read the
1278 * resulting stream, {@code null} is returned.
1279 *
1280 * <p> The current cache settings from {@code getUseCache} and
1281 * {@code getCacheDirectory} will be used to control caching in the
1282 * {@code ImageInputStream} that is created.
1283 *
1284 * <p> Note that there is no {@code read} method that takes a
1285 * filename as a {@code String}; use this method instead after
1286 * creating a {@code File} from the filename.
1287 *
1288 * <p> This method does not attempt to locate
1289 * {@code ImageReader}s that can read directly from a
1290 * {@code File}; that may be accomplished using
1291 * {@code IIORegistry} and {@code ImageReaderSpi}.
1292 *
1293 * @param input a {@code File} to read from.
1294 *
1295 * @return a {@code BufferedImage} containing the decoded
1296 * contents of the input, or {@code null}.
1297 *
1298 * @exception IllegalArgumentException if {@code input} is
1299 * {@code null}.
1300 * @exception IOException if an error occurs during reading or when not
1301 * able to create required ImageInputStream.
1302 */
1303 public static BufferedImage read(File input) throws IOException {
1304 if (input == null) {
1305 throw new IllegalArgumentException("input == null!");
1306 }
1307 if (!input.canRead()) {
1308 throw new IIOException("Can't read input file!");
1309 }
1310
1311 ImageInputStream stream = createImageInputStream(input);
1312 if (stream == null) {
1313 throw new IIOException("Can't create an ImageInputStream!");
1314 }
1315 BufferedImage bi = read(stream);
1316 if (bi == null) {
1317 stream.close();
1318 }
1319 return bi;
1320 }
1321
1322 /**
1323 * Returns a {@code BufferedImage} as the result of decoding
1324 * a supplied {@code InputStream} with an {@code ImageReader}
1325 * chosen automatically from among those currently registered.
1326 * The {@code InputStream} is wrapped in an
1327 * {@code ImageInputStream}. If no registered
1328 * {@code ImageReader} claims to be able to read the
1329 * resulting stream, {@code null} is returned.
1330 *
1331 * <p> The current cache settings from {@code getUseCache} and
1332 * {@code getCacheDirectory} will be used to control caching in the
1333 * {@code ImageInputStream} that is created.
1334 *
1335 * <p> This method does not attempt to locate
1336 * {@code ImageReader}s that can read directly from an
1337 * {@code InputStream}; that may be accomplished using
1338 * {@code IIORegistry} and {@code ImageReaderSpi}.
1339 *
1340 * <p> This method <em>does not</em> close the provided
1341 * {@code InputStream} after the read operation has completed;
1342 * it is the responsibility of the caller to close the stream, if desired.
1343 *
1344 * @param input an {@code InputStream} to read from.
1345 *
1346 * @return a {@code BufferedImage} containing the decoded
1347 * contents of the input, or {@code null}.
1348 *
1349 * @exception IllegalArgumentException if {@code input} is
1350 * {@code null}.
1351 * @exception IOException if an error occurs during reading or when not
1352 * able to create required ImageInputStream.
1353 */
1354 public static BufferedImage read(InputStream input) throws IOException {
1355 if (input == null) {
1356 throw new IllegalArgumentException("input == null!");
1357 }
1358
1359 ImageInputStream stream = createImageInputStream(input);
1360 if (stream == null) {
1361 throw new IIOException("Can't create an ImageInputStream!");
1362 }
1363 BufferedImage bi = read(stream);
1364 if (bi == null) {
1365 stream.close();
1366 }
1367 return bi;
1368 }
1369
1370 /**
1371 * Returns a {@code BufferedImage} as the result of decoding
1372 * a supplied {@code URL} with an {@code ImageReader}
1373 * chosen automatically from among those currently registered. An
1374 * {@code InputStream} is obtained from the {@code URL},
1375 * which is wrapped in an {@code ImageInputStream}. If no
1376 * registered {@code ImageReader} claims to be able to read
1377 * the resulting stream, {@code null} is returned.
1378 *
1379 * <p> The current cache settings from {@code getUseCache} and
1380 * {@code getCacheDirectory} will be used to control caching in the
1381 * {@code ImageInputStream} that is created.
1382 *
1383 * <p> This method does not attempt to locate
1384 * {@code ImageReader}s that can read directly from a
1385 * {@code URL}; that may be accomplished using
1386 * {@code IIORegistry} and {@code ImageReaderSpi}.
1387 *
1388 * @param input a {@code URL} to read from.
1389 *
1390 * @return a {@code BufferedImage} containing the decoded
1391 * contents of the input, or {@code null}.
1392 *
1393 * @exception IllegalArgumentException if {@code input} is
1394 * {@code null}.
1395 * @exception IOException if an error occurs during reading or when not
1396 * able to create required ImageInputStream.
1397 */
1398 public static BufferedImage read(URL input) throws IOException {
1399 if (input == null) {
1400 throw new IllegalArgumentException("input == null!");
1401 }
1402
1403 InputStream istream = null;
1404 try {
1405 istream = input.openStream();
1406 } catch (IOException e) {
1407 throw new IIOException("Can't get input stream from URL!", e);
1408 }
1409 ImageInputStream stream = createImageInputStream(istream);
1410 if (stream == null) {
1411 /* close the istream when stream is null so that if user has
1412 * given filepath as URL he can delete it, otherwise stream will
1413 * be open to that file and he will not be able to delete it.
1414 */
1415 istream.close();
1416 throw new IIOException("Can't create an ImageInputStream!");
1417 }
1418 BufferedImage bi;
1419 try {
1420 bi = read(stream);
1421 if (bi == null) {
1422 stream.close();
1423 }
1424 } finally {
1425 istream.close();
1426 }
1427 return bi;
1428 }
1429
1430 /**
1431 * Returns a {@code BufferedImage} as the result of decoding
1432 * a supplied {@code ImageInputStream} with an
1433 * {@code ImageReader} chosen automatically from among those
1434 * currently registered. If no registered
1435 * {@code ImageReader} claims to be able to read the stream,
1436 * {@code null} is returned.
1437 *
1438 * <p> Unlike most other methods in this class, this method <em>does</em>
1439 * close the provided {@code ImageInputStream} after the read
1440 * operation has completed, unless {@code null} is returned,
1441 * in which case this method <em>does not</em> close the stream.
1442 *
1443 * @param stream an {@code ImageInputStream} to read from.
1444 *
1445 * @return a {@code BufferedImage} containing the decoded
1446 * contents of the input, or {@code null}.
1447 *
1448 * @exception IllegalArgumentException if {@code stream} is
1449 * {@code null}.
1450 * @exception IOException if an error occurs during reading.
1451 */
1452 public static BufferedImage read(ImageInputStream stream)
1453 throws IOException {
1454 if (stream == null) {
1455 throw new IllegalArgumentException("stream == null!");
1456 }
1457
1458 Iterator<ImageReader> iter = getImageReaders(stream);
1459 if (!iter.hasNext()) {
1460 return null;
1461 }
1462
1463 ImageReader reader = iter.next();
1464 ImageReadParam param = reader.getDefaultReadParam();
1465 reader.setInput(stream, true, true);
1466 BufferedImage bi;
1467 try {
1468 bi = reader.read(0, param);
1469 } finally {
1470 reader.dispose();
1471 stream.close();
1472 }
1473 return bi;
1474 }
1475
1476 /**
1477 * Writes an image using the an arbitrary {@code ImageWriter}
1478 * that supports the given format to an
1479 * {@code ImageOutputStream}. The image is written to the
1480 * {@code ImageOutputStream} starting at the current stream
1481 * pointer, overwriting existing stream data from that point
1482 * forward, if present.
1483 *
1484 * <p> This method <em>does not</em> close the provided
1485 * {@code ImageOutputStream} after the write operation has completed;
1486 * it is the responsibility of the caller to close the stream, if desired.
1487 *
1488 * @param im a {@code RenderedImage} to be written.
1489 * @param formatName a {@code String} containing the informal
1490 * name of the format.
1491 * @param output an {@code ImageOutputStream} to be written to.
1492 *
1493 * @return {@code false} if no appropriate writer is found.
1494 *
1495 * @exception IllegalArgumentException if any parameter is
1496 * {@code null}.
1497 * @exception IOException if an error occurs during writing.
1498 */
1499 public static boolean write(RenderedImage im,
1500 String formatName,
1501 ImageOutputStream output) throws IOException {
1502 if (im == null) {
1503 throw new IllegalArgumentException("im == null!");
1504 }
1505 if (formatName == null) {
1506 throw new IllegalArgumentException("formatName == null!");
1507 }
1508 if (output == null) {
1509 throw new IllegalArgumentException("output == null!");
1510 }
1511
1512 return doWrite(im, getWriter(im, formatName), output);
1513 }
1514
1515 /**
1516 * Writes an image using an arbitrary {@code ImageWriter}
1517 * that supports the given format to a {@code File}. If
1518 * there is already a {@code File} present, its contents are
1519 * discarded.
1520 *
1521 * @param im a {@code RenderedImage} to be written.
1522 * @param formatName a {@code String} containing the informal
1523 * name of the format.
1524 * @param output a {@code File} to be written to.
1525 *
1526 * @return {@code false} if no appropriate writer is found.
1527 *
1528 * @exception IllegalArgumentException if any parameter is
1529 * {@code null}.
1530 * @exception IOException if an error occurs during writing or when not
1531 * able to create required ImageOutputStream.
1532 */
1533 public static boolean write(RenderedImage im,
1534 String formatName,
1535 File output) throws IOException {
1536 if (output == null) {
1537 throw new IllegalArgumentException("output == null!");
1538 }
1539
1540 ImageWriter writer = getWriter(im, formatName);
1541 if (writer == null) {
1542 /* Do not make changes in the file system if we have
1543 * no appropriate writer.
1544 */
1545 return false;
1546 }
1547
1548 output.delete();
1549 ImageOutputStream stream = createImageOutputStream(output);
1550 if (stream == null) {
1551 throw new IIOException("Can't create an ImageOutputStream!");
1552 }
1553 try {
1554 return doWrite(im, writer, stream);
1555 } finally {
1556 stream.close();
1557 }
1558 }
1559
1560 /**
1561 * Writes an image using an arbitrary {@code ImageWriter}
1562 * that supports the given format to an {@code OutputStream}.
1563 *
1564 * <p> This method <em>does not</em> close the provided
1565 * {@code OutputStream} after the write operation has completed;
1566 * it is the responsibility of the caller to close the stream, if desired.
1567 *
1568 * <p> The current cache settings from {@code getUseCache} and
1569 * {@code getCacheDirectory} will be used to control caching.
1570 *
1571 * @param im a {@code RenderedImage} to be written.
1572 * @param formatName a {@code String} containing the informal
1573 * name of the format.
1574 * @param output an {@code OutputStream} to be written to.
1575 *
1576 * @return {@code false} if no appropriate writer is found.
1577 *
1578 * @exception IllegalArgumentException if any parameter is
1579 * {@code null}.
1580 * @exception IOException if an error occurs during writing or when not
1581 * able to create required ImageOutputStream.
1582 */
1583 public static boolean write(RenderedImage im,
1584 String formatName,
1585 OutputStream output) throws IOException {
1586 if (output == null) {
1587 throw new IllegalArgumentException("output == null!");
1588 }
1589 ImageOutputStream stream = createImageOutputStream(output);
1590 if (stream == null) {
1591 throw new IIOException("Can't create an ImageOutputStream!");
1592 }
1593 try {
1594 return doWrite(im, getWriter(im, formatName), stream);
1595 } finally {
1596 stream.close();
1597 }
1598 }
1599
1600 /**
1601 * Returns {@code ImageWriter} instance according to given
1602 * rendered image and image format or {@code null} if there
1603 * is no appropriate writer.
1604 */
1605 private static ImageWriter getWriter(RenderedImage im,
1606 String formatName) {
1607 ImageTypeSpecifier type =
1608 ImageTypeSpecifier.createFromRenderedImage(im);
1609 Iterator<ImageWriter> iter = getImageWriters(type, formatName);
1610
1611 if (iter.hasNext()) {
1612 return iter.next();
1613 } else {
1614 return null;
1615 }
1616 }
1617
1618 /**
1619 * Writes image to output stream using given image writer.
1620 */
1621 private static boolean doWrite(RenderedImage im, ImageWriter writer,
1622 ImageOutputStream output) throws IOException {
1623 if (writer == null) {
1624 return false;
1625 }
1626 writer.setOutput(output);
1627 try {
1628 writer.write(im);
1629 } finally {
1630 writer.dispose();
1631 output.flush();
1632 }
1633 return true;
1634 }
1635 }
1636