Sdílet prostřednictvím


Opětovné kódování obrázku JPEG s metadaty

Následující příklad ukazuje, jak překódovat obrázek a jeho metadata do nového souboru stejného formátu. Kromě toho tento příklad přidá metadata, která demonstrují výraz s jednou položkou používaný zapisovačem dotazů.

Toto téma obsahuje následující části.

Požadavky

Abyste pochopili toto téma, měli byste být obeznámeni se systémem metadat Windows Imaging Component (WIC), jak je popsáno v Přehled metadat WIC. Měli byste být také obeznámeni s komponentami kodeku WIC, jak je popsáno v Windows Imaging Component Overview.

Část 1: Dekódování obrázku

Než budete moct kopírovat data nebo metadata obrázků do nového souboru obrázku, musíte nejprve vytvořit dekodér existujícího obrázku, který chcete znovu zakódovat. Následující kód ukazuje, jak vytvořit dekodér WIC pro soubor obrázku 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);
    }

Volání CreateDecoderFromFilename použilo hodnotu WICDecodeMetadataCacheOnDemand z výčtu WICDecodeOptions jako čtvrtý parametr. To dekodéru říká, aby metadata uložil do mezipaměti, když jsou metadata potřebná, a to buďto získáním čtečky pro dotazy, nebo použitím základní čtečky metadat. Pomocí této možnosti můžete zachovat datový proud k metadatům, která jsou vyžadována pro rychlé kódování metadat a umožňují bezeztrátové dekódování a kódování obrázků JPEG. Alternativně můžete použít další WICDecodeOptions hodnotu WICDecodeMetadataCacheOnLoad, která ukládá metadata vložené image do mezipaměti hned po načtení image.

Část 2: Vytvoření a inicializace kodéru obrázků

Následující kód ukazuje vytvoření kodéru, který použijete ke kódování obrázku, který jste dříve dekódovali.

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

Datový proud souboru WIC piFileStream je vytvořen a inicializován pro zápis do souboru obrázku "test2.jpg". PiFileStream se pak použije k inicializaci kodéru a informuje kodér, kde zapisovat bity obrázků po dokončení kódování.

Část 3: Kopírování informací o dekódovaném rámečku

Následující kód zkopíruje každý rámec obrázku do nového rámce kodéru. Tato kopie zahrnuje velikost, rozlišení a formát pixelů; které jsou všechny potřebné k vytvoření platného rámu.

Poznámka

Obrázky JPEG budou mít pouze jeden rámec a následující smyčka není technicky nezbytná, ale je zahrnuta k předvedení použití více snímků pro formáty, které ho podporují.

 

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

Následující kód provede rychlou kontrolu a určí, jestli jsou zdrojové a cílové formáty imagí stejné. To je potřeba, protože část 4 ukazuje operaci, která je podporována pouze v případě, že zdrojový a cílový formát jsou stejné.

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

Část 4: Kopírování metadat

Poznámka

Kód v této části je platný pouze v případě, že jsou stejné formáty zdrojové a cílové image. Při kódování do jiného formátu obrázku nelze zkopírovat všechna metadata obrázku v jedné operaci.

 

Chcete-li zachovat metadata při opětovném kódování obrázku do stejného formátu obrázku, jsou k dispozici metody pro kopírování všech metadat v jedné operaci. Každá z těchto operací následuje podobný vzorec; nastavuje metadata dekódovaného rámce přímo do nového rámce, který se kóduje. Všimněte si, že se to provádí pro každý jednotlivý rámec obrázku.

Upřednostňovanou metodou kopírování metadat je inicializace zapisovače bloků nového rámce pomocí čtečky bloků dekódovaného rámce. Následující kód ukazuje tuto metodu.

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

V tomto příkladu jednoduše získáte čtečku bloku a zapisovač bloku ze zdrojového rámce a cílového rámce. Zapisovač bloku se pak inicializuje ze čtečky bloků. Tím se inicializuje zapisovač bloku s předem vyplněnými metadaty čtečky bloků. Další metody kopírování metadat najdete v části Zápis metadat v Přehled čtení a zápisu metadat obrázků.

Tato operace opět funguje pouze v případě, že zdrojové a cílové image mají stejný formát. Důvodem je to, že různé formáty obrázků ukládají bloky metadat v různých umístěních. Například formát JPEG i Tagged Image File Format (TIFF) podporují bloky metadat XMP (Extensible Metadata Platform). V obrázcích JPEG je blok XMP uvnitř bloku kořenových metadat, jak je znázorněno v přehledu metadat WIC. Na obrázku TIFF je však blok XMP vložen do kořenového bloku IFD.

Část 5: Přidání dalších metadat

Následující příklad ukazuje, jak přidat metadata do cílové image. To se provádí voláním metody zapisovače dotazů SetMetadataByName pomocí dotazového výrazu a dat uložených v 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);
            }

Další informace o výrazu dotazu naleznete v Přehled jazyka pro dotazování metadat.

Část 6: Dokončení zakódovaného obrázku

Posledními kroky kopírování obrázku jsou zapsání pixelových dat pro snímek, uložení snímku do kodéru a uložení kodéru. Kodér při potvrzení zapisuje datový proud obrázku do souboru.

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

Metoda WriteSource rámce se používá k zápisu pixelových dat obrázku. Všimněte si, že se to provádí po zápisu metadat. To je nezbytné, aby metadata měli dostatek místa v souboru obrázku. Po zápisu obrazových dat se rámec zapíše do datového proudu pomocí metody Commit rámce. Po zpracování všech snímků se kodér (a tím i obrázek) dokončí pomocí metody Commit kodéru.

Po potvrzení rámce je nutné uvolnit objekty COM vytvořené ve smyčce.

Ukázkový kód pro překódování JPEG

Následuje kód z částí 1 až 6 v jednom pohodlném bloku.

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

koncepční

přehled metadat WIC

Přehled dotazovacího jazyka metadat

Přehled čtení a zápisu metadat obrázků

Přehled rozšiřitelnosti metadat