元数据扩展性概述

本主题介绍为 Windows 映像组件 (WIC) 创建自定义元数据处理程序的要求,包括元数据读取器和编写器。 此外,还讨论了扩展 WIC 运行时组件发现以包含自定义元数据处理程序的要求。

本主题包含以下各节:

先决条件

若要了解本主题,应深入了解 WIC、其组件和图像元数据。 有关 WIC 元数据的详细信息,请参阅 WIC 元数据概述。 有关 WIC 组件的详细信息,请参阅 Windows 映像组件概述

简介

WIC 元数据概述中所述,图像中通常有多个元数据块,每个块以不同的元数据格式公开不同类型的信息。 若要与嵌入在图像中的元数据格式交互,应用程序必须使用适当的元数据处理程序。 WIC 提供了多个元数据处理程序 (元数据读取器和编写器) ,使你能够读取和写入特定类型的元数据,例如 Exif 或 XMP。

除了提供的本机处理程序外,WIC 还提供 API,使你能够创建参与 WIC 运行时组件发现的新元数据处理程序。 这使使用 WIC 的应用程序能够读取和写入自定义元数据格式。

以下步骤使元数据处理程序能够参与 WIC 的运行时元数据发现。

  • (IWICMetadataReader) 实现元数据读取器处理程序类,该类公开读取自定义元数据格式所需的 WIC 接口。 这使基于 WIC 的应用程序能够以读取本机元数据格式的相同方式读取元数据格式。
  • (IWICMetadataWriter) 实现元数据编写器处理程序类,该类公开编码自定义元数据格式所需的 WIC 接口。 这使基于 WIC 的应用程序能够将元数据格式序列化为支持的图像格式。
  • 对元数据处理程序进行数字签名和注册。 这样,通过在注册表中的标识模式与图像文件中嵌入的模式匹配,可以在运行时发现元数据处理程序。

创建元数据读取器

main通过每个 WIC 编解码器实现的 IWICMetadataBlockReader 接口访问编解码器中的元数据块。 此接口枚举以图像格式嵌入的每个元数据块,以便可以发现并实例化每个块的相应元数据处理程序。 WIC 无法识别的元数据块被视为未知的,并定义为 GUID CLSID_WICUnknownMetadataReader。 若要让 WIC 识别元数据格式,必须创建实现三个接口的类: IWICMetadataReaderIWICPersistStreamIWICStreamProvider

注意

如果元数据格式存在导致所需接口的某些方法不适当的限制,则此类方法应返回WINCODEC_ERR_UNSUPPORTEDOPERATION。

 

IWICMetadataReader 接口

创建元数据读取器时,必须实现 IWICMetadataReader 接口。 此接口提供对元数据格式数据流中下部元数据项的访问。

以下代码显示 wincodecsdk.idl 文件中定义的元数据读取器接口的定义。

interface IWICMetadataReader : IUnknown
{
    HRESULT GetMetadataFormat(
        [out] GUID *pguidMetadataFormat
        );

    HRESULT GetMetadataHandlerInfo(
        [out] IWICMetadataHandlerInfo **ppIHandler
        );

    HRESULT GetCount(
        [out] UINT *pcCount
        );

    HRESULT GetValueByIndex(
        [in] UINT nIndex,
        [in, out, unique] PROPVARIANT *pvarSchema,
        [in, out, unique] PROPVARIANT *pvarId,
        [in, out, unique] PROPVARIANT *pvarValue
        );

    HRESULT GetValue(
        [in, unique] const PROPVARIANT *pvarSchema,
        [in] const PROPVARIANT *pvarId,
        [in, out, unique] PROPVARIANT *pvarValue
        );

    HRESULT GetEnumerator(
        [out] IWICEnumMetadataItem **ppIEnumMetadata
        );
};

GetMetadataFormat 方法返回元数据格式的 GUID。

GetMetadataHandlerInfo 方法返回一个 IWICMetadataHandlerInfo 接口,该接口提供有关元数据处理程序的信息。 这包括哪些图像格式支持元数据格式以及元数据读取者是否需要访问完整元数据流等信息。

GetCount 方法返回单个元数据项的数量, (包括嵌入的元数据块) 在元数据流中找到。

GetValueByIndex 方法按索引值返回元数据项。 此方法使应用程序能够循环访问元数据块中的每个元数据项。 以下代码演示了应用程序如何使用此方法检索元数据块中的每个元数据项。

PROPVARIANT readerValue;
IWICMetadataBlockReader *blockReader = NULL;
IWICMetadataReader *reader = NULL;

PropVariantInit(&readerValue);

hr = pFrameDecode->QueryInterface(IID_IWICMetadataBlockReader, (void**)&blockReader);

if (SUCCEEDED(hr))
{
    // Retrieve the third block in the image. This is image specific and
    // ideally you should call this by retrieving the reader count
    // first.
    hr = blockReader->GetReaderByIndex(2, &reader);
}

if (SUCCEEDED(hr))
{
    UINT numValues = 0;

    hr = reader->GetCount(&numValues);

    // Loop through each item and retrieve by index
    for (UINT i = 0; SUCCEEDED(hr) && i < numValues; i++)
    {
        PROPVARIANT id, value;

        PropVariantInit(&id);
        PropVariantInit(&value);

        hr = reader->GetValueByIndex(i, NULL, &id, &value);

        if (SUCCEEDED(hr))
        {
            // Do something with the metadata item.
            //...
        }
        PropVariantClear(&id);
        PropVariantClear(&value);
    }               
}

GetValue 方法按架构和/或 ID 检索特定的元数据项。 此方法类似于 GetValueByIndex 方法,只不过它检索具有特定架构或 ID 的元数据项。

GetEnumerator 方法返回元数据块中每个元数据项的枚举器。 这使应用程序可以使用枚举器来导航元数据格式。

如果元数据格式没有元数据项的架构概念,则 GetValue...方法应忽略此属性。 但是,如果格式支持架构命名,则应预期 NULL 值。

如果元数据项是嵌入的元数据块,请从嵌入内容的子流创建元数据处理程序并返回新的元数据处理程序。 如果没有可用于嵌套块的元数据读取器,请实例化并返回未知的元数据读取器。 若要为嵌入块创建新的元数据读取器,请调用组件工厂的 CreateMetadataReaderFromContainerCreateMetadataReader 方法,或调用 WICMatchMetadataContent 函数。

如果元数据流包含 big-endian 内容,则元数据读取器负责交换它处理的任何数据值。 它还负责通知任何嵌套元数据读取器他们正在使用大数据数据流。 但是,所有值都应以 little-endian 格式返回。

通过支持元数据项 ID 为 VT_CLSID 与元数据格式对应的 GUID) (查询来实现对命名空间导航的支持。 如果在分析过程中标识了该格式的嵌套元数据读取器,则必须返回该格式。 这使应用程序能够使用元数据查询读取器搜索元数据格式。

按 ID 获取元数据项时,应使用 PropVariantChangeType 函数 将 ID 强制转换为预期类型。 例如,IFD 读取器将强制键入 VT_UI2 ID,以便与 IFD 标记 ID USHORT 的数据类型一致。 输入类型和预期类型必须都是 PROPVARIANT 才能执行此操作。 这不是必需的,但执行此操作可简化调用读取器以查询元数据项的代码。

IWICPersistStream 接口

IWICPersistStream 接口继承自 IPersistStream,并提供其他方法用于使用 WICPersistOptions 枚举保存和加载对象。

以下代码显示 wincodecsdk.idl 文件中定义的 IWICPersistStream 接口的定义。

interface IWICPersistStream : IPersistStream
{
    HRESULT LoadEx(
        [in, unique] IStream *pIStream,
        [in, unique] const GUID *pguidPreferredVendor,
        [in] DWORD dwPersistOptions
        );

    HRESULT SaveEx(
        [in] IStream *pIStream,
        [in] DWORD dwPersistOptions,
        [in] BOOL fClearDirty
        );
};

LoadEx 方法为元数据读取器提供包含元数据块的数据流。 读者分析此流以访问基础元数据项。 元数据读取器使用位于原始元数据内容开头的子流进行初始化。 如果读取器不需要完整流,则子流的范围仅限于元数据块的内容:否则,会提供完整的元数据流,并在元数据块的开头设置位置。

元数据编写器使用 SaveEx 方法序列化元数据块。 在元数据读取器中使用 SaveEx 时,它应返回WINCODEC_ERR_UNSUPPORTEDOPERATION。

IWICStreamProvider 接口

借助 IWICStreamProvider 接口,元数据读取器可以提供对其内容流的引用、提供有关流的信息以及刷新流的缓存版本。

以下代码显示 wincodecsdk.idl 文件中定义的 IWICStreamProvider 接口的定义。

interface IWICStreamProvider : IUnknown
{
    HRESULT GetStream(
        [out] IStream **ppIStream
        );

    HRESULT GetPersistOptions(
        [out] DWORD *pdwPersistOptions
        );

    HRESULT GetPreferredVendorGUID(
        [out] GUID *pguidPreferredVendor
        );

    HRESULT RefreshStream(
        );
};

GetStream 方法检索对元数据流的引用。 返回的流应将流指针重置为起始位置。 如果元数据格式需要完全流访问,则起始位置应为元数据块的开始位置。

GetPersistOptions 方法从 WICPersistOptions 枚举返回流的当前选项。

GetPreferredVendorGUID 方法返回元数据读取器供应商的 GUID。

RefreshStream 方法刷新元数据流。 此方法必须为任何嵌套元数据块调用具有 NULL 流的 LoadEx。 这是必要的,因为嵌套元数据块及其项可能不再存在,因为就地编辑。

创建元数据编写器

元数据编写器是一种元数据处理程序,它提供了一种将元数据块序列化为图像帧或单个帧(如果图像格式支持)的方法。 main通过每个 WIC 编码器实现的 IWICMetadataBlockWriter 接口访问编解码器中的元数据编写器。 此接口使应用程序能够枚举嵌入在图像中的每个元数据块,以便可以发现并实例化每个元数据块的相应元数据编写器。 没有相应元数据编写器的元数据块被视为未知的,并定义为 GUID CLSID_WICUnknownMetadataReader。 若要使已启用 WIC 的应用程序能够序列化和写入元数据格式,必须创建实现以下接口的类: IWICMetadataWriterIWICMetadataReaderIWICPersistStreamIWICStreamProvider

注意

如果元数据格式存在导致所需接口的某些方法不适当的限制,则此类方法应返回WINCODEC_ERR_UNSUPPORTEDOPERATION。

 

IWICMetadataWriter 接口

IWICMetadataWriter 接口必须由元数据编写器实现。 此外,由于 IWICMetadataWriter 继承自 IWICMetadataReader,因此还必须实现 IWICMetadataReader 的所有方法。 由于这两种处理程序类型需要相同的接口继承,因此可能需要创建一个同时提供读取和写入功能的类。

以下代码显示 wincodecsdk.idl 文件中定义的元数据编写器接口的定义。

interface IWICMetadataWriter : IWICMetadataReader
{
    HRESULT SetValue(
        [in, unique] const PROPVARIANT *pvarSchema,
        [in] const PROPVARIANT *pvarId,
        [in] const PROPVARIANT *pvarValue
        );

    HRESULT SetValueByIndex(
        [in] UINT nIndex,
        [in, unique] const PROPVARIANT *pvarSchema,
        [in] const PROPVARIANT *pvarId,
        [in] const PROPVARIANT *pvarValue
        );

    HRESULT RemoveValue(
        [in, unique] const PROPVARIANT *pvarSchema,
        [in] const PROPVARIANT *pvarId
        );

    HRESULT RemoveValueByIndex(
        [in] UINT nIndex
        );
};

SetValue 方法将指定的元数据项写入元数据流。

SetValueByIndex 方法将指定的元数据项写入元数据流中的指定索引。 索引不引用 ID,而是引用项在元数据块中的位置。

RemoveValue 方法从元数据流中删除指定的元数据项。

RemoveValueByIndex 方法从元数据流中删除指定索引处的元数据项。 删除项后,如果索引不是最后一个索引,则预计剩余元数据项将占用已空索引。 预计在删除项后,计数也会更改。

元数据编写者负责将 PROPVARIANT 项转换为格式所需的基础结构。 但是,与元数据读取器不同,VARIANT 类型通常不应强制为不同类型的类型,因为调用方专门指示要使用的数据类型。

元数据编写器必须将所有元数据项提交到图像流,包括隐藏或无法识别的值。 这包括未知的嵌套元数据块。 但是,编码器负责在启动保存操作之前设置任何关键元数据项。

如果元数据流包含 big-endian 内容,则元数据编写器负责交换它处理的任何数据值。 它还负责通知任何嵌套元数据编写者,他们在保存时使用大端数据流。

通过支持对元数据项执行 (与元数据格式对应的 GUID) 类型的 VT_CLSID 设置和删除操作,实现对命名空间创建和删除的支持。 元数据编写器调用 WICSerializeMetadataContent 函数,将嵌套的元数据编写器内容正确嵌入父元数据编写器中。

如果元数据格式支持就地编码,则负责管理所需的填充。 有关就地编码的详细信息,请参阅 WIC 元数据概述读取和写入图像元数据概述

IWICPersistStream 接口

IWICPersistStream 接口继承自 IPersistStream,并提供其他方法用于使用 WICPersistOptions 枚举保存和加载对象。

以下代码显示 wincodecsdk.idl 文件中定义的 IWICPersistStream 接口的定义。

interface IWICPersistStream : IPersistStream
{
    HRESULT LoadEx(
        [in, unique] IStream *pIStream,
        [in, unique] const GUID *pguidPreferredVendor,
        [in] DWORD dwPersistOptions
        );

    HRESULT SaveEx(
        [in] IStream *pIStream,
        [in] DWORD dwPersistOptions,
        [in] BOOL fClearDirty
        );
};

LoadEx 方法为元数据处理程序提供包含元数据块的数据流。

SaveEx 方法将元数据序列化为流。 如果提供的流与初始化流相同,则应执行就地编码。 如果支持就地编码,则当没有足够的填充来执行就地编码时,此方法应返回WINCODEC_ERR_TOOMUCHMETADATA。 如果不支持就地编码,此方法应返回WINCODEC_ERR_UNSUPPORTEDOPERATION。

必须实现 IPersistStream::GetSizeMax 方法,并且必须返回将在后续保存中写入的元数据内容的确切大小。

如果元数据编写器通过流初始化,则应实现 IPersistStream::IsDirty 方法,以便图像可以可靠地确定其内容是否已更改。

如果元数据格式支持嵌套元数据块,则元数据编写器应在保存到流时委托嵌套元数据编写器对其内容的序列化。

IWICStreamProvider 接口

元数据编写器的 IWICStreamProvider 接口实现与元数据读取器实现相同。 有关详细信息,请参阅本文档中的创建元数据读取器部分。

安装和注册元数据处理程序

若要安装元数据处理程序,必须提供处理程序程序集并将其注册到系统注册表中。 可以决定如何以及何时填充注册表项。

注意

为了便于阅读,本文档以下部分所示的注册表项中不会显示实际的十六进制 GUID。 若要查找指定友好名称的十六进制值,请参阅 wincodec.idl 和 wincodecsdk.idl 文件。

 

元数据处理程序注册表项

每个元数据处理程序由唯一的 CLSID 标识,每个处理程序都需要在元数据处理程序的类别 ID GUID 下注册其 CLSID。 每个处理程序类型的类别 ID 在 wincodec.idl 中定义;读取器的类别 ID 名称CATID_WICMetadataReader,编写器的类别 ID 名称CATID_WICMetadataWriter。

每个元数据处理程序由唯一的 CLSID 标识,每个处理程序都需要在元数据处理程序的类别 ID GUID 下注册其 CLSID。 每个处理程序类型的类别 ID 在 wincodec.idl 中定义;读取器的类别 ID 名称CATID_WICMetadataReader,编写器的类别 ID 名称CATID_WICMetadataWriter。

注意

在以下注册表项列表中,{Reader CLSID} 引用您为元数据读取者提供的唯一 CLSID。 {Writer CLSID} 是指为元数据编写器提供的唯一 CLSID。 {Handler CLSID} 引用读取器的 CLSID 和/或编写器的 CLSID,具体取决于你提供的处理程序。 {Container GUID} 是指可以包含元数据块 (映像格式或元数据格式) 容器对象。

 

以下注册表项将元数据处理程序注册到其他可用的元数据处理程序:

[HKEY_CLASSES_ROOT\CLSID\{CATID_WICMetadataReaders}\Instance\{Reader CLSID}]
CLSID={Reader CLSID}
Friendly Name="Reader Name"

[HKEY_CLASSES_ROOT\CLSID\{CATID_ WICMetadataWriters}\Instance\{Writer CLSID}]
CLSID={Writer CLSID}
Friendly Name="Writer Name"

除了在各自的类别中注册处理程序外,还必须注册提供特定于处理程序的信息的其他键。 读取器和编写器共享类似的注册表项要求。 以下语法演示如何注册处理程序。 必须使用各自的 CLSID 以这种方式注册读取器处理程序和编写器处理程序:

[HKEY_CLASSES_ROOT\CLSID\{CLSID}]
"Vendor"={VendorGUID}
"Date"="yyyy-mm-dd"
"Version"="Major.Minor.Build.Number"
"SpecVersion"="Major.Minor.Build.Number"
"MetadataFormat"={MetadataFormatGUID}
"RequiresFullStream"=dword:1|0
"SupportsPadding"= dword:1|0
"FixedSize"=0

[HKEY_CLASSES_ROOT\CLSID\{CLSID}\InProcServer32]
@="drive:\path\yourdll.dll"
"ThreadingModel"="Apartment"

[HKEY_CLASSES_ROOT\CLSID\{CLSID}\{LCID}]
Author="Author's Name"
Description = " Metadata Description"
DeviceManufacturer ="Manufacturer Name"
DeviceModels="Device,Device"
FriendlyName="Friendly Name"

元数据读取器

元数据读取器注册还包括描述读取器如何以容器格式嵌入的键。 容器格式可以是图像格式,例如 TIFF 或 JPEG;它也可以是另一种元数据格式,例如 IFD 元数据块。 本机支持的映像容器格式在 wincodec.idl 中列出;每个映像容器格式都定义为名称以 GUID_ContainerFormat 开头的 GUID。 本机支持的元数据容器格式在 wincodecsdk.idl 中列出;每种元数据容器格式定义为名称以GUID_MetadataFormat开头的 GUID。

以下密钥注册元数据读取器支持的容器,以及从该容器读取所需的数据。 必须以这种方式注册读取器支持的每个容器。

[HKEY_CLASSES_ROOT\CLSID\{Reader CLSID}\Containers\{Container GUID}\0]
"Position"=dword:00000000
"Pattern"=hex:ff,ff,ff,...
"Mask"=hex:ff,ff,ff,...
"DataOffset"=dword:00000006

Pattern 键描述用于将元数据块与读取器匹配的二进制模式。 为元数据读取器定义模式时,正匹配意味着元数据读取者可以理解正在处理的元数据块中的元数据,这应该足够可靠。

DataOffset 键描述元数据与块标头的固定偏移量。 此键是可选的,如果未指定,则表示无法使用块标头的固定偏移量来定位实际元数据。

元数据编写器

元数据编写器注册还包括描述如何在嵌入容器格式的元数据内容之前写出标头的键。 与读取器一样,容器格式可以是图像格式或其他元数据块。

以下键注册元数据编写器支持的容器,以及写入标头和元数据所需的数据。 编写器支持的每个容器都必须以这种方式注册。

[HKEY_CLASSES_ROOT\CLSID\{Writer CLSID}\Containers\{Container GUID}]
"WritePosition"=dword:00000000
"WriteHeader"=hex:ff,ff,ff,...
"WriteOffset"=dword:00000000

WriteHeader 键描述要写入的元数据块标头的二进制模式。 此二进制模式与元数据格式的读取器模式键一致。

WriteOffset 键描述应写入元数据的块标头的固定偏移量。 此键是可选的,如果未指定,则表示不应使用 标头写出实际元数据。

对元数据处理程序进行签名

所有元数据处理程序都必须经过数字签名才能参与 WIC 发现过程。 WIC 不会加载任何未由受信任的证书颁发机构签名的处理程序。 有关数字签名的详细信息,请参阅 代码签名简介

特殊注意事项

以下部分包括创建自己的元数据处理程序时必须考虑的其他信息。

PROPVARIANTS

WIC 使用 PROPVARIANT 来表示用于读取和写入的元数据项。 PROPVARIANT 为元数据格式中使用的元数据项提供数据类型和数据值。 作为元数据处理程序的编写者,在以元数据格式存储数据的方式以及数据在元数据块中的表示方式方面具有很大的灵活性。 下表提供了指导,可帮助你确定要在不同情况下使用的适当 PROPVARIANT 类型。

元数据类型为... 使用 PROPVARIANT 类型 PROPVARIANT 属性
空或不存在。 VT_EMPTY 不适用。
未定义。 VT_BLOB 使用 blob 属性设置使用 CoTaskMemAlloc 分配的 BLOB 对象的大小和指针。
元数据块。 VT_UNKNOWN 此类型使用 punkVal 属性。
类型的数组。 VT_VECTOR |VT_{type} 使用 ca{type} 属性可设置使用 CoTaskMemAlloc 分配的数组的计数和指针。
元数据块的数组。 VT_VECTOR |VT_VARIANT 使用 capropvar 属性可设置变体数组。
有符号有理值。 VT_I8 使用 hVal 属性设置值。 将高字设置为分母,将低字设置为分子。
一个有理值。 VT_UI8 使用 uhVal 属性设置值。 将 HighPart 设置为分母,将 LowPart 设置为分子。 请注意,LowPart 是一个无符号的 int。应将分子从无符号 int 转换为 int,以确保如果存在符号位,则保留该符号位。

 

为了避免表示数组项时出现冗余,请勿使用安全数组;仅使用简单数组。 这减少了应用程序在解释 PROPVARIANT 类型时需要执行的工作。

尽可能避免使用 VT_BYREF 并存储内联值。 VT_BYREF 对于小型类型 (常见的元数据项) 效率低下,并且不提供大小信息。

在使用 PROPVARIANT 之前,请始终调用 PropVariantInit 来初始化值。 完成 PROPVARIANT 后,请始终调用 PropVariantClear 以释放为变量分配的任何内存。

8BIM 处理程序

为 8BIM 元数据块编写元数据处理程序时,必须使用封装 8BIM 签名和 ID 的签名。 例如,本机 8BIMIPTC 元数据读取器为读取器发现提供以下注册表信息:

[HKEY_CLASSES_ROOT\CLSID\{0010668C-0801-4DA6-A4A4-826522B6D28F}\Containers\{16100D66-8570-4BB9-B92D-FDA4B23ECE67}\0]
"Position"=dword:00000000
"Pattern"=hex:38,42,49,4d,04,04
"Mask"=hex:ff,ff,ff,ff,ff,ff
"DataOffset"=dword:00000006

8BIMIPTC 读取器的注册模式为0x38、0x42、0x49、0x4D、0x04、0x04。 前四个字节 (0x38 0x42、0x49、0x4D) 是 8BIM 签名,最后两个字节 (0x04,0x04) 是 IPTC 记录的 ID。

因此,若要为解析信息编写 8BIM 元数据读取器,需要注册0x38、0x42、0x49、0x4D、0x03 0xED模式。 同样,前四个字节 (0x38 0x42、0x49 0x4D) 是 8BIM 签名。 但是,最后两个字节 (0x03,0xED) 是 PSD 格式定义的解析信息 ID。

概念性

Windows 映像组件概述

WIC 元数据概述

元数据查询语言概述

读取和写入图像元数据概述

操作说明:使用元数据重新编码 JPEG 图像

其他资源

如何编写WIC-Enabled CODEC