Implementing IWICMetadataBlockReader

IWICMetadataBlockReader

Multiple blocks of metadata often exist within an image, each exposing different types of information in different formats. In the Windows Imaging Component (WIC) model, metadata handlers are distinct components that, like decoders, are discoverable at run time. Each metadata format has a separate handler, and each of these metadata handlers can be used with any image format that supports the metadata format it handles. Therefore, if your image format supports EXIF, XMP, IPTC, or another format, you can take advantage of the standard metadata handlers for these formats that ship with WIC, and you do not need to write your own. Of course, if you create a new metadata format, you must write a metadata handler for it, which will be discovered and invoked at run time just as the standard ones are.

Note

If your image format is based on a Tagged Image File Format (TIFF) or JPEG container, you won’t need to write any metadata handlers (unless you develop a new or proprietary metadata format). In TIFF and JPEG containers, blocks of metadata are located within IFDs, and each container has a different IFD structure. WIC provides IFD handlers for both of these container formats that navigate the IFD structure and delegate to the standard metadata handlers to access the metadata within them. So, if your image format is based on either of these containers, you can automatically take advantage of the WIC IFD handlers. However, if you have a proprietary container format that has its own unique top-level metadata structure, you must write a handler that can navigate that top-level structure and delegate to the appropriate metadata handlers, just like the IFD handlers do.)

 

The same way WIC provides a layer of abstraction for applications that allows them to work with all image formats in the same way through a consistent set of interfaces, WIC provides a layer of abstraction for codec authors with regard to metadata formats. As noted previously, codec authors generally do not need to work directly with the various metadata formats that may be present in an image. However, every codec author is responsible for providing a way to enumerate the blocks of metadata so an appropriate metadata handler can be discovered and instantiated for each block.

You must implement this interface on your frame-level decoding class. You may also need to implement it on your container-level decoder class if your image format exposes global metadata outside of any individual image frames.

interface IWICMetadataBlockReader : IUnknown
{
   // All methods required
   HRESULT GetContainerFormat ( GUID *pguidContainerFormat );
   HRESULT GetCount ( UINT *pcCount );
   HRESULT GetEnumerator ( IEnumUnknown **ppIEnumMetadata );
   HRESULT GetReaderByIndex ( UINT nIndex, IWICMetadataReader **ppIMetadataReader );
}

GetContainerFormat

GetContainerFormat is the same as the GetContainerFormat method on Implementing IWICBitmapDecoder.

GetCount

GetCount returns the number of top-level metadata blocks associated with the frame.

GetEnumerator

GetEnumerator returns an enumerator that the caller can use to enumerate over the metadata blocks in the frame and read their metadata. To implement this method, you need to create a metadata reader for each block of metadata, and implement an enumeration object that enumerates over the collection of metadata readers. The enumeration object must implement IEnumUnknown so you can cast it to IEnumUnknown when you return it in the ppIEnumMetadata parameter.

When implementing the enumeration object, you can create all the metadata readers when you first create the IWICMetadataBlockReader object or when you first create the enumeration object, or you can create them lazily inside the implementation of the IEnumUnknown::Next method. In many cases, it’s more efficient to create them lazily but, in the following example, the block readers are all created in the constructor to save space.

public class MetadataReaderEnumerator : public IEnumUnknown
{
   UINT m_current;
   UINT m_blockCount;
   IWICMetadataReader** m_ppMetadataReader;
   IStream* m_pStream;

   MetadataReaderEnumerator() 
   {
       // Set m_blockCount to the number of metadata blocks in the frame. 
      ...
      m_ppMetadataReader = IWICMetadataReader*[m_blockCount];
       m_current = 0;

      for (UINT x=0; x < m_blockCount; x++) 
      {
         // Find the position in the file where the xth
         // block of metadata lives and seek m_piStream 
         // to that position.
         ...

         m_pComponentFactory->CreateMetadataReaderFromContainer(
            GUID_ContainerFormatTiff,
                        NULL,
                        WICPersistOptions.WICPersistOptionsDefault | 
            WICMetadataCreationOptions.WICMetadataCreationDefault, 
                        m_pStream, &m_ppMetadataReader[x]);
        }
    }

    // Implementation of IEnumUnknown and IUnknown interfaces
    ...
}

To create the metadata readers, you use the CreateMetadataReaderFromContainer method. When invoking this method, you pass in the GUID of the container format in the guidContainerFormat parameter. If you have a preference of vendor for a metadata reader, you can pass the GUID of your preferred vendor in the pGuidVendor parameter. For example, if your company writes metadata handlers, and you want to use your own if present, you can pass in your vendor GUID. In most cases, you would just pass NULL, and let the system select the appropriate metadata reader. If you do request a specific vendor, and that vendor does have a metadata reader installed on the computer, WIC will return that vendor’s reader. However, if the requested vendor does not have a metadata reader installed on the computer, and if there is an appropriate metadata reader available, that reader will be returned even though it is not from the preferred vendor. If there is no metadata reader on the computer for the type of metadata in the block, the component factory will return the Unknown Metadata Handler, which will treat the block of metadata as a binary large object (BLOB), and will deserialize the block of metadata from the file without any attempt at parsing it.

For the dwOptions parameter, perform an OR operation between the appropriate WICPersistOptions with the appropriate WICMetadataCreationOptions. The WICPersistOptions describe how your container is laid out. Little-endian is the default.

enum WICPersistOptions
{   
   WICPersistOptionDefault,
   WICPersistOptionLittleEndian,
   WICPersistOptionBigEndian,
   WICPersistOptionStrictFormat,
   WICPersistOptionNoCacheStream,
   WICPersistOptionPreferUTF8
};

The WICMetadataCreationOptions specify whether you want to get back the UnknownMetadataHandler if no metadata reader is found on the machine that can read the metadata format of a particular block. WICMetadataCreationAllowUnknown is the default, and you should always allow creation of the UnknownMetadtataHandler. The UnknownMetadataHandler treats unrecognized metadata as a BLOB. It cannot parse it, but it writes it out into the stream as a BLOB, and persists it intact when it’s written back to the stream during encoding. This makes it safe to create metadata handlers for proprietary metadata or metadata formats that don’t ship with the system. Because metadata is preserved intact, even if no handler is present on the computer that recognizes it, when an appropriate metadata handler is later installed, the metadata will still be there and can be read. If you don’t allow creation of the UnknownMetadataHandler, the alternative is discarding or overwriting unrecognized metadata. This is a form of data loss.

Note

If you write your own metadata handler for proprietary metadata, you should never include references to anything outside the metadata block itself. Even though the UnknownMetadataHandler preserves metadata intact, metadata does get moved when files are edited, and any references to anything outside its own block will no longer be valid when this happens.

 

enum WICMetadataCreationOptions
{
   WICMetadataCreationDefault = 0x00000000,
   WICMetadataCreationAllowUnknown = WICMetadataCreationDefault,
   WICMetadataCreationFailUnknown = 0x00010000,
   WICMetadataCreationMask = 0xFFFF0000
};

The pIStream parameter is the actual stream that you are decoding. Before passing in the stream, you should seek to the beginning of the metadata block for which you’re requesting a reader. The appropriate metadata reader for the metadata block at the current position in the IStream will be returned in the ppiReader parameter.

GetReaderByIndex

GetReaderByIndex returns the metadata reader at the requested index in the collection.

Reference

IWICMetadataBlockReader

Conceptual

Implementing IWICBitmapFrameDecode

Implementing IWICBitmapSourceTransform

How to Write a WIC-Enabled CODEC

Windows Imaging Component Overview