Read IFD and EXIF Metadata with WIC and MFC

A graphic file can store additional information abaout image (camera model, camera manufacturer, date and time, etc). Windows Imaging Component (WIC) provides interfaces which allow dealing with image metadata. Basically, for reading metadata we need an IWICMetadataQueryReader instance to call its GetMetadataByName method. The first argument of GetMetadataByName is based in Metadata Query Language and is composed by a path in metadata tree and an item name/identifier or a block name/identifier (a block contains itself items and/or other blocks). For more details, please see Metadata Query Language Overview in MSDN library. The second argument is a PROPVARIANT structure that gets the value. If returned value is of type VT_UNKNOWN then we’ve got a block, otherwise we’ve got an item value.

Among other metadata blocks defined in TIFF standard, which are are common to other graphic file formats (e.g. JPEG and raw image file formats like NEF) there are IFD (Image File Directory) and EXIF metadata blocks. I’ll not provide more details here (they can be found in MSDN or in other documents and articles). Just to note that EXIF is nested into IFD block and, while IFD block is placed in the metadata root for TIFF, it is nested/embedded into APP1 block for JPEG file format. For example, to get the ISOSpeed EXIF value, we can call IWICMetadataQueryReader::GetMetadataByName passing “/ifd/exif/{ushort=34867}” in case of TIFF or “/app1/ifd/exif/{ushort=34867}” for JPEG format.

Reading IFD and EXIF metadata from a JPEG file

Here is a brief example:

void CWhateverMFCClass::ReadJPEGFileMetadata(LPCWSTR wzFilePath)
{
    // get IWICImagingFactory from global _AFX_D2D_STATE structure
    _AFX_D2D_STATE* pD2DState = AfxGetD2DState();
    IWICImagingFactory* pImagingFactory = pD2DState->GetWICFactory();
    ATLASSERT(pImagingFactory);

    try
    {
        // create bitmap decoder based on the given file
        CComPtr<IWICBitmapDecoder> spBitmapDecoder;
        HRESULT hr = pImagingFactory->CreateDecoderFromFilename(wzFilePath, NULL,
            GENERIC_READ, WICDecodeMetadataCacheOnDemand, &spBitmapDecoder);
        ATLENSURE_SUCCEEDED(hr);

        // get first image frame
        CComPtr<IWICBitmapFrameDecode> spBitmapFrameDecode;
        hr = spBitmapDecoder->GetFrame(0, &spBitmapFrameDecode);
        ATLENSURE_SUCCEEDED(hr);

        // get root metadata reader
        CComPtr<IWICMetadataQueryReader> spRootQueryReader;
        hr = spBitmapFrameDecode->GetMetadataQueryReader(&spRootQueryReader);
        ATLENSURE_SUCCEEDED(hr);

        PROPVARIANT value;
        ::PropVariantInit(&value);

        // get camera model
        CString strModel;
        hr = spRootQueryReader->GetMetadataByName(L"/app1/ifd/{ushort=272}", &value);
        if (SUCCEEDED(hr) && (value.vt == VT_LPSTR))
            strModel = CA2T(value.pszVal);
        ::PropVariantClear(&value);

        // get the date and time when the image was digitized
        CString strDateTimeDigitized;
        hr = spRootQueryReader->GetMetadataByName(L"/app1/ifd/exif/{ushort=36868}", &value);
        if(SUCCEEDED(hr) && (value.vt == VT_LPSTR))
            strDateTimeDigitized = CA2T(value.pszVal);
        ::PropVariantClear(&value);

        // and so on, and so on...
    }
    catch (COleException* e)
    {
        e->ReportError();
        e->Delete();
    }
}

As can be seen, this example assumes that we have a JPEG file and uses the root metadata query reader by passing the item full path.
Let’s improve it in order to read metadata also from TIFF, NEF and other formats. Let’s also get and use the embedded metadata readers both for IFD and EXIF blocks.

Reading IFD and EXIF metadata from an image file

First, we need a functions which finds out if we are dealing with JPEG or other format.

BOOL CWhateverMFCClass::IsJPEGFileFormat(CComPtr<IWICBitmapDecoder> spBitmapDecoder)
{
    GUID guidFormat = {0};
    HRESULT hr = spBitmapDecoder->GetContainerFormat(&guidFormat);
    ATLENSURE_SUCCEEDED(hr);

    return IsEqualGUID(guidFormat, GUID_ContainerFormatJpeg);
}

Next, we can write a function that gets the IFD query reader.

HRESULT CWhateverMFCClass::GetIfdQueryReader(
    CComPtr<IWICBitmapDecoder> spBitmapDecoder,
    CComPtr<IWICMetadataQueryReader> spRootQueryReader,
    CComPtr<IWICMetadataQueryReader>& spQueryReader)
{
    LPWSTR wszIFDPath = L"/ifd";
    if (IsJPEGFileFormat(spBitmapDecoder))
        wszIFDPath = L"/app1/ifd";

    PROPVARIANT value;
    ::PropVariantInit(&value);
    HRESULT hr = spRootQueryReader->GetMetadataByName(wszIFDPath, &value);
    if (FAILED(hr))
        return hr;
    else if (value.vt != VT_UNKNOWN)
        return E_FAIL;

    return value.punkVal->QueryInterface(IID_IWICMetadataQueryReader, (void**)&spQueryReader);
}

Also we need a function to get the embedded EXIF query reader. Here we can write a generic one, which takes the parent query reader and the block name.

HRESULT CWhateverMFCClass::GetEmbeddedQueryReader(CComPtr<IWICMetadataQueryReader> spParentQueryReader,
    LPCWSTR wszBlockName,
    CComPtr<IWICMetadataQueryReader>& spQueryReader)
{
    PROPVARIANT value;
    ::PropVariantInit(&value);
    HRESULT hr = spParentQueryReader->GetMetadataByName(wszBlockName, &value);
    if (FAILED(hr))
        return hr;
    else if (value.vt != VT_UNKNOWN)
        return E_FAIL;

    return value.punkVal->QueryInterface(IID_IWICMetadataQueryReader, (void**)&spQueryReader);
}  

Let’a also write a function that gets the root query reader.

HRESULT CWhateverMFCClass::GetRootQueryReader(CComPtr<IWICBitmapDecoder> spBitmapDecoder,
    CComPtr<IWICMetadataQueryReader>& spRootQueryReader)
{
    // get first image frame
    CComPtr<IWICBitmapFrameDecode> spBitmapFrameDecode;
    HRESULT hr = spBitmapDecoder->GetFrame(0, &spBitmapFrameDecode);
    if (FAILED(hr))
        return hr;

    return spBitmapFrameDecode->GetMetadataQueryReader(&spRootQueryReader);
}

Now, the function which reads the image IFD and EXIF metadata may look like this:

void ReadImageFileMetadata(LPCWSTR wzFilePath)
{
    // get IWICImagingFactory from global _AFX_D2D_STATE structure
    _AFX_D2D_STATE* pD2DState = AfxGetD2DState();
    IWICImagingFactory* pImagingFactory = pD2DState->GetWICFactory();
    ATLASSERT(pImagingFactory);

    try
    {
        // create bitmap decoder based on the given file
        CComPtr<IWICBitmapDecoder> spBitmapDecoder;
        HRESULT hr = pImagingFactory->CreateDecoderFromFilename(wzFilePath, NULL,
            GENERIC_READ, WICDecodeMetadataCacheOnDemand, &spBitmapDecoder);
        ATLENSURE_SUCCEEDED(hr);

        // get root metadata reader
        CComPtr<IWICMetadataQueryReader> spRootQueryReader; 
        hr = GetRootQueryReader(spBitmapDecoder, spRootQueryReader); 
        ATLENSURE_SUCCEEDED(hr); 

        // get IFD query reader 
        CComPtr<IWICMetadataQueryReader> spIfdQueryReader; 
        hr = GetIfdQueryReader(spBitmapDecoder, spRootQueryReader, spIfdQueryReader); 
        ATLENSURE_SUCCEEDED(hr); 

        PROPVARIANT value; 
        ::PropVariantInit(&value); 

        // get camera model 
        CString strModel; 
        hr = spIfdQueryReader->GetMetadataByName(L"/{ushort=272}", &value);
        if (SUCCEEDED(hr) && (value.vt == VT_LPSTR))
            strModel = CA2T(value.pszVal);
        ::PropVariantClear(&value);

        // read other IFD values...

        // get embedded EXIF query reader
        CComPtr<IWICMetadataQueryReader> spExifQueryReader;
        hr = GetEmbeddedQueryReader(spIfdQueryReader, L"/exif", spExifQueryReader);
        ATLENSURE_SUCCEEDED(hr);

        // get the date and time when the image was digitized
        CString strDateTimeDigitized;
        hr = spExifQueryReader->GetMetadataByName(L"/{ushort=36868}", &value);
        if (SUCCEEDED(hr) && (value.vt == VT_LPSTR))
            strDateTimeDigitized = CA2T(value.pszVal);
        ::PropVariantClear(&value);

        // read other EXIF values...
    }
    catch (CException* e)
    {
        e->ReportError();
        e->Delete();
    }
}

It’s a little bit better than the previous example but still needs to be completed and a lot of code refactoring. I’ll do that in a future article to present a “full featured” IFD/EXIF metadata reader.

References and related articles

Leave a Comment