Click here to Skip to main content
15,173,985 members
Articles / Multimedia / Image Processing
Article
Posted 1 May 2020

Stats

32.4K views
2.4K downloads
65 bookmarked

CompactExifLib: Access to EXIF Tags in JPEG, TIFF and PNG Files

Rate me:
Please Sign up or sign in to vote.
4.98/5 (47 votes)
24 Jun 2021CPOL33 min read
C# library for reading and writing EXIF tags in JPEG, TIFF and PNG image files
In this article, you will see how to read and write EXIF tags in JPEG, TIFF and PNG image files using a C# library.

Introduction

The EXIF standard is used by almost all photo and smartphone cameras for storing meta data in images. The EXIF data consists of a list of tags and each tag stores a small part of information about the image. For example, the date and time is stored when the image was taken or the GPS location where the image was taken. This article shows how to read and write EXIF tags in JPEG, TIFF and PNG image files using a C# library.

Background

There are many third-party libraries for reading EXIF tags of an image file and the .NET Framework also provides .NET classes for accessing EXIF tags. But most of the third-party libraries are not able to write EXIF tags to an image file. In the .NET Framework, there are classes for reading and writing EXIF tags. But they have much overhead so that they are very slow and they are not lossless, i.e., the image is uncompressed when loading it and it is newly compressed when the image is saved with the new or changed EXIF tags. For this reason, I decided to develop a new library called CompactExifLib.

As an example, the EXIF tag for the date taken was read from 400 JPEG photos and the time in milliseconds was measured and recorded in the following table:

WPF class BitmapMetadata Library CompactExifLib Speed factor
2730 ms 50 ms 54.6

In the first column, the WPF class BitmapMetadata of the .NET Framework was used to the read the EXIF tag and in the second column, the library CompactExifLib. As you see, the library CompactExifLib is more than 50 times faster than the library of the .NET Framework.

Benefits of the CompactExifLib library:

  • Very fast, because the EXIF tags are accessed directly with elementary file read and write methods.
  • Lossless image saving. The image matrix is not changed at all when the EXIF tags are saved.
  • Written completely in C#, no DLL is required.
  • Can be used both with Windows Forms and WPF applications.
  • Works with many .NET versions from .NET Framework 4.0

Demo Application

In the first download package, there is a demo application which lists all EXIF tags of an image file. The library CompactExifLib is also included and can be found in the file ExifData.cs.

Image 1

After selecting an image file you can see all EXIF tags of this image.

Sample Application "SuperPhotoView"

SuperPhotoView is a larger application that demonstrates the use of CompactExifLib. You can find this application as a binary file in the second download package.

Image 2

In this application you can select a folder with image files and the most important EXIF tags of these images are shown in a data table. The EXIF tags can be edited and saved in the image files. This can also be done for multiple images at once. In addition it is possible to the view the images in a slide show and the EXIF tag "Image description" is displayed in the footer of each image.

Using the Code

Reading and Writing Tags

The CompactExifLib library consists of one single .cs file. Therefore, it is very easy to use the library, just add the file ExifData.cs to your project and insert the namespace CompactExifLib with a using command. The main class in this library is the class ExifData which holds the complete EXIF data of an image file. For example, for reading the date taken of a photo "c:\temp\testimage.jpg", you can use the following code:

C#
using CompactExifLib;

...

ExifData TestExif;
DateTime DateTaken;

try
{
  TestExif = new ExifData(@"c:\temp\testimage.jpg");
  if (TestExif.GetTagValue(ExifTag.DateTimeOriginal, out DateTaken))
  {
    // The date taken is now available in variable "DateTaken"
  }
}
catch
{
  // Error occurred while reading image file
}

The ExifData constructor has the declaration:

C#
public ExifData(string FileNameWithPath, ExifLoadOptions Options = 0);

and it loads the EXIF data from the specified image file. If the file doesn't have an EXIF data block, an empty block is returned. If the loading fails, an exception is thrown. Possible reasons are:

  • The file does not exist.
  • The access to the file is denied.
  • The file has an illegal content, e.g., it is not a valid JPEG, TIFF or PNG file.

The ExifData constructor copies the EXIF data of the image file completely to the memory and the file is closed immediately. All read and write accesses are then executed on the memory copy of the EXIF data. When the EXIF data shall be written back to the image file, the ExifData method Save has to be called:

C#
public void Save(string DestFileNameWithPath = null, 
              ExifSaveOptions SaveOptions = ExifSaveOptions.None);

If the first parameter DestFileNameWithPath is null or omitted, the method Save overwrites the existing image file. It is also possible to save the image as a new file name by passing the file name in the parameter DestFileNameWithPath. The method Save throws an exception if the file could not be saved. Possible reasons are:

  • The file is write protected.
  • The access to the file is denied.
  • The file is not available any more, e.g., it was deleted or the volume was removed.
  • The EXIF data are too large. The maximum EXIF data size in JPEGs is 65526 bytes and in TIFFs and PNGs it is 2 GB.

A more detailed description for loading and saving EXIF data and the possibility to use streams can be found in the chapter "Loading and saving EXIF data".

In the following example code, the date taken of the image in the previous example is changed and then the EXIF data are written back to the image file.

C#
DateTaken.AddHours(2); // Add 2 hours to the time stamp
TestExif.SetTagValue(Exifag.DateTimeOriginal, DateTaken);
try
{
  TestExif.Save();
}
catch
{
  // Error occurred while writing image file
}

Tag IDs and Image File Directories (IFDs)

A tag is defined by a 16 bit value called the tag ID. The following example code shows some tag ID definitions.

C#
public enum ExifTagId
{
  ...
  Orientation = 0x0112,
  ImageDescription = 0x010E,
  DateTimeOriginal = 0x9003,
  ...
}

But the tag ID is not sufficient to specify a tag. Additionally, the IFD has to be specified. The EXIF tags are divided into several sections which are called image file directories (IFDs). If you want to read or write a tag, you have to specify the correct IFD. Which IFD should be used for a tag is defined in the EXIF standard [EXIF2.32]. For specifying an IFD, the following constants are available.

C#
public enum ExifIfd
{
  PrimaryData = 0,
  PrivateData = 1,
  GpsInfoData = 2,
  Interoperability = 3,
  ThumbnailData = 4
}

The IFD PrimaryData is the main IFD of the EXIF data and it contains basic image data and the IFD PrivateData contains additional image data. The IFD GpsInfoData stores the GPS data of the location where the image was taken. Interoperability is for internal use and ThumbnailData stores the EXIF data for the thumbnail image, which is a small preview image.

In order to make it easier to specify an EXIF tag, there are combined constants which contain an IFD in the upper 16 bits and a tag ID in the lower 16 bits of the value.

C#
public enum ExifTag
{
  ...
  Orientation = (ExifIfd.PrimaryData << 16) | ExifTagId.Orientation,
  ImageDescription = (ExifIfd.PrimaryData << 16) | ExifTagId.ImageDescription,
  DateTimeOriginal = (ExifIfd.PrivateData << 16) | ExifTagId.DateTimeOriginal,
  ...
}

Here, the constants Orientation and ImageDescription define tags which are stored in the IFD "PrimaryData" and the constant DateTimeOriginal defines a tag in the IFD "PrivateData". Furthermore, there are methods for creating values of type ExifTag and for extracting the IFD and tag ID from such values. These methods are described in the chapter "Other Useful Methods".

Tag Types

Each EXIF tag has a type and in this library, the tag type is specified by the enum type ExifTagType:

C#
public enum ExifTagType
{
  Byte = 1,
  Ascii = 2,
  UShort = 3,
  ULong = 4,
  URational = 5,
  SByte = 6, // Only for TIFFs
  Undefined = 7,
  SShort = 8, // Only for TIFFs
  SLong = 9,
  SRational = 10,
  Float = 11, // Only for TIFFs
  Double = 12 // Only for TIFFs
}

In the following table, the tag types are described:

Constant of type ExifTagType Official type name Description
Byte BYTE Array of unsigned 8 bit integer values
UShort SHORT Array of unsigned 16 bit integer values
ULong LONG Array of unsigned 32 bit integer values
SLong SLONG Array of signed 32 bit integer values
URational RATIONAL Array of unsigned 64 bit rational numbers. The numerator and denominator are both coded as unsigned 32 bit numbers and the numerator is stored first.
SRational SRATIONAL Array of signed 64 bit rational numbers. The numerator and denominator are both coded as signed 32 bit numbers and the numerator is stored first.
Ascii ASCII Array of 8 bit characters
Undefined UNDEFINED Array of 8 bit values. The interpretation of the content is defined in the corresponding tag.
SByte SBYTE Array of signed 8 bit integer values. Only for TIFF images defined and not fully supported by this library.
SShort SSHORT Array of signed 16 bit integer values. Only for TIFF images defined and not fully supported by this library.
Float FLOAT Array of 32 bit floating point values. Only for TIFF images defined and not fully supported by this library.
Double DOUBLE Array of 64 bit floating point values. Only for TIFF images defined and not fully supported by this library.

In general, a tag can store not only one value, but an array of several values.

Integer Numbers

Tags that store integer numbers can be read with the following overloaded methods:

C#
public bool GetTagValue(ExifTag TagSpec, out int Value, int Index = 0);

public bool GetTagValue(ExifTag TagSpec, out uint Value, int Index = 0);

In the first parameter TagSpec, the tag ID and the IFD of the tag is specified. In the second parameter Value, the tag content is given back as an integer value. The third parameter Index specifies the array index of the value to be read and the index 0 means the first value of the tag. The return value is true, if the operation was successful, and it is false if an error occurs. An error occurs in the following situations:

  • The tag does not exist.
  • The tag type is not one of the types ExifTagType.Byte, UShort, ULong or SLong.
  • The parameter Index specifies an array element outside the tag data.
  • If the parameter Value is of type int: The tag type is ExifTagType.ULong and the number stored in the tag is greater or equal than 0x80000000.
  • If the parameter Value is of type uint: The tag type is ExifTagType.SLong and the number stored in the tag is negative.

For writing an integer number into a tag, there are the overloaded methods SetTagValue:

C#
public bool SetTagValue(ExifTag TagSpec, int Value, ExifTagType TagType, int Index = 0);

public bool SetTagValue(ExifTag TagSpec, uint Value, ExifTagType TagType, int Index = 0);

If the tag does not exist, it is automatically created by SetTagValue. Therefore, the tag type has to be specified in the third parameter TagType. Here, the valid tag types are ExifTagType.Byte, UShort, ULong and SLong. The fourth parameter Index specifies the array index of the value to be written. If necessary, the tag memory is automatically enlarged so that the value can be stored in the specified index. If the tag memory is reallocated, the current tag content is copied to the new memory. The return value informs about an error which can occur in the following situations:

  • The specified tag type is invalid.
  • The number in the parameter Value is outside the range of the tag type specified in the parameter TagType.

The method SetTagValue only writes to the internal memory copy of the EXIF data and not to the image file. Therefore, an exception is never thrown by this method.

In the following example, the image orientation tag is written and read. As defined in the EXIF standard [EXIF2.32], this tag should be a 16 bit value of type SHORT that corresponds to the constant ExifTagType.UShort.

C#
ExifData TestExif;
int ImageOrientation;

...

TestExif.SetTagValue(ExifTag.Orientation, 6, ExifTagType.UShort);
TestExif.GetTagValue(ExifTag.Orientation, out ImageOrientation);

With the image orientation tag, it is possible to define a clockwise rotation of 90, 180 or 270 degrees and a reflection for the image matrix, provided that the image viewer considers this EXIF tag when drawing the image. For example, the value 6 for this tag defines a clockwise rotation by 90 degrees.

Array Tags

Most of the tags contain only a single value but there are some tags which store several values, i.e., an array of values. The arrays can be read and written with the known methods GetTagValue and SetTagValue and the parameter Index specifies the zero based array index. In addition to this, there are methods for reading and writing the number of array elements (=value count) of a tag. The method GetTagValueCount can read the value count:

C#
public bool GetTagValueCount(ExifTag TagSpec, out int ValueCount);

In the parameter ValueCount, the number of array elements of the tag is given back. If the tag exists, the return value is true. If the tag does not exist, ValueCount is 0 and the return value is false. For setting the value count of a tag, there are the two overloaded methods SetTagValueCount available:

C#
public bool SetTagValueCount(ExifTag TagSpec, int ValueCount); 

public bool SetTagValueCount(ExifTag TagSpec, int ValueCount, ExifTagType TagType);

The first method can only be used if the tag already exists and in this case, it returns true. Otherwise, the method fails and returns false. In contrast, the second method creates the tag if it doesn't already exist. Therefore the tag type has to be specified. Both methods reallocate the internal memory of the tag, if the currently allocated memory is to small. Then the current tag content is copied to the new tag memory. With the second method, it is additionally possible to change the tag type of an existing tag. But if you do this, you either have to overwrite the complete tag memory afterwards or you have to ensure that the tag content is compatible with the new tag type.

The following example shows how to read the tag "SubjectArea" that should contain 2, 3 or 4 values according to the EXIF standard.

C#
ExifData TestExif;

...

TestExif.GetTagValueCount(ExifTag.SubjectArea, out int c);
int[] v = new int[c];
for (int i = 0; i < v.Length; i++)
{
  TestExif.GetTagValue(ExifTag.SubjectArea, out v[i], i);
}

Strings

Strings are coded as character arrays and the following tag types are used for coding strings:

  • ExifTagType.Ascii: This is the default tag type for coding a string. The string is terminated with a null character.
  • ExifTagType.Undefined: Some string tags are coded with this type. A terminating null character is not present.
  • ExifTagType.Byte: Microsoft has defined special unicode string tags with 16 bit Unicode characters, which are stored in a byte array. The string is terminated with a Unicode null character.

For reading and writing tags as strings, there are the overloaded methods GetTagValue and SetTagValue available:

C#
public bool GetTagValue(ExifTag TagSpec, out string Value, StrCoding Coding);

public bool SetTagValue(ExifTag TagSpec, string Value, StrCoding Coding);

The method GetTagValue reads a string tag and removes all terminating null characters if there are any present. The method SetTagValue writes a string tag and adds a terminating null character, if the tag type is ExifTagType.Ascii or ExifTagType.Byte.

The return value of type bool is true when the operation was successful, otherwise it is false. An error occurs if a tag is read and the tag does not exist or the tag type is not correct.

Because there are several string codings defined in the EXIF standard, you have to look at which string coding and tag type is used for a particular EXIF tag. The last parameter Coding parameter specifies the code page and the tag type which should be used for reading or writing the tag. This parameter is of the enum type StrCoding and the constants of this type are listed in the following table:

Constant of type StrCoding Description Expected tag type
Utf8 Unicode code page UTF 8. The base is the US ASCII code page and special characters are coded with two, three or four bytes using code points from 128 to 255.

ExifTagType.Ascii

UsAscii US ASCII code page with one byte per character and the code points from 0 to 127. When the string is read, all illegal code points from 128 to 255 are set to a question mark. ExifTagType.Ascii
UsAscii_Undef Same as UsAscii, except the tag type is different. ExifTagType.Undefined
WestEuropeanWin Western european code page 1252 of Windows. The base is the US ASCII character set and special characters are coded as single bytes in the code points from 128 to 255.
Note: The code page 1252 is not available in .NET Core applications and an exception is thrown, when you try to use it. But you can install a NuGet package to expand the available code pages.
ExifTagType.Ascii
Utf16Le_Byte Unicode code page UTF 16 LE (Little Endian) with two or four bytes per character. The byte order is always LE even if the EXIF block is coded BE (Big Endian). ExifTagType.Byte
IdCode_Utf16 An 8 byte ID code is preceding the string. The ID code defines the string coding and the ID codes "Default", "Ascii" and "Unicode" are supported by this library.

Reading the tag:
If the ID code is "Default" or "Ascii", the string is read in the US ASCII code page. If the ID code is "Unicode", the string is read as UTF 16 string. The UTF 16 byte order is LE (Little Endian) or BE (Big Endian) depending on the byte order of the EXIF block.

Writing the tag:
The ID code is set to "Unicode" and the string is written in the UTF 16 LE or UTF 16 BE code page.

ExifTagType.Undefined
IdCode_UsAscii
The string is read and written in the US ASCII code page unless the ID code is "Unicode" when reading.
ExifTagType.Undefined
IdCode_WestEu The string is read and written in the western european code page 1252 unless the ID code is "Unicode" when reading. ExifTagType.Undefined

An unofficial table with all EXIF tags and their corresponding tag type and a description of the string coding can be found at [EXIV2]. In the following table, some popular EXIF string tags are listed:

EXIF tag Possible values for parameter "Coding" Note
ImageDescription Utf8, UsAscii, WestEuropeanWin  
Copyright Utf8, UsAscii, WestEuropeanWin  
Artist Utf8, UsAscii, WestEuropeanWin  
Make Utf8, UsAscii  
Model Utf8, UsAscii  
Software Utf8, UsAscii  
DateTime Utf8, UsAscii  
DateTimeOriginal Utf8, UsAscii  
DateTimeDigitized Utf8, UsAscii  
ExifVersion UsAscii_Undef  
FlashPixVersion UsAscii_Undef  
UserComment IdCode_Utf16, IdCode_UsAscii, IdCode_WestEu  
XpTitle Utf16Le_Byte Defined by Microsoft
XpComment Utf16Le_Byte Defined by Microsoft
XpAuthor Utf16Le_Byte Defined by Microsoft
XpKeywords Utf16Le_Byte Defined by Microsoft
XpSubject Utf16Le_Byte Defined by Microsoft

Perhaps you are wondering which string coding should be used for tags with several possible codings like the tag "ImageDescription". According to the EXIF standard, only US-ASCII characters (code points 0 to 127) are allowed in a string tag of type ExifTagType.Ascii, but almost all tools for editing EXIF tags use extended code pages like Utf8 or WestEuropeanWin for writing a tag. Unfortunately, there is no general method in order to decide which code page was used for coding the tag, but Utf8 can be used as the default. If no special characters like accent characters are used, the code pages Utf8, UsAscii and WestEuropeanWin are even identical and string tags which are written by photo cameras like the tags "Make" and "Model" don't use special characters.

The string tags defined by Microsoft are not part of the official EXIF standard and the names of these tags start with the letters "Xp".

In the following examples, some string tags are written and read.

C#
ExifData TestExif;
string s;

...

TestExif.SetTagValue(ExifTag.ImageDescription, "Smiley ☺", StrCoding.Utf8);
TestExif.GetTagValue(ExifTag.ImageDescription, out s, StrCoding.Utf8);

TestExif.SetTagValue(ExifTag.UserComment, "Comment Ω", StrCoding.IdCode_Utf16);
TestExif.GetTagValue(ExifTag.UserComment, out s, StrCoding.IdCode_Utf16);

TestExif.SetTagValue(ExifTag.ExifVersion, "1234", StrCoding.UsAscii_Undef);
TestExif.GetTagValue(ExifTag.ExifVersion, out s, StrCoding.UsAscii_Undef);

TestExif.SetTagValue(ExifTag.XpTitle, "Title Σ", StrCoding.Utf16Le_Byte);
TestExif.GetTagValue(ExifTag.XpTitle, out s, StrCoding.Utf16Le_Byte);

Rational Numbers

Some tags contain fraction numbers which are coded as two sequent 32 bit integer numbers, the numerator and denominator. There are signed and unsigned rational numbers available, see the tag types ExifTagType.SRational and ExifTagType.URational. In the library, the struct ExifRational is defined, which can store both a signed and an unsigned rational number.

C#
public struct ExifRational
{
  public uint Numer, Denom;
  public bool Sign; // true = Negative number or negative zero

  public ExifRational(int _Numer, int _Denom)
  {
    ...
  }

  public ExifRational(uint _Numer, uint _Denom, bool _Sign = false)
  {
    ...
  }


  ...
  
}

For reading and writing tags with rational numbers, the following overloaded methods can be used:

C#
public bool GetTagValue(ExifTag TagSpec, out ExifRational Value, int Index = 0);

public bool SetTagValue(ExifTag TagSpec, ExifRational Value, ExifTagType TagType, int Index = 0);

The method GetTagValue expects a tag of type ExifTagType.SRational or ExifTagType.URational, otherwise it fails. When writing a tag with SetTagValue, the parameter TagType can be either ExifTagType.SRational or ExifTagType.URational. A range check is implemented, for example, when you try to write a negative rational number and the parameter TagType is set to ExifTagType.URational, the method fails and returns false.

The following example shows how to write and read rational numbers.

C#
ExifData TestExif;
ExifRational r1, r2;

...

r1 = new ExifRational(1637, 1000);
TestExif.SetTagValue(ExifTag.ExposureTime, r1, ExifTagType.URational);
TestExif.GetTagValue(ExifTag.ExposureTime, out r2);

Here, the exposure time of the image is set to 1637/1000 = 1.637 seconds. As defined in the EXIF standard [EXIF2.32], the tag type has to be RATIONAL which corresponds to the constant ExifTagType.URational.

For converting a number between a decimal number and a rational number, the following ExifRational methods can be used:

C#
public static decimal ToDecimal(ExifRational Value);

public static ExifRational FromDecimal(decimal Value);

An initialization of the variable r1 in the example above with the value 1.637 is alternatively possible with:

C#
r1 = ExifRational.FromDecimal(1.637m);

Date and Time

In the EXIF data, there are the following tags for storing a date:

  • DateTime: Date and time when the image was last changed.
  • DateTimeOriginal: Date and time when the image was taken.
  • DateTimeDigitized: Date and time when the image was digitized.
  • GpsDateStamp: GPS date from the satellite.

All dates are stored as strings with the tag type ExifTagType.Ascii. With the following methods, these tags can be accessed using the DateTime struct:

C#
public bool GetTagValue(ExifTag TagSpec, out DateTime Value,
                        ExifDateFormat Format = ExifDateFormat.DateAndTime);

public bool SetTagValue(ExifTag TagSpec, DateTime Value,
                        ExifDateFormat Format = ExifDateFormat.DateAndTime);

There are two date formats and the last parameter Format specifies the format to be used:

  • ExifDateFormat.DateAndTime: A date and time are present and they are separated by a space character, e.g., "2019:12:22 15:23:47". This format is used for the three "DateTimeXXX" tags.
  • ExifDateFormat.DateOnly: Only a date is present, e.g., "2019:12:22". This format is used for the tag "GpsDateStamp". A GPS time of day is available in an additional tag, see below.

The three DateTimeXXX tags have a precision of 1 second. Some photo cameras write additionally the following EXIF tags that provide fractions of a second:

  • SubsecTime: Fractions of a second for the time when the image was last changed.
  • SubsecTimeOriginal: Fractions of a second for the time when the image was taken.
  • SubsecTimeDigitized: Fractions of a second for the time when the image was digitized.

With the following ExifData methods, you can comfortably access these tags because the fractions of a second are processed within the DateTime struct. All times are in the local time zone.

Method Description

EXIF tags

C#
public bool GetDateTaken(out DateTime Value);
Get date taken with a precision of 1 millisecond. DateTimeOriginal,
SubsecTimeOriginal
C#
public bool SetDateTaken(DateTime Value);
Set date taken with a precision of 1 millisecond. DateTimeOriginal,
SubsecTimeOriginal
C#
public void RemoveDateTaken();
Remove tags for the date taken. DateTimeOriginal,
SubsecTimeOriginal
C#
public bool GetDateDigitized(out DateTime Value);
Get date digitized with a precision of 1 millisecond.

DateTimeDigitized,
SubsecTimeDigitized

C#
public bool SetDateDigitized(DateTime Value);
Set date digitized with a precision of 1 millisecond. DateTimeDigitized,
SubsecTimeDigitized
C#
public void RemoveDateDigitized();
Remove tags for the date digitized. DateTimeDigitized,
SubsecTimeDigitized
C#
public bool GetDateChanged(out DateTime Value);
Get date changed with a precision of 1 millisecond. DateTime,
SubsecTime
C#
public bool SetDateChanged(DateTime Value);
Set date changed with a precision of 1 millisecond. DateTime,
SubsecTime
C#
public void RemoveDateChanged();
Remove tags for the date changed. DateTime,
SubsecTime

Because the known EXIF tag GpsDateStamp only provides a date, there is a second EXIF tag GpsTimeStamp which provides a time in the UTC time zone. In this tag, there may also be fractions of a second if the photo camera has recorded it. The access to both EXIF tags is possible with the ExifData methods in the following table:

Method Description

EXIF tags

C#
public bool GetGpsDateTimeStamp(out DateTime Value);
Get GPS date and time stamp in UTC time zone. GpsDateStamp,
GpsTimeStamp
C#
public bool SetGpsDateTimeStamp(DateTime Value);
Set GPS date and time stamp in UTC time zone. GpsDateStamp,
GpsTimeStamp
C#
public void RemoveGpsDateTimeStamp();
Remove GPS date and time stamp tags. GpsDateStamp,
GpsTimeStamp

Raw Data and Byte Order

It is also possible to read the raw data bytes of a tag without any interpretation. For this purpose, the method GetTagRawData is available:

C#
public bool GetTagRawData(ExifTag TagSpec, out ExifTagType TagType, out int ValueCount, 
    out byte[] RawData)

The EXIF tag consisting of IFD and tag ID is passed in the first parameter TagSpec. The tag data are returned in the following parameters:

  • TagType: Type of the tag.
  • ValueCount: Number of values which are stored in the tag. Remember that an EXIF tag is in general an array of values and not just a single value, so that ValueCount is the number of array elements. Only if the size of a single tag value is 1 byte (tag types ExifTagType.Byte, Ascii and Undefined), the number returned in ValueCount is also the number of raw data bytes. E. g., for the tag type ExifTagType.UShort, the number of raw data bytes is 2*ValueCount.
  • RawData: Array with the raw data bytes of the tag. The number of raw data bytes is RawData.Length.
  • Method return value: true = Reading of the tag data was successful, false = Tag doesn't exist.

The interpretation of the raw data bytes depends on the byte order of the EXIF data. EXIF data can be stored either in Little Endian (LE) or Big Endian (BE) format. Most of the photo cameras and image processing tools write the EXIF data in the Little Endian format, but there are some cameras and tools which use the Big Endian format. The byte order is important for all tags of the types ExifTagType.UShort, SShort, ULong, SLong, URational, SRational, Float and Double. Additionally, there are some tags of type Undefined which require an individual handling of the byte order. In this library, the byte order of the current EXIF block can be determined by the ExifData property ByteOrder:

C#
public enum ExifByteOrder { LittleEndian, BigEndian };

public ExifByteOrder ByteOrder { get; }

In the property ByteOrder, the value ExifByteOrder.LittleEndian means that the low byte of a multi-byte value is stored first, e.g., the 16 bit hex value 1A34 is stored as the byte sequence 34 1A. For the setting ExifByteOrder.BigEndian, the byte sequence would be 1A 34. In order to make it easier to read a 16 or 32 bit integer value from a byte array, there are two ExifData methods available:

C#
public ushort ExifReadUInt16(byte[] Data, int StartIndex);

public uint ExifReadUInt32(byte[] Data, int StartIndex);

The byte order for reading the tag data is taken from the property ByteOrder.

There is a second overloaded method GetTagRawData available that returns the raw data without copying them to a new array:

C#
public bool GetTagRawData(ExifTag TagSpec, out ExifTagType TagType, out int ValueCount,
    out byte[] RawData, out int RawDataIndex);

The raw data bytes are given back in the fourth parameter RawData, but this array may also contain data that does not belong to the specified tag! Here, the first raw data byte is stored in RawData[RawDataIndex] and the last raw data byte is stored in RawData[RawDataIndex + GetTagByteCount(TagType, ValueCount) - 1]. You can determine the number of raw data bytes by the static ExifData method GetTagByteCount:

C#
public static int GetTagByteCount(ExifTagType TagType, int ValueCount);

This method returns the number of raw data bytes, that a tag of the specified tag type and value count needs. When accessing the array RawData you should consider the following things:

  • The array RawData must not be changed by the caller of this method.
  • After new data has been written into the specified EXIF tag, the array RawData should not be used any
    more.
  • If the tag does not exist, RawData is null and the return value of the method is false.

For writing the raw data bytes of a tag, the method SetTagRawData can be used.

C#
public bool SetTagRawData(ExifTag TagSpec, ExifTagType TagType, int ValueCount, byte[] RawData,
  int RawDataIndex = 0);

The raw data is passed in the parameter RawData and the parameter RawDataIndex specifies the array index at which the first byte of the raw data is stored. Please note that the raw data is not copied by this method, so you must not change the array with the raw data after calling this method. The number of raw data bytes, which are passed to SetTagRawData, is implicitly defined by the parameters TagType and ValueCount (=number of array elements of the tag). You can determine the raw data byte count by the known method GetTagByteCount.

For writing a 16 or 32 bit integer value to a byte array, the following ExifData methods are available:

C#
public void ExifWriteUInt16(byte[] Data, int StartIndex, ushort Value);

public void ExifWriteUInt32(byte[] Data, int StartIndex, uint Value);

Loading and Saving EXIF Data

For loading EXIF data from an image, there are two ExifData constructors available:

C#
public ExifData(string FileNameWithPath, ExifLoadOptions Options = 0);

public ExifData(Stream ImageStream, ExifLoadOptions Options = 0);

The first constructor is known and it loads the EXIF data from the JPEG, TIFF or PNG file that is passed in the first parameter FileNameWithPath. With the second constructor, you can load the EXIF data from a stream. The stream position must be at the beginning of the image data and the stream must be seekable. The stream is not closed when the constructor returns. So you have to call the Stream method Dispose if you now longer need the stream. With the second parameter Options it is possible to load an empty EXIF block, i. e. to ignore the EXIF block of the image file. For this purpose set this parameter to the value ExifLoadOptions.CreateEmptyBlock.

Saving the EXIF data in a file is possible with the first Save method:

C#
public void Save(string DestFileNameWithPath = null, ExifSaveOptions SaveOptions = 0);

In the first parameter DestFileNameWithPath, the file name for saving the EXIF data is specified. If you set this parameter to null, the EXIF data are written to the original image file. Strictly speaking, a temporary file with the new EXIF data is created first, then the original image file is deleted and finally the temporary file is renamed to the original file name. So the overwriting is safe. Please note, that the original image file from which the EXIF data were loaded still must be available. Furthermore the method Save can only be used if the first ExifData constructor with a file name as parameter was used for loading the EXIF data. The second parameter SaveOptions is not used at the moment.

Saving the EXIF data in a stream is possible with the second Save method:

public void Save(Stream SourceStream, Stream DestStream, ExifSaveOptions SaveOptions = 0);

The first parameter SourceStream should be the stream, from which the original EXIF data were loaded. The stream position of SourceStream has to be at the beginning of the image data and SourceStream must be seekable. The second parameter DestStream specifies the stream to which the new image data should be written.

Removing Tags

For removing tags, the following methods are available:

C#
public bool RemoveTag(ExifTag TagSpec);

public bool RemoveAllTagsFromIfd(ExifIfd Ifd);

public void RemoveAllTags();

The method RemoveTag removes a single tag, the method RemoveAllTagsFromIfd removes all tags from a specific IFD and the method RemoveAllTags removes all tags from the image file, so that the EXIF block is empty afterwards. If the IFD ThumbnailData is removed, the thumbnail image is automatically removed, too. In TIFF images it is not possible to remove all EXIF tags because in the IFD PrimaryData there are tags which contain data of the internal image structure. Therefore a call of RemoveAllTags will not remove these tags if applied to a TIFF image. More information see chapter "Alternative meta data formats".

Alternative Meta Data Formats

In addition to EXIF data, there are other ways to specify meta data in image files. Popular alternative meta data formats are the IPTC and the XMP block, that are defined independent of an image file format. For example, if you write meta data in a JPEG file using the Explorer of Windows 10 the meta data is written to the EXIF block and additionally to the XMP block. An XMP block is always created by the Explorer if it doesn't exist.

In JPEG images there is the JPEG comment block which may contain an arbitrary text. PNG files have their own meta data format and these meta data are stored in several PNG file blocks. With the following methods you can detect and remove such alternative description blocks:

C#
public bool ImageFileBlockExists(ImageFileBlock BlockType);

public void RemoveImageFileBlock(ImageFileBlock BlockType);

public enum ImageFileBlock
{
  Unknown = 0, // Internal value, do not use
  Exif = 1,
  Iptc = 2,
  Xmp = 3,
  JpegComment = 4,
  PngMetaData = 5,
  PngDateChanged = 6
};

With the method RemoveImageFileBlock it is also possible to remove the EXIF block, i. e. to remove all EXIF tags from the image file:

C#
RemoveImageFileBlock(ImageFileBlock.Exif);

This will lead to the same result as the call

C#
RemoveAllTags();

But in TIFF images there are some specifics:

  • TIFF files don't have a file block structure, instead only EXIF tags can be stored. Therefore the IPTC and XMP block are stored using the specific EXIF tags ExifTag.IptcMetadata and ExifTag.XmpMetadata. These tags may only be used for TIFF files.
  • The removal of the EXIF block from a TIFF file will not really remove all EXIF tags! The TIFF internal EXIF tags, the EXIF tag for the IPTC block and the EXIF tag for the XMP block will not be removed.

GPS Data

If the camera writes GPS data into the image, you can access them with the EXIF tags in the IFD "GPS info data". In order to make it easier to access the longitude and latitude of the GPS location, the struct GeoCoordinate is available:

C#
public struct GeoCoordinate
{
  public decimal Degree;     // Integer number: 0 ≤ Degree ≤ 90 (for latitudes) 
                             // or 180 (for longitudes)
  public decimal Minute;     // Integer number: 0 ≤ Minute < 60
  public decimal Second;     // Fraction number: 0 ≤ Second < 60
  public char CardinalPoint; // For latitudes: 'N' or 'S'; for longitudes: 'E' or 'W'

  ...
}

The geographic coordinate is stored in the classical representation with degree, angular minute, angular second and cardinal point. It is also possible to convert the classical representation of a geographic coordinate to a single decimal value with sign using the following GeoCoordinate methods:

C#
public static decimal ToDecimal(GeoCoordinate Value);

public static GeoCoordinate FromDecimal(decimal Value, bool IsLatitude);

The sign of the decimal value represents the cardinal point. Here are some example values for latitudes in both representations:

46° 51' 2.3948" N = +46.850665°
46° 51' 2.3948" S = -46.850665°

Most of the GPS values are split into two EXIF tags. To be able to access them more easily, the following table lists the ExifData methods for accessing GPS tags. The GPS date and time stamp can also be accessed, see chapter "Date and Time".

Method Description EXIF tags
C#
public bool GetGpsLongitude(out GeoCoordinate Value);
Get GPS longitude. GpsLongitude,
GpsLongitudeRef
C#
public bool SetGpsLongitude(GeoCoordinate Value);
Set GPS longitude. GpsLongitude,
GpsLongitudeRef
C#
public void RemoveGpsLongitude();
Remove GPS longitude tags. GpsLongitude,
GpsLongitudeRef
C#
public bool GetGpsLatitude(out GeoCoordinate Value);
Get GPS latitude. GpsLatitude,
GpsLatitudeRef
C#
public bool SetGpsLatitude(GeoCoordinate Value);
Set GPS latitude. GpsLatitude,
GpsLatitudeRef
C#
public void RemoveGpsLatitude();
Remove GPS latitude tags. GpsLatitude,
GpsLatitudeRef
C#
public bool GetGpsAltitude(out decimal Value);
Get height in meters relating to sea level. A positive value mans "above sea level" and a negative value "below sea level". GpsAltitude,
GpsAltitudeRef
C#
public bool SetGpsAltitude(decimal Value);
Set height in meters relating to sea level. GpsAltitude,
GpsAltitudeRef
C#
public void RemoveGpsAltitude();
Remove GPS height tags. GpsAltitude,
GpsAltitudeRef

There are a few more GPS tags available. In order to remove all GPS tags from an image, you can use the ExifData method RemoveAllTagsFromIfd:

C#
ExifData TestExif;

...

TestExif.RemoveAllTagsFromIfd(ExifIfd.GpsInfoData);

If you want to check if there are any GPS tags available, you can use the ExifData method IfdExists:

C#
if (TestExif.IfdExists(ExifIfd.GpsInfoData)) ...

Thumbnail Image

In JPEG files a thumbnail image can be stored which is a small preview image. For TIFF and PNG files this is not supported. The thumbnail image is stored within the EXIF data and because the EXIF block in JPEG files is limited to 64 kB, the size of the thumbnail image is limited, too. The following method ThumbnailImageExists checks if a thumbnail image exists:

C#
public bool ThumbnailImageExists();

For reading the thumbnail image, the method GetThumbnailImage is available:

C#
public bool GetThumbnailImage(out byte[] ThumbnailData, out int ThumbnailIndex,
                              out int ThumbnailByteCount);

In the first parameter ThumbnailData, an array with the thumbnail image is given back. This array may contain further data and it must be not be changed by the caller. The first byte of the thumbnail image is stored at the array index, that is returned in the second parameter, ThumbnailIndex. The size of the thumbnail image is returned in the last parameter ThumbnailByteCount. If no thumbnail is defined, the return value is false and the array ThumbnailData is null.

The method SetThumbnailImage sets a new thumbnail image which is specified as an array in the parameter ThumbnailData. The array is not copied by the method SetThumbnailImage so you should not change this array after calling this method.

C#
public bool SetThumbnailImage(byte[] ThumbnailData, int ThumbnailIndex = 0,
                              int ThumbnailByteCount = -1);

The second parameter ThumbnailIndex specifies the array index where the thumbnail starts. The third parameter ThumbnailByteCount specifies the number of bytes of the thumbnail and if this parameter is set to -1, the remaining length of the array ThumbnailData is specified as byte count.

With the method RemoveThumbnailImage, you can remove the thumbnail image.

C#
public void RemoveThumbnailImage(bool RemoveAlsoThumbnailTags);

If the parameter RemoveAlsoThumbnailTags is set to true, additionally all tags in the IFD thumbnail data are removed.

Differences between the image files

JPEG TIFF PNG
The EXIF block is optional and can be removed. If removed the appearance of the image is not changed except the EXIF tag "Orientation" may change the image rotation. An EXIF block is very common and widespread. The complete file consists of an EXIF block or a sequence of multiple EXIF blocks. For this reason the EXIF block is essential and cannot be removed. There are some EXIF tags that contain internal image data and changing or removing these internal EXIF tags will damage the image! There is an own PNG meta data format and the possibility to use EXIF data wasn't added to the PNG standard until later. Therefore an EXIF block is very rare and only a few tools support EXIF data.
One image can be stored. There may be multiple images and EXIF bocks stored in a single file. This is also called a multi-page TIFF file. Only one image can be stored.
A thumbnail image within the EXIF block is supported. A thumbnail image within the first EXIF block is not supported, but you may store a thumbnail image as a second image. A thumbnail image within the EXIF block is not supported.
The size of the EXIF block is limited to 65526 bytes. In theory, the size of the EXIF block can be up to 4 GB, but in this library the limit is 2 GB. In theory, the size of the EXIF block can be up to 4 GB, but in this library the limit is 2 GB.

Other Useful Methods

Check if an EXIF tag or an IFD exists:

C#
public bool TagExists(ExifTag TagSpec);

public bool IfdExists(ExifIfd Ifd);

Get the type of an EXIF tag:

C#
public bool GetTagType(ExifTag TagSpec, out ExifTagType TagType);

Enumerate all tags of an IFD. An example can be found in the demo application.

C#
public bool InitTagEnumeration(ExifIfd Ifd);

public bool EnumerateNextTag(out ExifTag TagSpec);

Create an EXIF tag specification from an IFD and a tag ID:

C#
public static ExifTag ComposeTagSpec(ExifIfd Ifd, ExifTagId TagId);

Get the IFD or the tag ID from an EXIF tag specification:

C#
public static ExifIfd ExtractIfd(ExifTag TagSpec);

public static ExifTagId ExtractTagId(ExifTag TagSpec);

Replace all EXIF tags and the thumbnail image by the EXIF data of another image file:

C#
public void ReplaceAllTagsBy(ExifData SourceExifData);

The method ReplaceAllTagsBy first removes all existing tags and the thumbnail image in the current ExifData object. Then the EXIF tags of the object in parameter SourceExifData are copied to the current ExifData object. If this method is used with TIFF images, the TIFF internal EXIF tags remain unchanged, i. e. they are neither copied nor removed.

References

ID Description Link
[EXIF2.32] Official EXIF specification V 2.32 http://cipa.jp/std/documents/download_e.html?DC-008-Translation-2019-E
[EXIV2] Inofficial table with EXIF tags https://www.exiv2.org/tags.html
[JPEGWiki] Specification for JPEG files https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
[TIFF6] Specification for TIFF files https://www.adobe.io/content/dam/udp/en/open/standards/tiff/TIFF6.pdf
[PNGWiki] Specification for PNG files https://en.wikipedia.org/wiki/Portable_Network_Graphics

History

Version number Date Description
1.6 2021-06-25
  • Reading and writing of PNG images added.
  • Sample application "SuperPhotoView" added.
1.5 2020-05-30
  • Reading and writing of TIFF images added.
  • New property "ImageType" added.
  • Method "ImageFileBlockExists" added. With this method, it can be checked if an EXIF, XMP, IPTC or JPEG comment block exists.
  • Method "IsExifBlockEmpty" removed. Instead, use the new method "ImageFileBlockExists".
  • Method "RemoveImageFileBlock" added. With this method an EXIF, XMP, IPTC or JPEG comment block can be removed.
  • All constants in the enum type "ExifSaveOptions" removed. Instead, use the new method "RemoveImageFileBlock".
  • Static methods "ReadUInt16", WriteUInt16", "ReadUInt32" and "WriteUInt32" replaced by non-static methods "ExifReadUInt16", "ExifWriteUInt16", "ExifReadUInt32" and "ExifWriteUInt32".
  • TIFF tag types SByte, SShort, Float and Double added, but these tag types are not fully supported.
  • Method "Empty" added.
  • First parameter of "ExifData" constructor must not be "null" any more. Instead, use the new method "Empty".
1.4 2021-03-31
  • Bug-fix: If a tag ID incorrectly exists more than once, an exception is not thrown any more. Instead, the first occurrence of the tag ID is used.
  • New exception class "ExifException" added
  • Load options for "ExifData" constructor added. Now an empty EXIF block can be created.
  • "ExifData" constructor without parameters removed
1.3 2021-03-15
  • Chapter "Loading and saving" added
  • "ExifData" constructor: Overloaded constructor added which can load from a stream
  • "ExifData" constructor: Overloaded constructor added to create an empty EXIF block
  • Method "Save": Overloaded method added which can save the EXIF data in a stream
  • Method "GetTagValueCount": Parameter structure of the method changed. Info, if the tag exists, is now given back.
  • Method "SetTagValueCount": Overloaded method added which doesn't have a parameter for the tag type
  • Method "GetTagType" added for reading the tag type
1.2 2021-02-13
  • Bug-fix: String comparison of file names changed from linguistic to ordinal comparison
  • Method "IfdExists" added
  • Type "ExifRational" extended by methods "ToDecimal" and "FromDecimal"
  • Type "GeoCoordinate" and special methods for reading and writing of GPS tags added: "Get-/SetGpsLongitude", etc.
  • Methods for reading and writing of date and time values with milliseconds added: "Get-/SetDateTaken", etc.
  • Searching for a tag ID speeded up by using the C# class "Dictionary" instead of "ArrayList".
  • Overloaded method "GetTagRawData" added which copies the raw data
  • Type declarations "uint" and "ushort" removed from the enum types "ExifTag", "ExifIfd", "ExifTagId" and "ExifTagType"
  • Enum type "TimeFormat" renamed to "ExifDateFormat"
  • Method "GetByteCountOfTag" renamed to "GetTagByteCount"
  • Enum type "StrCodingFormat" added
  • Demo application added
1.1 2020-05-16
  • Additional string coding constants for tag "UserComment" added
  • Constant "StrCoding.Utf16Id_Undef" renamed to "StrCoding.IdCode_Utf16"
  • The method "Save" extended by an optional parameter. With this parameter, it is possible to remove JPEG blocks with alternative description formats like IPTC-IIM, MPF and the Adobe Photoshop Information Resource Block.
  • In the method "Save" the order, in which the JPEG blocks are written, revised
1.0 2020-05-01 Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Hans-Peter Kalb
Software Developer
Germany Germany
No Biography provided

Comments and Discussions

 
QuestionUnable to add GpsSatellites to file Pin
lemanados11-Dec-21 13:12
Memberlemanados11-Dec-21 13:12 
AnswerRe: Unable to add GpsSatellites to file Pin
Hans-Peter Kalb12-Dec-21 19:41
MemberHans-Peter Kalb12-Dec-21 19:41 
GeneralRe: Unable to add GpsSatellites to file Pin
lemanados13-Dec-21 12:37
Memberlemanados13-Dec-21 12:37 
GeneralRe: Unable to add GpsSatellites to file Pin
Hans-Peter Kalb13-Dec-21 22:04
MemberHans-Peter Kalb13-Dec-21 22:04 
QuestionIPTC Pin
Member 1465543815-Nov-21 0:09
MemberMember 1465543815-Nov-21 0:09 
AnswerRe: IPTC Pin
Hans-Peter Kalb19-Nov-21 5:11
MemberHans-Peter Kalb19-Nov-21 5:11 
QuestionGrabbing GPS values Pin
410berry11-Oct-21 6:23
Member410berry11-Oct-21 6:23 
AnswerRe: Grabbing GPS values Pin
Hans-Peter Kalb12-Oct-21 4:36
MemberHans-Peter Kalb12-Oct-21 4:36 
GeneralMy vote of 5 Pin
William Costa Rodrigues28-Jun-21 13:54
MemberWilliam Costa Rodrigues28-Jun-21 13:54 
PraiseGreat Article - Added some ... Pin
mtmo13-Jun-21 16:03
Membermtmo13-Jun-21 16:03 
GeneralRe: Great Article - Added some ... Pin
Hans-Peter Kalb14-Jun-21 19:33
MemberHans-Peter Kalb14-Jun-21 19:33 
GeneralMy vote of 5 Pin
mkwats4-Jun-21 3:00
Membermkwats4-Jun-21 3:00 
PraiseExcellent Article and Software! Pin
Curt C4-May-21 12:16
MemberCurt C4-May-21 12:16 
QuestionPossible to use with TIF images also ? Pin
cvcvideo10-Apr-21 9:44
Membercvcvideo10-Apr-21 9:44 
AnswerRe: Possible to use with TIF images also ? Pin
Hans-Peter Kalb11-Apr-21 7:17
MemberHans-Peter Kalb11-Apr-21 7:17 
GeneralMy vote of 5 Pin
Member 135286792-Apr-21 2:04
MemberMember 135286792-Apr-21 2:04 
Bugan item with the same key has already been added Pin
Olegis27-Mar-21 3:20
MemberOlegis27-Mar-21 3:20 
GeneralRe: an item with the same key has already been added Pin
Hans-Peter Kalb30-Mar-21 3:19
MemberHans-Peter Kalb30-Mar-21 3:19 
GeneralRe: an item with the same key has already been added Pin
Olegis1-Apr-21 5:55
MemberOlegis1-Apr-21 5:55 
GeneralRe: an item with the same key has already been added Pin
Hans-Peter Kalb1-Apr-21 20:33
MemberHans-Peter Kalb1-Apr-21 20:33 
QuestionStream is not a jpg file exception Pin
fverdou18-Mar-21 8:40
Memberfverdou18-Mar-21 8:40 
AnswerRe: Stream is not a jpg file exception Pin
Hans-Peter Kalb19-Mar-21 7:40
MemberHans-Peter Kalb19-Mar-21 7:40 
QuestionZip file is not complete Pin
Member 1081072615-Mar-21 6:01
MemberMember 1081072615-Mar-21 6:01 
AnswerRe: Zip file is not complete Pin
Hans-Peter Kalb15-Mar-21 6:35
MemberHans-Peter Kalb15-Mar-21 6:35 
GeneralMy vote of 5 Pin
reisenklaus15-Mar-21 1:25
Memberreisenklaus15-Mar-21 1:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.