jpeg exif_CompactExifLib:访问JPEG文件中的EXIF标签

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 BitmapMetadataLibrary CompactExifLibSpeed factor
1774 ms40.2 ms44.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:

如果参数NewFileNameWithPathnull或被省略,则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".

在这里,常量OrientationImageDescription定义了存储在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 constantOfficial type nameDescription
ExifTagType.ByteBYTEArray of unsigned 8 bit integer values
ExifTagType.UShortSHORTArray of unsigned 16 bit integer values
ExifTagType.ULongLONGArray of unsigned 32 bit integer values
ExifTagType.SLongSLONGArray of signed 32 bit integer values
ExifTagType.URationalRATIONALArray 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.SRationalSRATIONALArray 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.AsciiASCIIArray of 8 bit characters
ExifTagType.UndefinedUNDEFINEDArray 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 type int: The tag type is ExifTagType.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 type uint: The tag type is ExifTagType.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 parameter TagType.

    参数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.

大多数标签仅包含一个值,但是有些标签存储多个值,即值的数组。 可以使用已知方法GetTagValueSetTagValue ,并且参数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 a string. The string is terminated with a null character.

    ExifTagType.Ascii :这是编码string的默认标记类型。 该stringnull字符结尾。

  • ExifTagType.Undefined: Some string tags are coded with this type. A terminating null 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. The string is terminated with a Unicode null character.

    ExifTagType.Byte :Microsoft定义了带有16位Unicode字符的特殊unicode字符串标签,这些标签存储在字节数组中。 该string以Unicode null字符终止。

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

为了以string s读写标签,可以使用重载的方法GetTagValueSetTagValue

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.AsciiExifTagType.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 StrCodingDescriptionExpected tag type
Utf8Unicode 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

UsAsciiUS 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_UndefSame as UsAscii, except the tag type is different.ExifTagType.Undefined
WestEuropeanWinWestern 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_ByteUnicode 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_Utf16An 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_UsAsciiReading 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_WestEuReading 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之间的代码点编码为两个,三个或四个字节。

ExifTagType.Ascii

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 tagPossible values for parameter "Coding"Note
ImageDescriptionUtf8, UsAscii, WestEuropeanWin 
CopyrightUtf8, UsAscii, WestEuropeanWin 
ArtistUtf8, UsAscii, WestEuropeanWin 
MakeUtf8, UsAscii 
ModelUtf8, UsAscii 
DateTimeUtf8, UsAscii 
DateTimeOriginalUtf8, UsAscii 
DateTimeDigitizedUtf8, UsAscii 
ExifVersionUsAscii_Undef 
FlashPixVersionUsAscii_Undef 
UserCommentIdCode_Utf16, IdCode_UsAscii, IdCode_WestEu 
XpTitleUtf16Le_ByteDefined by Microsoft
XpCommentUtf16Le_ByteDefined by Microsoft
XpAuthorUtf16Le_ByteDefined by Microsoft
XpKeywordsUtf16Le_ByteDefined by Microsoft
XpSubjectUtf16Le_ByteDefined 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.Asciistring标签中仅允许使用US-ASCII字符(代码点0到127),但是几乎所有用于编辑EXIF标签的工具都使用扩展代码页(例如UTF8或WestEuropeanWin)来编写标签。 不幸的是,没有通用的方法来确定在标签类型为ExifTagType.Ascii的标签中使用哪个代码页,但是Utf8可以用作默认值。 如果未使用特殊字符(如重音符号),则代码页Utf8UsAsciiWestEuropeanWin甚至是相同的,并且由诸如“ 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.SRationalExifTagType.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.SRationalExifTagType.URational类型的标签,否则它将失败。 使用SetTagValue编写标签时,参数TagType可以是ExifTagType.SRationalExifTagType.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.Asciistring存储,例如“ 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 is null and the return value is false.

    如果标签不存在,则RawDatanull ,返回值为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的原始数据字节数由参数TagTypeValueCount (=标签的数组元素数)隐式定义。 您可以通过已知方法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 ,而数组ThumbnailDatanull

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)

IDDescriptionLink
[EXIF2.3]Official EXIF specification V 2.3http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf
[EXIF2.32]Official EXIF specification V 2.32http://cipa.jp/std/documents/download_e.html?DC-008-Translation-2019-E
[EXIV2]Inofficial table with EXIF tagshttps://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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值