读取和写入元数据

某些图像文件包含可以读取以确定图像特征的元数据。 例如,数码照片可能包含可以读取以确定用于捕获图像的相机的品牌和型号。 使用 Windows GDI+,可以读取现有元数据,还可以将新元数据写入图像文件。

GDI+ 提供了一种统一的方法,用于以各种格式存储和检索图像文件中的元数据。 在 GDI+ 中,一段元数据称为 属性项。 可以通过调用 Image 类的 SetPropertyItemGetPropertyItem 方法来存储和检索元数据,无需担心特定文件格式存储该元数据的详细信息。

GDI+ 目前支持 TIFF、JPEG、Exif 和 PNG 文件格式的元数据。 Exif 格式指定如何存储由数码相机捕获的图像,是基于 TIFF 和 JPEG 格式构建的。 Exif 对未压缩像素数据使用 TIFF 格式,对压缩像素数据使用 JPEG 格式。

GDI+ 定义一组用于标识属性项的属性标记。 某些标记是常规用途的;也就是说,上一段中提到的所有文件格式都支持它们。 其他标记是特殊用途的,仅适用于某些格式。 如果尝试将属性项保存到不支持该属性项的文件中,GDI+ 将忽略该请求。 更具体地说, Image::SetPropertyItem 方法返回 PropertyNotSupported。

可以通过调用 Image::GetPropertyIdList 来确定存储在图像文件中的属性项。 如果尝试检索不在文件中的属性项,GDI+ 将忽略该请求。 更具体地说, Image::GetPropertyItem 方法返回 PropertyNotFound。

从文件读取元数据

以下控制台应用程序调用 Image 对象的 GetPropertySize 方法,以确定文件FakePhoto.jpg有多少个元数据片段。

#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
using namespace Gdiplus;
INT main()
{
   // Initialize <tla rid="tla_gdiplus"/>.
   GdiplusStartupInput gdiplusStartupInput;
   ULONG_PTR gdiplusToken;
   GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
   UINT    size = 0;
   UINT    count = 0;
   Bitmap* bitmap = new Bitmap(L"FakePhoto.jpg");
   bitmap->GetPropertySize(&size, &count);
   printf("There are %d pieces of metadata in the file.\n", count);
   printf("The total size of the metadata is %d bytes.\n", size);
 
   delete bitmap;
   GdiplusShutdown(gdiplusToken);
   return 0;
}

前面的代码以及特定文件FakePhoto.jpg生成了以下输出:

There are 7 pieces of metadata in the file.
The total size of the metadata is 436 bytes.

GDI+ 将单个元数据段存储在 PropertyItem 对象中。 可以调用 Image 类的 GetAllPropertyItems 方法来检索文件中的所有元数据。 GetAllPropertyItems 方法返回 PropertyItem 对象的数组。 在调用 GetAllPropertyItems 之前,必须分配一个足够大的缓冲区来接收该数组。 可以调用 Image 类的 GetPropertySize 方法,以获取所需缓冲区) 的大小 (字节数。

PropertyItem 对象具有以下四个公共成员:

说明
id 标识元数据项的标记。 可以分配给 id (PropertyTagImageTitle、PropertyTagEquipMake、PropertyTagExifExposureTime 等) 的值在 Gdiplusimaging.h 中定义。
length 数据成员 指向的值数组的长度(以字节为单位)。 请注意,如果 类型 数据成员设置为 PropertyTagTypeASCII,则长度数据成员是以 null 结尾的字符串的 长度 ,包括 NULL 终止符。
type 值数据成员指向的数组中值的数据类型。 图像属性标记类型常量 (PropertyTagTypeByte、PropertyTagTypeASCII 等常量以及表示各种数据类型的类似)
value 指向值数组的指针。

 

以下控制台应用程序读取并显示文件中的七段元数据FakePhoto.jpg。 main 函数依赖于帮助程序函数 PropertyTypeFromWORD,该函数在 main 函数后面显示。

#include <windows.h>
#include <gdiplus.h>
#include <strsafe.h>
using namespace Gdiplus;

INT main()
{
   // Initialize GDI+
   GdiplusStartupInput gdiplusStartupInput;
   ULONG_PTR gdiplusToken;
   GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

   UINT  size = 0;
   UINT  count = 0;

   #define MAX_PROPTYPE_SIZE 30
   WCHAR strPropertyType[MAX_PROPTYPE_SIZE] = L"";

   Bitmap* bitmap = new Bitmap(L"FakePhoto.jpg");

   bitmap->GetPropertySize(&size, &count);
   printf("There are %d pieces of metadata in the file.\n\n", count);

   // GetAllPropertyItems returns an array of PropertyItem objects.
   // Allocate a buffer large enough to receive that array.
   PropertyItem* pPropBuffer =(PropertyItem*)malloc(size);

   // Get the array of PropertyItem objects.
   bitmap->GetAllPropertyItems(size, count, pPropBuffer);

   // For each PropertyItem in the array, display the id, type, and length.
   for(UINT j = 0; j < count; ++j)
   {
      // Convert the property type from a WORD to a string.
      PropertyTypeFromWORD(
         pPropBuffer[j].type, strPropertyType, MAX_PROPTYPE_SIZE);

      printf("Property Item %d\n", j);
      printf("  id: 0x%x\n", pPropBuffer[j].id);
      wprintf(L"  type: %s\n", strPropertyType);
      printf("  length: %d bytes\n\n", pPropBuffer[j].length);
   }

   free(pPropBuffer);
   delete bitmap;
   GdiplusShutdown(gdiplusToken);
   return 0;
} // main

// Helper function
HRESULT PropertyTypeFromWORD(WORD index, WCHAR* string, UINT maxChars)
{
   HRESULT hr = E_FAIL;

   WCHAR* propertyTypes[] = {
      L"Nothing",                   // 0
      L"PropertyTagTypeByte",       // 1
      L"PropertyTagTypeASCII",      // 2
      L"PropertyTagTypeShort",      // 3
      L"PropertyTagTypeLong",       // 4
      L"PropertyTagTypeRational",   // 5
      L"Nothing",                   // 6
      L"PropertyTagTypeUndefined",  // 7
      L"Nothing",                   // 8
      L"PropertyTagTypeSLONG",      // 9
      L"PropertyTagTypeSRational"}; // 10

   hr = StringCchCopyW(string, maxChars, propertyTypes[index]);
   return hr;
}

前面的控制台应用程序生成以下输出:

Property Item 0
  id: 0x320
  type: PropertyTagTypeASCII
  length: 16 bytes
Property Item 1
  id: 0x10f
  type: PropertyTagTypeASCII
  length: 17 bytes
Property Item 2
  id: 0x110
  type: PropertyTagTypeASCII
  length: 7 bytes
Property Item 3
  id: 0x9003
  type: PropertyTagTypeASCII
  length: 20 bytes
Property Item 4
  id: 0x829a
  type: PropertyTagTypeRational
  length: 8 bytes
Property Item 5
  id: 0x5090
  type: PropertyTagTypeShort
  length: 128 bytes
Property Item 6
  id: 0x5091
  type: PropertyTagTypeShort
  length: 128 bytes

前面的输出显示每个属性项的十六进制 ID 号。 可以在 Image 属性标记常量 中查找这些 ID 号,并找出它们表示以下属性标记。

十六进制值 属性标记
0x0320 0x010f
  0x0110
  0x9003
  0x829a
  0x5090
  0x5091
PropertyTagImageTitle PropertyTagEquipMake
  PropertyTagEquipModel
  PropertyTagExifDTOriginal
  PropertyTagExifExposureTime
  PropertyTagLuminanceTable
  PropertyTagChrominanceTable

 

列表中的第二个 (索引 1) 属性项具有 id PropertyTagEquipMake, 类型为 PropertyTagTypeASCII。 以下示例是上一个控制台应用程序的延续,显示该属性项的值:

printf("The equipment make is %s.\n", pPropBuffer[1].value);

上述代码行生成以下输出:

The equipment make is Northwind Traders.

列表中的第五个 (索引 4) 属性项的 id 为 PropertyTagExifExposureTime, 类型为 PropertyTagTypeRational。 PropertyTagTypeRational) (数据类型为 LONG对。 以下示例是上一个控制台应用程序的延续,将这两个 LONG 值显示为小数。 该分数(1/125)是曝光时间(以秒为单位)。

long* ptrLong = (long*)(pPropBuffer[4].value);
printf("The exposure time is %d/%d.\n", ptrLong[0], ptrLong[1]);

前面的代码产生以下输出:

The exposure time is 1/125.

将元数据写入文件

若要将元数据项写入 Image 对象,请初始化 PropertyItem 对象,然后将该 PropertyItem 对象的地址传递给 Image 对象的 SetPropertyItem 方法。

以下控制台应用程序将一项 (图像标题) 元数据写入 Image 对象,然后将映像保存在磁盘文件中FakePhoto2.jpg。 main 函数依赖于帮助程序函数 GetEncoderClsid,该函数在检索编码器的类标识符主题中显示。

#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
using namespace Gdiplus;
INT main()
{
   // Initialize <tla rid="tla_gdiplus"/>.
   GdiplusStartupInput gdiplusStartupInput;
   ULONG_PTR gdiplusToken;
   GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
   Status stat;
   CLSID  clsid;
   char   propertyValue[] = "Fake Photograph";
   Bitmap* bitmap = new Bitmap(L"FakePhoto.jpg");
   PropertyItem* propertyItem = new PropertyItem;
   // Get the CLSID of the JPEG encoder.
   GetEncoderClsid(L"image/jpeg", &clsid);
   propertyItem->id = PropertyTagImageTitle;
   propertyItem->length = 16;  // string length including NULL terminator
   propertyItem->type = PropertyTagTypeASCII; 
   propertyItem->value = propertyValue;
   bitmap->SetPropertyItem(propertyItem);
   stat = bitmap->Save(L"FakePhoto2.jpg", &clsid, NULL);
   if(stat == Ok)
      printf("FakePhoto2.jpg saved successfully.\n");
   
   delete propertyItem;
   delete bitmap;
   GdiplusShutdown(gdiplusToken);
   return 0;
}