Search code examples
c#.netimageexif

Not all image properties retrieved with Image.PropertyItems


I am trying to get a list of all the EXIF tags in a given image. using the Image.propertyItems[] array, I can enumerate all of the items that instance of the image object reads, and output their ID.

I see there are items that ARE NOT listed there, that other applications including windows explorer seem to have no issue retrieving.

E.G. in the following code...

Image img = new Bitmap("C:\\IMAG0648.jpg");
foreach (PropertyItem property in img.PropertyItems)
  {
    Trace.WriteLine(property.Id);
  }

I get a listing of most of the items, specifically though I am missing (among possibly others) the 0x010F and 0x0110. I know these items are there because I can open the same image in another EXIF editor like PhotoME, and they are clearly there, and clearly labeled as the correct ID.

Are there nesting levels in the EXIF and I am not walking out far enough, or is there some reason this would not work in its basic form above?


Solution

  • Try ExifLib - there is a demo project with source that extracts all tags from an image that you could use.

    Edit: I'm going to append my answer to explain why I believe the .Net Image class doesn't handle all EXIF tags as we might expect. However, I'm going to leave my initial link and suggestion above as I believe it's the best option in the end.

    Ok, on to the Why:

    My testing described below used images where Windows 7 showed EXIF data for make and model (by right clicking on file > Properties > 'Details' tab > 'Camera' section), but in .Net the PropertyItems do not contain these same EXIF tags. Images taken with the HTC One were mentioned in another answer, so I found images that satisfy my test here. (Specifically, I used original-2.jpg, original-4.jpg, and original-8.jpg from this webpage.)

    First, I looked at img.PropertyItems.Count() and got 32. So there is data in there! (Clearly, we know Windows 7 in my case is seeing the EXIF tags). But, when I iterate over the .PropertyItems and look at each Id (per your original question):

    • I get 32 results
    • I don't get resulting Ids of 271 (0x010F - Camera Make) or 272 (0x0110 - Camera Model)
    • I did get 282, 283, 296, 305, 306, and 27 other 5 digit ids
    • If I use img.PropertyItems.Single(x => x.Id == 305).Value I do get the SoftwareUsed (or if I use 206 I get the DateTime) EXIF property

    So, to your point, why is the .Net Image class not seeing the EXIF tags with Ids of 271/272 (again, Camera Make/Model)?

    I dug into ExifLib's source code (link at top of answer) a little bit and think I see what's happening:

    • If you look at the private byte[] GetTagBytes(...) method in ExifReader.cs (line 655) you'll see that byte offsets are used to find the 'Exif IDF' and total space per tag
    • This method returns a specific byte array that is then converted to the proper data type
    • If that type is an ASCII string - null characters are removed and date formats are adjusted

    So it would seem that the .Net Image class is able to return the EXIF tag properly via PropertyItems when there aren't any byte offsets in play.

    Using ExifDataView (http://www.nirsoft.net/utils/exif_data_view.html) I was able to validate this:

    • I found I was always able to use .Net to properly retrieve Make and Model when the value length is 4 ("HTC" + null character)
    • I was never able to use .Net when the length was 5 ("HTC" + null + null?)
    • In my test above (with the original-4.jpg image) the SoftwareUsed and DateTime tags worked because the length of these tags were (string length) + 1 for the null character. The Camera Make tag length was (string length) + 2 and didn't work. This seems to thrown everything off.

    The ExifLib code handles these offsets and/or multiple null characters correctly and .Net does not.

    The result is the .Net Image class doesn't iterate over these properties as part of PropertyItems and returns a 'Sequence contains no matching element' when using image.PropertyItems.Single(x => x.Id == tagMake).Value

    Bottom line: use ExifLib or some other 3rd party library designed to handle extracting EXIF data if you want to it work on all images properly. Use the .Net Image class if you want it to work some of the time when the bytes are just right :-)