The Source for Java Technology Collaboration
User: Password:



   

Image I/O Utilities Grab Bag Image I/O Utilities Grab Bag

by Jeff Friesen
03/06/2007

Image I/O is a powerful API for reading and writing images in an extensible variety of formats. Because this power comes with a degree of complexity that can overwhelm developers new to Image I/O (and possibly frustrate experienced developers), this article presents a grab bag of useful Image I/O utilities that can make your Image I/O experiences more productive. Help yourself to them.

These utilities are implemented by the ca.mb.javajeff.iioutil package's IIOUtilities class and its static methods, and by the UnsupportedFormatException support class. After introducing these methods, and three example applications that demonstrate their usefulness, I use Apache Ant to build and package these classes into an iioutil.jar JAR file, and build these applications.

Note: I built and tested this article's code with Sun's J2SE 5.0 SDK and Apache Ant 1.6.5. Windows 98 SE was the underlying platform.

Create Image Viewer Accessory for File Chooser

A javax.swing.JFileChooser instance can be customized through an accessory (a javax.swing.JComponent subclass instance) to view images, to present additional dialog box controls, or to do something else. After creating the accessory, this component is attached to the file chooser by invoking JFileChooser's public void setAccessory(JComponent newAccessory) method.

Because an image viewer is a common accessory, IIOUtilities provides a public static JComponent createImageViewerAccessory(JFileChooser fc, int width, int height) method. This method creates and returns an image viewer accessory for the specified file chooser, having the specified dimensions. No exceptions are thrown.

I've created an Example1 application that demonstrates createImageViewerAccessory(). This application creates and displays a single open file chooser with the image viewer accessory. When the user highlights a file whose filename suffix matches one of Image I/O's supported suffixes, the image is rendered on the image viewer's drawing area. Check out Figure 1.

The image viewer centers small images on its drawing area
Figure 1. The image viewer centers small images on its drawing area

This application's public static void main(String [] args) method first creates a JFileChooser that references the current directory. This method next invokes createImageViewerAccessory() to create the accessory, followed by setAccessory() to attach the accessory to the file chooser. This open file chooser is then shown (with no parent component):

JFileChooser fcOpen =
  new JFileChooser (new File ("."));
fcOpen.setAccessory (IIOUtilities.
  createImageViewerAccessory (fcOpen, 200, 150));
fcOpen.showOpenDialog (null);

The createImageViewerAccessory() method creates the accessory as an instance of its IV (Image Viewer) local class. This method passes its arguments to IV's constructor, and then returns the resulting component. The following IIOUtilities excerpt presents and explains the workings of createImageViewerAccessory() and its IV class:

public static JComponent
  createImageViewerAccessory (JFileChooser fc,
                              int width, int height)
{
   class IV extends JComponent
     implements PropertyChangeListener
   {
      // Reference to BufferedImage whose contents
      // are displayed. Nothing is displayed if
      // reference is null.

      private BufferedImage image;

      // The following JFileChooser reference is
      // needed for registering a
      // PropertyChangeListener.

      private JFileChooser fc;

      // Accessory dimensions.

      private int width, height;

      // Construct the component.

      private IV (JFileChooser fc, int width,
                  int height)
      {
         this.fc = fc;
         this.width = width;
         this.height = height;

         // Register a property change listener
         // with the file chooser so that this
         // component is made aware of file chooser
         // events (such as a user selecting a file).

         fc.addPropertyChangeListener (this);

         // Set this component's dimensions to
         // accommodate scaled images. The specified
         // values figure into the overall size of
         // the file chooser.

         setPreferredSize (new Dimension (width,
                                          height));
      }

      // Paint this component in response to a
      // repaint() method call.

      protected void paintComponent (Graphics g)
      {
         // Please see this article's code file for
         // the contents of this method.
      }

      // Respond to property change events sent to
      // this component by the file chooser.

      public void propertyChange (PropertyChangeEvent
                                  evt)
      {
         // Please see this article's code file for
         // the contents of this method.
      }
   }

   // Return an IV accessory that works with the
   // specified filechooser. Furthermore, the
   // accessory's drawing area is bounded by the
   // specified dimensions.

   return new IV (fc, width, height);
}

Capture Screen Contents to File

The java.awt.Robot class provides a public BufferedImage createScreenCapture(Rectangle screenRect) method for capturing screen contents (without the mouse cursor) to a java.awt.image.BufferedImage. Because it is convenient to capture screen contents and save these contents to a file in one step, IIOUtilities provides the following two methods:

  • public static void captureScreenToFile(String filename) captures the entire screen's contents to the file identified by filename. The filename's suffix determines the format in which the contents are saved--jpg identifies the JPEG format, for example.

    This method throws java.awt.AWTException (screen cannot be captured), java.io.IOException (capture cannot be saved to a file), or UnsupportedFormatException (filename's suffix is not recognized by Image I/O).

  • public static void captureScreenToFile(Rectangle bounds, String filename) is similar to the previous method, except that a subset of the screen (as determined by the bounds parameter's screen coordinates) is saved.

    In addition to the previous method's exceptions, this captureScreenToFile() method throws an IllegalArgumentException if any boundary value is negative, or if the capture area exceeds the screen's dimensions.

I've created an Example2 application that demonstrates both captureScreenToFile() methods. This application's GUI, which is shown in Figure 2, presents two buttons for capturing the GUI's frame window and the entire screen. The frame window is captured to window.jpg, and the entire screen is captured to screen.jpg.

The button stays pressed during the capture, which occurs in the button's action listener
Figure 2. The button stays pressed during the capture, which occurs in the button's action listener

The "Capture frame window" button's action listener first determines the frame window's boundaries (for its width and height) and upper-left-corner screen location. After merging these values into a single java.awt.Rectangle, this rectangle and window.jpg are passed to the second captureScreenToFile() method, which captures the window to this file:

JButton btnWindow =
  new JButton ("Capture frame window");
ActionListener al;
al = new ActionListener ()
{
     public void actionPerformed (ActionEvent evt)
     {
        Rectangle bounds = frame.getBounds ();
        bounds.setLocation (frame.
                            getLocationOnScreen ());
        try
        {
            IIOUtilities.
            captureScreenToFile (bounds,
                                 "window.jpg");
            JOptionPane.
            showMessageDialog (frame,
                               "Window captured to "
                               + "window.jpg.");
        }
        catch (Exception exc)
        {
            JOptionPane.
            showMessageDialog (frame,
                               "Unable to capture "
                               + "window.");

            exc.printStackTrace ();
        }
     }
};
btnWindow.addActionListener (al);

The "Capture entire screen" button's action listener is similar. Rather than examine this listener, let's take a look at the captureScreenToFile(Rectangle bounds, String filename) method, to see how it works.:

public static void captureScreenToFile
  (Rectangle bounds, String filename)
  throws AWTException, IOException,
  UnsupportedFormatException
{
   // Obtain screen size in case bounds is null.
   // Size is also used to validate bounds if not
   // null.

   Dimension size =
     Toolkit.getDefaultToolkit ().getScreenSize ();

   if (bounds == null)
       bounds = new Rectangle (size);
   else
   {
       String message = null;

       if (bounds.x < 0)
           message = "x < 0";
       else
       if (bounds.width < 0)
           message = "width < 0";
       else
       if (bounds.x + bounds.width > size.width)
           message = "x + width > screen width";
       else
       if (bounds.y < 0)
           message = "y < 0";
       else
       if (bounds.height < 0)
           message = "height < 0";
       else
       if (bounds.y + bounds.height > size.height)
           message = "y + height > screen height";

       if (message != null)
           throw new IllegalArgumentException
             (message);
   }

   // Attempt to capture the screen and save this
   // capture to a file.

   saveImageToFile (new Robot ().
                        createScreenCapture (bounds),
                    filename);
}

By the way, the other captureScreenToFile() method's single line of code invokes this method, passing null to bounds.

Save and Load Images

Saving and loading images in a convenient manner is useful--the previous captureScreenToFile() source code's saveImageToFile (new Robot ().createScreenCapture (bounds), filename); method call illustrates this usefulness. To complete IIOUtilities, I've added the following image-save and image-load methods to this class:

  • public static void saveImageToFile(BufferedImage image, String filename) saves the contents of the BufferedImage to the file identified by filename. An IOException is thrown if the image cannot be saved; an UnsupportedFormatException is thrown if Image I/O does not support filename's suffix.

  • public static BufferedImage loadImageFromFile(String filename) loads an image from the file identified by filename into a new BufferedImage. An IOException is thrown if the image cannot be loaded; an UnsupportedFormatException is thrown if Image I/O does not support filename's suffix.

I've created an Example3 application that uses these methods for image conversion. Example3 takes two command-line arguments: the first argument identifies the source image's path and name, and the second argument identifies the destination image's path and name. The conversion is performed by IIOUtilities.saveImageToFile (IIOUtilities.loadImageFromFile (args [0]), args [1]);.

The saveImageToFile() method treats JPEG destinations in a special way: this method establishes a 95-percent compression quality so that the image is saved with low compression (and will look good when reloaded). If you prefer to dynamically specify compression quality, replace the hardcoded 0.95f value in this method's source code (shown below) with a private static field and suitable accessor methods:

public static void saveImageToFile
  (BufferedImage image, String filename)
  throws IOException, UnsupportedFormatException
{
   // Validate presence of file suffix, which is used
   // to obtain appropriate image writer. Throw an
   // exception if there is no suffix -- what format
   // would be used in this case?

   int index = filename.lastIndexOf ('.');
   if (index == -1)
       throw new UnsupportedFormatException
         ("No file suffix");

   // Extract file suffix and use it to obtain an
   // Iterator of appropriate image writers.

   String suffix = filename.substring (index+1);
   Iterator iter =
     ImageIO.getImageWritersBySuffix (suffix);

   // Throw exception if there are no image writers --
   // image format is not supported.

   if (!iter.hasNext ())
       throw new UnsupportedFormatException
         ("No writer for suffix " + suffix);

   // Extract writer and set the writer's output
   // destination to the passed filename.

   ImageWriter writer = (ImageWriter) iter.next ();
   File file = new File (filename); 
   writer.setOutput (ImageIO.
                     createImageOutputStream (file));

   // Write the image. If saving to a JPEG file,
   // assign a 95% compression quality -- the default
   // is 75%.

   if (suffix.equalsIgnoreCase ("jpeg") ||
       suffix.equalsIgnoreCase ("jpg"))
   {
       ImageWriteParam iwp =
         writer.getDefaultWriteParam ();
       iwp.setCompressionMode
         (ImageWriteParam.MODE_EXPLICIT);
       iwp.setCompressionQuality (0.95f);

       writer.write (null, new IIOImage (image, null,
                                         null), iwp);
   }
   else
       writer.write (image);

   // Release any resources being held by the writer.

   writer.dispose ();
}

Build the Image I/O Utilities JAR file and Example Applications

Now that we have explored the methods belonging to the IIOUtilities class, let's build this class and its UnsupportedFormatException support class, and package them into iioutil.jar--and also build the example applications. We will use Apache Ant with build.xml to automate these tasks. This script assumes the following directory structure:

src
  examples
    Example1.java
    Example2.java
    Example3.java
  jar
    ca
      mb
        javajeff
          iioutil
            IIOUtilities.java
            UnsupportedFormatException.java
build.xml
setup.bat

Before using Apache Ant, this tool's bin directory must be added to the PATH environment variable. Also, the JAVA_HOME environment variable must refer to Java SE 5's home directory. I accomplish both tasks on my Windows platform with the following setup.bat file--you might want to use something similar on your platform.

set JAVA_HOME=c:\progra~1\java\jdk1.5.0
set PATH=c:\ant\bin;c:\progra~1\java\jdk1.5.0\bin;
         c:\windows;c:\windows\command

After making sure that the directory containing src, the batch file, and the XML script file is current, type ant (by itself) to build iioutil.jar and the example applications. Ant creates a bin directory that stores the JAR file and an examples subdirectory with application classfiles. This tool uses the following build.xml file for these builds.

<project name="build" default="buildall" basedir=".">
  <target name="buildall" depends="buildjar,buildex"
          description="build everything">
  </target>

  <target name="buildex"
          description="build the examples">
    <mkdir dir="${basedir}/bin/examples"/>
    <javac classpath="${basedir}/bin/iioutil.jar"
           srcdir="${basedir}/src/examples"
           destdir="${basedir}/bin/examples"/>
  </target>

  <target name="buildjar"
          description="build the jar">
    <mkdir dir="${basedir}/bin/jar"/>
    <javac srcdir=
    "${basedir}/src/jar/ca/mb/javajeff/iioutil"
           destdir="${basedir}/bin/jar"/>
    <jar basedir="${basedir}/bin/jar"
         destfile="${basedir}/bin/iioutil.jar"/>
  </target>

  <target name="gendoc"
          description=
          "generate the API documentation">
    <javadoc destdir="docs/api" author="true"
             sourcepath="${basedir}/src/jar"
             packagenames=
             "ca.mb.javajeff.iioutil.*"/>
  </target>

  <target name="cleanup">
    <delete dir="${basedir}/bin"/>
    <delete dir="${basedir}/docs"/>
  </target>
</project>

Along with the default buildall task, build.xml reveals tasks for conveniently building the JAR file itself (ant buildjar), and building the examples without the JAR (ant buildex)--the JAR file must exist before building the examples. We can also generate Javadoc documentation (ant gendoc), and delete the binary and documentation directories (ant cleanup).

Let's run the example applications. At the command line, change to the Ant-created bin\examples directory. Specify the java command with the -cp or -classpath options to add iioutil.jar to the classpath, and specify an application classname. For example, specify java -cp ../iioutil.jar;. Example2 to run the Example2 application.

Conclusion

Want more Image I/O utilities? Chet Haase's "ImageIO: Just another example of better living by doing it yourself" blog entry presents an ImageScaler application for creating scaled JPEG images. Consider migrating this application into a version of IIOUtilities.saveImageToFile() that scales an image to a desired size, and then saves the scaled image.

Resources

Jeff Friesen is a freelance software developer and educator specializing in Java technology. Check out his site at javajeff.mb.ca.

View all java.net Articles.

 Feed java.net RSS Feeds