共用方式為


IWICBitmapFrameChainReader 介面 (wincodec.h)

透過不同類型的鏈結,提供連結至目前畫面格的畫面存取。

To provide access to subordinate frames, the frame object (which is represented by IWICBitmapFrameDecode), implements IWICBitmapFrameChainReader.

Inheritance

The IWICBitmapFrameChainReader interface inherits from the IUnknown interface.

Methods

The IWICBitmapFrameChainReader interface has these methods.

 
IWICBitmapFrameChainReader::GetChainedFrame

擷取指定型別鏈結的框架。
IWICBitmapFrameChainReader::GetChainedFrameCount

擷取指定型別鏈結的框架計數。

Remarks

IWICBitmapFrameChainReader represents one of a set of COM interfaces that allow WIC to expose chains of linked frames, of different types.

影像檔案的譯碼器可以提供多個畫面格。 每個畫面都代表個別的影像。 同樣地,編碼器可以接受多個畫面格,並將其編碼成單一檔案。 For example, when scanning a multi-page document into TIFF format, the IWICBitmapEncoder::CreateNewFrame method is used to create a frame for each scanned page, and the IWICBitmapDecoder::GetFrame method is used to retrieve each frame. 在該案例中,沒有框架階層(每個畫面都和另一個畫面一樣重要)。 某些影像格式,例如 HEIF 和 JPEG XL,支援連結至主要畫面格的次要畫面。 IWICBitmapFrameChainReader gives you a way to specify such relationships between frames in the WIC API. 次要框架的範例包括縮圖影像、預覽影像和 Alpha 平面點陣圖。

HEIF supports layered images (also known as overlay images). An overlay image is composed of multiple layer images that are stacked on top of each other in a specified order. 重迭影像是主要映射,而圖層影像是連結至主要映像的次要(次級)映射。 圖層影像通常不會顯示,但在某些情況下,影像編輯應用程式需要能夠讀取和寫入每個單獨的圖層影像。

重疊影像需要元數據來描述如何將不同的圖層組成單一影像。 You can work with that metadata with WIC metadata interfaces such as IWICMetadataReader and IWICMetadataWriter.

重疊影像通常需要無遺失的壓縮,因為這樣可讓影像編輯應用程式開啟並儲存檔案多次,而不會降低影像品質。 HEIF 影像檔案的 HEVC 和 AV1 壓縮格式會遺失。 透過 WIC 編碼 API,您可以使用 HEIF 搭配未壓縮的格式,例如 RGBA。

Nesting

While IWICBitmapFrameDecode has a GetThumbnail method, and IWICBitmapDecoder has a GetPreview method—to return a thumbnail and a preview image, respectively—those methods return only a single image of each type. 這些方法會將影像傳回為位圖,而不是框架;這表示元數據和色彩空間信息無法使用。

IWICBitmapFrameChainReader supports multiple frames of each type, and the frames can be nested. So, for example, a frame from an Alternate chain (see WICBitmapChainType) might itself have for example a Preview chain linked to it, or a Layer chain. Or a frame from a Thumbnail chain might have an AlphaMap chain or a GainMap chain.

WIC 允許最多 5 層深的框架巢狀。

您有責任以正確的順序新增框架,並建立符合實用用途的鏈結。 For example, adding frames to the Preview chain in the wrong order will still work, but it might not result in the optimal image-rendering experience in all cases. Similarly, it's possible to add multiple frames to the GainMap chain, but in practice only the first frame in that chain will be used.

Nesting

IWICBitmapFrameChainReader and IWICBitmapFrameChainWriter are supported by HEIF and JPEG XL. HEIF supports all chain types, but JPEG XL supports only Preview, Thumbnail and GainMap. Attempts to encode a JPEG XL image with an unsupported chain type will result in a WINCODEC_ERR_UNSUPPORTEDOPERATION error. You can use the IWICBitmapFrameChainWriter::DoesSupportChainType method to determine whether a given chain type is supported by the encoder.

Note

If a frame doesn't have a chain of a given type, then IWICBitmapFrameChainReader::GetChainedFrameCount will still succeed, but it will return a count of zero.

分層影像的元數據

Layered images are specific to the HEIF format, defined in ISO/IEC 23008-12, where they're known as Overlay images. The WICHeifProperties enumeration has the relevant constants WICHeifLayeredImageCanvasColor and WICHeifLayeredImageLayerPositions.

請參閱範例 1 和範例 2。

HEIF 的無遺失編碼

For lossless encoding in the HEIF format, you can use the enum constant WICHeifCompressionNone. For example, an encoding app can specify the WICHeifCompressionNone option, and invoke IWICBitmapFrameEncode::WritePixels or WriteSource with a bitmap in the GUID_WICPixelFormat32bppRGBA format.

The WICHeifCompressionOption enumeration has the relevant constants WICHeifCompressionJpegXL, WICHeifCompressionBrotli, and WICHeifCompressionDeflate.

Examples

範例 1:譯碼分層影像

This example shows how to determine whether an image is a layered image (known as an overlay image in the HEIF file format). 此範例示範如何查詢元數據,以及如何尋找每個圖層框架,這是撰寫分層影像所需的專案。

The example uses the IWICMetadataReader interface, which will work on all versions of Windows 11 where HEIF is supported.

#include <wil/com.h>
#include <wincodec.h>

// This function finds the IWICMetadataReader for HEIF-specific metadata.
HRESULT GetHeifMetadataReader(
    _In_ IWICBitmapFrameDecode* frame,
    _COM_Outptr_ IWICMetadataReader** reader)
{
    *reader = nullptr;

    wil::com_ptr_nothrow<IWICMetadataBlockReader> blockReader;
    RETURN_IF_FAILED(wil::com_query_to_nothrow(frame, &blockReader));

    UINT numberOfReaders;
    RETURN_IF_FAILED(blockReader->GetCount(&numberOfReaders));

    for (UINT index = 0; index < numberOfReaders; index++)
    {
        wil::com_ptr_nothrow<IWICMetadataReader> metadataReader;
        RETURN_IF_FAILED(blockReader->GetReaderByIndex(index, &metadataReader));

        GUID metadataFormat;
        RETURN_IF_FAILED(metadataReader->GetMetadataFormat(&metadataFormat));
        if (metadataFormat == GUID_MetadataFormatHeif)
        {
            *reader = metadataReader.detach();
            return S_OK;
        }
    }

    return E_NOT_SET;
}

HRESULT DisplayFrame(_In_ IWICBitmapFrameDecode* primaryFrame)
{
    // To determine whether this is a layered image, we need to find the
    // HEIF-specific metadata.
    wil::com_ptr_nothrow<IWICMetadataReader> metadataReader;
    if (SUCCEEDED(GetHeifMetadataReader(primaryFrame, &metadataReader)))
    {
        PROPVARIANT propertyId{};
        propertyId.vt = VT_UI2;

        propertyId.uiVal = WICHeifLayeredImageCanvasColor;
        wil::unique_prop_variant propvariant;
        (void)metadataReader->GetValue(nullptr, &propertyId, &propvariant);

        if (propvariant.vt == VT_UI4)
        {
            // We got the color of the canvas.
            WICColor canvasColor = propvariant.ulVal;

            // Get the size of the canvas.
            UINT canvasWidth;
            UINT canvasHeight;
            RETURN_IF_FAILED(primaryFrame->GetSize(&canvasWidth, &canvasHeight));

            // Draw an empty canvas (not shown.)
            RETURN_IF_FAILED(RenderEmptyCanvas(canvasWidth, canvasHeight, canvasColor));

            // Get the position on the canvas of each layer image.
            propertyId.uiVal = WICHeifLayeredImageLayerPositions;
            propvariant.reset();
            (void)metadataReader->GetValue(nullptr, &propertyId, &propvariant);

            UINT layerPositionCount = 0;
            POINT* layerPositions = nullptr;

            if (propvariant.vt == (VT_VECTOR | VT_UI8))
            {
                layerPositionCount = propvariant.cauh.cElems;
                layerPositions = reinterpret_cast<POINT*>(propvariant.cauh.pElems);
            }

            // Check whether there are any layer frames chained to the primary frame.
            // If there are none, then we have just a blank canvas.
            wil::com_ptr_nothrow<IWICBitmapFrameChainReader> chainReader;
            if (SUCCEEDED(wil::com_query_to_nothrow(primaryFrame, &chainReader)))
            {
                UINT layerCount = 0;
                (void)chainReader->GetChainedFrameCount(WICBitmapChainType_Layer, &layerCount);

                // Render each layer.
                for (UINT layerIndex = 0; layerIndex < layerCount; ++layerIndex)
                {
                    wil::com_ptr_nothrow<IWICBitmapFrameDecode> layerFrame;
                    RETURN_IF_FAILED(chainReader->GetChainedFrame(WICBitmapChainType_Layer, layerIndex,
                        &layerFrame));

                    // If we don't have layer positions for some layers, then the default position
                    // is [0,0].
                    POINT layerPosition = (layerIndex < layerPositionCount) ? 
                        layerPositions[layerIndex] : POINT{ 0, 0 };

                    // The "DisplayLayerFrame" function is omitted for brevity.
                    RETURN_IF_FAILED(DisplayLayerFrame(layerFrame.get(), canvasWidth, canvasHeight, 
                        layerPosition.x, layerPosition.y));
                }
            }
        }
        else
        {
            // If we get here, then the WICHeifLayeredImageCanvasColor property is not present.
            // That property is required for layered frames. That means that the primary frame is not a
            // layered frame, and we should render it as a normal frame.
            // (Function not shown for brevity.)
            RETURN_IF_FAILED(DisplayNormalFrame(primaryFrame));
        }
    }
    else
    {
        // If we get here, then the IWICMetadataReader for HEIF is not available.
        // This means that the frame is not from a HEIF file, and it is definitely not a layered frame.
        RETURN_IF_FAILED(DisplayNormalFrame(primaryFrame));
    }
    return S_OK;
}

範例 2:譯碼分層影像

This example shows how to decode a layered image using the IWICMetadataQueryReader. 此範例的功能相當於上一個範例。 已省略與上一個範例相同的部分,以避免重複。 The main difference is the use of IWICMetadataQueryReader instead of IWICMetadataReader. While IWICMetadataQueryReader is easier to use, the disadvantage is that the IWICMetadataQueryReader supports querying for only layered image metadata on Windows 11 version 25H2, and later. So using IWICMetadataQueryReader on older versions of Windows than that will result in the GetMetadataByName method returning an error.

#include <wil/com.h>
#include <wincodec.h>

HRESULT DisplayFrame(_In_ IWICBitmapFrameDecode* primaryFrame)
{
    wil::com_ptr_nothrow<IWICMetadataQueryReader> metadataQueryReader;
    RETURN_IF_FAILED(primaryFrame->GetMetadataQueryReader(&metadataQueryReader));

    wil::unique_prop_variant propvariant;
    (void)metadataQueryReader->GetMetadataByName(L"/heifProps/LayeredImageCanvasColor", &propvariant);

    if (propvariant.vt == VT_UI4)
    {
        // We got the color of the canvas.
        WICColor canvasColor = propvariant.ulVal;

        // Get the position on the canvas of each layer image.
        propvariant.reset();
        (void)metadataQueryReader->GetMetadataByName(L"/heifProps/LayeredImageLayerPositions", &propvariant);

        UINT layerPositionCount = 0;
        POINT* layerPositions = nullptr;

        if (propvariant.vt == (VT_VECTOR | VT_UI8))
        {
            layerPositionCount = propvariant.cauh.cElems;
            layerPositions = reinterpret_cast<POINT*>(propvariant.cauh.pElems);
        }

        // Draw the canvas, and display each layer image (if any).
        // Function not shown for brevity.
        RETURN_IF_FAILED(DisplayCanvasAndAllLayers(primaryFrame, canvasColor, layerPositionCount, 
            layerPositions));
    }
    else
    {
        // If we get here, then we were unable to find the "/heifProps/LayeredImageCanvasColor" property,
        // probably because it's not present in the file.
        // That property is required for layered frames. That suggests that the primary frame is not a
        // layered frame. We will render it as a normal frame.
        // (Function not shown for brevity.)
        RETURN_IF_FAILED(DisplayNormalFrame(primaryFrame));
    }
    return S_OK;
}

範例 3:編碼分層影像

This example shows how to encode a layered image using the IWICMetadataQueryWriter and IWICBitmapFrameChainWriter.

#include <wil/com.h>
#include <wincodec.h>

HRESULT CreateLayerImage(
    _In_ IWICImagingFactory* factory,
    _In_ IWICStream* outputStream,
    const SIZE& canvasSize,
    WICColor canvasColor,
    UINT numLayers,
    _In_reads_(numLayers) const POINT* layerPositions,
    _In_reads_(numLayers) IWICBitmapSource* layerBitmaps[])
{
    wil::com_ptr_nothrow<IWICBitmapEncoder> heifEncoder;
    RETURN_IF_FAILED(factory->CreateEncoder(GUID_ContainerFormatHeif, nullptr, &heifEncoder));

    RETURN_IF_FAILED(heifEncoder->Initialize(outputStream, WICBitmapEncoderCacheInMemory));

    wil::com_ptr_nothrow<IWICBitmapFrameEncode> layeredFrame;
    RETURN_IF_FAILED(heifEncoder->CreateNewFrame(&layeredFrame, nullptr));
    RETURN_IF_FAILED(layeredFrame->Initialize(nullptr));

    RETURN_IF_FAILED(layeredFrame->SetSize(canvasSize.cx, canvasSize.cy));

    // Create a chain of layer images, chained to the layered image.
    wil::com_ptr_nothrow<IWICBitmapFrameChainWriter> chainWriter;
    RETURN_IF_FAILED(wil::com_query_to_nothrow(layeredFrame, &chainWriter));

    for (ULONG layerIndex = 0; layerIndex < numLayers; ++layerIndex)
    {
        wil::com_ptr_nothrow<IWICBitmapFrameEncode> layerFrame;

        RETURN_IF_FAILED(chainWriter->AppendFrameToChain(WICBitmapChainType_Layer, layerFrame.get()));
        RETURN_IF_FAILED(layerFrame->Initialize(nullptr));

        RETURN_IF_FAILED(layerFrame->WriteSource(layerBitmaps[layerIndex], nullptr));
        RETURN_IF_FAILED(layerFrame->Commit());
    }

    // Write the background color and the position of each layer as metadata on the layered image.
    // (The chain and the metadata can also be added in opposite order to that shown here).
    wil::com_ptr_nothrow<IWICMetadataQueryWriter> queryWriter;
    RETURN_IF_FAILED(layeredFrame->GetMetadataQueryWriter(&queryWriter));

    PROPVARIANT propvariant{};
    propvariant.vt = VT_UI4;
    propvariant.ulVal = canvasColor;

    if (SUCCEEDED(queryWriter->SetMetadataByName(L"/heifProps/LayeredImageCanvasColor", &propvariant)))
    {
        propvariant.vt = VT_VECTOR | VT_UI8;
        propvariant.cauh.cElems = numLayers;
        propvariant.cauh.pElems = reinterpret_cast<ULARGE_INTEGER*>(layerPositions);  

        RETURN_IF_FAILED(queryWriter->SetMetadataByName(L"/heifProps/LayeredImageLayerPositions", 
            &propvariant));

        // Set the type to VT_EMPTY to avoid accidentally clearing the PROPVARIANT,
        // because cauh.pElems is pointing to memory not allocated with CoTaskMemAlloc.
        propvariant.vt = VT_EMPTY;
    }

    // Finalize the layered frame and the HEIF file.
    RETURN_IF_FAILED(layeredFrame->Commit());
    RETURN_IF_FAILED(heifEncoder->Commit());
    return S_OK;
}

範例 4:編碼替代框架

這個範例說明您的應用程式如何使用無損 Deflate 壓縮來儲存未壓縮的影格,同時使用 HEVC 提供較低品質的替代編碼。 HEIF 中的未壓縮映像是在 Windows 外部可能不支援的新標準中定義的。 這意味著照片檢視應用程式(尤其是未在 Windows 上運行的應用程式)可能無法解碼未壓縮的幀。 透過提供以 HEVC 編碼的替代影格,不支援未壓縮影格的應用程式仍可顯示影像,但品質較低。

#include <wil/com.h>
#include <wincodec.h>

HRESULT AddUncompressedFrameWithHevcAlternate(
    _In_ IWICBitmapEncoder* heifEncoder,
    _In_ IWICBitmapSource* sourceBitmap)
{
    wil::com_ptr_nothrow<IWICBitmapFrameEncode> primaryFrame;
    wil::com_ptr_nothrow<IPropertyBag2> encoderOptions;
    RETURN_IF_FAILED(heifEncoder->CreateNewFrame(&primaryFrame, &encoderOptions));

    // Specify that the primary frame should be saved in uncompressed format with lossless Deflate compression applied to it.
    PROPBAG2 option{};
    option.pstrName = L"HeifCompressionMethod";

    wil::unique_variant prop;
    prop.vt = VT_UI1;
    prop.bVal = (BYTE)WICHeifCompressionDeflate;

    RETURN_IF_FAILED(primaryFrame->Initialize(encoderOptions.get()));
    RETURN_IF_FAILED(primaryFrame->WriteSource(sourceBitmap, nullptr));

    // Now create a chain of alternate frames, chained to the primary image.
    wil::com_ptr_nothrow<IWICBitmapFrameChainWriter> chainWriter;
    RETURN_IF_FAILED(wil::com_query_to_nothrow(primaryFrame, &chainWriter));

    // Create an alternate frame that is encoded using HEVC. This frame will be displayed by
    // photo viewing apps when uncompressed images aren't supported.
    // HEVC is the default, so we don't need to specify any encoding options for this frame.
    wil::com_ptr_nothrow<IWICBitmapFrameEncode> alternateFrame;
    RETURN_IF_FAILED(chainWriter->AppendFrameToChain(WICBitmapChainType_Alternate, &alternateFrame,
        nullptr));
    RETURN_IF_FAILED(alternateFrame->Initialize(nullptr));
    RETURN_IF_FAILED(alternateFrame->WriteSource(sourceBitmap, nullptr));
    RETURN_IF_FAILED(alternateFrame->Commit());

    RETURN_IF_FAILED(primaryFrame->Commit());
    RETURN_IF_FAILED(heifEncoder->Commit());
    return S_OK;
}

範例 5:檢查是否支援增益對映鏈

此範例示範您的應用程式如何檢查編碼器是否支援使用主要影像儲存增益地圖。 如果主要影像採用標準動態範圍 (SDR) 格式,則可以使用增益圖將主要影像增強為高動態範圍 (HDR)。 但並非所有檔案格式都支援取得對應,因此您的應用程式可能先想要檢查取得地圖是否支援取得對應,再花費資源來產生收益地圖。 This example shows how you can do that by using the IWICBitmapFrameChainWriter::DoesSupportChainType method.

#include <wil/com.h>
#include <wincodec.h>

HRESULT EncodeHdrBitmap(
    _In_ IWICBitmapEncoder* encoder,
    _In_ IWICBitmapSource* hdrBitmap)
{
    wil::com_ptr_nothrow<IWICBitmapFrameEncode> primaryFrame;
    wil::com_ptr_nothrow<IWICBitmapFrameEncode> gainMapFrame;
    wil::com_ptr_nothrow<IWICBitmapFrameChainWriter> chainWriter;

    RETURN_IF_FAILED(encoder->CreateNewFrame(&primaryFrame, nullptr));

    if (SUCCEEDED(wil::com_query_to_nothrow(primaryFrame, &chainWriter)))
    {
        // Check whether this encoder supports adding a gain map.
        BOOL isSupported;
        if (SUCCEEDED(chainWriter->DoesSupportChainType(WICBitmapChainType_GainMap, &isSupported)) &&
            isSupported)
        {
            RETURN_IF_FAILED(chainWriter->AppendFrameToChain(WICBitmapChainType_GainMap, &gainMapFrame,
                nullptr));
            RETURN_IF_FAILED(gainMapFrame->Initialize(nullptr));

            // Split the HDR frame into a base image (written to primaryFrame) and a gain map.
            // (This function is not shown, for brevity).
            RETURN_IF_FAILED(SplitHdrBitmapIntoSdrAndGainMap(hdrBitmap, primaryFrame.get(), 
                gainMapFrame.get()));
        }
    }

    if (gainMapFrame != nullptr)
    {
        // We successfully created a separate frame for the gain map. Commit the changes to the file.
        RETURN_IF_FAILED(gainMapFrame->Commit());
    }
    else
    {
        // The encoder doesn't support gain maps, so we write the full HDR image as a single frame. 
        RETURN_IF_FAILED(primaryFrame->WriteSource(hdrBitmap, nullptr));
    }

    RETURN_IF_FAILED(primaryFrame->Commit());
    RETURN_IF_FAILED(encoder->Commit());
    return S_OK;
}

Requirements

Requirement Value
Header wincodec.h

See also