使用英语阅读

通过


HEIF 扩展编解码器

重要

一些信息与预发布产品相关,在商业发行之前可能会发生实质性修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

有关通过 WIC 提供的高效图像格式 (HEIF) 扩展编解码器的信息。 要从 Microsoft Store 下载扩展编解码器,请参阅 HEIF 图像扩展

借助两个像素格式 GUID,照片查看器可以检索 HEIF 图像的备用表示形式。

  • 仅深度
    • GUID_WICPixelFormat8bppDepth
  • 仅增益
    • GUID_WICPixelFormat8bppGain

有关这些格式的详细信息,请参阅本机像素格式概述

使用编码选项的 WICHeifCompressionOption 枚举,应用可以选择创建 HEIF 图像文件时要使用的压缩格式。

使用 HEIF 扩展编解码器

如果具有使用 WIC 的应用,则可以使用像素格式 GUID 来请求图像的表示方式。 应用可以使用 IWICBitmapSourceTransform::GetClosestPixelFormat 方法检查 WIC 解码器是否支持特定像素格式。

然后,要将图像解码为使用特定像素格式的位图,应用可以调用 IWICBitmapSourceTransform::CopyPixels

例如,如果将图像转换为使用 GUID_WICPixelFormat24bppRGB 像素格式的位图,则意味着该位图的每个像素包含红色、绿色和蓝色信息。 (每个颜色组件 8 位。)GUID_WICPixelFormat32bppRGBA 会向每个像素添加 alpha(透明度)信息。 像素格式 GUID_WICPixelFormat8bppAlpha 用于每个像素仅包含 8 位 alpha 信息的位图

HEIF 的深度和增益信息

对于高效图像格式 (HEIF) 文件,像素格式 GUID_WICPixelFormat8bppDepth 是相关的。 它的工作方式类似于 GUID_WICPixelFormat8bppAlpha。 除了 alpha 指定像素的透明度外,深度可指定与图像查看器中像素的相对距离

在使用 GUID_WICPixelFormat8bppDepth 的位图中,位图包含一个 8 位值,其中包含每个像素的深度信息。 值为 0 表示图像颜色位图表示形式中的共置像素距查看器最近;值为 255 则表示颜色位图中的共置像素距查看器最远。

对于 HEIF,还有一种名为 GUID_WICPixelFormat8bppGain 的像素格式。 与 alpha 和深度的像素格式类似,GUID_WICPixelFormat8bppGain 为每个像素提供一个 8 位值。 增益信息的目的是指定可应用于 RGB 格式像素的亮度增益:可有效地将普通 RGB 图像转换为高动态范围 (HDR) 图像。 增益信息将按原样公开。 你的应用可以使用 HEIF 图像中的 Exif 或 XMP 元数据来确定哪个相机模型已生成增益。 目前,增益信息仅包含在已由最新 Apple iOS 设备创建的 HEIF 文件中。

图像不包含默认的深度或增益信息。 对请求不包含该信息的图像增益或深度表示形式的 IWICBitmapSourceTransform::CopyPixels 进行调用将会导致 WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT 错误

压缩选项

WICHeifCompressionOption 枚举用于在将位图编码为 HEIF 格式时指定名为 HeifCompressionMethod 的选项

HEIF 是一个类似于 TIFF 的容器,可以使用不同的压缩方法。 默认压缩方法为高效视频编解码器 (HEVC)。 使用 HEVC 压缩的 HEIF 文件通常使用 .heic 文件扩展名。 使用 WICHeifCompressionAV1 枚举值,可以指定应改用 AV1 编解码器。 使用 AV1 进行压缩的 HEIF 文件通常使用 .avif 文件扩展名。

HEIF 将尝试执行某些编辑,例如旋转和裁剪,而无需重新压缩图像。 可以使用 WICHeifCompressionNone 枚举值来强制不重新压缩图像。 如果需要重新压缩图像,则如果已指定 WICHeifCompressionNone,则提交操作将返回 WINCODEC_ERR_UNSUPPORTEDOPERATION

HEVC 和 AV1 编解码器可能无法在所有电脑上使用。 可以从 Microsoft Store 下载这些编解码器。 要检查是否安装了 HEVC 或 AV1,可以使用 MFTEnumEx 函数分别查询是否存在格式 MFVideoFormat_HEVC 和 MFVideoFormat_AV1 的编解码器

代码示例

这些代码示例使用 Windows 实现库 (WIL)。 安装 WIL 的一种便捷方法是转到 Visual Studio,单击“项目”>“管理 NuGet 包...”>“浏览”,在搜索框中键入或粘贴 Microsoft.Windows.ImplementationLibrary,选择搜索结果中的项,然后单击“安装”以安装该项目的包。

示例 1:深度

此示例演示如何检查深度信息是否可用,以及如何检索它。

using namespace wil;

bool IsPixelFormatSupported(_In_ IWICBitmapSourceTransform* bitmap, 
    REFWICPixelFormatGUID proposedPixelFormat)
{
    WICPixelFormatGUID closestPixelFormat = proposedPixelFormat;
    if (SUCCEEDED(bitmap->GetClosestPixelFormat(&closestPixelFormat)) &&
        closestPixelFormat == proposedPixelFormat)
    {
        return true;
    }
    return false;
}

bool IsDepthAvailable(_In_ IWICBitmapSourceTransform* bitmap)
{
    return IsPixelFormatSupported(bitmap, GUID_WICPixelFormat8bppDepth);
}

HRESULT GetDepthBitmap(_In_ IWICBitmapFrameDecode* frame, unique_cotaskmem_ptr<BYTE[]>& bitmap)
{
    bitmap.reset();

    com_ptr_nothrow<IWICBitmapSourceTransform> frameTransform;
    RETURN_IF_FAILED(com_query_to_nothrow(frame, &frameTransform));

    // Check whether depth information is available for this image. If there's no depth
    // information, then just return an empty bitmap.
    if (IsDepthAvailable(frameTransform.get()))
    {
        UINT width;
        UINT height;
        RETURN_IF_FAILED(frame->GetSize(&width, &height));

        size_t bitmapSize;
        RETURN_IF_FAILED(SizeTMult(width, height, &bitmapSize));

        bitmap = make_unique_cotaskmem_nothrow<BYTE[]>(bitmapSize);
        RETURN_IF_NULL_ALLOC(bitmap);

        WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat8bppDepth;
        RETURN_IF_FAILED(frameTransform->CopyPixels(nullptr, width, height,
            &pixelFormat, WICBitmapTransformRotate0, width, bitmapSize, bitmap.get()));
    }
    return S_OK;
}

示例 2:增益

此示例演示如何检查增益信息是否可用,以及如何检索它。 此示例重用了示例 1 中所示的 IsPixelFormatSupported 函数

using namespace wil;

bool IsGainAvailable(_In_ IWICBitmapSourceTransform* bitmap)
{
    return IsPixelFormatSupported(bitmap, GUID_WICPixelFormat8bppGain);
}

HRESULT GetGainBitmap(_In_ IWICBitmapFrameDecode* frame, unique_cotaskmem_ptr<BYTE[]>& bitmap)
{
    bitmap.reset();

    com_ptr_nothrow<IWICBitmapSourceTransform> frameTransform;
    RETURN_IF_FAILED(com_query_to_nothrow(frame, &frameTransform));

    // Check whether gain information is available for this image. If there's no gain
    // information, then just return an empty bitmap.
    if (IsGainAvailable(frameTransform.get()))
    {
        UINT width;
        UINT height;
        RETURN_IF_FAILED(frame->GetSize(&width, &height));

        size_t bitmapSize;
        RETURN_IF_FAILED(SizeTMult(width, height, &bitmapSize));

        bitmap = make_unique_cotaskmem_nothrow<BYTE[]>(bitmapSize);
        RETURN_IF_NULL_ALLOC(bitmap);

        WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat8bppGain;
        RETURN_IF_FAILED(frameTransform->CopyPixels(nullptr, width, height,
            &pixelFormat, WICBitmapTransformRotate0, width, bitmapSize, bitmap.get()));
    }
    return S_OK;
}

示例 3:指定必须使用 AV1 压缩

此示例演示如何使用 AV1 压缩对 HEIF 文件进行编码,从而创建 .avif 文件。

using namespace wil;

HRESULT EncodeSourceAsAvif(IWICBitmapSource* source, IWICStream* outputStream, IWICImagingFactory* factory)
{
    com_ptr_nothrow<IWICBitmapEncoder> encoder;
    RETURN_IF_FAILED(factory->CreateEncoder(GUID_ContainerFormatHeif, nullptr, &encoder));
    RETURN_IF_FAILED(encoder->Initialize(outputStream, WICBitmapEncoderCacheInMemory));

    com_ptr_nothrow<IWICBitmapFrameEncode> outputFrame;
    com_ptr_nothrow<IPropertyBag2> encoderOptions;
    RETURN_IF_FAILED(encoder->CreateNewFrame(&outputFrame, &encoderOptions));

    // Configure the output frame to compress the source image using the AV1 encoder.
    PROPBAG2 option{};
    option.pstrName = L"HeifCompressionMethod";

    VARIANT propertyValue{};
    propertyValue.vt = VT_UI1;
    propertyValue.bVal = static_cast<BYTE>(WICHeifCompressionAV1);

    RETURN_IF_FAILED(encoderOptions->Write(1, &option, &propertyValue));
    RETURN_IF_FAILED(outputFrame->Initialize(encoderOptions.get()));
    RETURN_IF_FAILED(outputFrame->WriteSource(source, nullptr));

    // Do the actual encoding of the source image. If the AV1 encoder is missing,
    // then this call fails.
    RETURN_IF_FAILED(outputFrame->Commit());
    RETURN_IF_FAILED(encoder->Commit());
    return S_OK;
}

示例 4:指定不允许使用压缩

此示例演示如何在 HEIF 容器中保存图像,以及如何对图像应用旋转和裁剪;但强制要求不对图像进行压缩。 如果该源已采用 HEVC 或 AV1 格式,则会成功。 但如果该源是原始位图或采用某种其他格式,则不允许进行编码时,该函数将返回错误。

using namespace wil;

// Rotate and optionally crop the source image, and produce a HEIF image file as output.
// If the input parameter allowEncoding is false, and the image needs to be encoded,
// then this function will return WINCODEC_ERR_UNSUPPORTEDOPERATION.
HRESULT RotateAndCropSource(
    IWICBitmapSource* source,
    IWICStream* outputStream,
    IWICImagingFactory* factory,
    bool allowCompression,
    WICBitmapTransformOptions transformOptions,
    WICRect* cropRectangle = nullptr)
{
    com_ptr_nothrow<IWICBitmapEncoder> encoder;
    RETURN_IF_FAILED(factory->CreateEncoder(GUID_ContainerFormatHeif, NULL, &encoder));
    RETURN_IF_FAILED(encoder->Initialize(outputStream, WICBitmapEncoderCacheInMemory));

    com_ptr_nothrow<IWICBitmapFrameEncode> outputFrame;
    com_ptr_nothrow<IPropertyBag2> encoderOptions;
    RETURN_IF_FAILED(encoder->CreateNewFrame(&outputFrame, &encoderOptions));

    VARIANT propertyValue{};

    if (!allowCompression)
    {
        // Specify that compression of the image is not allowed.
        PROPBAG2 option{};
        option.pstrName = L"HeifCompressionMethod";

        propertyValue.vt = VT_UI1;
        propertyValue.bVal = static_cast<BYTE>(WICHeifCompressionNone);
        RETURN_IF_FAILED(encoderOptions->Write(1, &option, &propertyValue));
    }

    if (transformOptions != WICBitmapTransformRotate0)
    {
        PROPBAG2 option{};
        option.pstrName = L"BitmapTransform";

        propertyValue.vt = VT_UI1;
        propertyValue.bVal = static_cast<BYTE>(transformOptions);
        RETURN_IF_FAILED(encoderOptions->Write(1, &option, &propertyValue));
    }

    RETURN_IF_FAILED(outputFrame->Initialize(encoderOptions.get()));
    RETURN_IF_FAILED(outputFrame->WriteSource(source, cropRectangle));

    // Generate the HEIF file. If the image needs to be compressed, this call fails.
    RETURN_IF_FAILED(outputFrame->Commit());
    RETURN_IF_FAILED(encoder->Commit());
    return S_OK;
}