1.0 - Introduction
Image files can contain extra information about the file, settings from a camera, geodata, and other info. This metadata is organized as a specific set of items, each with a name, an ID and a value. With the ID you can find the item and read the information or change it. There are more than 200 different items defined, but in practice only a limited number is used. Items can be added or removed.
From the .NET point of view there are two ways of addressing metadata in imges-files, with GDI+ and with the WIC (Windows Imaging Component), installed with Vista, XP SP3 or .NET Framework 3.0.
In this article we look at GDI+ and how metadata can be addressed from code, using Visual Studio 2008 and Framework 2.0 or higher.
2.0 - The metadata environment in Visual Studio The prerequisites are Visual Studio 2008 and the .NET Framework 2.0 or higer. There are only a few classes for getting access to metadata in images: the Image and Bitmap classes in System.Drawing and the PropertyItem Class in System.Drawing.Imaging. Metadata in an image has a straightforward structure, it is a simple list with items. The Image (System.Drawing) has a PropertyItems Property, this is an array with all the pieces of metadata; the PropertyItem objects. Each PropertyItem has four properties, an ID (.Id), a value (.Value), the data-type of the Value (.Type) and the length of the data in Value (.Len). The strategy for retrieving the metadata is to open the file, get the collection of PropertyItem objects with the PropertyItems property of the image and then search for a specific item with the Id. When the PropertyItem is found you can set or get the properties Id, Value, Type and Len. The supported file formats are .jpg, .tif, .png and .exif. These are the classes for working with metadata: Table 1 - Image metadata support (GDI+).
Some examples of property-items: Table 2 - Image metadata property-items (GDI+).
Other property-items are for image properties, Exif, geodata (GPS), camera settings, thumbnails, printing, colors, and more. For a complete overview see [1]. For each property-item a data-type is specified (property PropertyItem.Type). These are all the possible types: Table 3 - Image Property Tag Type Constants (GDI+).
See also [2] for a full description. With these properties, methods and constants you can make code for reading and writing metadata in images. |
3.0 - Things to Do These are the functions we want to implement: We will make a class with some universal functions. The functions will have basic functionality for showing the priciples for working with metadata. You can change the functions or extend the class yourself. |
4.0 - Open an image file and get access to the metadata Opening a file and retrieving the contents as Bitmap is as simple as this: C# - Open an image from file.
However, the file is kept open until the bitmap is disposed. This prevents us from saving the bitmap again when the meta-data is modified. This is a tricky situation and it can lead to a lot of gesswork when files refuse to save. A standard workaround is to make a copy of the original Bitmap and dispose the original bitmap: Bitmap oBitmapNew = new Bitmap(oBitmapOld); oBitmapOld.Dispose(); However, in our case, the metadata is lost in this process. So, this is our new workaround when metadata is involved: 1) Open the imagefile as Bitmap. 2) Store all the metadata from the bitmap in a PropertyItems object. 3) Make a new Bitmap with the old bitmap as template. This new Bitmap has no metadata. 4) Copy the stored metadata from the PropertyItems object to the new Bitmap. 5) Dispose the original Bitmap, the file is closed and unlocked. C# - Open an image from file, and unlock.
This already shows how to get all the metadata from the file; with the Bitmap.PropertyItems property. This is an array with all the pieces of metadata (PropertyItem objects). We will use this in the next section to get an overview of all the items in the metadata. |
5.0 - Read all the metadata from the file to get an overview It is sometimes useful for having an overview of the metadata in an image-file, especially when you are developing a metadata application. For this purpose a function is made for showing the metadata as a single text. C# - Get a list of property-items from the metadata
The File_OpenImage() function returns a Bitmap from the file and the Bitmap.PropertyItems property is used to get all the metadata as an array of PropertyItem objects. In a for-loop the properties Id, Type and Length of all the PropertyItem objects are retrieved and converted to string. This is the result: Text - Property-items in the metadata.
The ID of a property-item identifies it (hence the name); there are only pre-defined items in the metadata, each with specific properties (see Table 2 for some examples). The item with ID=0x0301 is PropertyTagGamma, the gamma value attached to the image. The item with ID=0x010e is PropertyTagImageDescription, specifies the title of the image. The item with ID=0x0131 is PropertyTagSoftwareUsed, specifies the name and version of the software or firmware of the device used to generate the image. Item values are not shown with this function, this needs a specific action depending on the data-type (see Table 3). Hexadecimal numbers are 16-based, so "010E" converted to decimal is 0*(16^3) + 1*(16^2) + 0*(16^1) + 14*(16^0) = 270. |
6.0 - Get a specific piece of metadata When a specific property-item must be changed, or you want to know if a property-item exists in the metadata, you can search it with the ID (PropertyItem.Id). This can be done in two ways, iterate the array with PropertyItem objects from the Bitmap.PropertyItems property or use the Bitmap.GetPropertyItem Method. In both cases you need the ID (PropertyItem.Id) of the item to be searched. As an example here the Image_GetImageDescription() function from the download. This function searches for property-item PropertyTagImageDescription with ID=0x010E, the title of the image. In this function the Bitmap.PropertyItems array is iterated until the item with ID=0x010E is found. C# - Get the Value of the PropertyItem with ID=0x010E.
In the download is also function Image_SetImageDescription() which sets the value of the PropertyItem. This function uses the same method. C# - Set the Value of the PropertyItem with ID=0x010E.
When the item is found, the .Value property is set with the desired text (one of the parameters of the function). This value must be a byte-array so, the text is converted to byte-array first with the textConverter. Setting the value of a property-item is not sufficient, you must use the Bitmap.SetPropertyItem() method for adding the changes to the metadata in the bitmap. This function also creates the (searched) item if it does not exist in the meta-data. This is explained below. |
7.0 - Add property-items if they do not exist When new property-items must be added to the metadata, the first thing you want to do is to make a new PropertyItem object. This is, however, not possible directly because the PropertyItem class is not inheritable. See also [3]: A PropertyItem object encapsulates a metadata property to be included in an image file. A PropertyItem object is not intended to be used (as) a stand-alone object. A PropertyItem object is intended to be used by classes that are derived from System.Drawing.Image. A PropertyItem object is used to retrieve and change the metadata of existing image files, not to create the metadata. Therefore, the PropertyItem class does not have a defined Public constructor, and you cannot create an instance of a PropertyItem object. Microsoft suggests the following (amazing) workaround: 1) Make a dummy-image with metadata and put it somewhere on disk (almost any image created in a paint program has metadata). 2) Open (from code) the file, get the metadata, and retrieve the first PropertyItem. 3) Set the Id, Len, Type and Value properties. 4) Add this new PropertyItem to the metadata of the desired image. Instead of getting an image from file I made a bitmap in memory and tried to add meta-data by setting different properties, but this did not succeed. As a compromise I converted an existing image (with metadata) to Base64 code and assigned it to a constant (in code); this is a canned image. 1) Make a small image with metadata in a paint-program and save it. 2) Open this image from code and convert the bytes to Base64 code. 3) Assign the Base64 code to a constant. 4) When you need a PropertyItem object, convert the Base64 code to Bitmap again. The advantage of this workaround is, that there are no external files. In the download you can find the functions Bitmap_To_Base64() and Base64_To_Bitmap() for doing this. The dummy-image Base_8_8.png for creating the Base64 code is also included. The image (as Base64 code) looks like this: C# - Convert an image, as Base64 code, to Bitmap.
The image (as Base64 code) and the conversion to Bitmap with Base64_To_Bitmap() are encapsulated in the Image_CreateNewPropertyItem() function which creates a new PropertyItem object from scratch: C# - Create a new PropertyItem from scratch.
In the prevous section you have seen how this function is used. |
8.0 - Remove property-items from the metadata This is not tested. There are no methods for removing property-items from metadata directly, you can only retrieve and change them. A possible solution could be: 1) Open the image file and get the image as Bitmap. 2) Get the metadata as Bitmap.PropertyItems (an array with PropertyItem objects). 3) Remove all the metadata from the Bitmap with Bitmap oBitmapNew = new Bitmap(oBitmapOld). 4) Copy only the desired items from the Bitmap.PropertyItems array to the new bitmap using the Bitmap.SetPropertyItem method.5) Save the new Bitmap to the original file. See also function File_OpenImage() described earlier, most of the code is already present. |
9.0 - Save an image file after changing the metadata Saving the Bitmap when the metadata is changed seems to be the easy part, but when the file from which the metadata is retrieved, is not closed you get an error and the file will not save. See the discussion in H 4.0. C# - Save a Bitmap to file.
|
10.0 - Hacking metadata As has been said, this system of metadata is based on (a large number of) specific items, each with a predefined purpose. In the documentation [1] is a description for each item, what the purpose is, how large it is and the type of data which is allowed. As an example, the PropertyTagDateTime item is defined as 'Date and time the image was created', the type (property PropertyItem.Type) is PropertyTagTypeASCII (text), and the size (property PropertyItem.Len) is 20 bytes. There are, however, less specific items e.g. PropertyTagImageTitle and PropertyTagImageDescription. These items also contain text, but the length of the text is not fixed. When you look at the specification, you can handle the data in a strict way, but nothing holds you back when 'incorrect' data is stored in one or more items. Most items are for text-data so you can put any text in it. You could, more specifically, decide to use Base64 code (also text), but with Base64 you can store any data which can be expressed as byte-arry: XML, HTML, binary data, scripts, styleheets, other images, encrypted data, just what you like. Before you add the metadata you can also compress it, so the impact on the size of the image can be reduced. An alternative is storing data directly as byte-array in items which have Type=1 for the data (property PropertyItem.Type). Item PropertyTagThumbnailData is a suitable candidate for this. When you look at metadata in this way, an image can be considered as a universal resourcefile or as a container for data-transport. |
11.0 - Summary Metadata in images is organized as a simple list of items, each item with an ID and a value. There are more than 200 different items possible, for defining the properties of the image, camera settings, geo-information (GPS), Exif, thumbnails, printing, colors, and more. You can read all the information, set the information for each item and add- or remove items. Approaching and changing metadata in images with GDI+ is straightforward and easy to implement with Visual Studio as is shown in this article. |
No comments:
Post a Comment