Freigeben über


So codieren Sie ein JPEG-Bild mit Metadaten erneut

Im folgenden Beispiel wird veranschaulicht, wie ein Bild und seine Metadaten in eine neue Datei desselben Formats neu codiert werden. Darüber hinaus fügt dieses Beispiel Metadaten hinzu, um einen einzelelementigen Ausdruck zu veranschaulichen, der von einem Abfrageschreiber verwendet wird.

Dieses Thema enthält folgende Abschnitte:

Voraussetzungen

Um dieses Thema zu verstehen, sollten Sie mit dem Windows Imaging Component (WIC)-Metadatensystem vertraut sein, wie in der WIC-Metadatenübersicht beschrieben. Sie sollten auch mit den WIC-Codec-Komponenten vertraut sein, wie in der Übersicht über die Windows-Imageerstellungskomponente beschrieben.

Teil 1: Decodieren eines Bilds

Bevor Sie Bilddaten oder Metadaten in eine neue Bilddatei kopieren können, müssen Sie zuerst einen Decoder für das vorhandene Bild erstellen, das Sie neu codieren möchten. Der folgende Code veranschaulicht, wie sie einen WIC-Decoder für die Bilddatei test.jpgerstellen.

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

Der Aufruf von CreateDecoderFromFilename verwendet den Wert WICDecodeMetadataCacheOnDemand aus der WICDecodeOptions-Aufzählung als vierten Parameter. Dadurch wird der Decoder aufgefordert, die Metadaten zwischenzuspeichern, wenn die Metadaten benötigt werden, entweder durch Abrufen eines Abfragelesers oder mithilfe des zugrunde liegenden Metadatenlesers. Mithilfe dieser Option können Sie den Datenstrom in den Metadaten beibehalten, der für die Durchführung einer schnellen Metadatencodierung erforderlich ist und verlustfreie Decodierung und Codierung von JPEG-Bildern ermöglicht. Alternativ können Sie den anderen WICDecodeOptions-Wert WICDecodeMetadataCacheOnLoad verwenden, der die eingebetteten Bildmetadaten zwischenspeichert, sobald das Bild geladen wird.

Teil 2: Erstellen und Initialisieren des Bild-Encoders

Der folgende Code veranschaulicht die Erstellung des Encoders, den Sie zum Codieren des zuvor decodierten Bilds verwenden.

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

Ein WIC-Dateistream piFileStream wird erstellt und initialisiert, um in die Bilddatei "test2.jpg" zu schreiben. PiFileStream wird dann verwendet, um den Encoder zu initialisieren und den Encoder darüber zu informieren, wo die Bildbits geschrieben werden sollen, wenn die Codierung abgeschlossen ist.

Teil 3: Kopieren decodierter Frameinformationen

Der folgende Code kopiert jeden Frame eines Bilds in einen neuen Frame des Encoders. Diese Kopie enthält Größe, Auflösung und Pixelformat; alle, die zum Erstellen eines gültigen Frames erforderlich sind.

Hinweis

JPEG-Bilder haben nur einen Frame, und die folgende Schleife ist technisch nicht erforderlich, sondern ist enthalten, um die Verwendung mehrerer Frames für Formate zu veranschaulichen, die sie unterstützen.

 

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

Mit dem folgenden Code wird schnell überprüft, ob die Quell- und Zielbildformate identisch sind. Dies ist erforderlich, da Teil 4 einen Vorgang anzeigt, der nur unterstützt wird, wenn das Quell- und Zielformat identisch ist.

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

Teil 4: Kopieren der Metadaten

Hinweis

Der Code in diesem Abschnitt ist nur gültig, wenn die Quell- und Zielbildformate identisch sind. Sie können nicht alle Metadaten eines Bilds in einem einzigen Vorgang kopieren, wenn Sie ein anderes Bildformat codieren.

 

Um Metadaten beim erneuten Codieren eines Bilds im selben Bildformat beizubehalten, stehen Methoden zum Kopieren aller Metadaten in einem einzigen Vorgang zur Verfügung. Jeder dieser Vorgänge folgt einem ähnlichen Muster; jeder legt die Metadaten des decodierten Frames direkt in den neuen Frame fest, der codiert wird. Beachten Sie, dass dies für jeden einzelnen Bildrahmen erfolgt.

Die bevorzugte Methode zum Kopieren von Metadaten besteht darin, den Block writer des neuen Frames mit dem Blockleser des decodierten Frames zu initialisieren. Der folgende Code veranschaulicht diese Methode.

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

In diesem Beispiel erhalten Sie einfach den Blockleser bzw. den Blockschreiber aus dem Quellframe bzw. dem Zielframe. Der Blockschreiber wird dann vom Blockleser initialisiert. Dadurch wird der Block writer mit den bereits ausgefüllten Metadaten des Blocklesers initialisiert. Weitere Methoden zum Kopieren von Metadaten finden Sie im Abschnitt "Schreiben von Metadaten" in der Übersicht über das Lesen und Schreiben von Bildmetadaten.

Auch hier funktioniert dieser Vorgang nur, wenn die Quell- und Zielbilder das gleiche Format aufweisen. Dies liegt daran, dass unterschiedliche Bildformate die Metadatenblöcke an unterschiedlichen Speicherorten speichern. Beispielsweise unterstützen SOWOHL JPEG- als auch Tagged Image File Format (TIFF) Extensible Metadata Platform (XMP)-Metadatenblöcke. In JPEG-Bildern befindet sich der XMP-Block im Stammmetadatenblock, wie in der WIC-Metadatenübersicht dargestellt. In einem TIFF-Bild ist der XMP-Block jedoch in den IFD-Stammblock eingebettet.

Teil 5: Hinzufügen zusätzlicher Metadaten

Im folgenden Beispiel wird das Hinzufügen von Metadaten zum Zielbild veranschaulicht. Dazu rufen Sie die SetMetadataByName-Methode des Abfrageautors mithilfe eines Abfrageausdrucks und der in einem PROPVARIANT gespeicherten Daten auf.

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

Weitere Informationen zum Abfrageausdruck finden Sie in der Übersicht über die Metadatenabfragesprache.

Teil 6: Fertigstellen des codierten Bilds

Die letzten Schritte zum Kopieren des Bilds sind das Schreiben der Pixeldaten für den Frame, das Commit des Frames an den Encoder und das Commit des Encoders. Beim Commit des Encoders wird der Bilddatenstrom in die Datei geschrieben.

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

Die WriteSource-Methode des Frames wird verwendet, um die Pixeldaten für das Bild zu schreiben. Beachten Sie, dass dies geschieht, nachdem die Metadaten geschrieben wurden. Dies ist erforderlich, um sicherzustellen, dass die Metadaten genügend Speicherplatz in der Bilddatei haben. Nachdem die Pixeldaten geschrieben wurden, wird der Frame mithilfe der Commit-Methode des Frames in den Datenstrom geschrieben. Nachdem alle Frames verarbeitet wurden, wird der Encoder (und damit das Bild) mit der Commit-Methode des Encoders abgeschlossen.

Nachdem Sie einen Commit für den Frame ausgeführt haben, müssen Sie die in der Schleife erstellten COM-Objekte freigeben.

JPEG-Beispielcode neu codieren

Im Folgenden sehen Sie den Code aus Den Teilen 1 bis 6 in einem praktischen Block.

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

Konzeptionell

WIC-Metadatenübersicht

Übersicht über die Metadatenabfragesprache

Übersicht über Das Lesen und Schreiben von Bildmetadaten

Übersicht über die Metadatenerweiterung