Cara mengodekan ulang gambar JPEG dengan metadata

Contoh berikut menunjukkan cara mengodekan ulang gambar dan metadatanya ke file baru dengan format yang sama. Selain itu, contoh ini menambahkan metadata untuk menunjukkan ekspresi item tunggal yang digunakan oleh penulis kueri.

Topik ini berisi bagian berikut.

Prasyarat

Untuk memahami topik ini, Anda harus terbiasa dengan sistem metadata Komponen Pencitraan Windows (WIC) seperti yang dijelaskan dalam Gambaran Umum Metadata WIC. Anda juga harus terbiasa dengan komponen codec WIC seperti yang dijelaskan dalam Gambaran Umum Komponen Pencitraan Windows.

Bagian 1: Mendekode Gambar

Sebelum dapat menyalin data gambar atau metadata ke file gambar baru, Anda harus terlebih dahulu membuat dekoder untuk gambar yang sudah ada yang ingin Anda enkode ulang. Kode berikut menunjukkan cara membuat dekoder WIC untuk file gambar 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);
    }

Panggilan ke CreateDecoderFromFilename menggunakan nilai WICDecodeMetadataCacheOnDemand dari enumerasi WICDecodeOptions sebagai parameter keempat. Ini memberi tahu dekoder untuk menyimpan metadata saat metadata diperlukan, baik dengan mendapatkan pembaca kueri atau dengan menggunakan pembaca metadata yang mendasar. Menggunakan opsi ini memungkinkan Anda mempertahankan aliran ke metadata, yang diperlukan untuk melakukan pengodean metadata cepat dan memungkinkan decoding dan pengodean gambar JPEG tanpa kehilangan. Atau, Anda dapat menggunakan nilai WICDecodeOptions lainnya, WICDecodeMetadataCacheOnLoad, yang menyimpan metadata gambar yang disematkan segera setelah gambar dimuat.

Bagian 2: Membuat dan Menginisialisasi Encoder Gambar

Kode berikut menunjukkan pembuatan encoder yang akan Anda gunakan untuk mengodekan gambar yang sebelumnya Anda dekode.

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

PiFileStream aliran file WIC dibuat dan diinisialisasi untuk menulis ke file gambar "test2.jpg". piFileStream kemudian digunakan untuk menginisialisasi encoder, menginformasikan encoder tempat menulis bit gambar ketika pengodean selesai.

Bagian 3: Salin Informasi Bingkai yang Dikodekan

Kode berikut menyalin setiap bingkai gambar ke bingkai baru encoder. Salinan ini mencakup ukuran, resolusi, dan format piksel; yang semuanya diperlukan untuk membuat bingkai yang valid.

Catatan

Gambar JPEG hanya akan memiliki satu bingkai dan perulangan di bawah ini tidak diperlukan secara teknis tetapi disertakan untuk menunjukkan penggunaan multi-bingkai untuk format yang mendukungnya.

 

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

Kode berikut melakukan pemeriksaan cepat untuk menentukan apakah format gambar sumber dan tujuan sama. Ini diperlukan karena Bagian 4 menunjukkan operasi yang hanya didukung ketika format sumber dan tujuan sama.

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

Bagian 4: Salin Metadata

Catatan

Kode di bagian ini hanya valid ketika format gambar sumber dan tujuan sama. Anda tidak dapat menyalin semua metadata gambar dalam satu operasi saat mengodekan ke format gambar yang berbeda.

 

Untuk mempertahankan metadata saat mengodekan ulang gambar ke format gambar yang sama, ada metode yang tersedia untuk menyalin semua metadata dalam satu operasi. Masing-masing operasi ini mengikuti pola yang sama; masing-masing mengatur metadata bingkai yang didekodekan langsung ke bingkai baru yang dikodekan. Perhatikan bahwa ini dilakukan untuk setiap bingkai gambar individu.

Metode yang disukai untuk menyalin metadata adalah menginisialisasi penulis blok bingkai baru dengan pembaca blok bingkai yang didekode. Kode berikut menunjukkan metode ini.

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

Dalam contoh ini, Anda cukup mendapatkan pembaca blok dan penulis blok dari bingkai sumber dan bingkai tujuan, masing-masing. Penulis blok kemudian diinisialisasi dari pembaca blok. Ini menginisialisasi penulis blok dengan metadata yang telah diisi sebelumnya dari pembaca blok. Untuk mempelajari metode tambahan untuk menyalin metadata, lihat bagian Menulis Metadata di Gambaran Umum Membaca dan Menulis Metadata Gambar.

Sekali lagi, operasi ini hanya berfungsi ketika gambar sumber dan tujuan memiliki format yang sama. Ini karena format gambar yang berbeda menyimpan blok metadata di lokasi yang berbeda. Misalnya, JPEG dan Tagged Image File Format (TIFF) mendukung blok metadata Extensible Metadata Platform (XMP). Dalam gambar JPEG, blok XMP berada di blok metadata akar seperti yang diilustrasikan dalam Gambaran Umum Metadata WIC. Namun, dalam gambar TIFF, blok XMP disematkan di blok IFD akar.

Bagian 5: Menambahkan Metadata Tambahan

Contoh berikut menunjukkan cara menambahkan metadata ke gambar tujuan. Ini dilakukan dengan memanggil metode SetMetadataByName penulis kueri menggunakan ekspresi kueri dan data yang disimpan dalam 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);
            }

Untuk informasi selengkapnya tentang ekspresi kueri, lihat Gambaran Umum Bahasa Kueri Metadata.

Bagian 6: Menyelesaikan Gambar yang Dikodekan

Langkah terakhir untuk menyalin gambar adalah menulis data piksel untuk bingkai, menerapkan bingkai ke encoder, dan menerapkan encoder. Menerapkan encoder menulis aliran gambar ke file.

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

Metode WriteSource bingkai digunakan untuk menulis data piksel untuk gambar. Perhatikan bahwa ini dilakukan setelah metadata ditulis. Ini diperlukan untuk memastikan bahwa metadata memiliki ruang yang cukup dalam file gambar. Setelah data piksel ditulis, bingkai ditulis ke aliran menggunakan metode Penerapan bingkai. Setelah semua bingkai diproses, encoder (dan dengan demikian gambar) diselesaikan menggunakan metode Commit encoder.

Setelah menerapkan bingkai, Anda harus merilis objek COM yang dibuat dalam perulangan.

Kode Contoh Pengodean Ulang JPEG

Berikut ini adalah kode dari Bagian 1 hingga 6 dalam satu blok 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;
}

Konseptual

Gambaran Umum Metadata WIC

Gambaran Umum Bahasa Kueri Metadata

Gambaran Umum Membaca dan Menulis Metadata Gambar

Gambaran Umum Ekstensibilitas Metadata