Readers and Writers for Images


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

You can find a GIF encoder at http://www.acme.com/java.


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 Types

For 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:

  • An image format (such as "JPEG")

  • A file suffix (such as "jpg")

  • A MIME type (such as "image/jpeg")

NOTE

MIME is the Multipurpose Internet Mail Extensions standard. The MIME standard defines common data formats such as "image/jpeg" and "application/pdf". For an HTML version of the RFC (Request for Comments) that defines the MIME format, see http://www.oac.uci.edu/indiv/ehood/MIME.


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 Images

Some 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 

  • static BufferedImage read(File input)

  • static BufferedImage read(InputStream input)

  • static BufferedImage read(URL input)

    read an image from input.

  • static boolean write(RenderedImage image, String formatName, File output)

  • static boolean write(RenderedImage image, String formatName, OutputStream output)

    write an image in the given format to output. Return false if no appropriate writer was found.

  • static Iterator<ImageReader> getImageReadersByFormatName(String formatName)

  • static Iterator<ImageReader> getImageReadersBySuffix(String fileSuffix)

  • static Iterator<ImageReader> getImageReadersByMIMEType(String mimeType)

  • static Iterator<ImageWriter> getImageWritersByFormatName(String formatName)

  • static Iterator<ImageWriter> getImageWritersBySuffix(String fileSuffix)

  • static Iterator<ImageWriter> getImageWritersByMIMEType(String mimeType)

    get all readers and writers that are able to handle the given format (e.g., "JPEG"), file suffix (e.g., "jpg"), or MIME type (e.g., "image/jpeg").

  • static String[] getReaderFormatNames()

  • static String[] getReaderMIMETypes()

  • static String[] getWriterFormatNames()

  • static String[] getWriterMIMETypes()

    get all format names and MIME type names supported by readers and writers.

  • ImageInputStream createImageInputStream(Object input)

  • ImageOutputStream createImageOutputStream(Object output)

    create an image input or image output stream from the given object. The object can be a file, a stream, a RandomAccessFile, or another object for which a service provider exists. Return null if no registered service provider can handle the object.


 javax.imageio.ImageReader 1.4 

  • void setInput(Object input)

  • void setInput(Object input, boolean seekForwardOnly)

    set the input source of the reader.

    Parameters:

    input

    An ImageInputStream object or another object that this reader can accept.

     

    seekForwardOnly

    true if the reader should read forward only. By default, the reader uses random access and, if necessary, buffers image data.


  • BufferedImage read(int index)

    reads the image with the given image index (starting at 0). Throws an IndexOutOfBoundsException if no such image is available.

  • int getNumImages(boolean allowSearch)

    gets the number of images in this reader. If allowSearch is false and the number of images cannot be determined without reading forward, then -1 is returned. If allowSearch is TRue and the reader is in "seek forward only" mode, then an IllegalStateException is thrown.

  • int getNumThumbnails(int index)

    gets the number of thumbnails of the image with the given index.

  • BufferedImage readThumbnail(int index, int thumbnailIndex)

    gets the thumbnail with index thumbnailIndex of the image with the given index.

  • int getWidth(int index)

  • int getHeight(int index)

    get the image width and height. Throw an IndexOutOfBoundsException if no such image is available.

  • ImageReaderSpi getOriginatingProvider()

    gets the service provider that constructed this reader.


 javax.imageio.spi.IIOServiceProvider 1.4 

  • String getVendorName()

  • String getVersion()

    get the vendor name and version of this service provider.


 javax.imageio.spi.ImageReaderWriterSpi 1.4 

  • String[] getFormatNames()

  • String[] getFileSuffixes()

  • String[] getMIMETypes()

    get the format names, file suffixes, and MIME types supported by the readers or writers that this service provider creates.


 javax.imageio.ImageWriter 1.4 

  • void setOutput(Object output)

    sets the output target of this writer.

    Parameters:

    output

    An ImageOutputStream object or another object that this writer can accept


  • void write(IIOImage image)

  • void write(RenderedImage image)

    write a single image to the output.

  • void writeInsert(int index, IIOImage image, ImageWriteParam param)

    write an image into a multi-image file.

    Parameters:

    index

    The image index

     

    image

    The image to write

     

    param

    The write parameters, or null


  • boolean canInsertImage(int index)

    returns true if it is possible to insert an image at the given index.

  • ImageWriterSpi getOriginatingProvider()

    gets the service provider that constructed this writer.


 javax.imageio.IIOImage 1.4 

  • IIOImage(RenderedImage image, List thumbnails, IIOMetadata metadata)

    constructs an IIOImage from an image, optional thumbnails, and optional metadata.

    Parameters:

    image

    An image

     

    thumbnails

    A list of BufferedImage objects, or null

     

    metadata

    Metadata, or null




    Core JavaT 2 Volume II - Advanced Features
    Building an On Demand Computing Environment with IBM: How to Optimize Your Current Infrastructure for Today and Tomorrow (MaxFacts Guidebook series)
    ISBN: 193164411X
    EAN: 2147483647
    Year: 2003
    Pages: 156
    Authors: Jim Hoskins

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net