Skip to main content

Image I/O Utilities Grab Bag

March 6, 2007

{cs.r.title}



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

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

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:

[prettify]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);
}
[/prettify]

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:

[prettify]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);
[/prettify]

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

[prettify]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);
}
[/prettify]

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:

[prettify]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 ();
}
[/prettify]

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:

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

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.

[prettify]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
[/prettify]

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.

[prettify]<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>
[/prettify]

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

width="1" height="1" border="0" alt=" " />
Jeff Friesen is a freelance software developer and educator specializing in Java technology. Check out his site at javajeff.mb.ca.
Related Topics >> GUI   |