Procédure pour 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ête.

Cette rubrique contient les sections suivantes.

Prérequis

Pour comprendre cette rubrique, vous devez être familiarisé avec le système de métadonnées WIC (Windows Imaging Component) comme décrit dans vue d’ensemble des métadonnées WIC. Vous devez également vous familiariser avec les composants du codec WIC, comme décrit dans vue d’ensemble du composant d’acquisition d’images 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 réencoder. 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 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 rapide des métadonnées et permet le décodage sans perte et l’encodage des 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 utiliserez 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, indiquant à l’encodeur où écrire les bits d’image une fois l’encodage terminé.

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

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

Notes

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 de plusieurs images pour les formats qui la 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 case activée rapide pour déterminer si les formats d’image source et de destination sont identiques. Cela est nécessaire, car la partie 4 montre une opération qui n’est prise en charge que lorsque le format source et le format de destination sont identiques.

            // 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

Notes

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

 

Pour conserver les métadonnées lors de l’encodage d’une image au même format d’image, il existe des méthodes permettant de copier toutes les métadonnées en 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 la nouvelle image en cours d’encodage. Notez que cette opération est effectuée pour chaque image individuelle.

La méthode recommandée pour copier les métadonnées consiste à initialiser l’enregistreur de blocs de la nouvelle image avec le lecteur de blocs 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 de l’image source et de l’image de destination, respectivement. L’enregistreur de blocs est ensuite initialisé à partir du lecteur de blocs. Cela initialise l’enregistreur de blocs avec les métadonnées préremplies du lecteur de blocs. Pour découvrir d’autres méthodes de copie de métadonnées, consultez la section Écriture de métadonnées dans vue d’ensemble de la lecture et de l’écriture de 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 les différents formats d’image stockent les blocs de métadonnées à différents emplacements. Par instance, 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 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 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 pixel pour le cadre, de valider le cadre sur l’encodeur et de valider l’encodeur. La validation de l’encodeur écrit le flux d’image 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 pour l’image. Notez que cette opération est effectuée une fois que les métadonnées ont été écrites. Cela est nécessaire pour garantir que les métadonnées ont suffisamment d’espace dans le fichier image. Une fois les données de pixel écrites, le cadre est écrit dans le flux à l’aide de la méthode Commit du frame. Une fois toutes les images traitées, l’encodeur (et donc l’image) est finalisé à l’aide de la méthode Commit de l’encodeur.

Une fois que vous avez commité le cadre, vous devez libérer les objets COM créés dans la boucle.

Exemple de code JPEG

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

#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 de métadonnées d’image

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