Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Articles
Search: 

Saving and retrieving metadata information from your pictures with GDI+
Cesar Chalom, April 1, 2006
Did you know that all the JPEGs from your digital camera contain a lot of extra information? We can easily retrieve some interesting information such as: Title, Equipment Make, Camera Model, Shutter Speed, Lens Aperture, Flash Mode, Date of Picture, and much more! These metadata "tags" are stored in...
Summary
Did you know that all the JPEGs from your digital camera contain a lot of extra information? We can easily retrieve some interesting information such as: Title, Equipment Make, Camera Model, Shutter Speed, Lens Aperture, Flash Mode, Date of Picture, and much more! These metadata "tags" are stored in a JPEG file to indicate various camera settings and picture taking conditions that occurred while creating the photo. Several image file formats enable you to store metadata along with an image, such as JPEG, TIFF and PNG.
Description
Did you know that all the JPEGs from your digital camera contain a lot of extra information?

Introduction

We can easily retrieve some interesting information such as: Title, Equipment Make, Camera Model, Shutter Speed, Lens Aperture, Flash Mode, Date of Picture, and much more! These metadata "tags" are stored in a JPEG file to indicate various camera settings and picture taking conditions that occurred while creating the photo. Several image file formats enable you to store metadata along with an image, such as JPEG, TIFF and PNG.

BMPs can store very restrict metadata, as you can see here: http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/BMP.html

The FFC class

The FFC class _gdiplus.vcx from VFP9 makes our lives easier, providing functions to retrieve these informations: GetPropertyCount, GetPropertyIdList and GetPropertyItem, stored in the GpImage class from _gdiplus.vcx .

Run the code below, and select an image from any Digital Camera, and you'll see all metadata stored.

lcSource = GETPICT()
LOCAL loImage AS GpImage OF ffc/_gdiplus.vcx
loImage = NEWOBJECT("GpImage", HOME() + "ffc/_gdiplus.vcx")
loImage.CreateFromFile(lcSource)

DIMENSION raPropIDList(1)
LOCAL nCount, n, lcTagName, lnProp, luProp

nCount = loImage.GetPropertyIdList(@raPropIDList)
FOR n = 1 TO nCount
   lnProp = raPropIDList(n)
   luProp = loImage.GetPropertyItem(lnProp)
   ? TRANSFORM(lnProp), TRANSFORM(luProp)
ENDFOR
The metadata from the image file can be retrieved using GetPropertyIdList and GetPropertyItem. It's important to notice that GetPropertyIdList receives an array as a parameter, and returns that array populated with the metadata. The PropertyID's are constants, that can be found at Craig Boyd's Blog, at http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,2d3a877d-3502-4650-bff3-c06c736dc177.aspx . At that link you can find almost all constants that GDI+ uses. For the metadata specific case, look for the constants between "PropertyTagTypeByte" and "PropertyTagGpsDestDistRef". To make our lives easier, I've prepared a table with all these and some more that I've found. Look for metatags.dbf in the file attached from this article.

Tag ID 0x9208 (37384) - LightSource
1 Daylight
2 Fluorescent
3 Tungsten
4 Flash
9 Fine Weather
10 Cloudy
11 Shade
12 Daylight Fluorescent
13 Day White Fluorescent
14 Cool White Fluorescent
15 White Fluorescent
17 Standard Light A
18 Standard Light B
19 Standard Light C
20 D55
21 D65
22 D75
23 D50
24 ISO Studio Tungsten
255 Other

Tag ID 0x9209 (37385) - Flash
0x0 No Flash
0x1 Fired
0x5 Fired, Return not detected
0x7 Fired, Return detected
0x9 On
0xd On, Return not detected
0xf On, Return detected
0x10 Off
0x18 Auto, Did not fire
0x19 Auto, Fired
0x1d Auto, Fired, Return not detected
0x1f Auto, Fired, Return detected
0x20 No flash function
0x41 Fired, Red-eye reduction
0x45 Fired, Red-eye reduction, Return not detected
0x47 Fired, Red-eye reduction, Return detected
0x49 On, Red-eye reduction
0x4d On, Red-eye reduction, Return not detected
0x4f On, Red-eye reduction, Return detected
0x59 Auto, Fired, Red-eye reduction
0x5d Auto, Fired, Red-eye reduction, Return not detected
0x5f Auto, Fired, Red-eye reduction, Return detected

At these links you can find some other great information about Metadata tags: http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/GDIPlusreference/constants/imagepropertytagconstants/propertyitemdescriptions.asp

It is obviously possible to remove, change or set the metadata property items from images with GDI+ too, but for this task, it is necessary use some extra code, once _gdiplus.vcx doesn't bring this feature.

Several image file formats enable you to store metadata along with an image. Metadata is information about an image, for example, title, width, camera model, and artist. Microsoft Windows GDI+ provides a uniform way of storing and retrieving metadata from image files in the Tagged Image File Format (TIFF), JPEG, Portable Network Graphics (PNG), and Exchangeable Image File (EXIF) formats.

In GDI+, a piece of metadata is called a property item, and an individual property item is identified by a numerical constant called a property tag. You can store, retrieve or delete metadata by calling the SetPropertyItem, GetPropertyItem and RemovePropertyItem methods of the Image class.

But as they are present in Gdiplus.dll (GdipRemovePropertyItem and GdipSetPropertyItem), we can create some code to manage them.

First of all, I've created a subclass from GpImage, the image class from _gdiplus.vcx, and added these two procedures: RemovePropertyItem and SetPropertyItem. To remove a property, all we need to know is the property ID, and call the procedure.

But to set a property, a little bit of knowledge is needed. You can find good information about the properties at MSDN, in http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/GDIPlusreference/constants/imagepropertytagconstants/propertyitemdescriptions.asp

Every property has its own property type, that can be one of these, depending on the type of metadata stored: Byte, ASCII, Short, Long, Rational, SLONG and SRational. We can use these tags to store some information about the images created, like author, software, description, date of creation, etc. Some of the most common tags are:

Tag ID in hexadecimal Tag ID in decimal Title Type
0x9c9b (40091) XPTitle Byte
0x9c9c (40092) XPComment Byte
0x9c9d (40093) XPAuthor Byte
0x9c9e (40094) XPKeywords Byte
0x9c9f (40095) XPSubject Byte
0x010f (00271) Equip Make ASCII
0x010e (00270) Image Description ASCII
0x0131 (00305) Software ASCII
0x0132 (00306) ModifyDate ASCII
0x013b (00315) Artist ASCII

We may call these procedures this way:

SET CLASSLIB TO HOME()+"ffc\_gdiplus" 
SET PROCEDURE TO MyGpImage.PRG ADDITIVE && MyGpImage.Prg contains my customized 
    && subclass of GpImage, that contains the procedures needed to work 
    && with MetaData Tags 
lcSource = GETPICT() 
LOCAL loImage AS GpImage OF ffc/_gdiplus.vcx 
loImage = CREATEOBJECT("myGpImage") 
loImage.CreateFromFile(lcSource) 
*!* Set Properties using all 3 parameters 
*!* SetPropertyItem(tnPropID, tuNewValue, tnPropType) 
*!* Check table MetaTags.DBF for the correspondant PropertyType Constant 
*!* for each PropID - field TagTypeC 
loImage.SetPropertyItem(0x9c9b, "Coral Reef in Tatuamunha", 1) && XPTitle 
loImage.SetPropertyItem(0x9c9c, "Great for diving", 1) && XPComment 
loImage.SetPropertyItem(0x9c9d, "Cesar Chalom", 1) && XPAuthor 
loImage.SetPropertyItem(0x9c9e, "Diving", 1) && XPKeywords 
loImage.SetPropertyItem(0x9c9f, "Vacations 2005", 1) && XPSubject 
loImage.SetPropertyItem(271,"Cesar Chalom", 2) && Equip Make 
loImage.SetPropertyItem(270,"Coral Reef In Tatuamunha", 2) && Image Description 
loImage.SetPropertyItem(305,"Visual FoxPro", 2) && Software 
loImage.SetPropertyItem(306,TRANSFORM(DATETIME()),2) && ModifyDate 
loImage.SetPropertyItem(315,"Cesar Chalom", 2) && Artist 
loImage.SetPropertyItem(274, 4, 3) && Orientation 
loImage.SetPropertyItem(37384, 1, 3) && LightSource 
loImage.SetPropertyItem(37385, 1, 3) && Flash 1 = Fired 
loImage.SetPropertyItem(33434, "1/14", 5) && ExifExposureTime 
*!* RemovePropertyItem(tnPropID) 
*!* Remove some Thumbnail Property related Items 
loImage.RemovePropertyItem(20515) 
loImage.RemovePropertyItem(20518) 
loImage.RemovePropertyItem(20519) 
*!* The Image with the modified tags needs to be saved in another name ! 
lcDestination = ADDBS(JUSTPATH(lcSource))+ "NewProp_" +; 
JUSTSTEM(lcSource)+".jpg" 
loImage.SaveToFile(lcDestination ,"image/jpeg", "quality=100") 
RETURN
Here's a brief description of the parameters from the two procedures:

RemovePropertyItem(tnPropID)

Parameter Description Required
tnPropID Property ID to remove from image (integer) Yes

SetPropertyItem(tnPropID, tuNewValue, tnPropType)

Parameter Description Required
tnPropID Property ID to add or change to Image (integer) Yes
tuNewValue New value to store
Depending on the type of metadata to store tuNewValue must be:
ASCII - String
Short, Long or SLong - Integer
Rational or SRational - String containing a fraction, eg. "1/12"
Yes
tnPropType PropertyType Constant (Integer)
if not passed, this procedure will try to guess it
No

The easiest and more secure way to change metadata is to always pass the 3 parameters to SetPropertyItem. If tnPropType is not passed, the procedure can try to guess it, first checking if the property is already stored in the image, so, before changing to the new value, it gets the property tag type from the old stored property.

I've prepared a table containing information about most of Metadata properties I've found in my searches on the web. Surely it's not complete, because there are some specific metadata tags for some brands, like Sony, Olympus, etc, but it's for sure an excellent point to start. It contains all tags provided by MSDN

If tuNewValue is a string containing the slash bar, "/", the procedure will understand that you are passing a Rational value, represented by a fraction, like "1/100". If tuNewVAlue is a string NOT containing the "/" the procedure will understand that you are passing an ASCII value. Otherwise, if you pass a numeric parameter, it'll work as Long type.

If the requested property is not defined, this method returns null (.NULL.) and the GetStatus() method returns 19 (GDIPLUS_STATUS_PropertyNotFound).

Unfortunately, it is not possible for us to open an image, make changes in the PropertyTags and save it. In my tests, _gdiplus.vcx tries to save the image, but no changes are applyed. One possible way to do it is to save the modified Image using another name, close the image object used from GpImage (loImage = NULL), delete or rename the original image file, and finally rename the file created to the original name. That's definitely not the way I dreamt, but it works.

At last you can create your own procedure to store some Metadata information in the pictures you (or your application) create, saving some important information, and also your copyrights. This will permit to you to identify very easily the pictures created by you, with no effort ! You can just run a procedure like the one I've presented here on every picture that you send, or create.

BUG

During my tests working with the property tags, I've found a very simple, but important bug in the Procedure GetPropertyItem from the class GpImage stored in _gdiplus.vcx. This procedure basically retrieves the specified property from the image metadata. It translates the retrieved data, depending on the propertytagtype. The mistake occurs when dealing with Tags from the type "Byte". The procedure originally retrieves only the ASCII code from the first character stored, instead of a string. It is very simple to reproduce this.

In Windows XP, open Windows explorer, and select any JPEG image, rightclick it, select "Properties", "Advanced", "Summary", and Windows will show the summary of the properties. Then click and fill manually some of these properties : Title, Author, Comment, Keywords or Subject. Then you can retrieve these property values using _gdiplus.vcx this way :

lcSource = GETPICT() 
LOCAL loImage AS GpImage OF ffc/_gdiplus.vcx 
loImage = NEWOBJECT("GpImage", HOME() + "ffc/_gdiplus.vcx") 
loImage.CreateFromFile(lcSource) 
LOCAL lnPropID, luProp 
lcMsg = "PropID" + CHR(9) + "Prop Value" + CHR(13) + CHR(13) 
FOR lnPropID = 40091 TO 40095 && these are the propID's modified manually 
    luProp = loImage.GetPropertyItem(lnPropID) 
    lcMsg = lcMsg + TRANSFORM(lnPropID) + CHR(9) + TRANSFORM(luProp) + CHR(13) 
ENDFOR 
MESSAGEBOX(lcMsg,64,"Incorrect Tag Retrieval")
The incorrect output, in my case was:

The propIDs responsible for the values that I've asked you to change manually are between decimals 40091 and 40095. These Ids are very interesting, Windows XP makes a lot of use of them. The value retrieved is the ASCII from every first character entered manually.

I've prepared a turnaround, changing very few code in GetPropertyItem procedure from the original class, to permit the retrieval of the full string stored in the file.

The code below uses the subclass of GpImage to fix it:

 
SET PROCEDURE TO myGpImage.prg ADDITIVE 
lcSource = GETPICT() 
LOCAL loImage AS GpImage OF ffc/_gdiplus.vcx 
loImage = CREATEOBJECT("MyGpImage") 
loImage.CreateFromFile(lcSource) 
LOCAL lnPropID, luProp 
lcMsg = "PropID" + CHR(9) + "Prop Value" + CHR(13) + CHR(13) 
FOR lnPropID = 40091 TO 40095 && these are the propID's modified manually 
    luProp = loImage.GetPropertyItem(lnPropID) 
    lcMsg = lcMsg + TRANSFORM(lnPropID) + CHR(9) + TRANSFORM(luProp) + CHR(13) 
ENDFOR 
MESSAGEBOX(lcMsg,64,"FIXED Tag Retrieval")
And the final result is:

Another slight modification was done in this procedure, allowing the retrieval of properties of type "Undefined". Originally, GetPropertyItem returned NULL for this specific case. But, as you can see in the attached table, METATAGS.DBF, there are some propertyID's that make use of it. In most cases, the values retrieved will be ununderstandable, but there may be some cases in which it will be helpful. In the example form attached, Metadata.scx, all "undefined" properties are retrieved, but you'll see that they are not understandable.

Source code

Cesar Chalom
Cesar is an independent software developer. He is an active member of brazilian Visual FoxPro groups.
More articles from this author
Cesar Chalom, June 1, 2006
Almost any application makes use of charts to display information to their users. Graphs are used for many different reasons, and can be found all over. We see them in the newspapers, magazines, and on television because they help us to communicate information. One of the commonest types of charts i...
Cesar Chalom, May 1, 2006
Certain Image types permit to save multiple images into a single file. It is possible to save several images to a single TIFF (Tagged Image File Format) file. GIF Image files also permit multi-frames, creating animated images, but is not totally supported by GDI+ version 1.
Cesar Chalom, August 1, 2006
Following previous articles on the topic, this one describes how to create some cool effects on images with the help of color matrices. Sometimes we may need to make some adjustments in the colors of an image, like to change Saturation, Contrast, Brightness, Hue, convert to Grayscales, to increase o...
Cesar Chalom, October 1, 2006
In the first part of this article, Cesar showed how we can apply some interesting effects to images with the use of color matrices and the new GdiPlus-X library. In this article, Cesar continues by showing more options to make use of some interesting applications of color matrices. This articles in...
Cesar Chalom, September 1, 2006
As a continuation of an article published in the Universal Thread Magazine May 2006 issue, entitled "Multiframe Images with GDI+", Cesar Chalom describes in this article how to manipulate TIFF images using the new GDIPlus-X library from VFP-X project. This article also shows how to create TIFFs usin...