메타데이터를 사용하여 JPEG 이미지를 다시 인코딩하는 방법

다음 예제에서는 이미지와 해당 메타데이터를 동일한 형식의 새 파일로 다시 인코딩하는 방법을 보여 줍니다. 또한 이 예제에서는 쿼리 작성기에서 사용하는 단일 항목 식을 보여 주는 메타데이터를 추가합니다.

이 항목에는 다음과 같은 섹션이 포함되어 있습니다.

사전 요구 사항

이 항목을 이해하려면 WIC 메타데이터 개요에 설명된 대로 WIC(Windows 이미징 구성 요소) 메타데이터 시스템에 대해 잘 알고 있어야 합니다. Windows 이미징 구성 요소 개요에 설명된 대로 WIC 코덱 구성 요소에 대해서도 잘 알고 있어야 합니다.

1부: 이미지 디코딩

이미지 데이터 또는 메타데이터를 새 이미지 파일에 복사하려면 먼저 다시 인코딩하려는 기존 이미지에 대한 디코더를 만들어야 합니다. 다음 코드는 이미지 파일 test.jpg 대한 WIC 디코더를 만드는 방법을 보여 줍니다.

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

CreateDecoderFromFilename에 대한 호출에서는 WICDecodeOptions 열거형의 WICDecodeMetadataCacheOnDemand 값을 네 번째 매개 변수로 사용했습니다. 이렇게 하면 쿼리 판독기를 가져오거나 기본 메타데이터 판독기를 사용하여 메타데이터가 필요할 때 메타데이터를 캐시하도록 디코더에 지시합니다. 이 옵션을 사용하면 빠른 메타데이터 인코딩을 수행하는 데 필요한 메타데이터에 스트림을 유지할 수 있으며 JPEG 이미지의 무손실 디코딩 및 인코딩을 사용할 수 있습니다. 또는 이미지가 로드되는 즉시 포함된 이미지 메타데이터를 캐시하는 다른 WICDecodeOptions 값인 WICDecodeMetadataCacheOnLoad를 사용할 수 있습니다.

2부: 이미지 인코더 만들기 및 초기화

다음 코드는 이전에 디코딩한 이미지를 인코딩하는 데 사용할 인코더를 만드는 방법을 보여 줍니다.

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

WIC 파일 스트림 piFileStream이 만들어지고 이미지 파일 "test2.jpg"에 쓰기 위해 초기화됩니다. 그런 다음 piFileStream을 사용하여 인코더를 초기화하여 인코더에 인코딩이 완료되면 이미지 비트를 쓸 위치를 알릴 수 있습니다.

3부: 디코딩된 프레임 정보 복사

다음 코드는 이미지의 각 프레임을 인코더의 새 프레임에 복사합니다. 이 복사본에는 크기, 해상도 및 픽셀 형식이 포함됩니다. 모두 유효한 프레임을 만드는 데 필요합니다.

참고

JPEG 이미지에는 하나의 프레임만 있으며 아래 루프는 기술적으로 필요하지 않지만 이를 지원하는 형식에 대한 다중 프레임 사용을 보여 주는 데 포함됩니다.

 

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

다음 코드는 빠른 검사 수행하여 원본 및 대상 이미지 형식이 동일한지 여부를 확인합니다. 이는 4부에서 원본 및 대상 형식이 동일한 경우에만 지원되는 작업을 표시하기 때문에 필요합니다.

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

4부: 메타데이터 복사

참고

이 섹션의 코드는 원본 및 대상 이미지 형식이 동일한 경우에만 유효합니다. 다른 이미지 형식으로 인코딩하는 경우 단일 작업에서 이미지의 메타데이터를 모두 복사할 수 없습니다.

 

이미지를 동일한 이미지 형식으로 다시 인코딩하는 동안 메타데이터를 유지하기 위해 단일 작업에서 모든 메타데이터를 복사하는 데 사용할 수 있는 메서드가 있습니다. 이러한 각 작업은 비슷한 패턴을 따릅니다. 각 은 디코딩된 프레임의 메타데이터를 인코딩되는 새 프레임으로 직접 설정합니다. 이 작업은 각 개별 이미지 프레임에 대해 수행됩니다.

메타데이터를 복사하는 기본 방법은 디코딩된 프레임의 블록 판독기를 사용하여 새 프레임의 블록 기록기를 초기화하는 것입니다. 다음 코드에서는 이 메서드를 보여 줍니다.

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

이 예제에서는 각각 소스 프레임과 대상 프레임에서 블록 판독기 및 블록 기록기를 가져옵니다. 그런 다음 블록 작성기가 블록 판독기에서 초기화됩니다. 이렇게 하면 블록 판독기의 미리 채워진 메타데이터를 사용하여 블록 작성기가 초기화됩니다. 메타데이터를 복사하는 추가 방법을 알아보려면 이미지 메타데이터 읽기 및 쓰기 개요에서 메타데이터 작성 섹션을 참조하세요.

다시 말하지만, 이 작업은 원본 및 대상 이미지의 형식이 동일한 경우에만 작동합니다. 이는 이미지 형식이 서로 다른 위치에 메타데이터 블록을 저장하기 때문입니다. instance 경우 JPEG 및 TIFF(태그가 지정된 이미지 파일 형식)는 모두 XMP(Extensible Metadata Platform) 메타데이터 블록을 지원합니다. JPEG 이미지에서 XMP 블록은 WIC 메타데이터 개요에 설명된 대로 루트 메타데이터 블록에 있습니다. 그러나 TIFF 이미지에서 XMP 블록은 루트 IFD 블록에 포함됩니다.

5부: 추가 메타데이터 추가

다음 예제에서는 대상 이미지에 메타데이터를 추가하는 방법을 보여 줍니다. 이 작업은 쿼리 식 및 PROPVARIANT에 저장된 데이터를 사용하여 쿼리 작성자의 SetMetadataByName 메서드를 호출하여 수행됩니다.

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

쿼리 식에 대한 자세한 내용은 메타데이터 쿼리 언어 개요를 참조하세요.

6부: 인코딩된 이미지 완료

이미지를 복사하는 마지막 단계는 프레임의 픽셀 데이터를 쓰고, 인코더에 프레임을 커밋하고, 인코더를 커밋하는 것입니다. 인코더를 커밋하면 이미지 스트림이 파일에 기록됩니다.

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

프레임의 WriteSource 메서드는 이미지의 픽셀 데이터를 작성하는 데 사용됩니다. 이 작업은 메타데이터가 작성된 후에 수행됩니다. 메타데이터에 이미지 파일 내에 충분한 공간이 있는지 확인하는 데 필요합니다. 픽셀 데이터를 쓴 후 프레임은 프레임의 Commit 메서드를 사용하여 스트림에 기록됩니다. 모든 프레임이 처리되면 인코더의 Commit 메서드를 사용하여 인코더(및 이미지)가 완료됩니다.

프레임을 커밋한 후에는 루프에서 만든 COM 개체를 해제해야 합니다.

JPEG 다시 인코딩 예제 코드

다음은 하나의 convienient 블록에 있는 파트 1부터 6까지의 코드입니다.

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

개념

WIC 메타데이터 개요

메타데이터 쿼리 언어 개요

이미지 메타데이터 읽기 및 쓰기 개요

메타데이터 확장성 개요