Partager via


Interface IWICBitmapFrameChainReader (wincodec.h)

Fournit l’accès aux images liées à l’image actuelle par le biais de chaînes de différents types.

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

Récupère un cadre pour une chaîne d’un type donné.
IWICBitmapFrameChainReader::GetChainedFrameCount

Récupère le nombre d’images pour une chaîne d’un type donné.

Remarks

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

Le décodeur d’un fichier image peut fournir plusieurs images. Chaque image représente une image distincte. De même, l’encodeur peut accepter plusieurs images et les encoder dans un seul fichier. 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. Dans ce scénario, il n’existe aucune hiérarchie de trames (chaque image est aussi importante qu’une autre). Certains formats d’image, tels que HEIF et JPEG XL, prennent en charge les images secondaires liées à une trame primaire. IWICBitmapFrameChainReader gives you a way to specify such relationships between frames in the WIC API. Les images secondaires incluent des images miniatures, des images d’aperçu et des bitmaps de plan 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. L’image de superposition est l’image principale et les images de couche sont des images secondaires (subordonnées) liées à l’image primaire. Les images de couche ne sont généralement pas affichées, mais il existe des cas où une application d’édition d’images doit être en mesure de lire et d’écrire chaque image de couche individuelle.

Une image de superposition nécessite des métadonnées pour décrire comment composer les différentes couches en une seule image. You can work with that metadata with WIC metadata interfaces such as IWICMetadataReader and IWICMetadataWriter.

Une image superposée nécessite souvent une compression sans perte, car cela permet aux applications de modification d’image d’ouvrir et d’enregistrer un fichier plusieurs fois sans dégrader la qualité de l’image. Les formats de compression HEVC et AV1 pour les fichiers image HEIF sont perdus. Avec les API d’encodage WIC, vous pouvez utiliser HEIF avec des formats non compressés tels que RVBA.

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. Et ces méthodes retournent les images sous la forme d’une bitmap plutôt que sous forme de trame ; cela signifie que les métadonnées et les informations d’espace de couleur ne sont pas disponibles.

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 permet l’imbrication des images jusqu’à 5 niveaux de profondeur seulement.

C’est votre responsabilité en tant qu’appelant d’ajouter des trames dans l’ordre correct, et de créer des chaînes qui servent un but utile. 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.

Métadonnées pour les images en couches

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.

Consultez l’exemple 1 et l’exemple 2.

Encodage sans perte pour 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

Exemple 1 : Décoder une image en couches

This example shows how to determine whether an image is a layered image (known as an overlay image in the HEIF file format). L’exemple montre comment interroger les métadonnées et comment rechercher chaque trame de couche, qui est nécessaire pour composer l’image en couches.

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;
}

Exemple 2 : Décoder une image en couches

This example shows how to decode a layered image using the IWICMetadataQueryReader. L’exemple est fonctionnellement équivalent à l’exemple précédent. Certaines parties qui seraient identiques à celles de l’exemple précédent ont été omises pour éviter la répétition. 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;
}

Exemple 3 : Encoder une image en couches

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;
}

Exemple 4 : Encoder un autre frame

Cet exemple montre comment votre application peut enregistrer une trame non compressée à l’aide de la compression de déflate sans perte, tout en fournissant un encodage de remplacement de qualité inférieure à l’aide d’HEVC. Les images non compressées dans HEIF sont définies dans une nouvelle norme qui peut ne pas être prise en charge en dehors de Windows. Cela signifie qu’il est possible qu’une application d’affichage de photos, en particulier une application qui ne s’exécute pas sur Windows, ne puisse pas décoder des images non compressées. En fournissant une autre image encodée dans HEVC, les applications qui ne prennent pas en charge les images non compressées pourront toujours afficher l’image, bien qu’à une qualité inférieure.

#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;
}

Exemple 5 : vérification de la prise en charge des chaînes de carte

Cet exemple montre comment votre application peut vérifier si l’encodeur prend en charge le stockage d’une carte de gain avec l’image principale. Si l’image primaire est au format SDR (Standard-Dynamic-Range), une carte de gain peut être utilisée pour améliorer l’image primaire à la plage haute dynamique (HDR). Toutefois, tous les formats de fichiers ne prennent pas en charge les mappages de gain. Votre application peut d’abord vérifier si les mappages de gain sont pris en charge avant de dépenser des ressources pour générer la carte de gain. 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