图像元数据

本文介绍如何使用 GeotagHelper 实用工具类读取和写入图像元数据属性以及如何对文件进行地理标记。

图像属性

StorageFile.Properties属性返回一个 StorageItemContentProperties 对象,该对象提供对文件内容相关信息的访问。 通过调用 GetImagePropertiesAsync 获取特定于映像的属性。 返回的 ImageProperties 对象公开包含基本图像元数据字段(如图像标题和捕获日期)的成员。

private async void GetImageProperties(StorageFile imageFile)
{
    ImageProperties props = await imageFile.Properties.GetImagePropertiesAsync();

    string title = props.Title;
    if (title == null)
    {
        // Format does not support, or image does not contain Title property
    }

    DateTimeOffset dateTaken = props.DateTaken;
}

若要访问较大的文件元数据集,请使用Windows属性系统,这是一组可以使用唯一字符串标识符检索的文件元数据属性。 创建字符串列表,并添加要检索的每个属性的标识符。 ImageProperties.RetrievePropertiesAsync 方法采用此字符串列表,并返回键/值对字典,其中键是属性标识符,值为属性值。

private async void GetWindowsProperties(StorageFile imageFile)
{
    ImageProperties props = await imageFile.Properties.GetImagePropertiesAsync();

    var requests = new System.Collections.Generic.List<string>();
    requests.Add("System.Photo.Orientation");
    requests.Add("System.Photo.Aperture");

    IDictionary<string, object> retrievedProps = await props.RetrievePropertiesAsync(requests);

    ushort orientation;
    if (retrievedProps.ContainsKey("System.Photo.Orientation"))
    {
        orientation = (ushort)retrievedProps["System.Photo.Orientation"];
    }

    double aperture;
    if (retrievedProps.ContainsKey("System.Photo.Aperture"))
    {
        aperture = (double)retrievedProps["System.Photo.Aperture"];
    }
}
  • 有关Windows属性的完整列表,包括每个属性的标识符和类型,请参阅 Windows Properties

  • 某些属性仅支持某些文件容器和映像编解码器。 有关每种图像类型支持的图像元数据的列表,请参阅 照片元数据策略

  • 由于不支持的属性在检索时可能会返回 null 值,因此在使用返回的元数据值之前,请始终检查 null。

地理标签助手

GeotagHelper 是一个工具类,可直接使用 Windows.Devices.Geolocation API 轻松为图像添加地理数据标记,而无需手动解析或构造元数据格式。

如果你已经有一个表示要在图像中进行标记的位置的 Geopoint 对象,无论该对象是来自先前对地理位置 API 的调用还是来自其他来源,都可以通过调用 GeotagHelper.SetGeotagAsync 并传入 StorageFileGeopoint 来设置地理标记数据。

private async void SetGeoDataFromPoint(StorageFile imageFile)
{
    var point = new Geopoint(
        new BasicGeoposition
        {
            Latitude = 48.8567,
            Longitude = 2.3508,
        });

    await GeotagHelper.SetGeotagAsync(imageFile, point);
}

若要使用设备的当前位置设置地理标记数据,请创建一个新的 Geolocator 对象,并调用 GeotagHelper.SetGeotagFromGeolocatorAsync,传入 Geolocator 和要标记的文件。

private async void SetGeoDataFromGeolocator(StorageFile imageFile)
{
    var locator = new Geolocator();

    // Shows the user consent UI if needed
    var accessStatus = await Geolocator.RequestAccessAsync();
    if (accessStatus == GeolocationAccessStatus.Allowed)
    {
        await GeotagHelper.SetGeotagFromGeolocatorAsync(imageFile, locator);
    }
}

若要获取表示图像文件的地理标记位置的 GeoPoint,请调用 GetGeotagAsync

private async void GetGeoData(StorageFile imageFile)
{
    Geopoint geoPoint = await GeotagHelper.GetGeotagAsync(imageFile);
}

解码和编码图像元数据

使用图像数据的最高级方法是使用 BitmapDecoderBitmapEncoder 读取和写入流级别的属性。 对于这些操作,可以使用Windows属性来指定要读取或写入的数据,但也可以使用Windows图像处理组件(WIC)提供的元数据查询语言来指定所请求属性的路径。

使用此技术读取图像元数据,要求你先拥有一个使用源图像文件流创建的 BitmapDecoder。 有关如何执行此操作的信息,请参阅 创建、编辑和保存位图图像

获取解码器后,使用Windows属性标识符字符串或 WIC 元数据查询为要检索的每个元数据属性创建字符串列表并添加新条目。 在解码器的 BitmapProperties 成员上调用 BitmapPropertiesView.GetPropertiesAsync 方法,以请求指定的属性。 属性在包含属性名称或路径和属性值的键/值对字典中返回。

private async void ReadImageMetadata(BitmapDecoder bitmapDecoder)
{
    var requests = new System.Collections.Generic.List<string>();
    requests.Add("System.Photo.Orientation"); // Windows property key for EXIF orientation
    requests.Add("/xmp/dc:creator"); // WIC metadata query for Dublin Core creator

    try
    {
        var retrievedProps = await bitmapDecoder.BitmapProperties.GetPropertiesAsync(requests);

        ushort orientation;
        if (retrievedProps.ContainsKey("System.Photo.Orientation"))
        {
            orientation = (ushort)retrievedProps["System.Photo.Orientation"].Value;
        }

        string creator;
        if (retrievedProps.ContainsKey("/xmp/dc:creator"))
        {
            creator = (string)retrievedProps["/xmp/dc:creator"].Value;
        }
    }
    catch (Exception err)
    {
        switch (err.HResult)
        {
            case unchecked((int)0x88982F41): // WINCODEC_ERR_PROPERTYNOTSUPPORTED
                // The file format does not support the requested metadata.
                break;
            case unchecked((int)0x88982F81): // WINCODEC_ERR_UNSUPPORTEDOPERATION
                // The file format does not support any metadata.
            default:
                throw;
        }
    }
}
  • 有关 WIC 元数据查询语言和支持的属性的信息,请参阅 WIC 图像格式本机元数据查询

  • 许多元数据属性仅受图像类型的子集支持。 如果与解码器关联的图像不支持所请求的某个属性,GetPropertiesAsync 将因错误代码 0x88982F41 而失败;如果该图像根本不支持元数据,则将因错误代码 0x88982F81 而失败。 与这些错误代码相关的常量分别为 WINCODEC_ERR_PROPERTYNOTSUPPORTED 和 WINCODEC_ERR_UNSUPPORTEDOPERATION,它们定义在 winerror.h 头文件中。

  • 由于图像可能或可能不包含特定属性的值,因此请使用 IDictionary.ContainsKey 在尝试访问属性之前验证结果中是否存在属性。

若要将图像元数据写入流,需要一个与图像输出文件相关联的 BitmapEncoder

创建 BitmapPropertySet 对象以包含所需的属性值。 创建 BitmapTypedValue 对象来表示属性值。 此对象使用 object 作为定义值类型的 PropertyType 枚举的值和成员。 将 BitmapTypedValue 添加到 BitmapPropertySet ,然后调用 BitmapProperties.SetPropertiesAsync ,使编码器将属性写入流。

private async void WriteImageMetadata(BitmapEncoder bitmapEncoder)
{
    var propertySet = new Windows.Graphics.Imaging.BitmapPropertySet();
    var orientationValue = new Windows.Graphics.Imaging.BitmapTypedValue(
        1, // Defined as EXIF orientation = "normal"
        Windows.Foundation.PropertyType.UInt16);

    propertySet.Add("System.Photo.Orientation", orientationValue);

    try
    {
        await bitmapEncoder.BitmapProperties.SetPropertiesAsync(propertySet);
    }
    catch (Exception err)
    {
        switch (err.HResult)
        {
            case unchecked((int)0x88982F41): // WINCODEC_ERR_PROPERTYNOTSUPPORTED
                // The file format does not support this property.
                break;
            default:
                throw;
        }
    }
}