Udostępnij za pośrednictwem


Jak ponownie zakodować obraz JPEG przy użyciu metadanych

W poniższym przykładzie pokazano, jak ponownie zakodować obraz i jego metadane do nowego pliku w tym samym formacie. Ponadto w tym przykładzie dodano metadane, aby zademonstrować wyrażenie pojedynczego elementu używane przez twórcę zapytań.

Ten temat zawiera następujące sekcje.

Warunki wstępne

Aby zrozumieć ten temat, należy zapoznać się z systemem metadanych Windows Imaging Component (WIC), opisanym w WIC Metadata Overview. Należy również zapoznać się ze składnikami koderów WIC zgodnie z opisem w Windows Imaging Component Overview.

Część 1. Dekodowanie obrazu

Zanim będzie można skopiować dane obrazu lub metadane do nowego pliku obrazu, musisz najpierw utworzyć dekoder dla istniejącego obrazu, który chcesz ponownie zakodować. Poniższy kod pokazuje, jak utworzyć dekoder WIC dla pliku obrazu 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);
    }

Użyto wartości WICDecodeMetadataCacheOnDemand z wyliczenia WICDecodeOptions jako czwarty parametr w wywołaniu metody CreateDecoderFromFilename. Dzięki temu dekoder buforuje metadane, gdy są potrzebne, uzyskując czytnik zapytań lub korzystając z podstawowego czytnika metadanych. Użycie tej opcji umożliwia zachowanie strumienia metadanych, co jest konieczne do szybkiego kodowania metadanych i umożliwia dekodowanie bezstratne oraz kodowanie obrazów JPEG. Alternatywnie możesz użyć drugiej WICDecodeOptions wartości WICDecodeMetadataCacheOnLoad, która buforuje osadzone metadane obrazu zaraz po załadowaniu obrazu.

Część 2. Tworzenie i inicjowanie kodera obrazów

Poniższy kod demonstruje tworzenie kodera, którego użyjesz do zakodowania wcześniej zdekodowanego obrazu.

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

Strumień plików WIC piFileStream jest tworzony i inicjowany do zapisywania w pliku obrazu "test2.jpg". PiFileStream jest następnie używany do inicjowania kodera, informowania kodera, gdzie mają być zapisywane bity obrazu po zakończeniu kodowania.

Część 3. Kopiowanie zdekodowanych informacji o ramce

Poniższy kod kopiuje każdą klatkę obrazu do nowej klatki enkodera. Ta kopia obejmuje rozmiar, rozdzielczość i format pikseli; wszystkie z nich są niezbędne do utworzenia prawidłowej ramki.

Notatka

Obrazy JPEG będą miały tylko jedną ramkę, a poniższa pętla nie jest technicznie niezbędna, ale jest zawarta w celu zademonstrowania użycia wielu ramek dla formatów, które go obsługują.

 

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

Poniższy kod przeprowadza szybką kontrolę, aby określić, czy formaty obrazu źródłowego i docelowego są takie same. Jest to konieczne, ponieważ część 4 pokazuje, że operacja jest obsługiwana tylko wtedy, gdy format źródłowy i docelowy są takie same.

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

Część 4. Kopiowanie metadanych

Notatka

Kod w tej sekcji jest prawidłowy tylko wtedy, gdy formaty obrazu źródłowego i docelowego są takie same. Nie można skopiować wszystkich metadanych obrazu w jednej operacji podczas kodowania do innego formatu obrazu.

 

Aby zachować metadane podczas ponownego kodowania obrazu w tym samym formacie obrazu, dostępne są metody kopiowania wszystkich metadanych w ramach jednej operacji. Każda z tych operacji jest zgodna z podobnym wzorcem; każda z nich ustawia metadane dekodowanej ramki bezpośrednio w nową ramkę, która jest kodowana. Należy pamiętać, że jest to wykonywane dla każdej ramki obrazu.

Preferowaną metodą kopiowania metadanych jest zainicjowanie modułu zapisywania bloków nowej ramki za pomocą czytnika bloków dekodowanej ramki. Poniższy kod demonstruje tę metodę.

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

W tym przykładzie po prostu uzyskujesz czytnik bloków z ramki źródłowej i zapisator bloków z ramki docelowej. Moduł zapisywania bloków jest następnie inicjowany przez czytnik bloków. Spowoduje to zainicjowanie modułu zapisywania bloków za pomocą wstępnie wypełnionych metadanych czytnika bloków. Aby poznać dodatkowe metody kopiowania metadanych, zobacz sekcję Pisanie metadanych w Omówienie odczytywania i zapisywania metadanych obrazu.

Ponownie ta operacja działa tylko wtedy, gdy obrazy źródłowe i docelowe mają ten sam format. Dzieje się tak, ponieważ różne formaty obrazów przechowują bloki metadanych w różnych lokalizacjach. Na przykład pliki JPEG i Tagged Image File Format (TIFF) obsługują bloki metadanych Extensible Metadata Platform (XMP). Na obrazach JPEG blok XMP znajduje się w głównym bloku metadanych, jak pokazano w WIC Metadata Overview. Jednak na obrazie TIFF blok XMP jest osadzony w głównym bloku IFD.

Część 5. Dodawanie dodatkowych metadanych

W poniższym przykładzie pokazano, jak dodać metadane do obrazu docelowego. Jest to wykonywane przez wywołanie metody SetMetadataByName zapisywacza zapytań używając wyrażenia zapytania oraz danych zmagazynowanych w 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);
            }

Aby uzyskać więcej informacji na temat wyrażenia zapytania, zobacz Metadata Query Language Overview.

Część 6. Finalizowanie zakodowanego obrazu

Ostatnimi krokami kopiowania obrazu są zapisanie danych pikseli dla ramki, przekazanie ramki do kodera oraz zatwierdzenie kodera. Zatwierdzenie kodowania zapisuje strumień obrazu do pliku.

            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 ramki służy do zapisywania danych pikseli dla obrazu. Należy pamiętać, że odbywa się to po zapisaniu metadanych. Jest to konieczne, aby zapewnić, że metadane mają wystarczającą ilość miejsca w pliku obrazu. Po zapisaniu danych pikseli ramka jest zapisywana w strumieniu przy użyciu metody Commit ramki. Po przetworzeniu wszystkich ramek obraz (i tym samym koder) zostanie sfinalizowany przy użyciu metody Commit kodera.

Po zatwierdzeniu ramki należy zwolnić obiekty COM utworzone w pętli.

Kod przykładowy ponownego kodowania JPEG

Poniżej znajduje się kod z części od 1 do 6 w jednym wygodnym 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;
}

Koncepcje

Przegląd metadanych WIC

Przegląd języka zapytań metadanych

Omówienie metadanych obrazów: odczytywanie i zapisywanie

Rozszerzalność metadanych — omówienie