Erneutes Codieren eines JPEG-Bilds mit Metadaten

Im folgenden Beispiel wird veranschaulicht, wie Sie ein Bild und seine Metadaten in eine neue Datei desselben Formats umcodieren. Darüber hinaus werden in diesem Beispiel Metadaten hinzugefügt, um einen ausdruck mit einem einzelnen Element zu veranschaulichen, der von einem Abfrageschreiber verwendet wird.

Dieses Thema enthält folgende Abschnitte:

Voraussetzungen

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

Teil 1: Decodieren eines Bilds

Bevor Sie Bilddaten oder Metadaten in eine neue Bilddatei kopieren können, müssen Sie zunächst 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.jpg erstellen.

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

Beim Aufruf von CreateDecoderFromFilename wurde der Wert WICDecodeMetadataCacheOnDemand aus der WICDecodeOptions-Enumeration als vierter Parameter verwendet. Dadurch wird der Decoder angewiesen, die Metadaten zwischenzuspeichern, wenn die Metadaten benötigt werden, entweder durch Abrufen eines Abfragelesers oder mithilfe des zugrunde liegenden Metadatenlesers. Mit dieser Option können Sie den Datenstrom in den Metadaten beibehalten, was für eine schnelle Metadatencodierung erforderlich ist und eine 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 Image geladen wird.

Teil 2: Erstellen und Initialisieren des Image-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 zum Schreiben in die Bilddatei "test2.jpg" initialisiert. 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 umfasst Größe, Auflösung und Pixelformat. alle sind erforderlich, um einen gültigen Frame zu erstellen.

Hinweis

JPEG-Bilder verfügen nur über einen Frame, und die folgende Schleife ist technisch nicht erforderlich, sondern ist enthalten, um die Verwendung mehrerer Bilder 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);
            }

Der folgende Code führt eine schnelle Überprüfung durch, um festzustellen, ob die Quell- und Zielbildformate identisch sind. Dies ist erforderlich, da Teil 4 einen Vorgang zeigt, der nur unterstützt wird, wenn das Quell- und Zielformat identisch sind.

            // 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 Zielimageformate identisch sind. Beim Codieren in einem anderen Bildformat können Sie nicht alle Metadaten eines Bilds in einem einzigen Vorgang kopieren.

 

Zum Beibehalten von Metadaten beim Erneuten Codieren eines Bilds in dasselbe Bildformat stehen Methoden zum Kopieren aller Metadaten in einem einzigen Vorgang zur Verfügung. Jeder dieser Vorgänge folgt einem ähnlichen Muster. jede 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 Blockschreiber 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 rufen Sie einfach den Blockleser und den Blockschreiber aus dem Quellframe bzw. zielframe ab. Der Blockschreiber wird dann vom Blockleser initialisiert. Dadurch wird der Blockschreiber mit den vorab 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 dieser Vorgang funktioniert nur, wenn die Quell- und Zielbilder das gleiche Format haben. Dies liegt daran, dass unterschiedliche Bildformate die Metadatenblöcke an unterschiedlichen Speicherorten speichern. Für instance unterstützen sowohl JPEG als auch TIFF (Tagged Image File Format) XMP-Metadatenblöcke (Extensible Metadata Platform). In JPEG-Bildern befindet sich der XMP-Block im Stammmetadatenblock, wie in der WIC-Metadatenübersicht veranschaulicht. In einem TIFF-Image ist der XMP-Block jedoch in den IFD-Stammblock eingebettet.

Teil 5: Hinzufügen zusätzlicher Metadaten

Im folgenden Beispiel wird veranschaulicht, wie Dem Zielimage Metadaten hinzugefügt werden. Dazu wird die SetMetadataByName-Methode des Abfrageautors mithilfe eines Abfrageausdrucks und der in einer PROPVARIANT gespeicherten Daten aufgerufen.

            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 Metadatenabfragesprache Übersicht.

Teil 6: Fertigstellen des codierten Bilds

Die letzten Schritte zum Kopieren des Bilds sind das Schreiben der Pixeldaten für den Frame, das Commit für den Frame an den Encoder und das Commit für den Encoder. Durch das Committen 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 über genügend Speicherplatz in der Bilddatei verfügen. Nachdem die Pixeldaten geschrieben wurden, wird der Frame mithilfe der Commit-Methode des Frames in den Stream geschrieben. Nachdem alle Frames verarbeitet wurden, wird der Encoder (und damit das Bild) mit der Commit-Methode des Encoders abgeschlossen.

Nachdem Sie den Frame committen, müssen Sie die com-Objekte freigeben, die in der Schleife erstellt wurden.

JPEG-Beispielcode neu codieren

Es folgt der Code aus den Teilen 1 bis 6 in einem konvienten 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;
}

Konzept

Übersicht über WIC-Metadaten

Übersicht über die Metadaten-Abfragesprache

Übersicht über das Lesen und Schreiben von Bildmetadaten

Metadatenerweiterbarkeit – Übersicht