Partager via


Comment réencoder une image JPEG avec des métadonnées

L’exemple suivant montre comment réencoder une image et ses métadonnées dans un nouveau fichier du même format. En outre, cet exemple ajoute des métadonnées pour illustrer une expression à élément unique utilisée par un enregistreur de requêtes.

Cette rubrique contient les sections suivantes.

Prerequisites

Pour comprendre cette rubrique, vous devez être familiarisé avec le système de métadonnées WIC (Windows Imaging Component), comme décrit dans la vue d’ensemble des métadonnées WIC. Vous devez également être familiarisé avec les composants de codec WIC, comme décrit dans la vue d’ensemble du composant d’imagerie Windows.

Partie 1 : Décoder une image

Avant de pouvoir copier des données d’image ou des métadonnées dans un nouveau fichier image, vous devez d’abord créer un décodeur pour l’image existante que vous souhaitez recoder. Le code suivant montre comment créer un décodeur WIC pour le fichier image test.jpg.

    // Initialize COM.
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);

    IWICImagingFactory *piFactory = NULL;
    IWICBitmapDecoder *piDecoder = NULL;

    // Create the COM imaging factory.
    if (SUCCEEDED(hr))
    {
        hr = CoCreateInstance(CLSID_WICImagingFactory,
        NULL, CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&piFactory));
    }

    // Create the decoder.
    if (SUCCEEDED(hr))
    {
        hr = piFactory->CreateDecoderFromFilename(L"test.jpg", NULL, GENERIC_READ,
            WICDecodeMetadataCacheOnDemand, //For JPEG lossless decoding/encoding.
            &piDecoder);
    }

L’appel à CreateDecoderFromFilename a utilisé la valeur WICDecodeMetadataCacheOnDemand à partir de l’énumération WICDecodeOptions comme quatrième paramètre. Cela indique au décodeur de mettre en cache les métadonnées lorsque les métadonnées sont nécessaires, soit en obtenant un lecteur de requête, soit en utilisant le lecteur de métadonnées sous-jacent. L’utilisation de cette option vous permet de conserver le flux vers les métadonnées, ce qui est nécessaire pour effectuer un encodage de métadonnées rapide et permet le décodage sans perte et l’encodage d’images JPEG. Vous pouvez également utiliser l’autre valeur WICDecodeOptions , WICDecodeMetadataCacheOnLoad, qui met en cache les métadonnées d’image incorporées dès que l’image est chargée.

Partie 2 : Créer et initialiser l’encodeur d’image

Le code suivant illustre la création de l’encodeur que vous allez utiliser pour encoder l’image que vous avez précédemment décodée.

    // Variables used for encoding.
    IWICStream *piFileStream = NULL;
    IWICBitmapEncoder *piEncoder = NULL;
    IWICMetadataBlockWriter *piBlockWriter = NULL;
    IWICMetadataBlockReader *piBlockReader = NULL;

    WICPixelFormatGUID pixelFormat = { 0 };
    UINT count = 0;
    double dpiX, dpiY = 0.0;
    UINT width, height = 0;

    // Create a file stream.
    if (SUCCEEDED(hr))
    {
        hr = piFactory->CreateStream(&piFileStream);
    }

    // Initialize our new file stream.
    if (SUCCEEDED(hr))
    {
        hr = piFileStream->InitializeFromFilename(L"test2.jpg", GENERIC_WRITE);
    }

    // Create the encoder.
    if (SUCCEEDED(hr))
    {
        hr = piFactory->CreateEncoder(GUID_ContainerFormatJpeg, NULL, &piEncoder);
    }
    // Initialize the encoder
    if (SUCCEEDED(hr))
    {
        hr = piEncoder->Initialize(piFileStream,WICBitmapEncoderNoCache);
    }

Un flux de fichiers WIC piFileStream est créé et initialisé pour l’écriture dans le fichier image «test2.jpg». piFileStream est ensuite utilisé pour initialiser l’encodeur, en informant l’encodeur où écrire les bits d’image lorsque l’encodage est terminé.

Partie 3 : Copier les informations de trame décodées

Le code suivant copie chaque image d’une image dans un nouveau cadre de l’encodeur. Cette copie inclut la taille, la résolution et le format de pixel ; tous ceux qui sont nécessaires pour créer une trame valide.

Note

Les images JPEG n’auront qu’une seule image et la boucle ci-dessous n’est pas techniquement nécessaire, mais elle est incluse pour illustrer l’utilisation multi-images pour les formats qui le prennent en charge.

 

    if (SUCCEEDED(hr))
    {
        hr = piDecoder->GetFrameCount(&count);
    }

    if (SUCCEEDED(hr))
    {
        // Process each frame of the image.
        for (UINT i=0; i<count && SUCCEEDED(hr); i++)
        {
            // Frame variables.
            IWICBitmapFrameDecode *piFrameDecode = NULL;
            IWICBitmapFrameEncode *piFrameEncode = NULL;
            IWICMetadataQueryReader *piFrameQReader = NULL;
            IWICMetadataQueryWriter *piFrameQWriter = NULL;

            // Get and create the image frame.
            if (SUCCEEDED(hr))
            {
                hr = piDecoder->GetFrame(i, &piFrameDecode);
            }
            if (SUCCEEDED(hr))
            {
                hr = piEncoder->CreateNewFrame(&piFrameEncode, NULL);
            }

            // Initialize the encoder.
            if (SUCCEEDED(hr))
            {
                hr = piFrameEncode->Initialize(NULL);
            }
            // Get and set the size.
            if (SUCCEEDED(hr))
            {
                hr = piFrameDecode->GetSize(&width, &height);
            }
            if (SUCCEEDED(hr))
            {
                hr = piFrameEncode->SetSize(width, height);
            }
            // Get and set the resolution.
            if (SUCCEEDED(hr))
            {
                piFrameDecode->GetResolution(&dpiX, &dpiY);
            }
            if (SUCCEEDED(hr))
            {
                hr = piFrameEncode->SetResolution(dpiX, dpiY);
            }
            // Set the pixel format.
            if (SUCCEEDED(hr))
            {
                piFrameDecode->GetPixelFormat(&pixelFormat);
            }
            if (SUCCEEDED(hr))
            {
                hr = piFrameEncode->SetPixelFormat(&pixelFormat);
            }

Le code suivant effectue une vérification rapide pour déterminer si les formats d’image source et de destination sont identiques. Cette opération est nécessaire, car la partie 4 montre une opération qui n’est prise en charge que lorsque le format source et de destination est le même.

            // Check that the destination format and source formats are the same.
            bool formatsEqual = FALSE;
            if (SUCCEEDED(hr))
            {
                GUID srcFormat;
                GUID destFormat;

                hr = piDecoder->GetContainerFormat(&srcFormat);
                if (SUCCEEDED(hr))
                {
                    hr = piEncoder->GetContainerFormat(&destFormat);
                }
                if (SUCCEEDED(hr))
                {
                    if (srcFormat == destFormat)
                        formatsEqual = true;
                    else
                        formatsEqual = false;
                }
            }

Partie 4 : Copier les métadonnées

Note

Le code de cette section est valide uniquement lorsque les formats d’image source et de destination sont identiques. Vous ne pouvez pas copier toutes les métadonnées d’une image dans une seule opération lors de l’encodage dans un autre format d’image.

 

Pour conserver les métadonnées lors de la re-encodage d’une image au même format d’image, il existe des méthodes disponibles pour copier toutes les métadonnées dans une seule opération. Chacune de ces opérations suit un modèle similaire ; chaque définit les métadonnées de l’image décodée directement dans le nouveau frame encodé. Notez que cette opération est effectuée pour chaque cadre d’image individuel.

La méthode recommandée pour la copie des métadonnées consiste à initialiser l’enregistreur de blocs du nouveau frame avec le lecteur de bloc de l’image décodée. Le code suivant illustre cette méthode.

            if (SUCCEEDED(hr) && formatsEqual)
            {
                // Copy metadata using metadata block reader/writer.
                if (SUCCEEDED(hr))
                {
                    piFrameDecode->QueryInterface(IID_PPV_ARGS(&piBlockReader));
                }
                if (SUCCEEDED(hr))
                {
                    piFrameEncode->QueryInterface(IID_PPV_ARGS(&piBlockWriter));
                }
                if (SUCCEEDED(hr))
                {
                    piBlockWriter->InitializeFromBlockReader(piBlockReader);
                }
            }

Dans cet exemple, vous obtenez simplement le lecteur de blocs et l’enregistreur de blocs à partir du frame source et de l’image de destination, respectivement. L’enregistreur de blocs est ensuite initialisé à partir du lecteur de bloc. Cela initialise l’enregistreur de blocs avec les métadonnées préremplies du lecteur de bloc. Pour découvrir des méthodes supplémentaires pour copier des métadonnées, consultez la section Écriture de métadonnées dans la vue d’ensemble de la lecture et de l’écriture des métadonnées d’image.

Là encore, cette opération fonctionne uniquement lorsque les images source et de destination ont le même format. Cela est dû au fait que différents formats d’image stockent les blocs de métadonnées dans différents emplacements. Par exemple, JPEG et TIFF (Tagged Image File Format) prennent en charge les blocs de métadonnées XMP (Extensible Metadata Platform). Dans les images JPEG, le bloc XMP se trouve au niveau du bloc de métadonnées racine, comme illustré dans la vue d’ensemble des métadonnées WIC. Toutefois, dans une image TIFF, le bloc XMP est incorporé dans le bloc IFD racine.

Partie 5 : Ajouter des métadonnées supplémentaires

L’exemple suivant montre comment ajouter des métadonnées à l’image de destination. Pour ce faire, appelez la méthode SetMetadataByName de l’enregistreur de requêtes à l’aide d’une expression de requête et des données stockées dans un PROPVARIANT.

            if(SUCCEEDED(hr))
            {
                hr = piFrameEncode->GetMetadataQueryWriter(&piFrameQWriter);
            }
            if (SUCCEEDED(hr))
            {
                // Add additional metadata.
                PROPVARIANT    value;
                value.vt = VT_LPWSTR;
                value.pwszVal= L"Metadata Test Image.";
                hr = piFrameQWriter->SetMetadataByName(L"/xmp/dc:title", &value);
            }

Pour plus d’informations sur l’expression de requête, consultez la vue d’ensemble du langage de requête de métadonnées.

Partie 6 : Finaliser l’image encodée

Les dernières étapes de copie de l’image sont d’écrire les données de pixels de l’image, de valider l’image dans l’encodeur et de valider l’encodeur. La validation de l’encodeur écrit le flux d’images dans le fichier.

            if (SUCCEEDED(hr))
            {
                hr = piFrameEncode->WriteSource(
                    static_cast<IWICBitmapSource*> (piFrameDecode),
                    NULL); // Using NULL enables JPEG loss-less encoding.
            }

            // Commit the frame.
            if (SUCCEEDED(hr))
            {
                hr = piFrameEncode->Commit();
            }

            if (piFrameDecode)
            {
                piFrameDecode->Release();
            }

            if (piFrameEncode)
            {
                piFrameEncode->Release();
            }

            if (piFrameQReader)
            {
                piFrameQReader->Release();
            }

            if (piFrameQWriter)
            {
                piFrameQWriter->Release();
            }
        }
    }

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

    if (SUCCEEDED(hr))
    {
        piFileStream->Commit(STGC_DEFAULT);
    }

    if (piFileStream)
    {
        piFileStream->Release();
    }
    if (piEncoder)
    {
        piEncoder->Release();
    }
    if (piBlockWriter)
    {
        piBlockWriter->Release();
    }
    if (piBlockReader)
    {
        piBlockReader->Release();
    }

La méthode WriteSource du frame est utilisée pour écrire les données de pixels de l’image. Notez que cela est effectué une fois les métadonnées écrites. Cela est nécessaire pour vous assurer que les métadonnées ont suffisamment d’espace dans le fichier image. Une fois les données de pixel écrites, le frame est écrit dans le flux à l’aide de la méthode Commit de l’image. Une fois que toutes les images ont été traitées, l’encodeur (et donc l’image) est finalisé à l’aide de la méthode Commit de l’encodeur.

Une fois que vous validez l’image, vous devez libérer les objets COM créés dans la boucle.

Code d’exemple de code JPEG

Voici le code des parties 1 à 6 dans un bloc pratique.

#include <Windows.h>
#include <Wincodecsdk.h>

int main()
{
    // Initialize COM.
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);

    IWICImagingFactory *piFactory = NULL;
    IWICBitmapDecoder *piDecoder = NULL;

    // Create the COM imaging factory.
    if (SUCCEEDED(hr))
    {
        hr = CoCreateInstance(CLSID_WICImagingFactory,
        NULL, CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&piFactory));
    }

    // Create the decoder.
    if (SUCCEEDED(hr))
    {
        hr = piFactory->CreateDecoderFromFilename(L"test.jpg", NULL, GENERIC_READ,
            WICDecodeMetadataCacheOnDemand, //For JPEG lossless decoding/encoding.
            &piDecoder);
    }

    // Variables used for encoding.
    IWICStream *piFileStream = NULL;
    IWICBitmapEncoder *piEncoder = NULL;
    IWICMetadataBlockWriter *piBlockWriter = NULL;
    IWICMetadataBlockReader *piBlockReader = NULL;

    WICPixelFormatGUID pixelFormat = { 0 };
    UINT count = 0;
    double dpiX, dpiY = 0.0;
    UINT width, height = 0;

    // Create a file stream.
    if (SUCCEEDED(hr))
    {
        hr = piFactory->CreateStream(&piFileStream);
    }

    // Initialize our new file stream.
    if (SUCCEEDED(hr))
    {
        hr = piFileStream->InitializeFromFilename(L"test2.jpg", GENERIC_WRITE);
    }

    // Create the encoder.
    if (SUCCEEDED(hr))
    {
        hr = piFactory->CreateEncoder(GUID_ContainerFormatJpeg, NULL, &piEncoder);
    }
    // Initialize the encoder
    if (SUCCEEDED(hr))
    {
        hr = piEncoder->Initialize(piFileStream,WICBitmapEncoderNoCache);
    }

    if (SUCCEEDED(hr))
    {
        hr = piDecoder->GetFrameCount(&count);
    }

    if (SUCCEEDED(hr))
    {
        // Process each frame of the image.
        for (UINT i=0; i<count && SUCCEEDED(hr); i++)
        {
            // Frame variables.
            IWICBitmapFrameDecode *piFrameDecode = NULL;
            IWICBitmapFrameEncode *piFrameEncode = NULL;
            IWICMetadataQueryReader *piFrameQReader = NULL;
            IWICMetadataQueryWriter *piFrameQWriter = NULL;

            // Get and create the image frame.
            if (SUCCEEDED(hr))
            {
                hr = piDecoder->GetFrame(i, &piFrameDecode);
            }
            if (SUCCEEDED(hr))
            {
                hr = piEncoder->CreateNewFrame(&piFrameEncode, NULL);
            }

            // Initialize the encoder.
            if (SUCCEEDED(hr))
            {
                hr = piFrameEncode->Initialize(NULL);
            }
            // Get and set the size.
            if (SUCCEEDED(hr))
            {
                hr = piFrameDecode->GetSize(&width, &height);
            }
            if (SUCCEEDED(hr))
            {
                hr = piFrameEncode->SetSize(width, height);
            }
            // Get and set the resolution.
            if (SUCCEEDED(hr))
            {
                piFrameDecode->GetResolution(&dpiX, &dpiY);
            }
            if (SUCCEEDED(hr))
            {
                hr = piFrameEncode->SetResolution(dpiX, dpiY);
            }
            // Set the pixel format.
            if (SUCCEEDED(hr))
            {
                piFrameDecode->GetPixelFormat(&pixelFormat);
            }
            if (SUCCEEDED(hr))
            {
                hr = piFrameEncode->SetPixelFormat(&pixelFormat);
            }

            // Check that the destination format and source formats are the same.
            bool formatsEqual = FALSE;
            if (SUCCEEDED(hr))
            {
                GUID srcFormat;
                GUID destFormat;

                hr = piDecoder->GetContainerFormat(&srcFormat);
                if (SUCCEEDED(hr))
                {
                    hr = piEncoder->GetContainerFormat(&destFormat);
                }
                if (SUCCEEDED(hr))
                {
                    if (srcFormat == destFormat)
                        formatsEqual = true;
                    else
                        formatsEqual = false;
                }
            }

            if (SUCCEEDED(hr) && formatsEqual)
            {
                // Copy metadata using metadata block reader/writer.
                if (SUCCEEDED(hr))
                {
                    piFrameDecode->QueryInterface(IID_PPV_ARGS(&piBlockReader));
                }
                if (SUCCEEDED(hr))
                {
                    piFrameEncode->QueryInterface(IID_PPV_ARGS(&piBlockWriter));
                }
                if (SUCCEEDED(hr))
                {
                    piBlockWriter->InitializeFromBlockReader(piBlockReader);
                }
            }

            if(SUCCEEDED(hr))
            {
                hr = piFrameEncode->GetMetadataQueryWriter(&piFrameQWriter);
            }
            if (SUCCEEDED(hr))
            {
                // Add additional metadata.
                PROPVARIANT    value;
                value.vt = VT_LPWSTR;
                value.pwszVal= L"Metadata Test Image.";
                hr = piFrameQWriter->SetMetadataByName(L"/xmp/dc:title", &value);
            }
            if (SUCCEEDED(hr))
            {
                hr = piFrameEncode->WriteSource(
                    static_cast<IWICBitmapSource*> (piFrameDecode),
                    NULL); // Using NULL enables JPEG loss-less encoding.
            }

            // Commit the frame.
            if (SUCCEEDED(hr))
            {
                hr = piFrameEncode->Commit();
            }

            if (piFrameDecode)
            {
                piFrameDecode->Release();
            }

            if (piFrameEncode)
            {
                piFrameEncode->Release();
            }

            if (piFrameQReader)
            {
                piFrameQReader->Release();
            }

            if (piFrameQWriter)
            {
                piFrameQWriter->Release();
            }
        }
    }

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

    if (SUCCEEDED(hr))
    {
        piFileStream->Commit(STGC_DEFAULT);
    }

    if (piFileStream)
    {
        piFileStream->Release();
    }
    if (piEncoder)
    {
        piEncoder->Release();
    }
    if (piBlockWriter)
    {
        piBlockWriter->Release();
    }
    if (piBlockReader)
    {
        piBlockReader->Release();
    }
    return 0;
}

Conceptuel

Vue d’ensemble des métadonnées WIC

Vue d’ensemble du langage de requête de métadonnées

Vue d’ensemble de la lecture et de l’écriture des métadonnées d’image

Vue d’ensemble de l’extensibilité des métadonnées