|
Prior to version 1.4, the JDK had very limited capabilities for reading and writing image files. It was possible to read GIF and JPEG images, but there was no official support for writing images at all. This situation is now much improved. JDK 1.4 introduces the javax.imageio package that contains "out of the box" support for reading and writing several common file formats, as well as a framework that enables third parties to add readers and writers for other formats. Specifically, the JDK contains readers for the GIF, JPEG, and PNG formats and writers for JPEG and PNG. (We suspect that writing GIF files is not supported because of patent issues.) NOTE
The basics of the library are extremely straightforward. To load an image, use the static read method of the ImageIO class: File f = . . .; BufferedImage image = ImageIO.read(f); The ImageIO class picks an appropriate reader, based on the file type. It may consult the file extension and the "magic number" at the beginning of the file for that purpose. If no suitable reader can be found or the reader can't decode the file contents, then the read method returns null. Writing an image to a file is just as simple: File f = . . .; String format = . . .; ImageIO.write(image, format, f); Here the format string is a string identifying the image format, such as "JPEG" or "PNG". The ImageIO class picks an appropriate writer and saves the file. Obtaining Readers and Writers for Image File TypesFor more advanced image reading and writing operations that go beyond the static read and write methods of the ImageIO class, you first need to get the appropriate ImageReader and ImageWriter objects. The ImageIO class enumerates readers and writers that match one of the following:
NOTE
For example, you can obtain a reader that reads JPEG files as follows: ImageReader reader = null; Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName("JPEG"); if (iter.hasNext()) reader = iter.next(); The getImageReadersBySuffix and getImageReadersByMIMEType method enumerate readers that match a file extension or MIME type. It is possible that the ImageIO class can locate multiple readers that can all read a particular image type. In that case, you have to pick one of them, but it isn't clear how you can decide which one is the best. To find out more information about a reader, obtain its service provider interface: ImageReaderSpi spi = reader.getOriginatingProvider(); Then you can get the vendor name and version number: String vendor = spi.getVendor(); String version = spi.getVersion(); Perhaps that information can help you decide among the choices, or you may just present a list of readers to your program users and let them choose. However, for now, we assume that the first enumerated reader is at least adequate. In the sample program at the end of this section, we have a couple of problems that the API doesn't address well. First, we want to find all file suffixes of all available readers so that we can use them in a file filter. The IOImage class is willing to tell us the names of all reader formats and the names of all supported MIME types. But we can't enumerate all supported file suffixes, nor can we get all readers. So, we first enumerate all format names, then we get all readers for a given format name. An ImageReader won't reveal the supported file suffixes, but the associated service provider interface object does. With a couple of nested loops and the handy addAll method of the Set interface, we manage to collect all file suffixes. You can find the full code in the getreaderSuffixes method in Example 7-9. For saving files, we have a similar problem. We'd like to present the user with a menu of all supported image types. Unfortunately, the getWriterFormatNames of the IOImage class returns a rather curious list with redundant names, such as JPG jpeg jpg PNG JPEG png That's not something one would want to present in a menu. It would be nice if there were some notion of a "preferred" format name. To pare down the list, we pick the first format name and look up the first writer associated with it. Then we ask it what its format names are, in the hope that it will list the most popular one first. Indeed, for the JPEG writer, this works fine: It lists "JPEG" before the other options. The PNG writer, on the other hand, lists "png" in lower case before "PNG". We hope this behavior will be addressed at some time in the future. (We don't want to force the format name into upper casethat wouldn't work for a format such as "PostScript".) Once we pick a writer, we add the first format name to the format name set and remove all its format names from the original set. We keep going until all format names are handled. The details are in the getWriterFormats method of Example 7-9. Note that this method would break down should someone provide a single writer that can write multiple image formats. As a practical matter, most programmers won't be bothered by these issues. If you have a fixed number of file formats that your application supports, then you can simply find the appropriate readers, writers, and file suffixes for them. Reading and Writing Files with Multiple ImagesSome files, in particular, animated GIF files, contain multiple images. The read method of the ImageIO class reads a single image. To read multiple images, turn the input source (for example, an input stream or file) into an ImageInputStream. InputStream in = . . .; ImageInputStream imageIn = ImageIO.createImageInputStream(in); Then attach the image input stream to the reader: reader.setInput(imageIn, true); The second parameter indicates that the input is in "seek forward only" mode. Otherwise, random access is used, either by buffering stream input as it is read or by using random file access. Random access is required for certain operations. For example, to find out the number of images in a GIF file, you need to read the entire file. If you then want to fetch an image, the input must be read again. This consideration is only important if you read from a stream, if the input contains multiple images, and if the image format doesn't have the information that you request (such as the image count) in the header. If you read from a file, simply use File f = . . .; ImageInputStream imageIn = ImageIO.createImageInputStream(f); reader.setInput(imageIn); Once you have a reader, you can read the images in the input by calling BufferedImage image = reader.read(index); where index is the image index, starting with 0. If the input is in "seek forward only" mode, you keep reading images until the read method throws an IndexOutOfBoundsException. Otherwise, you can call the getNumImages method: int n = reader.getNumImages(true); Here, the parameter indicates that you allow a search of the input to determine the number of images. That method throws an IllegalStateException if the input is in "seek forward only" mode. Alternatively, you can set the "allow search" parameter to false. Then the getNumImages method returns -1 if it can't determine the number of images without a search. In that case, you'll have to switch to Plan B and keep reading images until you get an IndexOutOfBoundsException. Some files contain thumbnails, smaller versions of an image for preview purposes. You can get the number of thumbnails of an image with the call int count = reader.getNumThumbnails(index); Then you get a particular index as BufferedImage thumbnail = reader.getThumbnail(index, thumbnailIndex); Another consideration is that you sometimes want to get the image size before actually getting the image, in particular, if the image comes from a slow network connection. Use the calls int width = reader.getWidth(index); int height = reader.getHeight(index); to get the dimensions of an image with a given index. The image dimensions should be available before the actual image data. If there is no image with the given index, the methods throw an IndexOutOfBoundsException. To write a file with multiple images, you first need an ImageWriter. The ImageIO class can enumerate the writers that are capable of writing a particular image format: String format = . . .; ImageWriter writer = null; Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName( format ); if (iter.hasNext()) writer = iter.next(); Next, turn an output stream or file into an ImageOutputStream and attach it to the writer. For example, File f = . . .; ImageOutputStream imageOut = ImageIO.createImageOutputStream(f); writer.setOutput(imageOut); You must wrap each image into an IIOImage object. You can optionally supply a list of thumbnails and image metadata (such as compression algorithms and color information). In this example, we just use null for both; see the JDK documentation for additional information. IIOImage iioImage = new IIOImage(images[i], null, null); Write out the first image, using the write method: writer.write(new IIOImage(images[0], null, null)); For subsequent images, use if (writer.canInsertImage(i)) writer.writeInsert(i, iioImage, null); The third parameter can contain an ImageWriteParam object to set image writing details such as tiling and compression; use null for default values. Not all file formats can handle multiple images. In that case, the canInsertImage method returns false for i > 0, and only a single image is saved. The program in Example 7-9 lets you load and save files in the formats for which the JDK supplies readers and writers. The program displays multiple images (see Figure 7-29), but not thumbnails. Example 7-9. ImageIOTest.java[View full width] 1. import java.awt.*; 2. import java.awt.event.*; 3. import java.awt.image.*; 4. import java.io.*; 5. import java.util.*; 6. import java.util.List; 7. import javax.imageio.*; 8. import javax.imageio.stream.*; 9. import javax.swing.*; 10. 11. /** 12. This program lets you read and write image files in the 13. formats that the JDK supports. Multifile images are 14. supported. 15. */ 16. public class ImageIOTest 17. { 18. public static void main(String[] args) 19. { 20. JFrame frame = new ImageIOFrame(); 21. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 22. frame.setVisible(true); 23. } 24. } 25. 26. /** 27. This frame displays the loaded images. The menu has items 28. for loading and saving files. 29. */ 30. class ImageIOFrame extends JFrame 31. { 32. public ImageIOFrame() 33. { 34. setTitle("ImageIOTest"); 35. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 36. 37. JMenu fileMenu = new JMenu("File"); 38. JMenuItem openItem = new JMenuItem("Open"); 39. openItem.addActionListener(new 40. ActionListener() 41. { 42. public void actionPerformed(ActionEvent event) 43. { 44. openFile(); 45. } 46. }); 47. fileMenu.add(openItem); 48. 49. JMenu saveMenu = new JMenu("Save"); 50. fileMenu.add(saveMenu); 51. Iterator<String> iter = writerFormats.iterator(); 52. while (iter.hasNext()) 53. { 54. final String formatName = iter.next(); 55. JMenuItem formatItem = new JMenuItem(formatName); 56. saveMenu.add(formatItem); 57. formatItem.addActionListener(new 58. ActionListener() 59. { 60. public void actionPerformed(ActionEvent event) 61. { 62. saveFile(formatName); 63. } 64. }); 65. } 66. 67. JMenuItem exitItem = new JMenuItem("Exit"); 68. exitItem.addActionListener(new 69. ActionListener() 70. { 71. public void actionPerformed(ActionEvent event) 72. { 73. System.exit(0); 74. } 75. }); 76. fileMenu.add(exitItem); 77. 78. 79. JMenuBar menuBar = new JMenuBar(); 80. menuBar.add(fileMenu); 81. setJMenuBar(menuBar); 82. } 83. 84. /** 85. Open a file and load the images. 86. */ 87. public void openFile() 88. { 89. JFileChooser chooser = new JFileChooser(); 90. chooser.setCurrentDirectory(new File(".")); 91. 92. chooser.setFileFilter(new 93. javax.swing.filechooser.FileFilter() 94. { 95. public boolean accept(File f) 96. { 97. if (f.isDirectory()) return true; 98. String name = f.getName(); 99. int p = name.lastIndexOf('.'); 100. if (p == -1) return false; 101. String suffix = name.substring(p + 1).toLowerCase(); 102. return readerSuffixes.contains(suffix); 103. } 104. public String getDescription() 105. { 106. return "Image files"; 107. } 108. }); 109. int r = chooser.showOpenDialog(this); 110. if (r != JFileChooser.APPROVE_OPTION) return; 111. File f = chooser.getSelectedFile(); 112. Box box = Box.createVerticalBox(); 113. try 114. { 115. String name = f.getName(); 116. String suffix = name.substring(name.lastIndexOf('.') + 1); 117. Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix); 118. ImageReader reader = iter.next(); 119. ImageInputStream imageIn = ImageIO.createImageInputStream(f); 120. reader.setInput(imageIn); 121. int count = reader.getNumImages(true); 122. images = new BufferedImage[count]; 123. for (int i = 0; i < count; i++) 124. { 125. images[i] = reader.read(i); 126. box.add(new JLabel(new ImageIcon(images[i]))); 127. } 128. } 129. catch (IOException e) 130. { 131. JOptionPane.showMessageDialog(this, e); 132. } 133. setContentPane(new JScrollPane(box)); 134. validate(); 135. } 136. 137. /** 138. Save the current image in a file 139. @param formatName the file format 140. */ 141. public void saveFile(final String formatName) 142. { 143. if (images == null) return; 144. Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName(formatName); 145. ImageWriter writer = iter.next(); 146. final List<String> writerSuffixes 147. = Arrays.asList(writer.getOriginatingProvider().getFileSuffixes()); 148. JFileChooser chooser = new JFileChooser(); 149. chooser.setCurrentDirectory(new File(".")); 150. 151. chooser.setFileFilter(new 152. javax.swing.filechooser.FileFilter() 153. { 154. public boolean accept(File f) 155. { 156. if (f.isDirectory()) return true; 157. String name = f.getName(); 158. int p = name.lastIndexOf('.'); 159. if (p == -1) return false; 160. String suffix = name.substring(p + 1).toLowerCase(); 161. return writerSuffixes.contains(suffix); 162. } 163. public String getDescription() 164. { 165. return formatName + " files"; 166. } 167. }); 168. 169. int r = chooser.showSaveDialog(this); 170. if (r != JFileChooser.APPROVE_OPTION) return; 171. File f = chooser.getSelectedFile(); 172. try 173. { 174. ImageOutputStream imageOut = ImageIO.createImageOutputStream(f); 175. writer.setOutput(imageOut); 176. 177. writer.write(new IIOImage(images[0], null, null)); 178. for (int i = 1; i < images.length; i++) 179. { 180. IIOImage iioImage = new IIOImage(images[i], null, null); 181. if (writer.canInsertImage(i)) 182. writer.writeInsert(i, iioImage, null); 183. } 184. } 185. catch (IOException e) 186. { 187. JOptionPane.showMessageDialog(this, e); 188. } 189. } 190. 191. /** 192. Gets a set of all file suffixes that are recognized by image readers. 193. @return the file suffix set 194. */ 195. public static Set<String> getReaderSuffixes() 196. { 197. TreeSet<String> readerSuffixes = new TreeSet<String>(); 198. for (String name : ImageIO.getReaderFormatNames()) 199. { 200. Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName(name); 201. while (iter.hasNext()) 202. { 203. ImageReader reader = iter.next(); 204. String[] s = reader.getOriginatingProvider().getFileSuffixes(); 205. readerSuffixes.addAll(Arrays.asList(s)); 206. } 207. } 208. return readerSuffixes; 209. } 210. 211. /** 212. Gets a set of "preferred" format names of all image writers. The preferred format name is 213. the first format name that a writer specifies. 214. @return the format name set 215. */ 216. public static Set<String> getWriterFormats() 217. { 218. TreeSet<String> writerFormats = new TreeSet<String>(); 219. TreeSet<String> formatNames 220. = new TreeSet<String>(Arrays.asList(ImageIO.getWriterFormatNames())); 221. while (formatNames.size() > 0) 222. { 223. String name = formatNames.iterator().next(); 224. Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName(name); 225. ImageWriter writer = iter.next(); 226. String[] names = writer.getOriginatingProvider().getFormatNames(); 227. writerFormats.add(names[0]); 228. formatNames.removeAll(Arrays.asList(names)); 229. } 230. return writerFormats; 231. } 232. 233. private BufferedImage[] images; 234. private static Set<String> readerSuffixes = getReaderSuffixes(); 235. private static Set<String> writerFormats = getWriterFormats(); 236. private static final int DEFAULT_WIDTH = 400; 237. private static final int DEFAULT_HEIGHT = 400; 238. } Figure 7-29. An animated GIF image javax.imageio.ImageIO 1.4
javax.imageio.ImageReader 1.4
javax.imageio.spi.IIOServiceProvider 1.4
javax.imageio.spi.ImageReaderWriterSpi 1.4
javax.imageio.ImageWriter 1.4
javax.imageio.IIOImage 1.4
|
|