编码概述

重要

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

编码器可将图像数据写入流。 编码器可以在将图像像素写入流之前通过多种方式压缩、加密和更改图像像素。 使用某些编码器会导致权衡,例如 JPEG 会为了提高压缩效果而牺牲颜色信息。 其他编码器则不会导致此类损失,例如位图 (BMP)。 由于许多编解码器使用专有技术来实现更好的压缩和图像保真度,因此有关如何对图像进行编码的详细信息取决于编解码器开发人员。

IWICBitmapEncoder

IWICBitmapEncoder 是将图像编码为目标格式的主接口,用于将缩略图 (SetThumbnail) 和帧 (CreateNewFrame) 等图像组件序列化为图像文件。

序列化的发生方式和时间取决于编解码器开发人员。 目标文件格式中的每个数据块应能够独立于顺序进行设置,但同样,这是由编解码器开发人员决定的。 但是,调用 Commit 方法后,不应允许对图像进行更改,并且应关闭流。

IWICBitmapFrameEncode

IWICBitmapFrameEncode 是用于对图像的各个帧进行编码的接口。 它提供了设置单个帧图像组件的方法,例如缩略图和帧,以及图像尺寸、DPI 和像素格式。

单个帧可以使用特定于帧的元数据进行编码,因此 IWICBitmapFrameEncode 通过 GetMetadataQueryWriter 方法提供了对元数据编写器的访问权限

帧的 Commit 方法会提交对单个帧的所有更改,并指示不应再接受对该帧所做的更改

编码示例 (TIFF)

在以下示例中,标记图像文件格式 (TIFF) 是使用 IWICBitmapEncoderIWICBitmapFrameEncode 来编码的。 TIFF 输出则是使用 WICTiffCompressionOption 自定义的,而位图帧则是使用给定选项初始化的。 使用 WritePixels 创建图像后,帧会通过 Commit 方式提交,并使用 Commit 保存图像。

IWICImagingFactory *piFactory = NULL;
IWICBitmapEncoder *piEncoder = NULL;
IWICBitmapFrameEncode *piBitmapFrame = NULL;
IPropertyBag2 *pPropertybag = NULL;

IWICStream *piStream = NULL;
UINT uiWidth = 640;
UINT uiHeight = 480;

HRESULT hr = CoCreateInstance(
                CLSID_WICImagingFactory,
                NULL,
                CLSCTX_INPROC_SERVER,
                IID_IWICImagingFactory,
                (LPVOID*) &piFactory);

if (SUCCEEDED(hr))
{
    hr = piFactory->CreateStream(&piStream);
}

if (SUCCEEDED(hr))
{
    hr = piStream->InitializeFromFilename(L"output.tif", GENERIC_WRITE);
}

if (SUCCEEDED(hr))
{
   hr = piFactory->CreateEncoder(GUID_ContainerFormatTiff, NULL, &piEncoder);
}

if (SUCCEEDED(hr))
{
    hr = piEncoder->Initialize(piStream, WICBitmapEncoderNoCache);
}

if (SUCCEEDED(hr))
{
    hr = piEncoder->CreateNewFrame(&piBitmapFrame, &pPropertybag);
}

if (SUCCEEDED(hr))
{        
    // This is how you customize the TIFF output.
    PROPBAG2 option = { 0 };
    option.pstrName = L"TiffCompressionMethod";
    VARIANT varValue;    
    VariantInit(&varValue);
    varValue.vt = VT_UI1;
    varValue.bVal = WICTiffCompressionZIP;      
    hr = pPropertybag->Write(1, &option, &varValue);        
    if (SUCCEEDED(hr))
    {
        hr = piBitmapFrame->Initialize(pPropertybag);
    }
}

if (SUCCEEDED(hr))
{
    hr = piBitmapFrame->SetSize(uiWidth, uiHeight);
}

WICPixelFormatGUID formatGUID = GUID_WICPixelFormat24bppBGR;
if (SUCCEEDED(hr))
{
    hr = piBitmapFrame->SetPixelFormat(&formatGUID);
}

if (SUCCEEDED(hr))
{
    // We're expecting to write out 24bppRGB. Fail if the encoder cannot do it.
    hr = IsEqualGUID(formatGUID, GUID_WICPixelFormat24bppBGR) ? S_OK : E_FAIL;
}

if (SUCCEEDED(hr))
{
    UINT cbStride = (uiWidth * 24 + 7)/8/***WICGetStride***/;
    UINT cbBufferSize = uiHeight * cbStride;

    BYTE *pbBuffer = new BYTE[cbBufferSize];

    if (pbBuffer != NULL)
    {
        for (UINT i = 0; i < cbBufferSize; i++)
        {
            pbBuffer[i] = static_cast<BYTE>(rand());
        }

        hr = piBitmapFrame->WritePixels(uiHeight, cbStride, cbBufferSize, pbBuffer);

        delete[] pbBuffer;
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
}

if (SUCCEEDED(hr))
{
    hr = piBitmapFrame->Commit();
}    

if (SUCCEEDED(hr))
{
    hr = piEncoder->Commit();
}

if (piFactory)
    piFactory->Release();

if (piEncoder)
    piEncoder->Release();

if (piBitmapFrame)
    piBitmapFrame->Release();

if (pPropertybag)
    pPropertybag->Release();

if (piStream)
    piStream->Release();

return hr;

编码器选项用法

不同格式的不同编码器需要公开图像编码方式的不同选项。 Windows 图像组件 (WIC) 提供一致的机制来表达编码选项是否是必需的,同时仍支持应用程序使用多个编码器,而无需了解特定格式。 这是通过在 CreateNewFrame 方法和 Initialize 方法上 提供 IPropertyBag 参数来实现的

组件工厂提供了一个简单的创建点,用于创建编码器选项属性包。 如果编解码器需要提供简单、直观且不冲突的编码器选项集,则可以使用此服务。 创建过程中,必须使用与该编解码器相关的所有编码器选项初始化图像处理属性包。 对于规范集中的编码器选项,将在写入时强制实施值范围。 对于更高级的需求,编解码器应编写自己的属性包实现。

在帧创建过程中,将会向应用程序提供编码器选项包,并且必须在初始化编码器帧之前配置任何值。 对于 UI 驱动的应用程序,它可以为规范编码器选项提供固定 UI,并为剩余选项提供高级视图。 可以通过 Write 方法一次进行一次更改,并将通过 IErrorLog 报告任何错误。 如果更改导致级联效果,则 UI 应用程序应始终在进行更改后重新读取和显示所有选项。 对于仅通过属性包提供最少错误报告的编解码器,应用程序应准备好处理失败的帧初始化。

编码器选项

应用程序应会遇到以下一组编码器选项。 编码器选项反映了编码器的功能和基础容器格式,因此,它们在本质上并非真正与编解码器无关。 如果可能,应对新选项进行规范化处理,以便将其应用于出现的新编解码器。

属性名称 VARTYPE 适用的编解码器
BitmapTransform VT_UI1 WICBitmapTransformOptions JPEG、HEIF
CompressionQuality VT_R4 0-1.0 TIFF
HeifCompressionMethod WICHeifCompressionOption various HEIF
ImageQuality VT_R4 0-1.0 JPEG、HDPhoto、HEIF
Lossless VT_BOOL TRUEFALSE HDPhoto

ImageQualty 为 0.0 表示最低保真度呈现,1.0 则表示最高保真度,这也可能意味着无损,具体取决于编解码器。

CompressionQuality 为 0.0 表示可用的压缩方案效率最低,通常会导致快速编码但输出更大。 值为 1.0 表示可用的方案效率最高,通常需要更多时间进行编码,但会生成较小的输出。 根据编解码器的功能,此范围可以映射到一组离散的可用压缩方法。

无损表示编解码器将图像编码为无损图像,且不会损失任何图像数据。 如果启用无损,则忽略 ImageQuality。

除上述通用编码器选项之外,WIC 提供的编解码器还支持以下选项。 如果编解码器需要支持与这些提供的编解码器中的用法一致的选项,则建议这样做。

属性名称 VARTYPE 适用的编解码器
InterlaceOption VT_BOOL 打开/关闭 PNG
FilterOption VT_UI1 WICPngFilterOption PNG
TiffCompressionMethod VT_UI1 WICTiffCompressionOption TIFF
亮度 VT_UI4/VT_ARRAY 64 个条目 (DCT) JPEG
色度 VT_UI4/VT_ARRAY 64 个条目 (DCT) JPEG
JpegYCrCbSubsampling VT_UI1 WICJpegYCrCbSubsamplingOption JPEG
SuppressApp0 VT_BOOL JPEG
EnableV5Header32bppBGRA VT_BOOL 打开/关闭 BMP

使用 VT_EMPTY 指示 *未设置* 为默认值。 如果设置了其他属性,但不受支持,则编码器应忽略它们;这会在应用程序需要可能存在或可能不存在的功能时,允许应用程序编写较少的逻辑代码。

编码器选项示例

在上面的 TIFF 编码示例中,设置了特定的编码器选项。 PROPBAG2 结构的 pstrName 成员设置为适当的属性名称,VARIANT 则设置为相应的 VARTYPE 和所需值 - 在本例中为 WICTiffCompressionOption 枚举的成员

if (SUCCEEDED(hr))
{
    hr = piEncoder->CreateNewFrame(&piBitmapFrame, &pPropertybag);
}

if (SUCCEEDED(hr))
{        
    // This is how you customize the TIFF output.
    PROPBAG2 option = { 0 };
    option.pstrName = L"TiffCompressionMethod";
    VARIANT varValue;    
    VariantInit(&varValue);
    varValue.vt = VT_UI1;
    varValue.bVal = WICTiffCompressionZIP;      
    hr = pPropertybag->Write(1, &option, &varValue);        
    if (SUCCEEDED(hr))
    {
        hr = piBitmapFrame->Initialize(pPropertybag);
    }
}

要使用默认编码器选项,只需使用创建帧时返回的属性包初始化位图帧。

if (SUCCEEDED(hr))
{
    hr = piEncoder->CreateNewFrame(&piBitmapFrame, &pPropertybag);
}

if (SUCCEEDED(hr))
{        
    // Accept the default encoder options.
    if (SUCCEEDED(hr))
    {
        hr = piBitmapFrame->Initialize(pPropertybag);
    }
}

如果没有考虑编码器选项,还可以消除属性包。

if (SUCCEEDED(hr))
{
    hr = piEncoder->CreateNewFrame(&piBitmapFrame, 0);
}

if (SUCCEEDED(hr))
{        
    // No encoder options.
    if (SUCCEEDED(hr))
    {
        hr = piBitmapFrame->Initialize(0);
    }
}