I recently worked on a project that required obtaining the resolution of photograph. The approach I used was to limit the photographs to Jpeg format images only, which I could then use the new imageGetEXIFMetada() function in ColdFusion 8 to obtain the X-Resolution and Y-Resolution tags. Here is a quick summary of some of my findings and problems.

First off, lets make sure we’re all on the same page with EXIF - because I had no idea what the EXIF metadata was when I first started the project. So, lets have our friend wikipedia explain it to us:

Exchangeable image file format (Exif) is a specification for the image file format used by digital cameras. The specification uses the existing JPEG, TIFF Rev. 6.0, and RIFF WAV file formats, with the addition of specific metadata tags. It is not supported in JPEG 2000, PNG, or GIF.

Basically, the Jpeg file format allows for a variety of custom metadata segments to be stored in the Jpeg image file including the Exif metadata and the IPTC format (usually stores information about the author). So, if you have a digital camera, your Jpeg images will contain information about the camera and the photograph, including the resolution of the image.

EXIF - a standard?

However, from what I have experienced, this “standard” isn’t really that consistent, and depends largely upon the implementation chosen by camera manufacturers and photo editing software makers. Also, keep in mind that not all Jpegs will contain EXIF or IPTC metadata or may only contain some information.

IFD - wtf?

So, after performing some tests on several images using CF 8.0.1 and the imageGetExifMetadat() function, I thought perhaps there was more there than what was being returned by CF.

The Exif format uses several Image File Directories (IFD) to store information about the main image, a link to the sub IFD (if applicable), and a link to the location of IFD1, which is the IFD for the thumbnail image. In the end, this means that there are potentially 3 directories of metadata: 0 contains information about the main image, SubIFD contains information about the digital camera (such as shutter speed, focal length, etc…), and 1 contains information about the thumbnail and the thumbnail itself.

The first thing I did was download the Firefox plugin called Exif Viewer. This tool allows you to view all of the EXIF and IPTC metadata embedded into a local or remote Jpeg file. First thing I noticed with this is that it appears that CF (which in turn uses the metadata-extractor Java library) is pulling the resolution from the thumbnail properties.

Note: the reason why my thumbnail and main image have different resolutions is because I used PS to adjust the image’s resolution and size. I think it is safe to assume that many people will crop/alter photos prior to uploading them into a web application. However, for those that do not, the resolution of both the main image and the thumbnail should be the same - so you may not experience this issue when that is the case.

Testing it out

Using a sample image, lets look at the difference between the structure returned by CF (remember, its just a Java metadata-extractor library). Here is what is returned from the Exif Viewer Firefox plugin:

Exif IFD0
* Camera Make = Apple
* Camera Model = iPhone
* Picture Orientation = normal (1)
* X-Resolution = 3000000/10000 = 300
* Y-Resolution = 3000000/10000 = 300
* X/Y-Resolution Unit = inch (2)
* Software/Firmware Version = Adobe Photoshop CS3 Windows
* Last Modified Date/Time = 2008:04:15 09:15:42

Exif Sub IFD
* Lens F-Number/F-Stop = 14/5 = F2.8
* Original Date/Time = 2007:11:08 12:26:54
* Digitization Date/Time = 2007:11:08 12:26:54
* Colour Space = sRGB (1)
* Image Width = 1200 pixels
* Image Height = 900 pixels
* Unknown tag: Tagnum = 0xa500 ===> data = 11/5

Exif IFD1
* Compression = JPEG compression (6)
* X-Resolution = 72/1 = 72
* Y-Resolution = 72/1 = 72
* X/Y-Resolution Unit = inch (2)

And, here is what is returned from ColdFusion:

Wait a second? CF is telling me that the image has a resolution of 72 DPI, when Exif viewer is telling me that in fact the resolution of the image is 300 dpi and the resolution of the embedded thumbnail is 72 dpi. My inclinations tell me that CF is pulling the wrong information, but we know that CF is just putting a nice wrapper on the metadata-extractor Java lib. So, my thinking is that either CF’s implementation is flawed, the Java lib is flawed, or this whole concept of Exif metadata is flawed (which some have argued).

Using Metadata-extractor

Based on my previous test (and other tests that I performed), I wanted to determine if I could pull the resolution from the correct IFD using the metadata-extractor Java lib. For some reason I thought I could do better than the engineers at Adobe (hah! riggghhhttt) who are Java programmers (and I haven’t touched much Java code since college). Well, just to prove myself wrong, I tried (and didn’t get anywhere, if its worth noting) and here is what I came up with.

First, there are several approaches that can be used to get at the Exif metadata using this library. I decided to try 3 different methods:

  1. ExifReader class implementation,
  2. JpegSegmentReader class implementation, and
  3. JpegMetadataReader class implementation.

For each implementation I used some common variables that I param out at the beginning:

[-]View Code COLDFUSION
1
 

The following is code to read the Exif meta data and loop over each directory and output all the tag information for each implementation above:

ExifReader implementation

Code:

[-]View Code COLDFUSION
1
2
3
4
5
6
7
8
9
10
11
extractor = createObject("java","com.drew.metadata.exif.ExifReader").init(createObject("java","java.io.File").init(expandPath('./')&photoname));
md = extractor.extract();
directories = md.getDirectoryIterator();
while (directories.hasNext()){
    directory = directories.next();
    tags = directory.getTagIterator();
    while (tags.hasNext()) {
         tag = tags.next();
         WriteOutput(tag.getTagName()&': '&tag.getDescription()&'');
    }
}

Output:
Make: Apple
Model: iPhone
Orientation: Top, left side (Horizontal / normal)
X Resolution: 72 dots per inch
Y Resolution: 72 dots per inch
Resolution Unit: Inch
Software: Adobe Photoshop CS3 Windows
Date/Time: 2008:04:15 09:15:42
F-Number: F2.8
Date/Time Original: 2007:11:08 12:26:54
Date/Time Digitized: 2007:11:08 12:26:54
Color Space: sRGB
Exif Image Width: 1200 pixels
Exif Image Height: 900 pixels
Unknown tag (0xa500): 2.2
Compression: JPEG (old-style)
Thumbnail Offset: 442 bytes
Thumbnail Length: 8874 bytes
Thumbnail Data: [8874 bytes of thumbnail data]

JpegSegmentReader class implementation

Code:

[-]View Code COLDFUSION
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
segmentReaderObj = createObject("java","com.drew.imaging.jpeg.JpegSegmentReader");
segmentReader = segmentReaderObj.init(createObject("java","java.io.File").init(expandPath("./")&photoname));
exifSegment = segmentReader.readSegment(segmentReaderObj.SEGMENT_APP1);
iptcSegment = segmentReader.readSegment(segmentReaderObj.SEGMENT_APPD);
metadataObj = createObject("java","com.drew.metadata.Metadata");
exifReaderObj = createObject("java","com.drew.metadata.exif.ExifReader");
iptcReaderObj = createObject("java","com.drew.metadata.iptc.IptcReader");
metadata = metadataObj.init();
exifreader = exifReaderObj.init(exifSegment);
exifdata = exifreader.extract(metadata);
if (structKeyExists(variables,'iptcsegment')){
    iptcdata = iptcReaderObj.init(iptcSegment).extract(metadata);
}
 
//write out the exif data
directories = exifdata.getDirectoryIterator();
while (directories.hasNext()){
    directory = directories.next();
    tags = directory.getTagIterator();
    while (tags.hasNext()) {
         tag = tags.next();
         WriteOutput(tag.getTagName()&': '&tag.getDescription()&'');
    }
}
//write out the iptc data
if (structKeyExists(variables,'iptcsegment')){
    directories = iptcdata.getDirectoryIterator();
    while (directories.hasNext()){
        directory = directories.next();
        tags = directory.getTagIterator();
        while (tags.hasNext()) {
             tag = tags.next();
             WriteOutput(tag.getTagName()&': '&tag.getDescription()&'');
        }
    }
}

Output:
Make: Apple
Model: iPhone
Orientation: Top, left side (Horizontal / normal)
X Resolution: 72 dots per inch
Y Resolution: 72 dots per inch
Resolution Unit: Inch
Software: Adobe Photoshop CS3 Windows
Date/Time: 2008:04:15 09:15:42
F-Number: F2.8
Date/Time Original: 2007:11:08 12:26:54
Date/Time Digitized: 2007:11:08 12:26:54
Color Space: sRGB
Exif Image Width: 1200 pixels
Exif Image Height: 900 pixels
Unknown tag (0xa500): 2.2
Compression: JPEG (old-style)
Thumbnail Offset: 442 bytes
Thumbnail Length: 8874 bytes
Thumbnail Data: [8874 bytes of thumbnail data]
Directory Version: -23
Make: Apple
Model: iPhone
Orientation: Top, left side (Horizontal / normal)
X Resolution: 72 dots per inch
Y Resolution: 72 dots per inch
Resolution Unit: Inch
Software: Adobe Photoshop CS3 Windows
Date/Time: 2008:04:15 09:15:42
F-Number: F2.8
Date/Time Original: 2007:11:08 12:26:54
Date/Time Digitized: 2007:11:08 12:26:54
Color Space: sRGB
Exif Image Width: 1200 pixels
Exif Image Height: 900 pixels
Unknown tag (0xa500): 2.2
Compression: JPEG (old-style)
Thumbnail Offset: 442 bytes
Thumbnail Length: 8874 bytes
Thumbnail Data: [8874 bytes of thumbnail data]
Directory Version: -23

JpegMetadataReader class implementation

Code:

[-]View Code COLDFUSION
1
2
3
4
5
6
7
8
9
10
11
12
13
metadataReaderObj = createObject("java","com.drew.imaging.jpeg.JpegMetadataReader");
metadata = metadataReaderObj.readMetadata(createObject("java","java.io.File").init(expandPath("./")&photoname));
//write out the exif data
directories = metadata.getDirectoryIterator();
while (directories.hasNext()){
    directory = directories.next();
    tags = directory.getTagIterator();
    while (tags.hasNext()) {
         tag = tags.next();
         //WriteOutput(tag.getTagName()&': '&tag.getDescription()&'');
        WriteOutput(tag.toString()&'');
    }
}

Output:
[Exif] Make - Apple
[Exif] Model - iPhone
[Exif] Orientation - Top, left side (Horizontal / normal)
[Exif] X Resolution - 72 dots per inch
[Exif] Y Resolution - 72 dots per inch
[Exif] Resolution Unit - Inch
[Exif] Software - Adobe Photoshop CS3 Windows
[Exif] Date/Time - 2008:04:15 09:15:42
[Exif] F-Number - F2.8
[Exif] Date/Time Original - 2007:11:08 12:26:54
[Exif] Date/Time Digitized - 2007:11:08 12:26:54
[Exif] Color Space - sRGB
[Exif] Exif Image Width - 1200 pixels
[Exif] Exif Image Height - 900 pixels
[Exif] Unknown tag (0xa500) - 2.2
[Exif] Compression - JPEG (old-style)
[Exif] Thumbnail Offset - 442 bytes
[Exif] Thumbnail Length - 8874 bytes
[Exif] Thumbnail Data - [8874 bytes of thumbnail data]
[Iptc] Directory Version - -23
[Jpeg] Data Precision - 8 bits
[Jpeg] Image Height - 900 pixels
[Jpeg] Image Width - 1200 pixels
[Jpeg] Number of Components - 3
[Jpeg] Component 1 - Y component: Quantization table 0, Sampling factors 1 horiz/1 vert
[Jpeg] Component 2 - Cb component: Quantization table 1, Sampling factors 1 horiz/1 vert
[Jpeg] Component 3 - Cr component: Quantization table 1, Sampling factors 1 horiz/1 vert

Conclusion

In summary, it appears that there is not a consistent and reliable way to determine the resolution of an image using CF. I am aware that there are external tools and libraries outside of Java and CF that I could use to obtain this information, such as using imageMagik, however, I have not attempted any of these approaches.

Maybe I am wrong with my approaches and implementations, and would love to hear any suggestions, thoughts, and experiences you have had with EXIF metadata or determining the resolution of an image using ColdFusion.

This entry was posted on Thursday, April 17th, 2008 at 10:55 am.
Categories: ColdFusion, ColdFusion 8, Java.

No Comments, Comment or Ping

Reply to “Jpeg resolution via EXIF Metadata”