Aging bugs and setting dpi with java image io
A while back , I was adding support for several bitmap file formats to Trace Modeler so users could export their UML sequence diagrams.
Luckily, the java image I/O API lets you do just that. It also allows for setting the image metadata in a format-independent way. For example to specify the desired DPI, which was what I wanted to do.
As it turned out, setting the dpi was less straightforward than expected…
Metadata in the java image io api
The java image I/O API is plug-in based, for every file format you wish to use an appropriate plug-in must be available. The API provides the necessary abstractions for writing plug-in neutral code and has a repository of available implementations that you can query at run-time. As of J2SE 6, every JRE has to provide plug-ins for PNG, JPG and BMP file formats (plus some others that I haven’t tried out yet).
The spec for setting image metadata is the standard (plug-in neutral) metadata format specification. Strangely enough, the spec is an XML schema. That’s right, if you want to set the DPI, you’ll have to do it by building a DOM-tree using IIOMetadataNodes and merge it in with the rest! Sigh..
Beware that plug-ins can differ in their support for the standard metadata :
- some don’t support changing the standard metadata
- some don’t support the standard metadata format at all
- some don’t support merging your DOM-tree with the current metadata and will silently replace it
Anyway, the relevant tags when you want to set the DPI are HorizontalPixelSize and VerticalPixelSize :
<!ELEMENT "HorizontalPixelSize" EMPTY> <!-- The width of a pixel, in millimeters, as it should be rendered on media --> <!ATTLIST "HorizontalPixelSize" "value" #CDATA #REQUIRED> <!-- Data type: Float --> <!ELEMENT "VerticalPixelSize" EMPTY> <!-- The height of a pixel, in millimeters, as it should be rendered on media --> <!ATTLIST "VerticalPixelSize" "value" #CDATA #REQUIRED> <!-- Data type: Float -->
Note that the spec clearly states that both have to be expressed in millimeters per dot.
How to disregard your own spec, Sun style
Sun has implemented this metadata spec for their PNG and JPG plug-ins and includes it in their current JDK and JRE distributions. The relevant classes are
com.sun.imageio.plugins.png.PNGImageWriter
com.sun.imageio.plugins.jpeg.JPEGImageWriter
You can tell from the com.sun
package that they’re not part of the J2SE API, but specific to Sun’s implementation.
Remember how the spec required millimeters per dot? Well, have a look at the following table to see how Sun actually implemented the spec :
plug-in | unit | bug report | date reported |
---|---|---|---|
PNGImageWriter | dots per millimeter | bug 5106305 | 23 sep 2004 |
JPEGImageWriter | decimeter per dot | bug 6359243 | 05 dec 2005 |
These bugs have been known for a very long time and their fixes are really simple. Unfortunately, they were given a low priority and haven’t even been evaluated at the time of writing (July 2008).
Great. Now what?
Well, because the workaround is so trivial I decided to stick with the image I/O API. If you give these classes what they want, the bitmaps will come out fine. To ensure that your export code also works on platforms that implement the spec correctly, it must check the actual implementation classes that are being used and compensate for the bugs.
If you find yourself in a similar situation, make sure your workaround code will be able to cope when the bugs are fixed eventually. More on this in the article 'How bugs in the J2SE api can bite you twice'.
Oh, and if you use instanceof
to check for instances of a buggy class that is not guaranteed to exist on all platforms, be sure to catch NoClassDefFoundError
Send in your comments and I'll add them below.
Reader comments
Craig wrote :
Best not to use instanceof at all in this case, and use reflection to check the class instead. It might not be obvious, but this adds a hard dependency on the class (even though you may not reference it elsewhere). If the referenced class does not exist at runtime, your class will not even load, as there will be unresolved dependencies.
Yanic answered :
The need for reflection had occurred to me, but after checking the JVM spec (section 5.3 - creation and loading) and some experimentation I figured that catching NoClassDefFoundError would suffice.
When you speak of ‘unresolved dependencies’, what kind of error/exception did you have in mind?
In any case, when in doubt the reflective approach is definitely the safer solution (but I thought I had my bases covered).
A quick way to check it is
in file XYZ.java public class XYZ { } in file Root.java public class Root { static public void main(String[] args) { Object o = new Object(); if (o instanceof XYZ) { System.out.println("XYZ"); } else { System.out.println("not an XYZ"); } } }Compile, delete XYZ.class and execute.
Article copyright © Yanic Inghelbrecht 2007-2008, www.tracemodeler.com | last updated on 06/07/2008