This month's developer article talks about how to manipulate image files (like JPG or PNG files) with your code. While there are a number of third-party DLLs and applications you might tap into for this purpose, you'll probably find that you can do most, if not all, of the things you want to do using Java right out of the box.
Reading Images The built-in Java library for reading and writing images is the ImageIO library. This was available as an extra library in early versions of Java (you could download it from Sun and add it to your classpath), and it has been a part of the standard Java distribution since Java 1.4. That's Lotus Notes 7.0 and higher to you and me.
As Java versions have been updated with newer versions of the Notes client, we have also gained some additional functionality with the built-in ImageIO framework with newer versions of the client. Specifically, here are the image types we can read in Notes 7 and higher:
- Notes 7: JPG and PNG
- Notes 8: JPG, PNG, BMP, and GIF
- Notes 8.5: JPG, PNG, BMP, and GIF
An interesting thing to keep in mind is that while Notes 8 can read GIF files, support for writing GIF files (as we'll see later) is not available by default until Notes 8.5. Also, the ImageIO framework has an API for plugins, so there are additional plugins available that you can use to read and/or write other image formats.
I've also found that certain types of PNG files are not able to be read properly by the base Java ImageIO class. See this page for one instance and a little explanation. As with the use of additional file types, you might find that you have to use other libraries or plugins (like javapng from Google) to get full PNG support.
Reading a file for manipulation as a BufferedImage is as easy as writing a few lines of code like this:
FileInputStream fis = new FileInputStream(fileName);
BufferedImage bi = ImageIO.read(fis);
From there, you can modify the BufferedImage to make any alterations to the image before resaving it.
Modifying Images Modifying an image involves making modifications to the in-memory BufferedImage that we now have access to. There are two primary ways in which we will modify an image: redrawing the image and filtering the image.
Redrawing an image (cropping it, rotating it, adjusting its size) can be done by using the Graphics2D object to create a new "canvas" to redraw the BufferedImage onto it, thereby creating a new BufferedImage at the desired size, rotation, or position. For example, here's how to resize an image:
BufferedImage bi2 = new BufferedImage(width, height, bi.getType());
Graphics2D g2 = bi2.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(bi, 0, 0, width, height, null);
g2.dispose();
return bi2;
You can call g2.rotate() before the call to g2.drawImage() if you want to rotate the image, and you can actually crop the image by calling BufferedImage.getSubimage() to create a new cropped BufferedImage.
You can actually use AffineTransforms (which we'll discuss in the next paragraph) to do the image scaling or rotation as well. However, you have much better control over the quality of the resulting image if you use Graphics2D methods directly for this functionality. For a good workaround for the problem of poor image quality when you create image thumbnails, see Chris Campbell's article "The Perils of Image.getScaledInstance()".
Pretty much any other kind of image manipulation you want to do — like adjusting colors and contrast, blurring, sharpening, etc. — requires the use of a Java 2D BufferedImageOp filter. Some of the ones you'll find useful are:
- AffineTransformOp for
scaling, flipping, or rotating (although as mentioned above, you might get better results for scaling and rotating by using Graphics2D directly)
- ColorConvertOp for
adjusting image colors or converting to greyscale
- RescaleOp for
adjusting contrast and brightness
- ConvolveOp for
blurring, sharpening, or edge-detection
Here's an example of creating a greyscale version of an image:
ColorConvertOp op = new ColorConvertOp(
ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
return op.filter(bi, null);
None of these operations will directly modify the BufferedImage that they operate on; rather, they do their work and return a new instance of a BufferedImage that you must now retain in memory.
Saving Images The ImageIO class again comes into play when we save the BufferedImage that we've modified. We just have to make a call to ImageIO.write() to save the in-memory image to a file. For example:
String fileName = "c:\\myNewFile.png";
ImageIO.write(bi, "png", new File(fileName));
As with the file types that can be read, the file types that we can generate are different for different versions of Lotus Notes:
- Notes 7: JPG and PNG
- Notes 8: JPG, PNG, and BMP
- Notes 8.5: JPG, PNG, BMP, and GIF
Even if you don't make any changes to the image file at all, you can use the ImageIO class to convert from one file type to another. One limitation that I've run into is that the JPG output quality is often pretty poor. I don't know why that is, but a workaround I've found is to use code like this for generating JPG output:
Iterator iter = ImageIO.getImageWritersByFormatName("jpeg");
ImageWriter writer = (ImageWriter)iter.next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(quality); // 1f = 100%, 0.9f = 90%, etc.
File file = new File(fileName);
FileImageOutputStream out = new FileImageOutputStream(file);
writer.setOutput(out);
IIOImage image = new IIOImage(bi, null, null);
writer.write(null, image, iwp);
out.close();
writer.dispose();
As I mentioned in the "Reading Images" section, there are plugins and additional libraries available that can be used to generate other image types too.
What About LotusScript? If you're a LotusScript programmer, you can still take advantage of all this built-in functionality directly through your LotusScript code. Just use LS2J. In fact, an example of using many of the techniques I've talked about above as well as an LS2J wrapper to make calls to an image manipulation Java library is available on my Web site.
Limitations The primary (and obvious) limitation is the small number of file types that you are limited to. However, that's just an "out of the box" limitation that you can use plugins and other libraries to work around. Ask Google for help.
If you're doing a large amount of image processing (or working on very large images), make sure you don't hold too many BufferedImages in memory.
Also, because you will almost certainly be dealing with the local file system (reading files, writing files, or both), make sure that any server-based agents that use these techniques have the security set to "Allow restricted operations" and the agent signer is allowed to run unrestricted operations on the server.
BONUS: Embedding images into rich text fields As a related topic, see my December 2008 Clippings article "Four Things You Thought You Couldn't Do With Rich Text" to get some information on how you can embed an image file directly into a rich text field — as a real image, not just an attachment. |