Home > Guides > Programming > Java

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Image Manipulation

Last updated Oct 8, 2004.

Image manipulation in Java is far from a simple subject. However, by experimenting with the APIs you will learn a few helpful tips to make your task manageable. Looking back at listing 3, you can see that we read in the contents of an image file and inserted them into the database:

// Insert the image into the second Blob
File image = new File( filename );
FileInputStream fis = new FileInputStream( image );
ps.setBinaryStream( 3, fis, ( int )image.length() );

And then from listing 4, we read the image from the database byte by byte and served them to the caller:

// Get the image Blob from the database
Blob blob = rs.getBlob( 1 );
InputStream in = blob.getBinaryStream();

// Output the blob to the HttpServletResponse
res.setContentType( "image/jpeg" );
BufferedOutputStream out = new BufferedOutputStream( res.getOutputStream() );
byte by[] = new byte[ 32768 ];
int index = in.read( by, 0, 32768 );
while ( index != -1 )
{
 out.write( by, 0, index );
 index = in.read( by, 0, 32768 );
}
out.flush();

In both of these operations, we did not care about the underlying image; we were just moving bytes back and forth. But now we want to create an image thumbnail prior to inserting it in the database. To do that, we need to gain access to the underlying image and scale it down to a thumbnail.

The key to accessing information is to acquire either a java.awt.Image or the more usable java.awt.image.BufferedImage. Included with the 1.4 release of the JDK is the javax.imageio package. This package includes a very useful class for reading streams and converting them to BufferedImage instances: ImageIO. The ImageIO class provides a group of read() methods:

  • BufferedImage read(File input)

  • BufferedImage read(ImageInputStream stream)

  • BufferedImage read(InputStream input)

  • BufferedImage read(URL input)

These methods return a BufferedImage from a variety of sources (files, input streams, URLs). The BufferedImage class provides a plethora of information about its image as well as direct access to the RGB (red green blue) values of each pixel in the image. Although we won't use it in this example, you can retrieve and modify the RGB of individual image pixels with the following methods:

  • int getRGB( int x, int y)

  • void setRGB( int x, int y, int rgb )

The integer value of rgb is dependent upon its encoding, but for the purposes of this discussion (and JPEG images in general), we use the 16 bits of the integer and allocate bits as follows: 5 bits for red, 6 bits for green, 5 bits for blue, and 0 bits for the alpha channel. If you are interested in pursuing this further, I suggest you start by looking around Sun's Java site for examples.

For this example, our goal is to obtain the size of an image and then constrain it to some thumbnail dimension. We define a thumbnail to be constrained to a 128x128 pixel box. To obtain the image dimensions, we can call the BufferedImage's getWidth() and getHeight() methods:

BufferedImage input = ImageIO.read( myinputstream );
int srcHeight = input.getHeight();
int srcWidth = input.getWidth();

To create a thumbnail version of our image, we need our own BufferedImage to work with, but with the constrained dimensions (constrained to our 128x128 box). So, we must determine the width and height of the thumbnail from the source dimensions. For this we take the larger dimension of the source image and assign it the value of our box. In other words, if the source image is wider than it is tall, then we say the width will be 128 and we need to calculate the height. Similarly if the image is taller than it is wide, the height will be 128 and we need to calculate the width. The calculation for the unknown dimension can be derived from a simple ratio:

sourceWidth/sourceHeight = thumbWidth/thumbHeight

If the image is wider than it is tall, then we know that thumbnail width will be 128 pixels. Furthermore, we know the source width and height from the BufferedImage methods. The thumbnail height is calculated as follows:

// Original Ratio
sourceWidth/sourceHeight = 128/thumbHeight

// Solving for thumbHeight
thumbHeight = ( 128 * sourceHeight ) / sourceWidth

Putting this together with the fact that Integer division is not very nice to us (150/100 in Integer arithmetic is 1, not 1.5) gives us the following code:

int height = boxSize;
int width = boxSize;
if( srcHeight > srcWidth )
{
  width = ( int )( ( ( float )height / ( float )srcHeight ) * ( float )srcWidth );
}
else if( srcWidth > srcHeight )
{
  height = ( int )( ( ( float )width / ( float )srcWidth ) * ( float )srcHeight );
}

Armed with the dimensions of our thumbnail, we can build a new BufferedImage with those dimensions:

BufferedImage thumb = new BufferedImage( width, height, BufferedImage.TYPE_USHORT_565_RGB );

When you build a graphical applet or application, to draw on a panel or canvas by override the paint() method. The paint() method is passed a single argument: an instance of the java.awt.Graphics class that represents the drawing area of the underlying component. The Graphics class has methods for drawing Strings, shapes such as rectangles and ovals, and methods for drawing images. One of those Graphics methods is the following:

drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)

This method paints the specified image on the Graphics object at position (x, y) and scales it to the specified width and height. From the BufferedImage class, we obtain a Graphics object with which to paint on the image by calling its getGraphics() method:

Graphics g = thumb.getGraphics();

Then we can call the aforementioned drawImage() method to paint our source image onto our thumbnail, scaled to the size of the thumbnail:

g.drawImage( input, 0, 0, width, height, null );

We now have a BufferedImage that is a thumbnail of our source image!

Image I/O

We already looked at the ImageIO class that reads an image from a stream and returns a BufferedImage. Now our challenge is to take our BufferedImage and save it as a JPEG.

About five years ago, I took a graduate class at the University of Southern California on "Multimedia and Creative Technology." One of our tasks was to implement JPEG compression. My reason for mentioning this: it sure is hard to do by hand when it has already been done for us!

The ImageIO class provides access to the ImageWriters installed in your Java environment. We gain access to an ImageWriter that writes JPEGs by asking the ImageIO class to find writers by the JPEG format name:

Iterator iter = ImageIO.getImageWritersByFormatName( "JPG" );

This call returns an Iterator to all ImageWriters that support the JPG format. Unless you care which one you want to use, grab the first one as follows:

ImageWriter writer = (ImageWriter)iter.next();

One of the powerful features of the JPEG compression algorithm is that you can specify how much compression you would like. You can compress an image so small that you can't recognize what it is (not necessarily a good idea), but a pretty reasonable balance between image size and quality is about 75% compression. The ImageWriter class has a method to obtain the set of parameters that the ImageWriter uses to write its image: getDefaultWriteParam(). From this we can tell the ImageWriter to write to an explicit 75% compression:

ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode( ImageWriteParam.MODE_EXPLICIT );
iwp.setCompressionQuality( 0.75f );

The ImageWriter class provides a method to write an image to a configured output with the specified ImageWriteParams:

write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param)

Our final goal is to create a javax.imageio.IIOImage to hold our BufferedImage:

IIOImage image = new IIOImage(thumb, null, null);

Now we are ready to configure our output object and write the image:

writer.setOutput( os );
writer.write(null, image, iwp);

The output object has to implement the javax.imageio.stream.ImageOutputStream interface, which -- through the MemoryCacheImageOutputStream class -- we can pass any ordinary OutputStream to (such as a FileOutputStream):

MemoryCacheImageOutputStream mos = new MemoryCacheImageOutputStream( new FileOutputStream( destFilename ) );

If you are not confused by now, either you have done this before or you're not paying attention. But don't fret, I will give you a utility class to take care of all these details.

Writing a JPEG BufferedImage to a BLOB

As if this was not confusing enough, when we want to insert a stream into a BLOB, we call the PreparedStatement's setBinaryStream() method, passing it an InputStream. The PreparedStatement reads from the supplied InputStream; it is not an OutputStream to which you write. We had to jump through several hoops to write a BufferedImage to an OutputStream, but how do we convert that OutputStream to an InputStream from which the PreparedStatement can read?

Whenever you need to convert from an OutputStream to an InputStream, the answer is to fallback to the underlying bytes that it is managing. Two streams manage bytes directly:

  • ByteArrayInputStream

  • ByteArrayOutputStream

We already know how to write a BufferedImage out to any OutputStream by using a MemoryCacheImageOutputStream. If we pass it a ByteArrayOutputStream, we'll get a byte array containing our JPEG compressed image. Then we can call the ByteArrayOutputStream's toByteArray() method to obtain the raw byte array (byte[]), and create a new ByteArrayInputStream from that byte[] for the PreparedStatement to read.

Listing 6 shows the image utility class that I promised you.

Listing 6. ImageUtils.java

package com.javasrc.webphotogallery.utils;

// Import the image classes
import java.io.*;
import java.awt.*;
import java.awt.image.*;
import javax.imageio.*;
import javax.imageio.stream.*;
import java.util.Iterator;

public class ImageUtils
{
  public static void constrain( String srcFilename, String destFilename, int boxSize )
  {
    try
    {
      FileInputStream fis = new FileInputStream( srcFilename );
      MemoryCacheImageOutputStream mos = new MemoryCacheImageOutputStream( new FileOutputStream( destFilename ) );
      constrain( fis, mos, boxSize );
    }
    catch( Exception e )
    {
      e.printStackTrace();
    }
  }

  public static byte[] constrain( String srcFilename, int boxSize )
  {
    try
    {
      FileInputStream fis = new FileInputStream( srcFilename );
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      MemoryCacheImageOutputStream mos = new MemoryCacheImageOutputStream( baos );
      constrain( fis, mos, boxSize );
      return baos.toByteArray();
      //ByteArrayInputSteam bais = new ByteArrayInputStream( baos.toByteArray() );
      //return bais;
    }
    catch( Exception e )
    {
      e.printStackTrace();
    }
    return new byte[]{};
  }

  public static void constrain( String srcFilename, OutputStream os, int boxSize )
  {
    try
    {
      FileInputStream fis = new FileInputStream( srcFilename );
      MemoryCacheImageOutputStream mos = new MemoryCacheImageOutputStream( os );
      constrain( fis, mos, boxSize );
    }
    catch( Exception e )
    {
      e.printStackTrace();
    }
  }

  public static void constrain( InputStream is, ImageOutputStream os, int boxSize )
  {
    try
    {
      // Read the source file
      BufferedImage input = ImageIO.read( is );

      // Get the original size of the image
      int srcHeight = input.getHeight();
      int srcWidth = input.getWidth();

      // Constrain the thumbnail to a predefined box size
      int height = boxSize;
      int width = boxSize;
      if( srcHeight > srcWidth )
      {
        width = ( int )( ( ( float )height / ( float )srcHeight ) * ( float )srcWidth );
      }
      else if( srcWidth > srcHeight )
      {
        height = ( int )( ( ( float )width / ( float )srcWidth ) * ( float )srcHeight );
      }

      // Create a new thumbnail BufferedImage
      BufferedImage thumb = new BufferedImage( width, height, BufferedImage.TYPE_USHORT_565_RGB );
      Graphics g = thumb.getGraphics();
      g.drawImage( input, 0, 0, width, height, null );

      // Get Writer and set compression
      Iterator iter = ImageIO.getImageWritersByFormatName( "JPG" );
      if( iter.hasNext() ) 
      {
        ImageWriter writer = (ImageWriter)iter.next();
        ImageWriteParam iwp = writer.getDefaultWriteParam();
        iwp.setCompressionMode( ImageWriteParam.MODE_EXPLICIT );
        iwp.setCompressionQuality( 0.75f );
        writer.setOutput( os );
        IIOImage image = new IIOImage(thumb, null, null);
        writer.write(null, image, iwp);
      }
    }
    catch( Exception e )
    {
      e.printStackTrace();
    }
  }
}

Listing 7 shows the new LoadImage class that creates the thumbnail image and saves it to the image_thumb column of our Image table.

Listing 7. LoadImage.java

package com.javasrc.webphotogallery;

import java.io.*;
import java.sql.*;
import com.javasrc.webphotogallery.utils.*;

public class LoadImage
{
  public static void main( String[] args ) 
  {
    if( args.length < 3 )
    {
      System.out.println( "Usage: LoadImage <image-filename> <album-id> <desc>" );
      System.exit( 0 );
    }

    String filename = args[ 0 ];
    int albumId = Integer.parseInt( args[ 1 ] );
    String desc = args[ 2 ];

    Connection conn = null;
    PreparedStatement ps = null;
    try
    {
      Class.forName( "org.gjt.mm.mysql.Driver" );
      String url = "jdbc:mysql://localhost:3306/webphotogallery";
      String username = "root";
      String password = "";
      conn = DriverManager.getConnection( url, username, password );
      ps = conn.prepareStatement( "INSERT INTO Image (album_id, image_desc, image_thumb, image_full) VALUES( ?, ?, ?, ? )" );

      ps.setInt( 1, albumId );
      ps.setString( 2, desc );

      // Insert the thumbnail into the first blob
      byte[] thumbnail = ImageUtils.constrain( filename, 128 );
      ps.setBinaryStream( 3, new ByteArrayInputStream( thumbnail ), thumbnail.length ); 

      // Insert the image into the second Blob
      File image = new File( filename );
      FileInputStream fis = new FileInputStream( image );
      ps.setBinaryStream( 4, fis, ( int )image.length() );

      // Execute the INSERT
      int count = ps.executeUpdate();
      System.out.println( "Rows inserted: " + count );
    }
    catch( Exception e )
    {
      e.printStackTrace();
    }
    finally
    {
      try 
      {
        if( ps != null ) ps.close();
        if( conn != null ) conn.close();
      }
      catch( Exception ee )
      {
        ee.printStackTrace();
      }
    }
  }
}

Summary

In the first article in this series, we saw how to insert an image into a Binary Large OBject (BLOB) column of a database, retrieve the image out of the BLOB field, and serve that image as a JPEG from inside a Servlet. In this article, I showed you how to manipulate our source image and create a thumbnail; then we solved the problem of converting that image to a JPEG and writing it to a BLOB. In the next article, we will conclude the series by setting up a Web user interface around these capabilities so that we can browse albums, thumbnails, and images.

Online Resources

Sample jGuru article that demonstrates how to work with BLOBs

Discussions

Read and display the table in the document
Posted Nov 12, 2008 06:01 AM by StrongHead
1 Replies
Correction
Posted Nov 4, 2008 06:09 PM by youssef.mohammed
1 Replies
Instead of synchronising getInstance
Posted Nov 3, 2008 05:42 AM by grahamkelly
1 Replies

Make a New Comment

You must log in in order to post a comment.

Related Resources

Dustin SullivanIf You Are New to Java Programming...
By Dustin SullivanJune 2, 20092 Comments

We recently sat down with several top Java developers to talk about that state of the language as we approach this year's JavaOne.  As we were wrapping up, we threw one last question at them out of curiosity, and we thought you'd like to see what some of them said.

Steven HainesOracle Buys Sun of $7.4B
By Steven HainesApril 20, 2009 No Comments

In a stunning turn of events, Oracle steps in and buys Sun amist the breakdown of IBM's attempt to acquire Sun.

Steven HainesIBM in talks to buy Sun Microsystems for at least $6.5B
By Steven HainesMarch 18, 2009 No Comments

Reuters reported this morning that IBM is in talks to buy Sun Microsystems, which could "bolster their computer server products against rivals such as Hewlett-Packard Co."

See More Blogs

Informit Network