- Java Reference Guide
- Overview
- Table of Contents
- J2SE: Standard Java
- Java Windows NT Services
- Advanced J2SE
- J2SE 1.5.0: "Tiger"
- Java SE 6
- Core Computer Science Principles in Java (Data Structures)
- Annotations
- Java Generics
- Java New I/O
- Java Sound
- Java Applets
- JavaFX
- Java SE Threading
- Resource Management Using Semaphores
- Java Atomic Operations
- JavaTemplate Pages
- Executing Templates with the JtpExecutor
- Java Cryptography Extensions (JCE)
- Java Database Connectivity (JDBC) API
- Jakarta Commons - Net Class Library
- Jakarta Commons HttpClient
- Apache POI
- Regular Expressions
- JavaMail
- Cool Tools
- Building an Really Simple Syndication (RSS) Java App
- Logging with Log4J
- Inside Swing
- Swing Components
- SwingX
- Swing Styled Documents
- Web Rendering in Java Swing Applications
- Java Look-and-Feel Graphics Repository
- Java Media Framework
- Quicktime for Java
- Media in Java Review 2008
- Graphs and Charts
- Holiday Special: Electronic Greeting Card
- Media Framework: Presenter Application
- Standard Widget Toolkit
- JFace
- Java Performance Tuning
- J2EE Performance Tuning
- Caches and Pools
- Java Caching System
- Java Compression and Decompression
- Obfuscating Java Applications
- Continuous Integration
- Load Testing
- Tomcat Clustering
- Enterprise Java Testing
- Automated Unit Testing with JUnit and Ant
- Unit Testing: Tips From The Trenches
- Custom Ant Tasks
- Extensible Markup Language (XML)
- Java Web Technologies
- Web Frameworks
- Struts 2
- Wicket
- JavaServer Faces
- Distributed Programming / RMI
- Servlet Filters
- Building a Robust Java Server
- J2EE: Enterprise Java
- Spring
- Java Design Patterns
- XDoclet
- Hibernate
- Project Backup
- J2EE Project: Hands-On
- Enterprise Java Beans (EJB) 3.0
- Disaster Recovery
- Java Management Extensions (JMX)
- Service-Oriented Architecture
- Web Services
- RESTful Web Services New
- Project: Building a Web Photo Gallery
- Image Manipulation
- Web Photo Gallery Presentation
- The Web Interface
- J2ME: Micro Java
- Specialized J2ME
- Optional Packages
- Other Java Technologies
- Derivatives and Competitors
- Java, Engineered for Integration
- Additional Resources
- The World of Java Tools
- Building Java Applications with Ant
- Managing Java Build Lifecycles with Maven
- Source Control with Subversion
- Inversion of Control and Dependency Injection
- Certification
- Roadmap: Becoming an Enterprise Java Developer
- Roadmap: Becoming an Enterprise Java Developer in 2007
- The Business of Enterprise Software
- JavaOne 2006
- JavaOne 2007
- JavaOne 2008 Wrap-Up
- JavaOne 2009 Wrap-Up
- How to Survive in a Turbulent Job Market
- How to Hire the Best Talent
- Cloud Computing
- Enterprise Java in 2008 and Beyond
- Predictions for 2018
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




Account Sign In
View your cart