Storing DPI and Paper Size information in a JPEG with Java

谁说胖子不能爱 提交于 2019-12-01 09:42:14
Abdur Rahman

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…

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 :

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 ;)

Joop Eggen

This question is already partially answered: Write dpi metadata to a jpeg image in Java

From the image editor GIMP I know that .png has a resolution (DPI) one may save as meta data. There is EXIF data for JPEG, info of the camera in general, including resolution.

You may consider using Commons Sanselan, instead of ImageIO for this task.

See http://commons.apache.org/sanselan/whysanselan.html for more info.

dsummersl

I found this post for setting DPI on PNG Files. It pointed out that you should use 'metadata.mergeTree' to properly save your metadata.

With that in mind, here is some working groovy code that takes a BMP file and creates a JPG file at arbitrary DPI:

import java.awt.image.BufferedImage
import java.io.File
import java.util.Hashtable
import java.util.Map
import javax.imageio.*
import javax.imageio.stream.*
import javax.imageio.metadata.*
import javax.imageio.plugins.jpeg.*
import org.w3c.dom.*

File sourceFile = new File("sample.bmp")
File destinationFile = new File("sample.jpg")

dpi = 100

BufferedImage sourceImage = ImageIO.read(sourceFile)
ImageWriter imageWriter = ImageIO.getImageWritersBySuffix("jpeg").next();
ImageOutputStream ios = ImageIO.createImageOutputStream(destinationFile);
imageWriter.setOutput(ios);
def jpegParams = imageWriter.getDefaultWriteParam();

IIOMetadata data = imageWriter.getDefaultImageMetadata(new ImageTypeSpecifier(sourceImage), jpegParams);
Element tree = (Element)data.getAsTree("javax_imageio_jpeg_image_1.0");
Element jfif = (Element)tree.getElementsByTagName("app0JFIF").item(0);
jfif.setAttribute("Xdensity", Integer.toString(dpi));
jfif.setAttribute("Ydensity", Integer.toString(dpi));
jfif.setAttribute("resUnits", "1"); // density is dots per inch                 
data.mergeTree("javax_imageio_jpeg_image_1.0",tree)

// Write and clean up
imageWriter.write(data, new IIOImage(sourceImage, null, data), jpegParams);
ios.close();
imageWriter.dispose();

Worked fine for me in that OSX's Preview app and Gimp both reported that the resulting image was 100 DPI. As to Paper Size...I imagine this is directly determined by DPI? I couldn't find any JPEG property that would set that particular value.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!