メタデータを使用して JPEG イメージを再エンコードする方法
次の例では、イメージとそのメタデータを同じ形式の新しいファイルに再エンコードする方法を示します。 さらに、この例では、クエリ ライターによって使用される単一項目式を示すメタデータを追加します。
このトピックは、次のセクションで構成されています。
- 必要条件
- パート 1: イメージをデコードする
- パート 2: イメージ エンコーダーを作成して初期化する
- パート 3: デコードされたフレーム情報をコピーする
- パート 4: メタデータをコピーする
- パート 5: メタデータの追加
- パート 6: エンコードされたイメージの最終処理
- JPEG 再エンコードのサンプル コード
- 関連トピック
前提条件
このトピックを理解するには、「 WIC メタデータの概要」の説明に従って、Windows イメージング コンポーネント (WIC) メタデータ システムについて理解している必要があります。 また、「 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 が 4 番目のパラメーターとして使用されました。 これにより、クエリ リーダーを取得するか、基になるメタデータ リーダーを使用して、メタデータが必要になったときにメタデータをキャッシュするようにデコーダーに指示します。 このオプションを使用すると、高速メタデータ エンコードを実行するために必要なメタデータへのストリームを保持でき、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 画像には 1 つのフレームのみが含まれ、以下のループは技術的には必要ありませんが、それをサポートする形式のマルチフレームの使用方法を示すために含まれています。
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: メタデータをコピーする
注意
このセクションのコードは、ソースとコピー先のイメージの形式が同じ場合にのみ有効です。 別のイメージ形式にエンコードする場合、1 回の操作ですべてのイメージのメタデータをコピーすることはできません。
イメージを同じイメージ形式に再エンコードするときにメタデータを保持するために、すべてのメタデータを 1 回の操作でコピーできるメソッドがあります。 これらの各操作は、同様のパターンに従います。各では、デコードされたフレームのメタデータが、エンコードされる新しいフレームに直接設定されます。 これは、個々のイメージ フレームごとに行われることに注意してください。
メタデータをコピーするための推奨される方法は、デコードされたフレームのブロック リーダーを使用して、新しいフレームのブロック ライターを初期化することです。 次のコードは、このメソッドを示しています。
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 画像では、 WIC メタデータの概要に示すように、XMP ブロックはルート メタデータ ブロックにあります。 ただし、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 までの 1 つの 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;
}
関連トピック