Generating Images with JSPs and Servlets Blog


    <  /tr>                               

    Parsing Input Parameters
    Drawing Into a Buffer
    Writing the Image
    Dynamic Text
    Image Thumbnails
    Thumbnail Examples

    In the old days of web design, we used strange server-side hacks to create sophisticated effects. The first time I saw animated graphics on the Web was the Batman Forever web site in 1995. The folks at Netscape had just implemented a way of streaming animation using a custom program on the server. It was an amazing effect, but difficult to implement and maintain. Fortunately, a decade of advances such as CSS have made most server image hacks unnecessary. Still, there are a few interesting uses.

    Server-side image generation gives us the ability to do two things: draw something that is impractical or impossible to do on the client side with HTML and JavaScript, like use a custom font or tables with non-rectangular edges; and draw on the fly to show information that is timely or specific to the current user. Both of these uses are especially powerful on the Java platform, where we have a plethora of readily available tools for image manipulation. In this article, we will explore how to generate images from JSPs and servlets with three examples: a pie chart, rendering text in a custom font, and thumbnail images with composited frames.

    Parsing Input Parameters

    Our first example is a pie chart, written as a JSP to keep the code small. It's a simple graphic that's easy to draw, but can't be done with just CSS and HTML. A pie chart is basically a set of pie slices, each with an angle and a color, so we will start by collecting the input parameters. We want our finished pie to look like Figure 1:

    Figure 1
    Figure 1. 60/300 degree pie chart

    The request to our JSP will look like this: color=ff0000&color=0000ff&width=100&height=100& background=ffffff

    You'll notice above that there is one slice and onecolor parameter per pie slice. Then we have aheight and width to draw the actual pie. And finally, a background to fill in the space between the pie circle and the rectangular image edges. This is the code to parse the parameters:

    String[] slices = request. getParameterValues("slice"); String[] colors = request. getParameterValues("color"); int[] sizes = new int[slices.length]; Color[] cols = new Color[slices.length]; for(int i=0; i<slices.length; i++) { u.p("sizes = " + slices[i]); u.p("colors = " + colors[i]); sizes[i] = Integer.parseInt(slices[i]); cols[i] = new Color(Integer. parseInt(colors[i],16)); } int width = Integer.parseInt(request. getParameter("width")); int height = Integer.parseInt(request. getParameter("height")); Color background = new Color(Integer.parseInt( request.getParameter("background"),16));

    First we get the slice and colorparameters. There should be more than one, so we have to userequest.getParameterValues(). Then we create an array to hold them and convert the string values into integers and colors. Notice that the color conversion uses theInteger.parseInt() function with a radix of16, meaning the values must be in hexadecimal. A production version could handle named colors such as "blue" or "teal," as well as hex values. Finally, the code above parses thewidth, height, andbackground parameters.

    Drawing Into a Buffer

    With the inputs set up, we are ready to actually draw our pie chart. But where? This is the first place where server-side images differ from those on the client side. In a normal Swing application, each component already has an image buffer with aGraphics on it. We just implement thepaint method and start drawing. On the server, however, we need to manually create a place to draw: theBufferedImage.

    A BufferedImage is simply an Imagebacked by a chunk of memory. After we draw on the image, we can then write it out using the javax.imageio APIs.

    BufferedImage buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g = buffer.createGraphics(); g.setColor(background); g.fillRect(0,0,width,height); int arc = 0; for(int i=0; i<sizes.length; i++) { g.setColor(cols[i]); g.fillArc(0,0,width,height,arc,sizes[i]); arc += sizes[i]; }

    This code first creates a buffered image with the requested width and height. Since we don't care about transparency,TYPE_INT_RGB is the best option.BufferedImage actually supports quite a large number of image types, but most of the time, you will only need RGB or ARGB (RGB with transparency). Next, we fill the image completely with the background color, and then draw each section of the arc, using the size parameters from the URL (specified in degrees).

    Writing the Image

    Now that we have a BufferedImage created and drawn, the last step is to write it out to an actual image file that the web browser can see. This usually means a GIF, JPEG, or PNG file. Due to copyright problems, the standard JDK does not include the ability to write GIF files. JPEG is lossy and better for photos, so that leaves us with PNG.

    In older versions of the JDK, writing out an image required using extra libraries or undocumented Sun classes. As of 1.4, however, the situation has vastly improved. Theimageio package provides an API for pluggable image encoders and decoders, including ones for reading and writing PNG files. All we have to do is give the API a place to write the bytes. It looks like this:

    response.setContentType("image/png"); OutputStream os = response.getOutputStream(); ImageIO.write(buffer, "png", os); os.close();

    The first two lines above set the content type toimage/png, which is the appropriate MIME type for PNG images, and get the output stream from the HTTPresponse object. The next line does the magic; using the static write method in the java.imageio.ImageIOclass, we pass in the image (buffer), the desired image format ("png"), and the output stream to write to (os). Close the output stream, and we're done! If you drop this into an app server and call with something like this URL (all on one line):

    piechart.jsp?slice=30&slice=60&slice=90&slice=180& color=00ff00&color=99ff99&color=bbffbb&color=ddffdd& width=100&height=100&background=ffffff

    you should see a pie chart like Figure 2:

    Figure 2
    Figure 2. A 30/60/90/180 degree pie chart

    This JSP produces only the image itself, and of course normally we wouldn't type that long URL into the browser. Instead, we would create a web page to link to the JSP as an image. This has the disadvantage of regenerating the image on every page load, but we'll get to disk caching later. Here's what a simple web page would look like (again, the src attribute is all on one line):

    <html> <body> <h3>A simple piechart</h3> <img src="piechart.jsp?slice=30&amp;slice=60&amp; slice=90&amp;slice=180&amp; color=00ff00&amp;color=99ff99&amp; color=bbffbb&amp;color=ddffdd&amp; width=100&amp;height=100&amp;background=ffffff"/> </body> </html>


    When creating images on the server side, programmers typically hit at least one of two problems. Either the image is output with the wrong MIME type, or the server throws exceptions.

    The MIME type problem results from the JSP deciding to set the MIME type to the default (usually text/html), even though you manually set it to something else. This varies by app server, but a common thing to look for is non-code white space near the top of your document. The JSP server will see the white space and try to output it, using the default MIME type (since you haven't set one yet).

    The code below might output as text/html

    <%@ page import="*" %> <%@ page import="java.awt.*"%> <% // java code

    Whereas this code removes the extra white space

    <%@ page import="*" %><%@ page import="java.awt.*" %><% // java code

    and should output properly.

    MIME type problems also can happen if you callrequest.getOutputStream() beforerequest.setMimeType(), so be sure to set the MIME type as early as possible.

    The exceptions are caused by a different problem; if you are developing this on Windows or Mac OS X, you probably won't see it, but if you use Linux (in particular, if you're running your code on a remote server), you probably will get an error like the one below. This is Java's way of saying it can't load up the AWT (Abstract Windowing Toolkit) subsystem. The reason goes back to the lowest levels of Java and the early days of AWT.

    org.apache.jasper.JasperException: Can&apos;t connect to X11 window server using &apos;:0.0&apos; as the value of the DISPLAY variable. at org.apache.jasper.servlet.JspServletWrapper. service( at org.apache.jasper.servlet.JspServlet. serviceJspFile( ...

    AWT was originally built to run on slow processors and inside of web browsers. To accomplish this, it depends on the native GUI, which for most Unix-based operating systems means X-Windows. If you don't have X running, then you can't run AWT. Since we aren't writing a GUI app, we don't need AWT, but the minute you try to load up an image it will initialize the entire AWT library. If this is running on a headless server, X won't be there, and AWT will fail, causing your image load to fail. In the old days, people would run special headless X servers, such as xvfb, the X Virtual Frame Buffer, to trick their image code into running. Fortunately, the last few releases of the JDK have an option to put the JVM into headless mode, eliminating the need for resource intensive hacks.

    To use headless mode, just add the parameter-Djava.awt.headless=true to your JVM when you start it. For Tomcat, this means editing the file and putting a line like this near the top:


    Dynamic Text

    Another good use for dynamic images is, ironically, displaying text. If you want a header to appear in a particular decorative font that most users are unlikely to have installed, then you can either degrade to a different font or generate an image in Photoshop. However, if the text changes (as in, say, the titles of weblog articles that are updated constantly), then updating it in Photoshop every day would quickly become impractical. Instead, we would like to generate the text on the fly with code and just link to the image with the text as a parameter. This system would also be useful for displaying mathematical symbols or characters in other languages, or any other situation where the desired font probably isn't installed on the end user's computer.

    To build a program that renders text in images like Photoshop does, we will take advantage of two key features of Java. First, Java supports anti-aliased rendering of text, just like Photoshop. Second, all JVMs are required to support loading TrueType fonts on the fly. This means we don't need the fonts pre-installed into the environment. If the TTF file is on disk (or inside of a JAR) we can load and render it. It almost sounds too easy!

    The top half of text.jsp

    // configure all of the parameters String text = "ABC abc XYZ xyz"; if(request.getParameter("text") != null) { text = request.getParameter("text"); } String font_file = "dungeon.ttf"; if(request.getParameter("font-file") != null) { font_file = request.getParameter("font-file"); } font_file = request.getRealPath(font_file); float size = 20.0f; if(request.getParameter("size") != null) { size = Float.parseFloat(request.getParameter("size")); } Color background = Color.white; if(request.getParameter("background") != null) { background = new Color(Integer.parseInt( request.getParameter("background"),16)); } Color color =; if(request.getParameter("color") != null) { color = new Color(Integer.parseInt( request.getParameter("color"),16)); }

    The code above is pretty much boilerplate, just collecting the input parameters. The text, font file, font size, background, and foreground colors are pulled from the request, providing reasonable defaults when a parameter is missing. All configuration is passed in on the URL line, so you don't have to reconfigure the app server to support new fonts or text.

    Next we need to prepare the font:

    Font font = Font.createFont(Font.TRUETYPE_FONT, new FileInputStream(font_file)); font = font.deriveFont(size);

    This code loads a custom font from a file. The first argument is the TRUETYPE_FONT constant, telling the loader that this font is in TrueType format, the only type guaranteed to be supported. The second argument is the input stream to load from (in this case, a file on disk). The font is created in memory with a point size of 1, so we have to derive a new font at the proper size (the default we set above is 20 points).


    With the font loaded, we need to create a buffer to draw it in. We've got a problem, though. We want the buffer to be the right size for the text, so we need to know the how big the text is. However, there is no way to get the size of the text without having a FontRenderContext to tell us. And that context has to come from the Graphics2D object, which will come from our buffer, which we haven't created yet! A bit of a chicken-and-egg problem. The solution is to create a scratch buffer just to get the FontRenderContext. Then we can callFont.getStringBounds() to get the size of the text and create the real buffer. The code below does this:

    BufferedImage buffer = new BufferedImage(1,1,BufferedImage.TYPE_INT_RGB); Graphics2D g2 = buffer.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); FontRenderContext fc = g2.getFontRenderContext(); Rectangle2D bounds = font.getStringBounds(text,fc); // calculate the size of the text int width = (int) bounds.getWidth(); int height = (int) bounds.getHeight(); // prepare some output buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); g2 = buffer.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setFont(font);

    Note that we set the rendering hint for antialiasing on both the scratch buffer and the real one. This makes sure that the size of the rendered text is the same on both.

    Now that we've got our font completely set up, it's time to do some drawing:

    // actually do the drawing g2.setColor(background); g2.fillRect(0,0,width,height); g2.setColor(color); g2.drawString(text,0,(int)-bounds.getY());

    The code above draws the actual text over a colored background. The y coordinate for the drawStringmethod is -bounds.getY(), because text is drawn with the origin at the baseline (bottom) of the text rather than the upper left corner, as rectangles and other drawing methods are.

    With the drawing done, we can just write the image out as a PNG, as we did before:

    // set the content type and get the output stream response.setContentType("image/png"); OutputStream os = response.getOutputStream(); // output the image as png ImageIO.write(buffer, "png", os); os.close();

    Now we have the ability to make the images in Figures 3, 4, and 5:

    Figure 3.text.jsp?text=Big%20Apple&font-file=bigapple.ttf&size=40

    Figure 4
    Figure 4.text.jsp?text=Fifties%20Diner&font-file=diner.ttf&size=45

    Figure 5
    Figure 5.text.jsp?text=jcLdfu&font-file=60schic.ttf&size=50

    Image Thumbnails

    Our final example is a thumbnail generator. There are plenty of programs, both graphical and command-line, that will generate thumbnails. You can even script Photoshop to do it. The advantage of doing it on the server is that we can scale our thumbnails automatically with no user intervention. This by itself is great for novice users who just want to dump a bunch of images into a directory and see a gallery. Whenever images are changed, the thumbnails will be automatically updated. And since we are already loading and scaling the images, it will be easy to put in extras such as attractive frames around each image, and cache images to keep the server running quickly.

    Since this program is a bit more complicated, we'll write it as a servlet instead of a JSP. This will help keep the code structured and get rid of the white space problems. Since a great deal of this code is replicated from the previous examples, we'll only delve into the new parts.

    Each thumbnail is going to be composed of two images scaled together. First is the photo image for which we want to make a thumbnail. The second is the frame that contains the picture frame, usually a box of some sort with the rest transparent. This is a bit complicated, so let's start with some pictures (Figure 6 and 7):

    Figure 6
    Figure 6. The photo

    Figure 7
    Figure 7. The frame

    The frame will be drawn on top of the image, as in Figure 8.

    Figure 8
    Figure 8. First composite

    We can already see a problem. The frame doesn't extend to the edges of the image, because it's non-rectangular and has a drop shadow. This lets the photo image show through. In order to cut out the parts of the photo that we don't want, we need a third image, called a mask (Figure 9). A mask indicates what parts of another image are to be retained and what parts are to be discarded. Here we just want the parts of the picture that will be inside of the frame.

    Figure 9
    Figure 9. The mask

    Now we can combine the mask with the photo to get Figure 10:

    Figure 10
    Figure 10. Photo + mask

    and then draw the frame on top to get our final image: Figure 11.

    Figure 11
    Figure 11. Photo + mask + frame

    This final image is what gets scaled down into a thumbnail.

    Let's get started. First up is calculating the input parameters.

    // the actual image file to load File file = new File ( request.getRealPath(request.getParameter("file"))); if(!file.exists()) { System.out.println("WARNING! the file " + file + " doesn't exist!!!!"); } String frame_path = request.getParameter("frame"); String mask_path = request.getParameter("mask");

    In addition to the parameters we've used before (width, height, andbackground_color), we want the filename of the image, its frame, and its mask. Next we create a file for the image in a temp directory that we use for caching. We've included the requested width in the filename so that we can cache differently sized thumbnails separately. If the cache file is missing or out of date, we call generateImage() before writing the image to the output stream.

    // this can be used to turn off caching String cache = request.getParameter("cache"); if(cache == null) { cache = "true"; } // the actual image file to load File file = new File( request.getRealPath(request.getParameter("file"))); if(!file.exists()) { System.out.println("WARNING! the file " + file + " doesn't exist!!!!"); } // calculate the image type // (we only support jpeg and png for output) String type = "jpeg"; if(file.getName().toLowerCase().endsWith(".png")) { type = "png"; } if(request.getParameter("type")!=null) { type = request.getParameter("type"); } // init the cache directory File temp = new File("c:/temp"); // load up the thumbnail File thumbfile = new File(temp, file.getName()+ ".w"+new_width+".thumb"); // only regenerate if the thumbnail is missing // or out of date if(!thumbfile.exists() || (file.lastModified() > thumbfile.lastModified()) || cache.equals("false")) { generateImage(file, thumbfile, type, new_width, frame_path, mask_path, background_color, request); } // actually write the image to the output stream outputImage(thumbfile,type,response);

    With all of that prep work out of the way, we can implement the actual drawing code in generateImage():

    // load the image Image image = new ImageIcon(file.toString()).getImage(); double w = image.getWidth(null); double h = image.getHeight(null); // if no explicit width then set the width // to the real image size double nw = new_width; if(new_width == -1) { nw = w; } // calculate the scaling factor double scalex = nw/w;

    The first steps above are to load the photo image, get its dimensions, and calculate the scaling factor. If no target width was set, then we use the original width of the photo. This would result in a scale of 1 for no change in size.

    Next we load the frame and mask, if specified, and calculate the scaling factor to make the frame the same size as the photo.

    // load the mask Image mask = null; double mask_scale = 1; if(mask_path != null) { mask = new ImageIcon(request.getRealPath(mask_path)).getImage(); mask_scale = nw/mask.getWidth(null); } // load the frame Image frame = null; double frame_scale = 1; if(frame_path != null) { frame = new ImageIcon( request.getRealPath(frame_path)).getImage(); frame_scale = nw/frame.getWidth(null); }

    Now we want to create the buffer into which to draw these images. Since the frame controls the size of the final image, we'll use that for the buffer dimensions, as shown below:

    // base the height off of the frame, if there is a frame double nh = 0; if(frame != null) { nh = frame_scale*frame.getHeight(null); } else { nh = scalex*image.getHeight(null); } // create a temporary image BufferedImage bufimg = new BufferedImage ((int)nw, (int)nh, BufferedImage.TYPE_INT_ARGB); Graphics2D g = bufimg.createGraphics();

    With the buffer in hand, we're ready to draw: first the photo, then the mask, and finally, the frame.

    // draw the image scaled g.setComposite(AlphaComposite.Src); AffineTransform aft = AffineTransform.getScaleInstance(scalex, scalex); g.drawImage(image,aft,null); // draw the mask scaled if(mask != null) { g.setComposite(AlphaComposite.DstIn); AffineTransform mask_aft = AffineTransform.getScaleInstance(mask_scale, mask_scale); g.drawImage(mask,mask_aft,null); } // draw the frame scaled if(frame != null) { g.setComposite(AlphaComposite.SrcOver); AffineTransform frame_aft = AffineTransform.getScaleInstance(frame_scale, frame_scale); g.drawImage(frame,frame_aft,null); }

    The important things to notice in the code above are theg.setComposite calls. These set the Porter-Duffmethod to determine how the two images are composited. We are used to thinking of a second image simply drawn on top of a first, but there are many of other ways to do it. The first method above,AlphaComposite.Src, just draws the source image into the buffer, with no compositing at all. The second method,AlphaComposite.DstIn, draws the part of the destination (what's already in the buffer) that's inside of the source (our mask). This means everywhere the mask is solid, we will see what's already in the buffer. Whatever is transparent in the mask will be transparent after compositing. The third composite, for the frame, uses SrcOver, which draws the source (the frame) over the destination (the result of the previous composite). This article at IBM has a very detailed explanation of Porter-Duff compositing.

    Now that we're done compositing, we want to draw the whole thing on top of a solid background (if requested) in the final image. This image could be partially transparent if the images don't cover the whole buffer, which would be useful for drop shadows that blend with the rest of the web page. However, JPEG doesn't support transparency, so we'll only create a transparent buffered image when the type is set to PNG.

    // create a background image; we can't use // transparency if this is a jpeg BufferedImage background_img = null; if(type.equals("jpeg")) { background_img = new BufferedImage((int)nw, (int)nh, BufferedImage.TYPE_INT_RGB); } else { background_img = new BufferedImage((int)nw, (int)nh, BufferedImage.TYPE_INT_ARGB); } Graphics2D back_g = background_img.createGraphics(); // draw the background color, if there is one if(background_color != null) { back_g.setColor(background_color); back_g.setComposite(AlphaComposite.Src); back_g.fillRect(0,0,(int)nw,(int)nh); } // put the foreground on to the background back_g.setComposite(AlphaComposite.SrcOver); back_g.drawImage(bufimg,0,0,null);

    With the final image created, we can write out to the thumbnail file as before. We must be sure to close the file before quitting so that the thumbnail will be ready for theoutputImage function:

    // open the thumbnail file FileOutputStream os = new FileOutputStream(thumbfile); // save the final image if(type.equals("jpeg")) { JPEGImageEncoder enc = JPEGCodec.createJPEGEncoder(os); enc.encode(background_img); } if(type.equals("png")) { // output the image as png ImageIO.write(background_img, "png", os); } // cleanup os.close();

    The outputImage() function loads the file and copies the bytes to the output stream, setting the MIME type first.

    public void outputImage(File thumbfile, String type, HttpServletResponse response) throws IOException { response.setContentType("image/"+type); OutputStream os = response.getOutputStream(); FileInputStream fin = new FileInputStream(thumbfile); byte[] buf = new byte[4096]; int count = 0; while(true) { int n =; if(n == -1) { break; } count = count + n; os.write(buf,0,n); } os.flush(); os.close(); fin.close(); }

    Thumbnail Examples

    Now we have a servlet that can generate (and cache) scaled versions of photos with custom frames. Having custom frames is especially nice for non-traditional users, who might like all sorts of wacky frames for their photos. I've included a few examples below, as Figures 12-14:

    Figure 12
    Figure 12. Frame for a photographer's site

    Figure 13
    Figure 13. Photo with watermark for a clip art site

    Figure 14
    Figure 14. Silly children's frame


    As we've seen, servlets, JSPs, and Java2D make it very easy to create robust server-side image applications. These three examples just touch on the possibilities. From here we could go on to advanced charting (like the stock statistics on Yahoo! Finance), rasterization of SVG for non-vector-capable browsers, or even image analysis for medical researchers. You can download the code for this article or find the code along with more examples at my site, Try it out and see what other interesting uses you can come up with.