Saturday, January 8, 2011

GETTING METADATA INFORMATION FROM YOUR PICTURES WITH GDI+


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.

Again GDI+ makes our lives easier, providing to us a function to get these information : GetPropertyItem, stored in the GpImage class from _gdiplus.vcx .
Download and execute this file, and select an image from any Digital Camera, and you'll see all metadata stored in it.
On the first part of this code, I get the most common properties, from the GpImage class, like ImageWidth, ImageHeight, HorizontalResolution, VerticalResolution and PixelFormat.
On the rest I retrieve the metadata from the image file, 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.
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

It is possible to get some other really cool information from pictures.
Take a look at the 2 last items in the picture, ExifLightSource and ExifFlash. In both cases we have a zero value. Check this table, to see what each possible value could mean :
TagID : 0x9208 (37384) - LightSource int16u ExifIFD        
     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                                               

TagID : 0x9209 (37385) - Flash int16u ExifIFD              
   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 this link you can find some other great information about Metadata tags :
http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
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.
You can download the sample file from here http://weblogs.foxite.com/cesarchalom/attachment/1252.ashx

kind of announcement

I can't find delete button here. BLOGSPOT developers pls. do some adjustments. Users need UCSD. 

GETTING METADATA INFORMATION FROM YOUR PICTURES WITH GDI+


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.

Again GDI+ makes our lives easier, providing to us a function to get these information : GetPropertyItem, stored in the GpImage class from _gdiplus.vcx .
Download and execute this file, and select an image from any Digital Camera, and you'll see all metadata stored in it.
On the first part of this code, I get the most common properties, from the GpImage class, like ImageWidth, ImageHeight, HorizontalResolution, VerticalResolution and PixelFormat.
On the rest I retrieve the metadata from the image file, 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.
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

It is possible to get some other really cool information from pictures.
Take a look at the 2 last items in the picture, ExifLightSource and ExifFlash. In both cases we have a zero value. Check this table, to see what each possible value could mean :
TagID : 0x9208 (37384) - LightSource int16u ExifIFD        
     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                                               

TagID : 0x9209 (37385) - Flash int16u ExifIFD              
   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 this link you can find some other great information about Metadata tags :
http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
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.
You can download the sample file from here http://weblogs.foxite.com/cesarchalom/attachment/1252.ashx

Monday, December 27, 2010

Extracting Image Properties in C#

One of the little tasks I have is maintaining the Thistle Society web site and that includes adding photos to the gallery pages.  I hold information about each picture in an XML file including the original file name, caption and date taken.   Up until now I have been entering all this information manually in the XML but I wanted to try to automatically extract the dates from the photos so I wrote a little application to make it easy to load and edit the XML file. 
Having read up on image file formats in Wikipedia and other places I know that all the information I need is will be stored in the image files as image metadata including the Date Created.  However, I tried parsing the file details myself – a long and complex job as there are several different versions in different image file formats.  So, when I found that GDI+ on Windows handles most of the messy bits I was somewhat relieved.
Starting with a new addition to the Kajabity Tools which provides all the file open/save/close functionality (it will be in the next release, coming soon) it is quite easy to build the application and display all the image details in a ListView (in Details mode).  Handling the Item Activated event I open a dialog to view and edit the original image file and stored details.
I added a PictureBox control to load and display the image and a small ListView control (Details mode) to display all of the metadata (PropertyItems).  Small problem when I initially tried this – the image hadn’t loaded so I got an exception that the object didn’t exist.  This was easily resolved by handling the PictureBox LoadCompleted event and filling the property list then – the columns are the property Id, property type, property length and property value. 
void PictureBoxLoadCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
    try
    {
        // The PictureBox control is pictureBox - get all the PropertyItems.
        PropertyItem[] propItems = pictureBox.Image.PropertyItems;

        foreach ( PropertyItem propItem in propItems )
        {
            ListViewItem item = new ListViewItem( "0x" + propItem.Id.ToString( "x" ) );
            item.SubItems.Add( new ListViewItem.ListViewSubItem( item, propItem.Type.ToString() ) );
            item.SubItems.Add( new ListViewItem.ListViewSubItem( item, propItem.Len.ToString() ) );

            // Code here to extract the value of the property item values...

            // The ListView control is listImageProperties.
            listImageProperties.Items.Add( item );
        }
    }
    catch( Exception ex )
    {
        MessageBox.Show( this, ex.Message, "Error loading image metadata" );
    }
}
The property value is a byte array whose contents depend on the property id and must be interpreted according to the type and length.  GDI+ uses a fixed set of standardised property codes – so once you know the one(s) you need, it’s easy to fetch them.  The property I need has Id 0×0132 (PropertyTagDateTime) which has a data type of 2 (PropertyTagTypeASCII – a text string) and a length of 20.
The sample code below shows how to fetch the creation date property and convert it to a DateTime.
try
{
    // GDI+ provides standardised definitions!
    // http://msdn.microsoft.com/en-us/library/ms534415(VS.85).aspx
    PropertyItem propItem = pictureBox.Image.GetPropertyItem( 0x132 );
    if( propItem != null )
    {
        System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
        string text = encoding.GetString( propItem.Value, 0, propItem.Len - 1 );

        CultureInfo provider = CultureInfo.InvariantCulture;
        DateTime dateCreated = DateTime.ParseExact( text, "yyyy:MM:d H:m:s", provider );

        Debug.WriteLine( "converted " + text + " to " + dateCreated.ToString() );
    }
    else
    {
        Debug.WriteLine( "No Property Found" );
    }
}
catch( Exception ex )
{
    Debug.WriteLine( "Error: " + ex.Message );
}
First, the code shows how to retrieve the value of the property with a special twist – the strings appear to end with a zero byte (like a C style string) so you need to exclude the last byte.
Next, I needed to convert the text to a DateTime.  The strings appear to all be formatted using colons – for example “2008:09:27 22:15:35”.  I used the DateTime ParseExact method to convert it to a DateTime.  Job done.
Of course there was one final problem I had to resolve – many of the pictures were taken on cameras where the date hadn’t been set – so I’m back to manually editing them.  Still, it’s a lot easier with a DateTimePicker control.
Technorati Tags: ,,
 
Source: http://www.kajabity.com/index.php/2010/01/extracting-image-properties-in-c-2/

Friday, December 24, 2010

METADATA using GDI+

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+).
Class Description
Image Class
PropertyItems PropertyGets all the property-items stored in the Image
GetPropertyItem MethodGets the specified property-item from the Image
SetPropertyItem MethodStores a property-item in the Image
Bitmap Class
PropertyItems PropertyGets all the property-items stored in the Image
GetPropertyItem MethodGets the specified property-item from the Image
SetPropertyItem MethodStores a property-item in the Image
PropertyItem Class
Id Property Gets or sets the ID of the property
Len Property Gets or sets the length (in bytes) of the Value property
Type Property Defines the type of data contained in the Value property
Value Property Gets or sets the value of the property-item


The Bitmap PropertyItems Property, GetPropertyItem Method and SetPropertyItem Method are inherited from Image. In practice you can work with Image or Bitmap as desired.
The PropertyItems Property returns an array with PropertyItem objects.
There are more than 200 different property-items. Each item has a specific name, and the properties id, Type (the data-type in Value), Value and Len (the length of the data in Value).

Some examples of property-items:


     Table 2 - Image metadata property-items (GDI+).
PropertyType name
[Constant]
Id
[hex]
Type name
[Constant]
Description
PropertyTagImageWidth 0x0100 PropertyTagTypeShort
or PropertyTagTypeLong
Number of pixels per row
PropertyTagImageHeight 0x0101 PropertyTagTypeShort
or PropertyTagTypeLong
Number of pixel rows
PropertyTagDocumentName 0x010D PropertyTagTypeASCII Name of document from which image was scanned
PropertyTagImageDescription 0x010E PropertyTagTypeASCII The title of the image
PropertyTagDateTime 0x0132 PropertyTagTypeASCII Date and time the image was created
PropertyTagArtist 0x013B PropertyTagTypeASCII Name of the person who created the image
PropertyTagImageTitle 0x0320 PropertyTagTypeASCII The title of the image
PropertyTagIndexTransparent 0x5104 PropertyTagTypeByte Index of transparent color in GIF
PropertyTagCopyright 0x8298 PropertyTagTypeASCII Copyright information


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+).
Type [Constant] Value
[dec]
Description
PropertyTagTypeByte 1An array of bytes
PropertyTagTypeASCII 2A null-terminated ASCII string
PropertyTagTypeShort 3An array of unsigned short (16-bit) integers
PropertyTagTypeLong 4An array of unsigned long (32-bit) integers
PropertyTagTypeRational 5An array of pairs of unsigned long integers
PropertyTagTypeUndefined6An array of bytes of any data type
PropertyTagTypeSLONG 7An array of signed long (32-bit) integers
PropertyTagTypeSRational10An array of pairs of signed long integers

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:

Open an image file and get access to the metadata.
Read all the metadata from the file to get an overview.
Get a specific piece of metadata (a property-item) and read or change its value.
Add property-items if they do not exist.
Remove property-items from the metadata.
Save an image file after changing the metadata.

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.

Do not copy code from these pages for use in Visual Studio. There might be some small differences compared with the code in the download and spaces and returns are added. Please use the code from the download.

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.
// Open bitmap from file.
 public Bitmap File_OpenImage(string sFileName)
 {
     Bitmap oBitmap = new Bitmap(sFileName);
     return oBitmap;
 }


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.
// ---------------------------------------------------------------
 // Date      110708
 // Purpose   Open a bitmap from file.
 // Entry     sFileName - Filename of the image (+ path).
 // Return    The Bitmap from the file.
 // Comments  The file is unlocked.
 // ---------------------------------------------------------------
 public Bitmap File_OpenImage(string sFileName)
 {
     // Open bitmap from file.
     Bitmap oBitmap = new Bitmap(sFileName);
     // Get all metadata.
     PropertyItem[] propItems = oBitmap.PropertyItems;
     // Unlock by making a copy (metadata is lost here).
     Bitmap oBitmap2 = new Bitmap(oBitmap);
     // Copy original metadate to new bitmap.
     for (int i = 0; i < propItems.Length; i++)
     {
         oBitmap2.SetPropertyItem(propItems[i]);
     }
     // Dispose original bitmap.
     oBitmap.Dispose();
     // Return copy.
     return oBitmap2;
 }


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
// ---------------------------------------------------------------
 // Date      110708
 // Purpose   Get info from all the property-items in an image.
 // Entry     oBitmap - The bitmap from the image.
 // Return    A single string with the property-items info.
 // Comments  The PropertyItem Value bytes are converted to string. 
 // ---------------------------------------------------------------
 public string Image_GetPropertyItemsInfo(Bitmap oBitmap)
 {
     string s = "";
     string r = "\r\n";
     string sID;

     PropertyItem[] propItems = oBitmap.PropertyItems;

     s += "Number of items: " + propItems.Length.ToString() + r;
     s += r;
     for (int i = 0; i < propItems.Length; i++)
     {
         s += "Item: " + i.ToString() + r;        // Item number in PropertyItem[].
         sID = propItems[i].Id.ToString("x2");                // Hex.
         if (sID.Length == 1) sID = "0x000" + sID;
         if (sID.Length == 2) sID = "0x00" + sID;
         if (sID.Length == 3) sID = "0x0" + sID;
         if (sID.Length == 4) sID = "0x" + sID;
         s += "ID: " + sID + r;                               // ID, format 0xHHHH.
         s += "Type: " + propItems[i].Type.ToString() + r;    // Type (1..10).
         s += "Length: " + propItems[i].Len.ToString() + r;   // Length (bytes).
         s += r;
     }
     return s;
 }


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.
Item: 0
 ID: 0x0301
 Type: 5
 Length: 8

 Item: 1
 ID: 0x010e
 Type: 2
 Length: 4

 Item: 2
 ID: 0x0131
 Type: 2
 Length: 16


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).

If the image has no property-items or if the image format does not support property-items, PropertyItems returns an empty array (that is, an array of length zero).
Property-item ID's are usually expressed in hexadecimal form, e.g. "0x010E", with a prefix "0x" indicating that this number is hex and "010E" the actual value.
Hexadecimal numbers are 16-based, so "010E" converted to decimal is
0*(16^3) + 1*(16^2) + 0*(16^1) + 14*(16^0) = 270.
The meta-data (in PropertyItems) has no reference to a specific file or bitmap, it can be copied from one bitmap to another and it can exist as stand-alone object.

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.
// ---------------------------------------------------------------
 // Date      110708
 // Purpose   Get value of the PropertyItem with 
 //           ID = PropertyTagImageDescription (0x010E) of an image.
 // Entry     oBitmap - The bitmap from the image.
 // Return    The vallue from the PropertyItem (as string).
 // Comments  PropertyTagImageDescription. 0x010E = 270.
 // ---------------------------------------------------------------
 public string Image_GetImageDescription(Bitmap oBitmap)
 {
     // int iH = 270;
     // MessageBox.Show(iH.ToString("x2"));

     int iID = 270;          // 0x010E = PropertyTagImageDescription.
     ASCIIEncoding textConverter = new ASCIIEncoding();

     string sValue = "";
     PropertyItem[] propItems = oBitmap.PropertyItems;
     for (int i = 0; i < propItems.Length; i++)
     {
         if (propItems[i].Id == iID)
         {
             sValue = textConverter.GetString(propItems[i].Value);
         }
     }
     return sValue;
 }


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.
// ---------------------------------------------------------------
 // Date      110708
 // Purpose   Set the value of the PropertyItem with 
 //           ID = PropertyTagImageDescription for an image.
 // Entry     oBitmap - The bitmap from the image.
 //           sImageDescription - The value for the PropertyItem.
 // Return    None (Bitmap is returned by ref)
 // Comments  When the PropertyItem with ID = PropertyTagImageDescription
 //           exists, the value of the item is replaced.
 //           When the PropertyItem does not exist, it is created and
 //           added to the image.
 // ---------------------------------------------------------------
 public void Image_SetImageDescription(ref Bitmap oBitmap, string sImageDescription)
 {

     int iID = 270;          // 0x010E = PropertyTagImageDescription.
     bool bItemExists = false;

     PropertyItem[] propItems = oBitmap.PropertyItems;

     // Set the value for an existing PropertyItem with 
     // ID = PropertyTagImageDescription.
     for (int i = 0; i < propItems.Length; i++)
     {
         if (propItems[i].Id == iID)
         {
             byte[] bytValue;
             ASCIIEncoding textConverter = new ASCIIEncoding();
             bytValue = textConverter.GetBytes(sImageDescription);
             propItems[i].Value = bytValue;
             oBitmap.SetPropertyItem(propItems[i]);
             bItemExists = true;
             break;
         }
     }

     // Create a new PropertyItem with ID = PropertyTagImageDescription (0x010E)
     // when such an item does not exist.
     if (bItemExists == false)
     {
         int iType = 2;              // = ASCII string.
         string sValue = sImageDescription;
         PropertyItem oItem = Image_CreateNewPropertyItem(iID, iType, sValue);
         // Add this new item to the image.
         oBitmap.SetPropertyItem(oItem);
     }
 }


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.
// ---------------------------------------------------------------
 // Date      140607
 // Purpose   Convert Base64 code to Bitmap.
 // Entry     sBase64 - The Base64 code (as single string).
 // Return    The bitmap.
 // Comments  
 // ---------------------------------------------------------------
 private Bitmap Base64_To_Bitmap(string sBase64)
 {
     Byte[] bytData = Convert.FromBase64String(sBase64);
     MemoryStream ms = new MemoryStream(bytData);
     Bitmap oB = new Bitmap(ms);
     ms.Close();
     ms.Dispose();
     return oB;
 }

 // Icon Base_8_8.png (8x8).
 private string Icon_Base_8_8 =
 "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNSR0IArs4c6QAAAARnQU1BAACx" +
 "jwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABZJREFU" +
 "KFNj/A8EDPgASAE+wDAsFAAAxQLjHkhOEUMAAAAASUVORK5CYIIAAAAAAAAAAAAAAAAAAAAAAAAA" +
 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";


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.
// ---------------------------------------------------------------
 // Date      110708
 // Purpose   Create a new PropertyItem from scratch.
 // Entry     iId - The PropertyItem ID.
 //           iType - The PropertyItem type.
 //           sValue - The PropertyItem value (as string).
 // Return    A new PropertyItem object.
 // Comments  The first PropertyItem from a dummy-image is used
 //           for making a new item.
 // ---------------------------------------------------------------
 public PropertyItem Image_CreateNewPropertyItem(int iId, int iType, string sValue)
 {
     ASCIIEncoding textConverter = new ASCIIEncoding();
     Bitmap oBitmap = Base64_To_Bitmap(Icon_Base_8_8);
     PropertyItem[] propItems = oBitmap.PropertyItems;
     if (propItems.Length != 0)
     {
         propItems[0].Id = iId;                  // 0x010E = PropertyTagImageDescription.
         propItems[0].Type = (short)iType;       // Null-terminated ASCII string.
         propItems[0].Value = textConverter.GetBytes(sValue);
         propItems[0].Len = sValue.Length;       // Length (+1 ?).
         return propItems[0];
     }
     else
     {
         return null;
     }
 }


Notice that this function is not quite universal: there is always a specific relationship between the properties of the PropertyItem depending on the item ID. In this case a PropertyTagImageDescription is assumed.

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.
// ---------------------------------------------------------------
 // Date      110708
 // Purpose   Save a bitmap to file.
 // Entry     oBitmap - The Bitmap to save.
 //           sFileName - Filename of the image (+ path).
 // Return    true if successful.
 // Comments  
 // ---------------------------------------------------------------
 public bool File_SaveImage(Bitmap oBitmap, string sFileName)
 {
     try
     {
         if (File.Exists(sFileName))
         {
             File.SetAttributes(sFileName, FileAttributes.Normal);
         }
         oBitmap.Save(sFileName);
         return true;
     }
     catch
     {
         return false;
     }
 }


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.

Saving Images & Image Format Properties in GDI+

This article has been excerpted from book "Graphics Programming with GDI+".

Now we move to the Save File menu item. It allows you to save images in different file formats.

The Image class provides the Save method, which is used to save images to a specified format. The Save method takes a fine name (as string type) or a stream (a stream object), and a specified format of type ImageFormat class. Table 7.4 describes the properties of the ImageFormat class.

Note: The Emf and Wmf properties in the ImageFormat enumeration do not save a real metafile, but save the bitmap as one metafile record. It will still be a bitmap.

TABLE 7.4: ImageFormat properties
Property
Description
Bmp
Specifies BMP format.
Emf
Specifies EMF (Enhanced Metafile Format).
Exif
Specifies EXIF format.
Gif
Specifies GIF format.
Guid
Specifies a GUID structure that represents the ImageFormat object.
Icon
Specifies Windows icon format.
Jpeg
Specifies JPEG format.
MemoryBmp
Specifies memory bitmap format.
Png
Specifies PNG format.
Tiff
Specifies Tiff format.
Wmf
Specifies WMF (Windows Metafile Format).
Now we add code for the SaveFileMenu click event handler, as shown in Listing 7.3. We use SaveFileDialog, which lets us specify the file name and saves an image using the format specified in the dialog. We read the extension of the file name entered by the user, and on that basis we pass the ImageFormat property in the Save method.

Note: The ImageFormat enumeration is defined in the System.Drawing.Imaging namespace. Don't forget to add a reference to this namespace in your application.

LISTING 7.3: Using the Save method to save images

private void SaveFileMenu_Click (object sender System.EventArgs e)
{
            //If image is created
            if (curImage == null)
                return;

            //Call SaveFileDialog
            SaveFileDialog saveDlg = new SaveFileDialog();
            saveDlg.Title = "Save Image As";
             saveDlg.OverwritePrompt = true;
             saveDlg.CheckPathExists = true;
            saveDlg.Filter =
            "Bitmap File (*.bmp) | *.bmp |" +
            "Gif File (*.gif) | *.gif | " +
            "JPEG File (*.jpg) | *.jpg" +
            "PNG File (*.png) | *.png";
             saveDlg.ShowHelp = true;

            //If selected, save
            if (saveDlg.ShowDialog() == DialogResult.OK)
            {
                //Get the user-selected file name
                string fileName = saveDlg.FileName;

                //Get the extension
                string strFilExtn =
                 fileName.Remove(0, fileName.Length - 3);

                //Save file
                switch (strFilExtn)
                {
                    case "bmp":
                         curImage.Save(fileName, ImageFormat.Bmp);
                        break;
                    case "jpg":
                         curImage.Save(fileName, ImageFormat.Jpeg);
                        break;
                    case "gif":
                         curImage.Save(fileName, ImageFormat.Gif);
                        break;
                    case "tif":
                         curImage.Save(fileName, ImageFormat.Tiff);
                        break;
                    case "png":
                         curImage.Save(fileName, ImageFormat.Png);
                        break;
                    default:
                        break;
                }
            }
}

Now we write code for the ExitMenu click event handler. This menu simply closes the application. Hence we call the Form.Close method on this event handler, as shown in Listing 7.4.

LISTING 7.4: The ExitMenu click event handler

private void ExitMenu_Click (object sender, System.EventArgs e)
{
             this.Close();
}

Retrieving Image Properties

Table 7.2 listed the Image class properties. Now we will read and display the properties of an image. We add a Properties menu item to the main menu and write the code in Listing 7.5 as this menu click event handler. We read the size, format, resolution, and pixel format of an image.

LISTING 7.5: Getting image properties

    private void PropertiesMenu_Click (object sender, System.EventArgs e)
{
        if (curImage!=null)
        {
        //Viewing image properties
        string imageProperties = "Size:" + curImage.Size;
        imageProperties +=",\n RawFormat:"+ curImage.RawFormat.ToString();
        imageProperties +=",\n Vertical Resolution:"+ curImage.VerticalResolution.ToString();
        imageProperties +=",\n Horizontal Resolution:"+ curImage.HorizontalResolution.ToString();
        imageProperties +=",\n PixelFormat:"+ curImage.PixelFormat.ToString();
         MessageBox.Show(imageProperties);
        }
}

Figure 7.6 shows the properties of an image.

Figure 7.6.gif

Simplify access to an image metadata using GDI+ in C#

Introduction

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.

In .NET Framework, you can store and retrieve metadata by calling the SetPropertyItem and GetPropertyItem methods of the Image class, and you don't have to be concerned with the details of how a particular file format stores that metadata.
Another question is how can you use this data. You will receive just an array of bytes, and it is your problem how you interpret it. This library will simplify access to this image metadata by converting them to ordinary .NET data types and organizing them in the form of class properties and/or hashtable.

I'd like also to thank Jeffrey S. Gangel whose article "Photo Properties" at CodeProject brought me to the idea to write this class. His program makes basically the same, the only difference is that I wanted to have image information in the form that seems to me to be more comfortable and conventional.
Not all image property tags are implemented as class properties. Those are accessible only from PropertyItems hashtable. If anyone needs this library at all and even needs the specific property, I'll be very glad to help him. But I think it is not necessary, because the whole source code is available and modifiable.

Usage


// Accessing image proprties
public void ShowEquipModel() 
{ 
    Info inf=new Info("c:\\tmp\\Example.jpeg"); 
    MessageBox.Show(inf.EquipModel);
}

// Accessing image proprties as a hastable 
public void ShowAllProperties() 
{ 
    Info inf=new Info("c:\\tmp\\Example.jpeg");
    listView.Items.Clear(); 
    foreach (string propertyname in inf.PropertyItems.Keys) {
        ListViewItem item1 = new ListViewItem(propertyname,0);
        item1.SubItems.Add((inf.PropertyItems[propertyname]).ToString());
        listView.Items.Add(item1); 
    } 
}
The properties of the Info class are listed below:
  • Brightness Brightness value. The unit is the APEX value. Ordinarily, it is given in the range of -99.99 to 99.99.
  • Copyright Copyright information.
  • DateTime Date and time the image was created.
  • DTDigitized Date and time when the image was stored as digital data. If, for example, an image was captured by DSC and at the same time the file was recorded, then DateTimeOriginal and DateTimeDigitized will have the same contents.
  • DTOrig Date and time when the original image data was generated. For a DSC, the date and time when the picture was taken.
  • EquipMake The manufacturer of the equipment used to record the image.
  • EquipModel The model name or model number of the equipment used to record the image.
  • ExposureProg Class of the program used by the camera to set exposure when the picture is taken.
  • FNumber F number.
  • FocalLength Actual focal length, in millimeters, of the lens. Conversion is not made to the focal length of a 35 millimeter film camera.
  • Image Sets or returns the current Image object.
  • ISOSpeed ISO speed and ISO latitude of the camera or input device as specified in ISO 12232.
  • MeteringMode Metering mode.
  • Orientation Image orientation viewed in terms of rows and columns.
  • PixXDim Type is PropertyTagTypeShort or PropertyTagTypeLong. Information specific to compressed data. When a compressed file is recorded, the valid width of the meaningful image must be recorded in this tag, whether or not there is padding data or a restart marker. This tag should not exist in an uncompressed file.
  • PixYDim Type is PropertyTagTypeShort or PropertyTagTypeLong. Information specific to compressed data. When a compressed file is recorded, the valid height of the meaningful image must be recorded in this tag whether or not there is padding data or a restart marker. This tag should not exist in an uncompressed file. Because data padding is unnecessary in the vertical direction, the number of lines recorded in this valid image height tag will be the same as that recorded in the SOF.
  • PropertyItems Returns a HashTable of all available properties of a given Image. Keys of this HashTable are display names of the Property Tags, and values are transformed (typed) data.
  • ResolutionUnit Unit of measure for the horizontal resolution and the vertical resolution. 2 - inch 3 - centimeter.
  • XResolution Number of pixels per unit in the image width (x) direction. The unit is specified by PropertyTagResolutionUnit.
  • YResolution Number of pixels per unit in the image height (y) direction. The unit is specified by PropertyTagResolutionUnit.

    **The primary source of information to write this class was MSDN.
    MSDN Home > MSDN Library > Graphics and Multimedia > GDI+ > GDI+ Reference > Constants