Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4434.html
 

Чтение метаданных (Exif/XMP) JPEG с помощью WIC

Опубликовано 18.08.2010 г. 00:28

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

Вообще задавшись таким вопросом можно сломать голову. С одной точки зрения все достаточно просто. Но когда доходишь до последнего этапа все начинает быть весьма запутанно и непонятно. Итак, о чем это я. Возьмем какое либо фото, я взял последнее имеющееся в моем альбомчике:
 
Если воспользовавшись проводником мы откроем окно свойств файла, то на вкладке "Подробно" увидим перечень метаданных:
 
Также некоторые метаданные интересные, например, для людей увлекающихся фотографией мы можем видеть в различных программах просмотра фото. На скрине - Faststone Image Viewer - который кстати написан на Delphi.

Вопрос в том, как получить подобные данные самостоятельно. Windows предоставляет нам такой инструмент для работы с изображениями как Windows Imaging Component - WIC, который в какой то степени используется в Delphi. Что может дать WIC для простого смертного программиста? С помощью WIC вы можете загружать изображения, использовать их где нить в Direct2D например, производить различные манипуляции как поворот, уменьшение/увеличение и т.п, а также читать/записывать метаданные. Вообще, файлы изображения могут содержать несколько картинок (фреймов), например как gif. Каждый фрейм содержит свои метаданные. Метаданные в свою очередь разбиты на блоки. Вот схематичное изображения блоков метаданных для изображения формата JPEG, которое может содержать только один единственный фрейм. Для чтения метаданных WIC предоставляет нам класс IWICMetadataQueryReader или есть еще BlockReader. Чтобы для нужной картинки получить подобный объект, необходимо провести ряд манипуляций. Создать экземпляр WIC-фабрики IWICImagingFactory, с ее помощью получить экземпляр декодера для изображения IWICBitmapDecoder, затем получить декодер для фрейма с которым мы хотим работать IWICBitmapFrameDecode, и уже затем мы можем получить экземпляр IWICMetadataQueryReader для чтения метаданных фрейма. Данная цепочка действия в Delphi будет выглядеть следующим образом (необходимо подключить wincodec):
var wicFactory : IWICImagingFactory;
    wicDecoder : IWICBitmapDecoder;
    frameDecode : IWICBitmapFrameDecode;
    mdReader : IWICMetadataQueryReader;
begin
    wicFactory := CreateComObject(CLSID_WICImagingFactory) as IWICImagingFactory;
    wicFactory.CreateDecoderFromFilename('e:\624.jpg',
                                         TGUID(nil^),
                                         GENERIC_READ,
                                         WICDecodeMetadataCacheOnDemand,
                                         wicDecoder);

    wicDecoder.GetFrame(0,frameDecode);

    if S_OK  < > frameDecode.GetMetadataQueryReader(mdReader) then begin
        showMessage('cant get reader');
        exit;
    end;
Все кажется весьма простым. Теперь остается только прочитать необходимые нам данные. Всего ничего. Но это как оказывается самая загадочная часть. MSDN предлагает такой пример кода для чтения рейтинга фотографии:
mdReader.GetMetaDataByName('/app1/ifd/{ushort=18249}', value);
Допустим, что app0 & ifd это блоки метаданных это понятно. Но вот волшебные записи вида {ushort=18249} заставляют немного задуматься. Доступ к полям метаданных предоставляется по их тегу, знать номер тега - ваша задача, и узнать номера тегов нужных данных, вы очевидно можете лишь изучив спецификации EXIF формата, например. Уже после того как мне все таки удалось считать наконец таки модель камеры, с помощью которой был сделан снимок, гугл натолкнул меня на один блог, владелец которого, вроде как был участником команды разработчиков WIC. Вот цитата из его поста про метаданные:
Метадата нынче пребывает в полном разброде, костыли и легаси в полный рост, MS даже не пытается протолкнуть свой формат в эту кутерьму. У нас ей занимается китаец Вей-Чанг, и я к нему стараюсь не лезть. Скажем, у JPEG есть App1-App13, куски файла для всяческой информации для разных программ. Поля App1 и кажется App2 хорошо стандартизованы, App13 используется Фотошопом, что уже стало неявным стандартом. Есть exif от JEITA и xmp от Adobe - стандарты данных об изображении в файле, которые по-разному встраиваются в разные форматы. exif это фактически набор пар "ключ-значение", а xmp - настоящий XML. И вот exif и xmp-данные могут быть встроены в разные места JPEG и TIF, и мы умеем по ним ходить, читать и править. У IWICBitmapFrameDecoder можно взять IWICMetadataQueryReader, а он может как достать проперти по путям типа "/ifd/xmp/description/x-default", так и честно выдать все поля, которые под ним лежат. К сожалению, нужно знать что и где лежит по каким тегам, что вообще говоря зависит от формата файла и того как там все лежит. Бардак, одним словом.
Формат Exif описывает какие данные содержатся для каждого тега, и как их представлять. Все понятно, например, с названием камеры, оно является строкой; со значением ISO - просто число. А для значения выдержки (времени экспозиции) значение - int64, одна из частей которого обозначает числитель, вторая знаменатель значения. В конечном итоге, если мы добавим на форму TValueListEditor и дополним наш код следующим образом, то получим некоторые метаданные. XMP значения читать гораздо проще, ибо теги - не числа а нормальные человеко-понятные строки (например, объектив : '/xmp/Lens' или '/xmp/str=Lens'). prop - переменная типа TPropVariant. Из кода убраны части с zeroMemory(@prop, sizeof(prop)) для сокращения объема кода, кроме первого, которые надо проводить перед вызовом getMetaDataByName.
    mdReader.GetMetadataByName('/app1/ifd/{uint=272}', prop);
    valuesList.Values['Модель'] := prop.pszVal;
    zeroMemory(@prop, sizeof(prop));  

    mdReader.GetMetadataByName('/app1/ifd/{uint=306}', prop);
    valuesList.Values['Дата изменения'] := prop.pszVal;

    mdReader.GetMetadataByName('/app1/ifd/exif/{uint=33434}', prop);
    valuesList.Values['Время экспозиции'] := Format('%d/%d сек.',[prop.hVal.LowPart, prop.hVal.HighPart]);

    mdReader.GetMetadataByName('/app1/ifd/exif/{uint=33437}', prop);
    valuesList.Values['Диафрагма'] := Format('F/%.1f',[prop.hVal.LowPart/prop.hVal.HighPart]);

    mdReader.GetMetadataByName('/app1/ifd/exif/{uint=34855}', prop);
    valuesList.Values['ISO'] := intToStr(prop.uiVal);

    mdReader.GetMetadataByName('/app1/ifd/exif/{uint=37386}', prop);
    valuesList.Values['Фокусное расстояние'] := intToStr(prop.hVal.LowPart);

    mdReader.GetMetadataByName('/app1/ifd/exif/{uint=37382}', prop);
    valuesList.Values['Расстояние до объекта'] := Format('%.2f м.', [prop.hVal.LowPart/prop.hVal.HighPart]);

    mdReader.GetMetadataByName('/xmp/Lens', prop);
    valuesList.values['Объектив'] := prop.pwszVal;
на code.msdn.miscrosoft.com есть утилитка WICExplorer которая перечисляет все метаданные для файла. Странно, но несмотря на то, что с помощью WIC можно прочитать упомянутую информацию об ориентации файла, и отобразить картинку правильно (а не вверх головой например), стандартный просмотрщик windows до сих пор не умеет это делать. Результат же работы кода, приведенного выше таков:
Метки:  WIC  |  exif 

Комментарии

Алексей Тимохин
18.08.2010 в 03:18
а в каких версиях Windows доступен WIC?
ter
18.08.2010 в 13:30
должен быть доступен с хп-сп3. для более ранних версий надо загружать отдельно.
Deksden
20.08.2010 в 20:06
Хороший способ решить задачу тут: http://delphihaven.wordpress.com/ccr-exif/
ter
01.02.2011 в 21:59
примерно как то так. К сожалению в нормальном виде код в комментарии не оформить.
program PhotoDateTest;
{$APPTYPE CONSOLE}
uses
wincodec, comObj,activex, sysUtils, windows;

const JPEG_FILE_NAME = 'e:\test.jpg';

function getPhotoDate(const filename : string):string;
var wicFactory : IWICImagingFactory;
wicDecoder : IWICBitmapDecoder;
frameDecode : IWICBitmapFrameDecode;
mdReader : IWICMetadataQueryReader;
prop : TPropVariant;
begin
wicFactory := CreateComObject(CLSID_WICImagingFactory) as IWICImagingFactory;
wicFactory.CreateDecoderFromFilename(PChar(filename),
TGUID(nil^),
GENERIC_READ,
WICDecodeMetadataCacheOnDemand,
wicDecoder);

wicDecoder.GetFrame(0,frameDecode);
if S_OK >< frameDecode.GetMetadataQueryReader(mdReader) then exit('cant get reader');

zeroMemory(@prop, sizeof(prop));
mdReader.GetMetadataByName('/xmp/CreateDate', prop);
result:= prop.pwszVal;
end;

var DateStr : string;
begin
CoInitialize(nil);

DateStr := getPhotoDate(JPEG_FILE_NAME);
writeln('Photo date and time: ', DateStr);
readln;

CoUninitialize();
end.
ArtZ
16.04.2011 в 13:44
Чет не работает.. Возвращает пустую строку при любой фотографии...
ArtZ
16.04.2011 в 13:35
prop.pwszVal равно nil
ArtZ
17.04.2011 в 15:41
прошу прощения, все работает) спасибо!
ter
17.04.2011 в 19:41
(:
ArtZ
17.04.2011 в 19:28
а нет, не все работает :(
некоторые файлы не проходят...
ArtZ
17.04.2011 в 20:35
все...
mdReader.GetMetadataByName('/app1/ifd/{uint=306}', prop);
result :=prop.pszVal;

вместо

mdReader.GetMetadataByName('/xmp/CreateDate', prop);
result:= prop.pwszVal;
ArtZ
18.04.2011 в 14:19
но все равно это не то... это дата изменения. А DateTimeOriginal почему то не вытягивается этим кодом, хотя в стандарте цифры указаны...
Грин
28.07.2011 в 12:17
Привет! Где вы нашли модуль wincodec?
ter
28.07.2011 в 17:34
входит в delphi 2010.
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно