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.
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.
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:
using CompactExifLib;
...
ExifData TestExif;
DateTime DateTaken;
try
{
TestExif = new ExifData(@"c:\temp\testimage.jpg");
if (TestExif.GetTagValue(ExifTag.DateTimeOriginal, out DateTaken))
{
}
}
catch
{
}
The ExifData
constructor has the declaration:
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:
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.
DateTaken.AddHours(2);
TestExif.SetTagValue(Exifag.DateTimeOriginal, DateTaken);
try
{
TestExif.Save();
}
catch
{
}
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.
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.
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.
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
:
public enum ExifTagType
{
Byte = 1,
Ascii = 2,
UShort = 3,
ULong = 4,
URational = 5,
SByte = 6,
Undefined = 7,
SShort = 8,
SLong = 9,
SRational = 10,
Float = 11,
Double = 12
}
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:
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
:
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
.
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:
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:
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.
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
String
s are coded as character arrays and the following tag types are used for coding string
s:
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 string
s, there are the overloaded methods GetTagValue
and SetTagValue
available:
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 Utf
8
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.
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.
public struct ExifRational
{
public uint Numer, Denom;
public bool Sign;
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:
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.
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:
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:
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
:
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:
ExifDat
eFormat.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
|
public bool GetDateTaken(out DateTime Value);
| Get date taken with a precision of 1 millisecond. | DateTimeOriginal ,
SubsecTimeOriginal |
public bool SetDateTaken(DateTime Value);
| Set date taken with a precision of 1 millisecond. | DateTimeOriginal ,
SubsecTimeOriginal |
public void RemoveDateTaken();
| Remove tags for the date taken. | DateTimeOriginal ,
SubsecTimeOriginal |
public bool GetDateDigitized(out DateTime Value);
| Get date digitized with a precision of 1 millisecond. | DateTimeDigitized ,
SubsecTimeDigitized
|
public bool SetDateDigitized(DateTime Value);
| Set date digitized with a precision of 1 millisecond. | DateTimeDigitized ,
SubsecTimeDigitized |
public void RemoveDateDigitized();
| Remove tags for the date digitized. | DateTimeDigitized ,
SubsecTimeDigitized |
public bool GetDateChanged(out DateTime Value);
| Get date changed with a precision of 1 millisecond. | DateTime ,
SubsecTime |
public bool SetDateChanged(DateTime Value);
| Set date changed with a precision of 1 millisecond. | DateTime ,
SubsecTime |
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
|
public bool GetGpsDateTimeStamp(out DateTime Value);
| Get GPS date and time stamp in UTC time zone. | GpsDateStamp ,
GpsTimeStamp |
public bool SetGpsDateTimeStamp(DateTime Value);
| Set GPS date and time stamp in UTC time zone. | GpsDateStamp ,
GpsTimeStamp |
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:
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
:
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:
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:
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
:
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.
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:
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:
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:
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:
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:
public bool ImageFileBlockExists(ImageFileBlock BlockType);
public void RemoveImageFileBlock(ImageFileBlock BlockType);
public enum ImageFileBlock
{
Unknown = 0,
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:
RemoveImageFileBlock(ImageFileBlock.Exif);
This will lead to the same result as the call
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:
public struct GeoCoordinate
{
public decimal Degree;
public decimal Minute;
public decimal Second;
public char CardinalPoint;
...
}
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:
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 |
public bool GetGpsLongitude(out GeoCoordinate Value);
| Get GPS longitude. | GpsLongitude ,
GpsLongitudeRef |
public bool SetGpsLongitude(GeoCoordinate Value);
| Set GPS longitude. | GpsLongitude ,
GpsLongitudeRef |
public void RemoveGpsLongitude();
| Remove GPS longitude tags. | GpsLongitude ,
GpsLongitudeRef |
public bool GetGpsLatitude(out GeoCoordinate Value);
| Get GPS latitude. | GpsLatitude ,
GpsLatitudeRef |
public bool SetGpsLatitude(GeoCoordinate Value);
| Set GPS latitude. | GpsLatitude ,
GpsLatitudeRef |
public void RemoveGpsLatitude();
| Remove GPS latitude tags. | GpsLatitude ,
GpsLatitudeRef |
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 |
public bool SetGpsAltitude(decimal Value);
| Set height in meters relating to sea level. | GpsAltitude ,
GpsAltitudeRef |
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
:
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
:
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:
public bool ThumbnailImageExists();
For reading the thumbnail image, the method GetThumbnailImage
is available:
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.
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.
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:
public bool TagExists(ExifTag TagSpec);
public bool IfdExists(ExifIfd Ifd);
Get the type of an EXIF tag:
public bool GetTagType(ExifTag TagSpec, out ExifTagType TagType);
Enumerate all tags of an IFD. An example can be found in the demo application.
public bool InitTagEnumeration(ExifIfd Ifd);
public bool EnumerateNextTag(out ExifTag TagSpec);
Create an EXIF tag specification from an IFD and a tag ID:
public static ExifTag ComposeTagSpec(ExifIfd Ifd, ExifTagId TagId);
Get the IFD or the tag ID from an EXIF tag specification:
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:
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 |