jpeg exif
介绍 (Introduction)
The EXIF standard is used by almost all photo and smartphone cameras for storing information about an image. The EXIF data consists of a list of tags and each tag stores an 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. With special EXIF editors, it is possible to add a description text after the photo was taken.
几乎所有照片和智能手机相机都使用EXIF标准来存储有关图像的信息。 EXIF数据由标签列表组成,每个标签存储有关图像的信息。 例如,日期和时间是在拍摄图像时或拍摄图像的GPS位置时存储的。 使用特殊的EXIF编辑器,可以在拍照后添加描述文字。
This article shows how to read and write EXIF tags in JPEG image files using a C# library. Although EXIF tags can also be stored in TIFF image files, this is not considered here.
本文介绍如何使用C#库在JPEG图像文件中读取和写入EXIF标签。 尽管EXIF标记也可以存储在TIFF图像文件中,但此处未考虑。
背景 (Background)
There are many third-party libraries for reading EXIF tags of a JPEG 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 a JPEG 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 JPEG image is uncompressed when loading it and it is newly compressed when the JPEG image is saved with the new or changed EXIF tags. For this reason, I have decided to develop a new library called CompactExifLib
.
有许多用于读取JPEG文件的EXIF标签的第三方库,.NET Framework还提供了用于访问EXIF标签的.NET类。 但是大多数第三方库都无法将EXIF标签写入JPEG文件。 在.NET Framework中,存在用于读取和写入EXIF标记的类。 但是它们有很多开销,因此它们非常慢并且不是无损的,即,在加载JPEG图像时未压缩JPEG图像,并且在使用新的或更改的EXIF标签保存JPEG图像时会对其进行新的压缩。 因此,我决定开发一个名为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:
例如,从400张JPEG照片中读取了拍摄日期的EXIF标签,并测量了以毫秒为单位的时间并将其记录在下表中:
WPF class BitmapMetadata | Library CompactExifLib | Speed factor |
1774 ms | 40.2 ms | 44.1 |
WPF类BitmapMetadata | 库CompactExifLib | 速度因素 |
1774毫秒 | 40.2毫秒 | 44.1 |
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 44 times faster than the library of the .NET framework.
在第一列中,使用.NET Framework的WPF类BitmapMetadata
读取EXIF标签,在第二列中,使用库CompactExifLib
。 如您所见, CompactExifLib
库比.NET框架的库快44倍以上。
Benefits of the CompactExifLib
library:
CompactExifLib
库的优点:
- Very fast, because the EXIF tags are accessed directly with elementary file read and write methods 速度非常快,因为使用基本文件读写方法可以直接访问EXIF标记
- Lossless image saving. The JPEG image matrix is not changed at all when the EXIF tags are saved. 无损图像保存。 保存EXIF标签时,JPEG图像矩阵完全不变。
- Written completely in C#, no DLL is required 完全用C#编写,不需要DLL
- Can be used both with Windows Forms and WPF applications 可以与Windows窗体和WPF应用程序一起使用
- Independent of the .NET Framework version 独立于.NET Framework版本
使用代码 (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:
CompactExifLib
库由一个单个的.cs文件组成。 因此,使用该库非常容易,只需将文件ExifData.cs添加到您的项目中,然后using
命令插入名称空间CompactExifLib
。 该库中的主要类是ExifData
类, ExifData
保存图像文件的完整EXIF数据。 例如,要读取照片“ c:\ temp \ testimage.jpg ”的日期,可以使用以下代码:
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:
ExifData
构造函数具有以下声明:
public ExifData(string FileNameWithPath);
and it loads the EXIF data from the specified image file. If the loading fails, an exception is thrown. Possible reasons are:
并从指定的图像文件加载EXIF数据。 如果加载失败,则会引发异常。 可能的原因是:
- 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 file. 该文件包含非法内容,例如,它不是有效的JPEG文件。
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:
ExifData
构造函数将图像文件的EXIF数据完全复制到内存中,并且该文件立即关闭。 然后,对EXIF数据的存储副本执行所有读取和写入访问。 当将EXIF数据写回到图像文件时,必须调用ExifData
方法Save
:
public void Save(string NewFileNameWithPath = null,
ExifSaveOptions SaveOptions = ExifSaveOptions.None);
If the parameter NewFileNameWithPath
is null
or is omitted, the method Save
overwrites the existing image file. It is also possible to save the image under a new file name by passing the file name in the parameter NewFileNameWithPath
. The method Save
throws an exception if the file could not be saved. Possible reasons are:
如果参数NewFileNameWithPath
为null
或被省略,则Save
方法Save
覆盖现有的图像文件。 也可以通过在参数NewFileNameWithPath
传递文件名来将图像保存为新文件名。 如果无法保存文件,则Save
方法Save
引发异常。 可能的原因是:
- The file is write protected. 该文件被写保护。
- The access to the file is denied. 对该文件的访问被拒绝。
- The file is not available any more, e.g., it was deleted by the user or the volume was removed. 该文件不再可用,例如,该文件已被用户删除或已删除该卷。
- The EXIF data are too large. The maximum EXIF data size is 65535 bytes. EXIF数据太大。 EXIF数据的最大大小为65535字节。
With the second parameter SaveOptions
additional save options can be defined and a list of these options can be found in the source code.
使用第二个参数SaveOptions
可以定义其他保存选项,并且可以在源代码中找到这些选项的列表。
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.
在以下示例代码中,更改了上一示例中图像的拍摄日期,然后将EXIF数据写回到图像文件中。
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
}
标签ID和图像文件目录(IFD) (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.
标签由称为标签ID的16位值定义。 以下示例代码显示了一些标签ID定义。
public enum ExifTagId : ushort
{
...
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. For specifying an IFD, the following constants are available.
但是标签ID不足以指定标签。 此外,必须指定IFD。 EXIF标签分为几个部分,称为图像文件目录(IFD)。 如果要读取或写入标签,则必须指定正确的IFD。 EXIF标准中定义了应将哪个IFD用于标签。 为了指定IFD,可以使用以下常量。
public enum ExifIfd : uint
{
PrimaryData = 0,
PrivateData = 1,
GpsInfoData = 2,
Interoperability = 3,
ThumbnailData = 4,
Count = 5 // Dummy value for the number of IFDs
}
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.
IFD PrimaryData
是EXIF数据的主要IFD,它包含基本图像数据,而IFD PrivateData
包含其他图像数据。 IFD GpsInfoData
存储拍摄图像的位置的GPS数据。 Interoperability
仅供内部使用, ThumbnailData
存储ThumbnailData
的EXIF数据,缩略图是小的预览图像。
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.
为了使指定EXIF标签更容易,这里有一些组合常量,它们的值的高16位包含IFD,低16位包含标签ID。
public enum ExifTag : uint
{
...
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
".
在这里,常量Orientation
和ImageDescription
定义了存储在IFD“ PrimaryData
”中的标记,而常量DateTimeOriginal
定义了在IFD“ PrivateData
”中的标记。
标签类型 (Tag Types)
Each EXIF tag has a type and in this library, the tag type is specified by the enum
type ExifTagType
:
每个EXIF标记都有一个类型,并且在此库中,标记类型由enum
类型ExifTagType
指定:
public enum ExifTagType : ushort
{
NotAvailable = 0,
Byte = 1,
Ascii = 2,
UShort = 3,
ULong = 4,
URational = 5,
Undefined = 7,
SLong = 9,
SRational = 10
}
In the following table, the tag types are described:
下表描述了标签类型:
Enum type constant | Official type name | Description |
ExifTagType.Byte | BYTE | Array of unsigned 8 bit integer values |
ExifTagType.UShort | SHORT | Array of unsigned 16 bit integer values |
ExifTagType.ULong | LONG | Array of unsigned 32 bit integer values |
ExifTagType.SLong | SLONG | Array of signed 32 bit integer values |
ExifTagType.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. |
ExifTagType.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. |
ExifTagType.Ascii | ASCII | Array of 8 bit characters |
ExifTagType.Undefined | UNDEFINED | Array of 8 bit values. The interpretation of the content is defined in the corresponding tag. |
枚举类型常量 | 正式类型名称 | 描述 |
ExifTagType.Byte | BYTE | 无符号8位整数值的数组 |
ExifTagType.UShort | SHORT | 无符号16位整数值的数组 |
ExifTagType.ULong | LONG | 无符号32位整数值的数组 |
ExifTagType.SLong | SLONG | 有符号的32位整数值的数组 |
ExifTagType.URational | RATIONAL | 无符号64位有理数数组。 分子和分母都被编码为无符号的32位数字,并且分子首先存储。 |
ExifTagType.SRational | SRATIONAL | 有符号的64位有理数数组。 分子和分母都被编码为带符号的32位数字,并且分子首先存储。 |
ExifTagType.Ascii | ASCII | 8位字符数组 |
ExifTagType.Undefined | UNDEFINED | 8位值的数组。 内容的解释在相应的标签中定义。 |
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:
在第一个参数TagSpec
,指定标签ID和标签的IFD。 在第二个参数Value
,标签内容作为整数值返回。 第三个参数Index
指定要读取的值的数组索引,索引0
表示标签的第一个值。 如果操作成功,则返回值为true
;如果发生错误,则返回false
。 在以下情况下会发生错误:
- The tag does not exist. 标签不存在。
The tag type is not one of the types
ExifTagType.Byte
,.UShort
,.ULong
or.SLong
.标签类型不是
ExifTagType.Byte
,.UShort
,.ULong
或.SLong
类型.SLong
。The parameter
Index
specifies an array element outside the tag data.参数
Index
指定标签数据之外的数组元素。If the parameter
Value
is of typeint
: The tag type isExifTagType.ULong
and the number stored in the tag is greater or equal than 0x80000000.如果参数
Value
的类型为int
:标签类型为ExifTagType.ULong
且标签中存储的数字大于或等于0x80000000。If the parameter
Value
is of typeuint
: The tag type isExifTagType.SLong
and the number stored in the tag is negative.如果参数
Value
的类型为uint
:标记类型为ExifTagType.SLong
并且标记中存储的数字为负。
For writing an integer number into a tag, there are the overloaded methods SetTagValue
:
为了将整数写到标签中,有一些重载的方法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
. In the third parameter TagType
, the tag type has to be specified and the valid tag types are the same as for the method GetTagValue
. The fourth parameter Index
specifies the array index of the value to be written. If necessary, the internal memory for the tag is automatically enlarged so that the value can be stored in the specified index. The return value informs about an error which can occur in the following situation:
如果标签不存在,则由SetTagValue
自动创建。 在第三个参数TagType
,必须指定标签类型,并且有效的标签类型与方法GetTagValue
相同。 第四个参数Index
指定要写入的值的数组索引。 如有必要,标签的内部存储器会自动扩大,以便可以将值存储在指定的索引中。 返回值通知在以下情况下可能发生的错误:
- The specified tag type is invalid. 指定的标签类型无效。
The number in the parameter
Value
is outside the range of the tag type specified in the parameterTagType
.参数
Value
中的数字超出参数TagType
指定的标记类型的范围。
Please note that 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.
请注意,方法SetTagValue
仅写入EXIF数据的内部存储器副本,而不写入图像文件。 因此,此方法永远不会引发异常。
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 ExifTagType.UShort
.
在以下示例中,将写入和读取图像方向标签。 根据EXIF标准[EXIF2.32]的定义,此标签应为ExifTagType.UShort
类型的16位值。
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.
使用图像方向标签,可以定义顺时针旋转90、180或270度,并定义图像矩阵的反射,前提是图像查看器在绘制图像时考虑使用此EXIF标签。 例如,此标签的值6定义顺时针旋转90度。
整数数组 (Array of Integer Numbers)
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 two methods for reading and writing the number of array elements of a tag.
大多数标签仅包含一个值,但是有些标签存储多个值,即值的数组。 可以使用已知方法GetTagValue
和SetTagValue
,并且参数Index
指定从零开始的数组索引。 除此之外,还有两种读取和写入标签的数组元素的方法。
public int GetTagValueCount(ExifTag TagSpec);
public bool SetTagValueCount(ExifTag TagSpec, int ValueCount, ExifTagType TagType);
The method GetTagValueCount
gives back the number of array elements of the tag and if the tag does not exist, the return value is 0
. With the method SetTagValueCount
, you can set the number of array elements. This method reallocates the internal memory of the tag and the content of the existing array elements remains unchanged if the tag type is not changed and the content of all new array elements is undefined. In the last parameter of SetTagValueCount
, the new tag type must be defined in order to be able to calculate the memory size for the tag.
GetTagValueCount
方法返回标签的数组元素的数量,如果标签不存在,则返回值为0
。 使用SetTagValueCount
方法,可以设置数组元素的数量。 此方法重新分配标签的内部存储器,并且如果标签类型未更改且所有新数组元素的内容未定义,则现有数组元素的内容保持不变。 在SetTagValueCount
的最后一个参数中,必须定义新的标签类型,以便能够计算标签的内存大小。
The following example shows how to read the tag "SubjectArea
" that should contain 2, 3 or 4 values according to the EXIF standard.
以下示例显示了如何读取标签“ SubjectArea
”,该标签应根据EXIF标准包含2、3或4个值。
ExifData TestExif;
int[] v;
int c;
...
c = TestExif.GetTagValueCount(ExifTag.SubjectArea);
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 astring
. Thestring
is terminated with anull
character.ExifTagType.Ascii
:这是编码string
的默认标记类型。 该string
以null
字符结尾。ExifTagType.Undefined
: Somestring
tags are coded with this type. A terminatingnull
character is not present.ExifTagType.Undefined
:一些string
标签使用这种类型编码。 不存在终止的null
字符。ExifTagType.Byte
: Microsoft has defined special unicode string tags with 16 bit Unicode characters, which are stored in a byte array. Thestring
is terminated with a Unicodenull
character.ExifTagType.Byte
:Microsoft定义了带有16位Unicode字符的特殊unicode字符串标签,这些标签存储在字节数组中。 该string
以Unicodenull
字符终止。
For reading and writing tags as string
s, there are the overloaded methods GetTagValue
and SetTagValue
available:
为了以string
s读写标签,可以使用重载的方法GetTagValue
和SetTagValue
:
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
.
方法GetTagValue
读取一个string
标签,并删除所有终止的null
字符(如果存在)。 如果标签类型为ExifTagType.Ascii
或ExifTagType.Byte
,则SetTagValue
方法将写入一个string
标签并添加一个终止的null
字符。
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.
操作成功时,类型bool
的返回值为true
,否则为false
。 如果读取标签并且标签不存在或标签类型不正确,则会发生错误。
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:
由于EXIF标准中定义了几种string
编码,因此您必须查看特定的EXIF标签使用哪种string
编码和标签类型。 最后一个参数Coding
参数指定应用于读取或写入标签的代码页和标签类型。 此参数为enum
类型StrCoding
,下表中列出了此类型的常量:
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. |
|
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. | 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 coded in the UTF 16 LE or UTF 16 BE code page. | ExifTagType.Undefined |
IdCode_UsAscii | 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 LE or UTF 16 BE string. Writing the tag: The ID code is set to "Ascii" and the string is coded in the US ASCII code page. | ExifTagType.Undefined |
IdCode_WestEu | Reading the tag: If the ID code is "Default" or "Ascii", the string is read in the western european code page 1252. If the ID code is "Unicode", the string is read as UTF 16 LE or UTF 16 BE string. Writing the tag: The ID code is set to "Ascii" and the string is coded in the western european code page 1252. | ExifTagType.Undefined |
类型为StrCoding的常量 | 描述 | 预期标签类型 |
Utf8 | Unicode代码页UTF8。基本代码是US ASCII代码页,特殊字符使用128、255之间的代码点编码为两个,三个或四个字节。 | |
UsAscii | 美国ASCII代码页,每个字符一个字节,并且代码点从0到127。读取字符串时,所有从128到255的非法代码点都设置为问号。 | ExifTagType.Ascii |
UsAscii_Undef | 与UsAscii 相同,除了标签类型不同。 | ExifTagType.Undefined |
WestEuropeanWin | Windows的西欧代码页1252。 基本是美国ASCII字符集,特殊字符在128到255的代码点中被编码为单个字节。 注意:代码页1252在.NET Core应用程序中不可用,并且在尝试使用它时会引发异常。 | ExifTagType.Ascii |
Utf16Le_Byte | Unicode代码页UTF 16 LE(小端),每个字符两个或四个字节。 即使将EXIF块编码为BE(大端),字节顺序也始终为LE。 | ExifTagType.Byte |
IdCode_Utf16 | 字符串前面有一个8字节的ID码。 ID代码定义了字符串编码,此库支持ID代码“默认”,“ Ascii”和“ Unicode”。 读取标签: 如果ID代码为“默认”或“ Ascii”,则在US ASCII代码页中读取字符串。 如果ID代码为“ Unicode”,则该字符串将读取为UTF 16字符串。 根据EXIF块的字节顺序,UTF 16字节顺序为LE(小端)或BE(大端)。 编写标签: ID代码设置为“ Unicode”,并且字符串在UTF 16 LE或UTF 16 BE代码页中编码。 | ExifTagType.Undefined |
IdCode_UsAscii | 读取标签: 如果ID代码为“默认”或“ Ascii”,则在US ASCII代码页中读取字符串。 如果ID代码为“ Unicode”,则将该字符串读取为UTF 16 LE或UTF 16 BE字符串。 编写标签: ID代码设置为“ Ascii”,字符串在US ASCII代码页中编码。 | ExifTagType.Undefined |
IdCode_WestEu | 读取标签: 如果ID代码为“默认”或“ Ascii”,则在西欧代码页1252中读取字符串。如果ID代码为“ Unicode”,则将字符串读取为UTF 16 LE或UTF 16 BE字符串。 编写标签: ID代码设置为“ Ascii”,字符串在西欧代码页1252中编码。 | 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标签及其对应标签类型的非正式表以及字符串编码的说明可在[EXIV2]中找到。 下表列出了一些流行的EXIF字符串标签:
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 | |
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 |
EXIF标签 | 参数“ Coding”的可能值 | 注意 |
ImageDescription | Utf8, UsAscii, WestEuropeanWin | |
Copyright | Utf8, UsAscii, WestEuropeanWin | |
Artist | Utf8, UsAscii, WestEuropeanWin | |
Make | Utf8, UsAscii | |
Model | 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 | 由微软定义 |
XpComment | Utf16Le_Byte | 由微软定义 |
XpAuthor | Utf16Le_Byte | 由微软定义 |
XpKeywords | Utf16Le_Byte | 由微软定义 |
XpSubject | Utf16Le_Byte | 由微软定义 |
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 is used in tags with the tag type ExifTagType.Ascii
, 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 "Make
" and "Model
" don't use special characters.
也许您想知道哪种string
编码应用于带有几种可能的编码的标记,例如标记“ ImageDescription
”。 根据EXIF标准,类型为ExifTagType.Ascii
的string
标签中仅允许使用US-ASCII字符(代码点0到127),但是几乎所有用于编辑EXIF标签的工具都使用扩展代码页(例如UTF8或WestEuropeanWin)来编写标签。 不幸的是,没有通用的方法来确定在标签类型为ExifTagType.Ascii
的标签中使用哪个代码页,但是Utf8可以用作默认值。 如果未使用特殊字符(如重音符号),则代码页Utf8
, UsAscii
和WestEuropeanWin
甚至是相同的,并且由诸如“ Make
”和“ Model
”之类的相机编写的string
标签不使用特殊字符。
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
".
Microsoft定义的string
标记不是官方EXIF标准的一部分,这些标记的名称以字母“ Xp
”开头。
In the following examples, some string
tags are written and read.
在以下示例中,将写入和读取一些string
标签。
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.
一些标签包含分数,它们被编码为两个连续的32位整数,即分子和分母。 有带符号和无符号有理数,请参见标签类型ExifTagType.SRational
和ExifTagType.URational
。 在库中,定义了struct
ExifRational
,它可以存储有符号和无符号有理数。
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:
为了使用有理数读写标签,可以使用以下重载方法:
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
.
方法GetTagValue
期望使用ExifTagType.SRational
或ExifTagType.URational
类型的标签,否则它将失败。 使用SetTagValue
编写标签时,参数TagType
可以是ExifTagType.SRational
或ExifTagType.URational
。 例如,当您尝试编写负有理数并将参数TagType
设置为ExifTagType.URational
时,将实现范围检查,该方法将失败并返回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, the tag type has to be ExifTagType.URational
. Afterwards, the tag is read.
此处,图像的曝光时间设置为1637/1000 = 1.637秒。 根据EXIF标准中的定义,标记类型必须为ExifTagType.URational
。 之后,读取标签。
日期和时间 (Date and Time)
In the EXIF data, there are tags which store a date and time:
在EXIF数据中,有存储日期和时间的标签:
DateTime
DateTime
DateTimeOriginal
DateTimeOriginal
DateTimeDigitized
DateTimeDigitized
GpsDateStamp
GpsDateStamp
Internally, a date and time stamp is stored as a string
with tag type ExifTagType.Ascii
, e.g., "2019:12:22 15:23:47
". With the following methods, these tags can be accessed using the C# type DateTime
:
在内部,日期和时间戳记以标签类型为ExifTagType.Ascii
的string
存储,例如“ 2019:12:22 15:23:47
”。 使用以下方法,可以使用C#类型DateTime
访问这些标签:
public bool GetTagValue(ExifTag TagSpec, out DateTime Value,
TimeFormat Format = TimeFormat.DateAndTime);
public bool SetTagValue(ExifTag TagSpec, DateTime Value,
TimeFormat Format = TimeFormat.DateAndTime);
There are two date formats and the last parameter Format
specifies the format to be used:
有两种日期格式,最后一个参数Format
指定要使用的格式:
TimeFormat.DateAndTime
: A date and time are present and they are separated by a space character. This format is used for the three "DateTimeXXX
" tags.TimeFormat.DateAndTime
:存在日期和时间,并用空格字符分隔。 此格式用于三个“DateTimeXXX
”标签。TimeFormat.DateOnly
: Only a date is present, e.g., "2019:12:22
". This format is used for the tag "GpsDateStamp
".TimeFormat.DateOnly
:仅存在日期,例如“2019:12:22
”。 此格式用于标签“GpsDateStamp
”。
Example:
例:
ExifData TestExif;
DateTime dt1, dt2;
...
TestExif.GetTagValue(ExifTag.DateTimeOriginal, out dt1);
TestExif.GetTagValue(ExifTag.GpsDateStamp, out dt2, TimeFormat.DateOnly);
原始数据和字节顺序 (Raw Data and Byte Order)
It is also possible to get the raw data bytes of a tag without any interpretation. For this purpose, the method GetTagRawData
is available.
也可以不做任何解释就获得标签的原始数据字节。 为此,可以使用方法GetTagRawData
。
public bool GetTagRawData(ExifTag TagSpec, out ExifTagType TagType, out int ValueCount,
out byte[] RawData, out int RawDataIndex);
In the second parameter, TagType
, the tag type is given back. The third parameter ValueCount
returns the number of values which are stored in the tag. Remember that a tag is in general an array of values and not just a single value, so that ValueCount
is the number of array elements. If the size of a single tag value is 1 byte (tag types ExifTagType.Byte
, .Ascii
and .Undefined
), the number returned in ValueCount
is the number of raw data bytes. For the tag type ExifTagType.UShort
, the number of raw data bytes is 2*ValueCount
, etc. You can determine the raw data byte count by the static ExifData
method GetByteCountOfTag
:
在第二个参数TagType
,返回标签类型。 第三个参数ValueCount
返回存储在标签中的值的数量。 请记住,标记通常是一个值数组,而不仅仅是一个值,因此ValueCount
是数组元素的数量。 如果单个标记值的大小为1个字节(标记类型为ExifTagType.Byte
, .Ascii
和.Undefined
),则ValueCount
返回的数字为原始数据字节数。 对于标签类型ExifTagType.UShort
,原始数据字节数为2*ValueCount
等。您可以通过静态ExifData
方法GetByteCountOfTag
确定原始数据字节数:
public static int GetByteCountOfTag(ExifTagType TagType, int ValueCount);
This method returns the number of raw data bytes, which a tag of the specified tag type and value count needs.
此方法返回原始数据字节数,具有指定标签类型和值计数的标签需要此字节。
In the method GetTagRawData
, the raw data bytes are given back as an array in the fourth parameter RawData
. This array may also contain data that does not belong to the specified tag! The fifth parameter RawDataIndex
specifies the index of the first raw data byte in the array RawData
, i.e., the first raw data byte of the specified tag is stored in RawData[RawDataIndex]
. The last raw data byte is then stored in RawData[RawDataIndex + GetByteCountOfTag(TagType, ValueCount) - 1]
.
在方法GetTagRawData
,原始数据字节在第四个参数RawData
作为数组RawData
。 此数组还可能包含不属于指定标签的数据! 第五个参数RawDataIndex
指定数组RawData
第一个原始数据字节的RawData
,即,指定标签的第一个原始数据字节存储在RawData[RawDataIndex]
。 然后将最后一个原始数据字节存储在RawData[RawDataIndex + GetByteCountOfTag(TagType, ValueCount) - 1]
。
Please consider the following things:
请考虑以下事项:
The data returned in the array
RawData
must not be changed by the caller of this method.RawData
should only be used for reading data!此方法的调用者不得更改数组
RawData
返回的数据。RawData
仅应用于读取数据!After new data has been written into the specified tag, the array
RawData
must not be used any将新数据写入指定的标记后,不得使用数组
RawData
more. Therefore, the data returned in
更多。 因此,返回的数据
RawData
should be copied as soon as possible.RawData
应该尽快复制。If the tag does not exist,
RawData
isnull
and the return value isfalse
.如果标签不存在,则
RawData
为null
,返回值为false
。- 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. Which format is used, depends on the camera or the tool which has written the image file. 原始数据字节的解释取决于EXIF数据的字节顺序。 EXIF数据可以以Little Endian(LE)或Big Endian(BE)格式存储。 使用哪种格式,取决于相机或写入图像文件的工具。
The byte order is important for all tags of the types ExifTagType.UShort
, .ULong
, .SLong
, .URational
and .SRational
. Additionally, there are some tags of type ExifTagType.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
.
字节顺序对于ExifTagType.UShort
, .ULong
, .SLong
, .URational
和.SRational
类型的所有标签都很重要。 另外,有些类型为ExifTagType.Undefined
标签需要对字节顺序进行单独处理。 在此库中,可以通过ExifData
属性ByteOrder
确定当前EXIF块的字节顺序。
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 value from a byte array, there are two static
ExifData
methods available:
在属性ByteOrder
,值ExifByteOrder.LittleEndian
表示首先存储多字节值的低字节,例如,将16位十六进制值1A34存储为字节序列34 1A 。 对于设置ExifByteOrder.BigEndian
,字节序列将为1A 34 。 为了使从字节数组中读取16位或32位值更容易,有两种static
ExifData
方法可用:
public static ushort ReadUInt16(byte[] Data, int StartIndex, ExifByteOrder ByteOrder);
public static uint ReadUInt32(byte[] Data, int StartIndex, ExifByteOrder ByteOrder);
The desired byte order for reading the value is specified in the last parameter ByteOrder
.
在最后一个参数ByteOrder
指定了用于读取值的所需字节顺序。
For writing the raw data bytes of a tag, the method SetTagRawData
can be used.
为了写入标签的原始数据字节,可以使用SetTagRawData
方法。
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 GetByteCountOfTag
.
原始数据在参数RawData
传递,参数RawDataIndex
指定用于存储原始数据的第一个字节的数组索引。 请注意,该方法不会复制原始数据,因此,调用此方法后,切勿使用原始数据更改数组。 传递给SetTagRawData
的原始数据字节数由参数TagType
和ValueCount
(=标签的数组元素数)隐式定义。 您可以通过已知方法GetByteCountOfTag
确定原始数据字节数。
For writing multi-byte values to a byte array, the following static
ExifData
methods are available:
要将多字节值写入字节数组,可以使用以下static
ExifData
方法:
public static void WriteUInt16
(byte[] Data, int StartIndex, ushort Value, ExifByteOrder ByteOrder);
public static void WriteUInt32
(byte[] Data, int StartIndex, uint Value, ExifByteOrder ByteOrder);
移除标签 (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. For example, in order to remove all GPS info data from an image, you can use the call:
方法RemoveTag
删除单个标签,方法RemoveAllTagsFromIfd
删除特定IFD的所有标签,方法RemoveAllTags
从图像文件删除所有标签,因此EXIF块随后为空。 如果删除了IFD ThumbnailData
,则缩略图也将自动删除。 例如,要从图像中删除所有GPS信息数据,可以使用以下调用:
ExifData TestExif;
...
TestExif.RemoveAllTagsFromIfd(ExifIfd.GpsInfoData);
缩图图片 (Thumbnail Image)
The thumbnail image is a small preview image for the JPEG image. The thumbnail image is stored within the EXIF data and because the complete EXIF data can have only 65535 bytes, the size of the thumbnail image is limited. The following method ThumbnailImageExists
checks if a thumbnail image exists:
缩略图图像是JPEG图像的小预览图像。 缩略图图像存储在EXIF数据中,并且由于完整的EXIF数据只能包含65535字节,因此缩略图图像的大小受到限制。 以下方法ThumbnailImageExists
检查ThumbnailImageExists
是否存在:
public bool ThumbnailImageExists();
For reading the thumbnail image, the method GetThumbnailImage
is available:
为了读取缩略图,可以使用方法GetThumbnailImage
:
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
.
在第一个参数ThumbnailData
,返回带有缩略图图像的数组。 该数组可能包含更多数据,调用者不得更改它。 缩略图的第一个字节存储在数组索引处,该索引在第二个参数ThumbnailIndex
返回。 缩略图的大小在最后一个参数ThumbnailByteCount
返回。 如果未定义缩略图,则返回值为false
,而数组ThumbnailData
为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.
方法SetThumbnailImage
设置一个新的缩略图,该ThumbnailData
在参数ThumbnailData
指定为数组。 该数组不会由SetThumbnailImage
方法复制,因此在调用此方法后不应更改此数组。
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.
第二个参数ThumbnailIndex
指定缩略图开始的数组索引。 第三个参数ThumbnailByteCount
指定ThumbnailByteCount
的字节数,如果将此参数设置为-1
,则将数组ThumbnailData
的剩余长度指定为字节数。
With the method RemoveThumbnailImage
, you can remove the thumbnail image.
使用RemoveThumbnailImage
方法,可以删除缩略图图像。
public bool RemoveThumbnailImage(bool RemoveAlsoThumbnailTags);
If the parameter RemoveAlsoThumbnailTags
is set to true
, additionally all tags in the IFD thumbnail data are removed.
如果参数RemoveAlsoThumbnailTags
设置为true
,则还将删除IFD缩略图数据中的所有标签。
其他有用的方法 (Other Useful Methods)
Check if a tag exists:
检查标签是否存在:
public bool TagExists(ExifTag TagSpec);
Enumerate all tags of an IFD:
枚举IFD的所有标签:
public bool InitTagEnumeration(ExifIfd Ifd);
public bool EnumerateNextTag(out ExifTag TagSpec);
Create an EXIF tag specification from an IFD and a tag ID:
根据IFD和标签ID创建EXIF标签规范:
public static ExifTag ComposeTagSpec(ExifIfd Ifd, ExifTagId TagId);
Get the IFD or the tag ID from an EXIF tag specification:
从EXIF标签规范中获取IFD或标签ID:
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. All existing tags and the thumbnail image of the current object will be removed before the new EXIF data is copied.
用另一个图像文件的EXIF数据替换所有EXIF标签和缩略图图像。 在复制新的EXIF数据之前,将删除所有现有标签和当前对象的缩略图。
public void ReplaceAllTagsBy(ExifData NewExifData);
参考文献 (References)
ID | Description | Link |
[EXIF2.3] | Official EXIF specification V 2.3 | http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf |
[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 |
ID | 描述 | 链接 |
[EXIF2.3] | 官方EXIF规范V 2.3 | http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf |
[EXIF2.32] | 官方EXIF规范V 2.32 | http://cipa.jp/std/documents/download_e.html?DC-008-Translation-2019-E |
[EXIV2] | 带有EXIF标签的非正式表格 | https://www.exiv2.org/tags.html |
翻译自: https://www.codeproject.com/Articles/5251929/CompactExifLib-Access-to-EXIF-tags-in-JPEG-files
jpeg exif