Exif Properties in .Net - Part 3 : converting properties

In the first post of this Exif series, I talked about a technique for loading Exif properties as fast as GDI+ is able to do.
The second post was dealing with adding new properties to an empty image.

Now it's time to look more precisely at the Exif properties. How is a PropertyItem structured ?
The final goal of this post is to convert the raw data exposed by GDI+ (and the .Net Framework) into more useable data types.

An Exif property consists in the following information:

  • An identifier, determining the "meaning" of the property.
  • A data type, indicating how the data are represented.
  • A length (the number of bytes the property data contains).
  • A value which is a raw array of bytes.

The Exif property data type is a 16-bit integer, but it's quite simple to build an enum type that maps the values to a more comprehensive type :

Code Copy HideScrollFull
/// <summary>
///
Defines types for Exif Properties.
/// </summary>
public enum ExifPropertyType {
/// <summary>
/// Specifies that the value data member is an array of bytes.
/// </summary>
Byte = 1,

/// <summary>
/// Specifies that the value data member is a null-terminated ASCII string.
/// </summary>
/// <remarks>If you set <see cref="PropertyItem.Type">PropertyItem.Type</see> to <see cref="PropertyItem.Type"/>, you should set the length data member to the length of the string including the NULL terminator. For example, the string HELLO would have a length of 6.</remarks>
Ascii = 2,

/// <summary>
/// Specifies that the value data member is an array of signed short (16-bit) integers.
/// </summary>
UInt16 = 3,

/// <summary>
/// Specifies that the value data member is an array of unsigned long (32-bit) integers.
/// </summary>
UInt32 = 4,

/// <summary>
/// Specifies that the value data member is an array of pairs of unsigned long integers. Each pair represents a fraction; the first integer is the numerator and the second integer is the denominator.
/// </summary>
URational = 5,

/// <summary>
/// Specifies that the value data member is an array of bytes that can hold values of any data type.
/// </summary>
Raw = 7,

/// <summary>
/// Specifies that the value data member is an array of signed long (32-bit) integers.
/// </summary>
Int32 = 9,

/// <summary>
/// Specifies that the value data member is an array of pairs of signed long integers. Each pair represents a fraction; the first integer is the numerator and the second integer is the denominator.
/// </summary>
Rational = 10,
}
. . .

As you can see in the code above, most of the data types are simple and can be directly mapped to .Net data types, excepted for rational and unsigned rational values. We need to create a .Net representation for each of them :

  • Rational :
Code Copy HideScrollFull
/// <summary>
///
Represents a signed rational number.
/// </summary>
public class Rational {
private int numerator;
private int denominator;

/// <summary>
/// Initializes a new instance of the <see cref="Rational"/> class.
/// </summary>
/// <param name="numerator">The numerator of the rational number.</param>
/// <param name="denominator">The denominator of the rational number.</param>
public Rational(int numerator, int denominator) {
this.numerator = numerator;
this.denominator = denominator;
}

/// <summary>
/// Gets the numerator of the rational number.
/// </summary>
public int Numerator {
get {
return numerator;
}
}

/// <summary>
/// Gets the denominator of the rational number.
/// </summary>
public int Denominator {
get {
return denominator;
}
}

/// <summary>
/// Gets the floating-point value of the rational number.
/// </summary>
public double Value {
get {
return (double) numerator/(double) denominator;
}
}

/// <exclude/>
public override string ToString() {
return Value.ToString();
}
}
. . .
  • URational :
Code Copy HideScrollFull
/// <summary>
///
Represents an unsigned rational number.
/// </summary>
public class URational {
private uint numerator;
private uint denominator;

/// <summary>
/// Initializes a new instance of the <see cref="Rational"/> class.
/// </summary>
/// <param name="numerator">The numerator of the rational number.</param>
/// <param name="denominator">The denominator of the rational number.</param>
public URational(uint numerator, uint denominator) {
this.numerator = numerator;
this.denominator = denominator;
}

/// <summary>
/// Gets the numerator of the rational number.
/// </summary>
public uint Numerator {
get {
return numerator;
}
}

/// <summary>
/// Gets the denominator of the rational number.
/// </summary>
public uint Denominator {
get {
return denominator;
}
}

/// <summary>
/// Gets the floating-point value of the rational number.
/// </summary>
public double Value {
get {
return (double) numerator/(double) denominator;
}
}

/// <exclude/>
public override string ToString() {
return Value.ToString();
}
}
. . .

According to the Exif specification, properties may contain arrays of values. Our conversion tool must consequently have the following signature :

Code Copy
public static Array FromPropertyItem(PropertyItem propertyItem) {}

The conversion code is quite simple (a switch... case construct branching on different code depending on the data type).
When converting raw data into integers, just take care that Exif properties are stored as big endian values.

Code Copy HideScrollFull
#region References

using System;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Text;

#endregion

namespace
ExifNative {
/// <summary>
/// Summary description for ExifConvert.
/// </summary>
public sealed class ExifConvert {
private ExifConvert() {}

/// <summary>
/// Converts a property item to an array of objects.
/// </summary>
/// <param name="propertyItem">The property item to convert.</param>
/// <returns>An array of <see cref="object"/> items.</returns>
public static Array FromPropertyItem(PropertyItem propertyItem) {
ExifPropertyType type = (ExifPropertyType) propertyItem.Type;

switch (type) {
case ExifPropertyType.Raw:
// The value represents raw data (a single byte[] value)
return new byte[][] {propertyItem.Value};

case ExifPropertyType.Ascii:
// The value represents an array of strings separated by \0 characters
string stringValue = Encoding.ASCII.GetString(propertyItem.Value, 0, propertyItem.Len - 1);
return stringValue.Split('\0');

case ExifPropertyType.Byte:
// The value represents an array of bytes
return propertyItem.Value;

case ExifPropertyType.UInt16:
// The value represents an array of unsigned 16-bit integers.
int ushortCount = propertyItem.Len/ushortSize;

ushort[] ushortResult = new ushort[ushortCount];
for (int i = 0; i < ushortCount; i++)
ushortResult[i] = ReadUInt16(propertyItem.Value, i * ushortSize);
return ushortResult;

case ExifPropertyType.Int32:
// The value represents an array of signed 32-bit integers.
int intCount = propertyItem.Len/intSize;

int[] intResult = new int[intCount];
for (int i = 0; i < intCount; i++)
intResult[i] = ReadInt32(propertyItem.Value, i * intSize);
return intResult;

case ExifPropertyType.UInt32:
// The value represents an array of unsigned 32-bit integers.
int uintCount = propertyItem.Len/uintSize;

uint[] uintResult = new uint[uintCount];
for (int i = 0; i < uintCount; i++)
uintResult[i] = ReadUInt32(propertyItem.Value, i * uintSize);
return uintResult;

case ExifPropertyType.Rational:
// The value represents an array of signed rational numbers
// Numerator is an Int32 value, denominator a UInt32 value.
int rationalCount = propertyItem.Len/rationalSize;

Rational[] rationalResult = new Rational[rationalCount];
for (int i = 0; i < rationalCount; i++)
rationalResult[i] = new Rational(
ReadInt32(propertyItem.Value, i * rationalSize),
ReadInt32(propertyItem.Value, i * rationalSize + intSize));
return rationalResult;

case ExifPropertyType.URational:
// The value represents an array of signed rational numbers
// Numerator and denominator are UInt32 values.
int urationalCount = propertyItem.Len/rationalSize;

URational[] urationalResult = new URational[urationalCount];
for (int i = 0; i < urationalCount; i++)
urationalResult[i] = new URational(
ReadUInt32(propertyItem.Value, i * urationalSize),
ReadUInt32(propertyItem.Value, i * urationalSize + uintSize));
return urationalResult;

default:
return null;
}
}

#region Static Fields

private static readonly int ushortSize = Marshal.SizeOf(typeof (ushort));
private static readonly int intSize = Marshal.SizeOf(typeof (int));
private static readonly int uintSize = Marshal.SizeOf(typeof (uint));
private static readonly int rationalSize = 2 * Marshal.SizeOf(typeof (int));
private static readonly int urationalSize = 2 * Marshal.SizeOf(typeof (uint));

#endregion

#region
Private Helpers

private static ushort ReadUInt16(byte[] buffer, int offset) {
return (ushort) (
((ushort) buffer[offset] +
((ushort) buffer[offset + 1] << 8)));
}

private static int ReadInt32(byte[] buffer, int offset) {
return (int) (
((uint) buffer[offset] +
((uint) buffer[offset + 1] << 8) +
((uint) buffer[offset + 2] << 16) +
((int) buffer[offset + 3] << 24)));
}

private static uint ReadUInt32(byte[] buffer, int offset) {
return (uint) (
((uint) buffer[offset] +
((uint) buffer[offset + 1] << 8) +
((uint) buffer[offset + 2] << 16) +
((uint) buffer[offset + 3] << 24)));
}

#endregion
}
}
. . .

The following code is a sample console application that make use of the code above to load an image and display the content of Exif properties.

Code Copy
[STAThread]
private static void Main(string[] args) {
using(Image image = Image.FromFile(args[0])) {
Console.WriteLine("{0} : {1} properties", args[0], image.PropertyItems.Length);

foreach (PropertyItem propertyItem in image.PropertyItems) {
Array result = ExifConvert.FromPropertyItem(propertyItem);
Console.WriteLine("Property #{0}, type : {1}, {2} value(s)", propertyItem.Id, (ExifPropertyType)propertyItem.Type, result.Length);

foreach (object value in result)
Console.WriteLine("\t{0}", value);
Console.WriteLine();
}
}
}

posted on Tuesday, April 19, 2005 12:20 PM

Feedback

No comments posted yet.
Title
 
Name
 
Url
Comments   
Protected by Clearscreen.SharpHIPEnter the code you see: