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
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:
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
RemovePropertyItem(tnPropID)
SetPropertyItem(tnPropID, tuNewValue, tnPropType)
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 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")
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