如何使用元數據重新編碼 JPEG 影像
下列範例示範如何將影像及其元數據重新編碼為相同格式的新檔案。 此外,此範例會新增元數據來示範查詢寫入器所使用的單一項目表達式。
本主題包含下列各節。
- 必要條件
- 第1部分:譯碼影像
- 第 2 部分:建立和初始化影像編碼器
- 第 3 部分:複製譯碼框架資訊
- 第 4 部分:複製元數據
- 第 5 部分:新增其他元數據
- 第 6 部分:完成編碼影像
- JPEG 重新編碼範例程式碼
- 相關主題
先決條件
若要瞭解本主題,您應該熟悉 Windows 映像處理元件 (WIC) 元數據系統,如 WIC 元數據概觀中所述。 您也應該熟悉 WIC 編解碼器元件,如 Windows 映射元件概觀中所述。
第 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);
}
}
在此範例中,您只需分別從來源框架和目的地框架取得區塊讀取器和區塊寫入器。 然後,區塊寫入器會從區塊讀取器初始化。 這會使用區塊讀取器預先填入的元數據,初始化區塊寫入器。 若要了解複製元數據的其他方法,請參閱讀取和寫入影像元數據 概觀中的寫入元數據一節。
同樣地,只有在來源和目的地影像具有相同格式時,此作業才能運作。 這是因為不同的影像格式會將元數據區塊儲存在不同的位置。 例如,JPEG 和標記影像檔格式 (TIFF) 都支援可延伸元數據平臺 (XMP) 元數據區塊。 在 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 重新編碼範例程式碼
以下是第 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;
}
相關主題