Чтение и запись метаданных

Некоторые файлы с изображениями содержат метаданные, которые можно прочитать, чтобы определить свойства изображения. Например, цифровая фотография может содержать метаданные, которые позволяют определить модель камеры, с помощью которой была получена данная фотография. С помощью Windows GDI+ можно считывать существующие метаданные, а также записывать новые метаданные в файлы изображений.

GDI+ предоставляет единый способ хранения и извлечения метаданных из файлов изображений в различных форматах. В GDI+ элемент метаданных называется элементом свойства. Вы можете хранить и извлекать метаданные, вызывая методы SetPropertyItem и GetPropertyItem класса Image , и вам не нужно беспокоиться о том, как в определенном формате файла хранятся эти метаданные.

В настоящее время GDI+ поддерживает метаданные для форматов файлов TIFF, JPEG, Exif и PNG. Формат Exif, определяющий способ хранения изображений, захваченных цифровыми камерами, основан на форматах TIFF и JPEG. Exif использует формат TIFF для несжатой пиксельной данных и формат JPEG для сжатых пиксельных данных.

GDI+ определяет набор тегов свойств, определяющих элементы свойств. Некоторые теги являются общими; то есть они поддерживаются всеми форматами файлов, упомянутыми в предыдущем абзаце. Другие теги являются специальными и применяются только к определенным форматам. При попытке сохранить элемент свойства в файл, который не поддерживает этот элемент свойства, GDI+ игнорирует запрос. В частности, метод Image::SetPropertyItem возвращает PropertyNotSupported.

Элементы свойств, хранящиеся в файле изображения, можно определить, вызвав Image::GetPropertyIdList. При попытке получить элемент свойства, которого нет в файле, GDI+ игнорирует запрос. В частности, метод Image::GetPropertyItem возвращает propertyNotFound.

Чтение метаданных из файла

Следующее консольное приложение вызывает метод GetPropertySize объекта Image , чтобы определить, сколько фрагментов метаданных содержится в файле 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 . Чтобы получить все метаданные из файла, можно вызвать метод GetAllPropertyItems класса Image . Метод GetAllPropertyItems возвращает массив объектов PropertyItem . Перед вызовом GetAllPropertyItems необходимо выделить буфер, достаточно большой для получения этого массива. Вы можете вызвать метод GetPropertySize класса Image , чтобы получить размер (в байтах) требуемого буфера.

Объект PropertyItem имеет следующие четыре открытых члена:

Описание
идентификатор Тег, идентифицирующий блок метаданных. Значения, которые могут быть назначены id (PropertyTagImageTitle, PropertyTagEquipMake, PropertyTagExifExposureTime и т. д.), определяются в Gdiplusimaging.h.
length Длина (в байтах) массива значений, на который указывает элемент данных значения . Обратите внимание, что если элемент данных типа имеет значение PropertyTagTypeASCII, то элемент данных length — это длина символьной строки, завершающейся null, включая признак конца NULL.
type Тип данных значений в массиве, на который указывает элемент данных значения. Константы (PropertyTagTypeByte, PropertyTagTypeASCII и т. д.), представляющие различные типы данных, описаны в разделе Константы типа тега свойства Image.
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

В предыдущих выходных данных показан шестнадцатеричный идентификатор для каждого элемента свойства. Эти идентификаторы можно найти в разделе Константы тегов свойств изображений и узнать, что они представляют следующие теги свойств.

Шестнадцатеричное значение Тег свойства
0x0320 0x010f
  0x0110
  0x9003
  0x829a
  0x5090
  0x5091
PropertyTagImageTitle PropertyTagEquipMake
  PropertyTagEquipModel
  PropertyTagExifDTOriginal
  PropertyTagExifExposureTime
  PropertyTagLuminanceTable
  PropertyTagChrominanceTable

 

Второй элемент свойства (индекс 1) в списке имеет идентификатор PropertyTagEquipMake и тип PropertyTagTypeASCII. В следующем примере, который является продолжением предыдущего консольного приложения, отображается значение этого элемента свойства:

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

В предыдущей строке кода выводятся следующие выходные данные:

The equipment make is Northwind Traders.

Пятый элемент свойства (индекс 4) в списке имеет идентификатор PropertyTagExifExposureTime и тип PropertyTagTypeRational. Этот тип данных (PropertyTagTypeRational) является парой longs. В следующем примере, который является продолжением предыдущего консольного приложения, эти два значения 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 методу SetPropertyItem объекта Image .

Следующее консольное приложение записывает один элемент (заголовок изображения) метаданных в объект 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;
}