第 11 章: Windows Imaging Component の使用
Windows 7 Imaging Component (WIC) は、画像とそのメタデータの読み込みと操作を可能にします。WIC アプリケーション プログラミング インターフェイス (API) では、すべての標準形式に対応する組み込みのコンポーネント サポートが提供されています。また、WIC で作成した画像を使用して Direct2D ビットマップを作成し、Direct2D で画像を変化させることができます。Windows 7 Imaging Component が、Hilo Browser および Annotator アプリケーションでどのように使われているかを説明します。
WIC の概要
Hilo Browser と Annotator はいずれも写真を表示する機能を持ち、Annotator では写真を編集することができます。Hilo における写真の定義にはあらゆる種類の画像が含まれるので、Hilo アプリケーションは幅広い種類のファイルを読み込んで表示できる必要があります。その機能を提供するのが、Windows 7 Imaging Component です。WIC は複数のフレームで構成される画像を読み込み、画像ファイル内のメタデータにアクセスすることができます。WIC は一般的な画像形式をすべてサポートしており、開発者が新しい形式のコーデック (コーダー デコーダー コンポーネント) を開発することもできます。
WIC を使用するには、wincodec.h ヘッダー ファイルをインクルードする必要があります。このファイルには、WIC で使用されるさまざまなインターフェイスの定義、WIC オブジェクトの構造体と GUID の定義、および標準のピクセル形式が含まれています。WIC は 1 つのコンポーネントではなく、サポートされるさまざまな形式をエンコードおよびデコードするための複数のコンポーネントで構成されます。画像形式が異なると、画像データの格納方法も異なるので、画像ファイルを読み込むには、データをアプリケーションで使用できる形式にデコードするデコーダー コンポーネントが必要になります。画像情報を保存するには、データを画像ファイル形式で定義されている形式にエンコードするエンコーダー コンポーネントが必要です。読み込む画像の種類がわかっている場合は、CoCreateInstanceEx を呼び出してデコーダーを作成し、デコーダー オブジェクトのクラス ID (CLSID) を指定できます。画像の種類がわからない場合は、WIC API を使用してファイルを調べ、適切なオブジェクトを選択することができます。これを行うために、WIC ファクトリ オブジェクトのインスタンスを作成します。
リスト 1 に、Hilo アプリケーションでファクトリ オブジェクトのインスタンスを作成するために使う Direct2DUtility::GetWICFactory メソッドを示します。ファクトリ オブジェクトは、他のすべての WIC オブジェクトと同じく COM オブジェクトであるため、このコードを COM アパートメントで呼び出す必要があります。STA または MTA のいずれかのアパートメントを初期化できます。
リスト 1 WIC ファクトリの作成
HRESULT Direct2DUtility::GetWICFactory(IWICImagingFactory** factory)
{
static ComPtr<IWICImagingFactory> m_pWICFactory;
HRESULT hr = S_OK;
if (nullptr == m_pWICFactory)
{
hr = CoCreateInstance(
CLSID_WICImagingFactory, nullptr,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pWICFactory) );
}
if (SUCCEEDED(hr))
{
hr = AssignToOutputPointer(factory, m_pWICFactory);
}
return hr;
}
IWICImagingFactory インターフェイスには、デコーダーとエンコーダー、ビットマップとストリームを作成するためのメソッドが含まれており、このインターフェイスによってカラー パレットや画像ファイル内のメタデータを変更するオブジェクトを作成できます。
画像の処理
概念的には、ビットマップ グラフィックスは、色のついたピクセルの列と考えることができます。各ピクセルの色は、カラー要素 (赤、青、緑またはシアン、マゼンタ、イエローなど) と、不透明度を決定するアルファ チャンネル要素で構成されます。これらの各要素が 256 個の値を持つ (ピクセル 1 つにつき 32 ビットを使用する) 場合、1000 × 1000 ピクセルの画像が 1 MB の記憶域を消費することになります。この程度の解像度の画像はごく一般的に使用されているため、記憶域を節約するため、ピクセルを圧縮した形式で表示し、画像サイズを小さくする必要があります。多くの画像では、似たピクセルが連続して現れ、また一部の色域しか使用されていないこともあります。そのため、こうした冗長性を減らすような画像圧縮の手法が開発されてきました。圧縮手法には、圧縮解除時にすべてのピクセルを元どおりに再現できる無損失圧縮と、人間の目には圧縮解除された画像が元の画像と同じように見えるものの、一部のピクセルの情報が失われる損失圧縮があります。
コーデックは画像を圧縮 (compress) および圧縮解除 (decompress) するコードであり、画像を圧縮するコードはエンコーダー、画像を圧縮解除するコードはデコーダーと呼ばれます。グラフィックス カードにも独自の画像形式があるので、コーデックは画像ファイルで使用される画像形式と Windows 7 で使用できる形式とを相互に変換できる必要があります。WIC デコーダーは、GDI 関数で使用できる RAW ピクセル情報を提供できます。さらに、Direct2D は (ID2D1Bitmap インターフェイスを使用して) WIC 画像からビットマップ オブジェクトを作成できるので 、WIC によって読み込まれた画像を 描画することができます。また、WIC 画像を基に Direct2D レンダー ターゲットを作成することもでき、これにより、Direct2D の描画アクションを使用した WIC 画像の編集が可能になります。
画像は複数の形式で提供できます。通常、画像はハード ディスク上のファイルであるため、WIC にはファイル名やファイル ハンドルから画像を読み込むためのメソッドが用意されています。画像は他のファイル (OLE ドキュメント) 内にストリームとして格納されている場合もあるので、WIC には OLE ストリームから読み込むためのメソッド (IStream インターフェイス) も用意されています。また、実行可能ファイルのリソースとして格納されている場合もあり、WIC API は HBITMAP または HICON データを介して画像を読み込むメソッドも提供しています。
多くの画像形式には、ピクセル情報に加えて、メタデータが含まれています。デジタル カメラでは、メタデータを使用して、カメラの設定情報 (ISO、シャッター速度、絞り、フラッシュ) および写真に関する情報 (時刻、日付、GPS データ) などを保存します。メタデータには標準的な形式がいくつかあり、WIC でサポートされている一般的な形式は Exif、XMP、IPTC の 3 つです。これらの形式では、メタデータを画像ファイル内に保存する方法が指定され、標準的なメタデータ項目も定義されています。特定の画像の種類で使用されるメタデータ形式の処理は、コーデックが行います。
GIF 画像や一部の TIFF 画像など、いくつかの画像形式では、ファイル内に複数の画像を保存することができます。多くのデジタル カメラでは、フル サイズの画像と共にサムネイル 画像も保存されます。WIC にはファイル内の各画像をフレームとして取得するメソッドがあり、グローバル サムネイルおよびフレームベースのサムネイルの読み込みをサポートしています。標準的なデコーダーはグローバル 画像データへのアクセスをサポートしていないので、画像ファイル内に 1 つしか画像が含まれていない場合でも、単一フレームとして読み込み、フレーム デコーダーを使用してそのフレームに関する情報を取得する必要があります。
ビットマップ ソースの使用
WIC API には、ビットマップ 画像へのアクセス提供するための、多態性を持つオブジェクトのコレクションが用意されています。これらのオブジェクトの基本インターフェイスは IWICBitmapSource であり、画像のピクセル形式、サイズ、および解像度を提供し、ピクセルのコピーを作成できるようにします。その他のインターフェイスは、画像のサイズ変更、ピクセル形式の変更、画像の回転などの機能を追加するオブジェクト用です。これらのインターフェイスを図 1 に示します。
図 1 WIC ビットマップ インターフェイス
このインターフェイス階層は、IWICBitmapSource インターフェイスが必要とされる場所で、他のすべてのインターフェイスのインスタンスを使用できることを示しています。よって、IWICBitmapSource を必要とする関数に、スケーラー オブジェクトで実装される IWICBitmapScaler インターフェイスを渡すことができます。関数がこのインターフェイスを介してビットマップに関する情報を読み取ると、スケーラー オブジェクトはこの情報に基づいて自動的に拡大縮小を実行します。図 1 に示されている子インターフェイスの多くは、IWICBitmapSource インターフェイスに、オブジェクトの動作に関する情報を提供するための Initialize メソッドを追加するものです。たとえば、IWICBitmapScaler インターフェイスの場合、Initialize メソッドにより、画像が拡大縮小されたときの新しい高さと幅が提供されます。
WIC による画像の読み込み
画像を読み込むには、IWICBitmapDecoder インターフェイスを実装するデコーダー オブジェクトを使用する必要があります。デコーダー オブジェクトは特定の画像形式のみをデコードするので、デコーダーを使用するには、適切な形式の画像を含むストリームを指定する必要があります。そのためには、IWICBitmapDecoder::Initialize メソッドを呼び出します。デコーダーが初期化されたら、IWICBitmapDecoder::GetFrameCount メソッドを呼び出して画像内のフレーム数を取得し、インデックスを指定して IWICBitmapDecoder::GetFrame メソッドを呼び出すことにより、デコードされた特定のフレームを取得できます。このフレームは、IWICBitmapFrameDecode インターフェイスを持つオブジェクトによって返されます。
IWICBitmapFrameDecode インターフェイスは IWICBitmapSource インターフェイスから派生したものであるため、画像に関する情報を取得するメソッドを含んでいます。たとえば、IWICBitmapSource::GetPixelFormat を使用してピクセル形式に関する情報 (「ネイティブ ピクセル形式の概要 (英語)」ページに示されている GUID の 1 つ) を取得した後、IWICBitmapSource::CopyPixels メソッドを呼び出して実際のピクセルを取得することもできます。また、IWICBitmapFrameDecode::GetMetadataQueryReader メソッドを呼び出して、IWICMetadataQueryReader インターフェイスによりメタデータ クエリ オブジェクトを取得し、このオブジェクトを介してフレームに関するメタデータを取得することもできます。
画像にはさまざまなピクセル形式があり、各カラー コンポーネントで使用されるビット数、カラー コンポーネントの順序、アルファ チャンネルの有無、パレットが使用されているかどうかなどによって決定されます。Direct2D では、ビットマップ ソースは 32bppPBGRA 形式である必要があります。つまり、各ピクセルは 4 バイトで構成され、1 バイトずつ青、緑、赤、およびアルファ チャンネルという配列になっています。Direct2D レンダー ターゲットに画像をレンダリングするには、画像をこの形式に変換し、ID2D1Bitmap インターフェイスを持つオブジェクトを通じて画像を作成します。
リスト 2 Direct2D ビットマップとしての画像の読み込み
ComPtr<IWICBitmapDecoder> decoder;
ComPtr<IWICBitmapFrameDecode> bitmapSource;
ComPtr<IWICFormatConverter> converter;
ComPtr<IWICImagingFactory> wicFactory;
GetWICFactory(&wicFactory);
wicFactory->CreateDecoderFromFilename(
uri, // ファイルの名前
nullptr, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &decoder);
decoder->GetFrame(0, &bitmapSource);
wicFactory->CreateFormatConverter(&converter);
converter->Initialize(
bitmapSource, GUID_WICPixelFormat32bppPBGRA,
WICBitmapDitherTypeNone, nullptr, 0.f,
WICBitmapPaletteTypeMedianCut);
// WIC ビットマップから Direct2D ビットマップを作成する。
hr = renderTarget->CreateBitmapFromWicBitmap(converter, nullptr, bitmap);
リスト 2 は、Hilo Common プロジェクトの Direct2DUtility::LoadBitmapFromFile メソッドのコードです。このメソッドは、Browser および Annotator アプリケーションで、画像ファイルを読み込むために使用されます。このコードは、最初に WIC ファクトリ オブジェクトを取得し、次に IWICImagingFactory::CreateDecoderFromFilename メソッドを呼び出して画像のデコーダーを読み込みます。ここで、uri 変数は画像ファイルのパスを示します。このメソッドは、画像の種類に合ったデコーダーをアクティブ化し、画像ファイルを読み込み、画像を使用してデコーダーを初期化します。したがって、このメソッドから返される IWICBitmapDecoder インターフェイスについて、Initialize メソッドを呼び出す必要はありません。リスト 2 のコードは、ビットマップの最初のフレームについて GetFrame を呼び出して、デコードされた画像を取得します。IWICBitmapFrameDecode インターフェイスは、画像から情報を読み取るときは常に、デコーダー オブジェクトを使用します。
この画像のピクセル形式はおそらく 32bppPBGRA 形式ではないため、リスト 2 ではコンバーター オブジェクトを作成しています。IWICFormatConverter インターフェイスは、IWICBitmapSource から派生したものであり、基本的に呼び出しをこのインターフェイスに委任して RAW 画像を取得し、必要に応じて変換を実行します。最後に、このコードは ID2D1RenderTarget::CreateBitmapFromWicBitmap メソッドを呼び出し、変換された IWICBitmapSource オブジェクトのデータを使用して、ID2D1Bitmap インターフェイスを持つオブジェクトを作成します。返された変数 (リスト 2 では、bitmap 変数) は ID2D1RenderTarget オブジェクト上にレンダリングすることができ、このビットマップから Direct2D レンダー ターゲットを作成し、Direct2D 描画操作を使用して画像を変更することが可能になります。Direct2D ビットマップ オブジェクトを呼び出すと、その呼び出しは形式コンバーターに渡され、形式コンバーターがフレーム デコーダーを呼び出し、フレーム デコーダーがデコーダー オブジェクトを呼び出します。これらの各段階で、データが変換されます。
WIC による画像の操作
リスト 2 では、WIC オブジェクトを作成して、画像から提供されるビットマップ データの形式を変更できることを示しましたが、同じ手法で画像のさまざまな要素を変更することもできます。たとえば、スケーラー オブジェクトを使用して自動的に画像のサイズを変更することが可能です。これを、Direct2DUtility::LoadBitmapFromFile メソッドの例で説明します。リスト 2 のコードは、拡大縮小を行わずに画像を読み込みます。リスト 3 では、拡大縮小を実行するための追加のコードが示されています。
リスト 3 拡大縮小の実行
ComPtr<IWICBitmapScaler> scaler;
if (destinationWidth != 0 || destinationHeight != 0)
{
unsigned int originalWidth, originalHeight;
bitmapSource->GetSize(&originalWidth, &originalHeight);
if (destinationWidth == 0)
{
float scaler = static_cast<float>(destinationHeight) / static_cast<float>(originalHeight);
destinationWidth = static_cast<unsigned int>(scaler * static_cast<float>(originalWidth));
}
else if (destinationHeight == 0)
{
float scaler = static_cast<float>(destinationWidth) / static_cast<float>(originalWidth);
destinationHeight = static_cast<unsigned int>(scaler * static_cast<float>(originalHeight));
}
wicFactory->CreateBitmapScaler(&scaler);
scaler->Initialize(
bitmapSource, destinationWidth, destinationHeight, WICBitmapInterpolationModeCubic);
converter->Initialize(
scaler, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone,
nullptr, 0.f, WICBitmapPaletteTypeMedianCut);
}
else // 画像を拡大縮小しない。
{
converter->Initialize(
bitmapSource, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone,
nullptr, 0.f, WICBitmapPaletteTypeMedianCut);
}
LoadBitmapFromFile メソッドの呼び出し元が、新しい幅または新しい高さを提供する場合、スケーラー オブジェクトが作成されます。どちらか一方のみが指定された場合、新しい画像は元の画像の縦横比を維持するように拡大縮小されます。縦横比を変更する場合は、新しい高さと新しい幅の両方を指定します。このコードは、新しい高さと幅を特定すると、IWICImagingFactory::CreateBitmapScaler メソッドを呼び出すことにより、WIC ファクトリ オブジェクトを介してスケーラー オブジェクトを作成します。このスケーラー オブジェクトは、IWICBitmapScaler::Initialize メソッドを呼び出し、ビットマップ ソース (ここではフレーム デコーダー) と拡大縮小パラメーターを指定することによって初期化されます。このメソッドの最後のパラメーターは拡大縮小の実行方法を指定します。この例ではバイキュービック補間が使用されています。最後に、ピクセル形式コンバーターが、フレーム デコーダーではなくスケーラーを使用して初期化され、画像にアクセスしたときの拡大縮小機能が追加されます。
WIC による画像の描画
WIC によって、拡大縮小、トリミング、回転、反転といった画像の基本的な操作を実行できます。より複雑な操作を実行する場合は、個々のピクセルを独自で変更する必要があります。Hilo では、WIC を使用して読み込んだ画像に基づいて Direct2D レンダー ターゲットを作成することにより、こうした変更を行います。画像の描画、トリミング、回転、または反転を行う場合、アクションは WIC ビットマップではなく、Direct2D レンダー ターゲット上で実行されます。Hilo では、変更を保存すると、元の画像のコピーが作成された後、変更された画像がディスクに保存されます。
Annotator は、起動時に、画像フォルダーと、そのフォルダー内の指定された写真を検索します。編集中の写真に関する情報は、SimpleImage クラスのインスタンス内に格納されます。このクラスは、シェル アイテムへの IShellItem 参照のインスタンスを保持し、元のファイルの場所を識別できるようにします。このクラスには、WIC が読み込んだオブジェクト (元の画像) に対する IWICBitmap 参照と、WIC ビットマップから変換された、変更の対象となる Direct2D への ID2D1Bitmap 参照が含まれます。
さらに、m_imageOperations および m_redoStack の 2 つのベクターがあります。これらのベクター内の各アイテムは、IImageOperation 型のインターフェイス ポインターであり、このインターフェイスを実装する 3 つのクラスとして、DrawGeometryOperation、ImageClippingOperation、および ImageTransformationOperation があります。変更可能な操作を画像に対して実行すると、これらのいずれかのクラスのインスタンスが作成され、操作の詳細情報を使用して初期化され、m_imageOperations ベクターに格納されるので、Annotator は実行された操作の一覧を取得できます。
画像を描画する際は、この情報が DrawGeometryOperation オブジェクトとして m_imageOperations ベクターに格納されます。このオブジェクトには、色とペン幅、および操作中にアクセスした点のベクターに関する情報が含まれます。画像を描画するには、位置情報を使用して、DrawGeometryOperation::UpdateGeometry メソッドで Direct2D ジオメトリ オブジェクトのインスタンスを作成し、各点間にベジェ セグメントを描画し、このベジェ曲線を ID2D1PathGeometry オブジェクトに追加します。
画面に表示されるのは、m_imageOperations ベクターの操作を、順に IWICBitmap オブジェクトに適用した結果です。元に戻すボタンをクリックすると、m_imageOperations ベクターから最後のアイテムが削除され、m_redoStack ベクターの先頭に配置されます。m_redoStack ベクターは別の操作を実行するとクリアされるため、元に戻した操作をやり直すことができるのは、操作を元に戻してから別の操作を実行するまでの間だけです。
Annotator ウィンドウのサイズ変更などのユーザー操作に対応して、画面に画像が描画される際は、SimpleImage::DrawImage メソッドが呼び出されます。このメソッドは、WIC に読み込まれた IWICBitmap 画像を変換した ID2D1Bitmap オブジェクトを、最初に画像 エディターのレンダー ターゲットに描画します。次に、DrawImage メソッドは各描画操作を反復処理し、各操作をレンダー ターゲットに適用します。ペンの描画操作は、ID2D1PathGeometry オブジェクトを使用して ID2D1RenderTarget::DrawGeometry メソッドを呼び出すことにより適用されます。
WIC による画像の保存
Hilo では、2 段階の手順で画像を保存します。最初に、画像エディターで実行した変更内容を含む WIC ビットマップを作成します。次に、WIC ビットマップをディスクに保存します。
WIC ビットマップの作成
画像を保存する際、コマンド ハンドラーは SimpleImage::Save メソッドを呼び出します。このメソッドの最初のアクションは、最終的にディスクに保存される WIC ビットマップを作成することです。リスト 4 に、この処理を行うコードを示します。このコードでは、最初に、トリミングされた画像の寸法と適用された回転に関する情報を基に新しい画像のサイズを決定します。次に、このサイズを使用して、IWICImagingFactory::CreateBitmap メソッドを呼び出します。WIC ビットマップは、使用される Direct2D ビットマップと同じピクセル形式 (GUID_WICPixelFormat32bppBGR) で作成されることに注目してください。
リスト 4 新しい WIC ビットマップの作成
ComPtr<IWICImagingFactory> wicFactory;
ComPtr<IWICBitmap> wicBitmap;
Direct2DUtility::GetWICFactory(&wicFactory);
// 現在の方向とクリッピング四角形に基づいて高さと幅を調整する
float width = m_isHorizontal ?
Direct2DUtility::GetRectWidth(m_clipRect) : Direct2DUtility::GetRectHeight(m_clipRect);
float height = m_isHorizontal ?
Direct2DUtility::GetRectHeight(m_clipRect) : Direct2DUtility::GetRectWidth(m_clipRect);
wicFactory->CreateBitmap(
static_cast<unsigned int>(width), static_cast<unsigned int>(height),
GUID_WICPixelFormat32bppBGR,
WICBitmapCacheOnLoad, &wicBitmap);
リスト 4 で作成されたビットマップは空であるため、SimpleImage::Save メソッドはここで元の画像と描画操作からビットマップを作成する必要があります。最初の手順は、WIC ビットマップに基づいてレンダー ターゲットを作成した後、レンダー ターゲットに元の画像を読み込むことです。これをリスト 5 に示します。最初にこのコードは ID2D1Factory::CreateWicBitmapRenderTarget メソッドを呼び出し、リスト 4 で作成された WIC ビットマップを渡します。返されるレンダー ターゲットは他のレンダー ターゲットと同様であり、Direct2D リソースを使用して描画することが可能です。異なるのは、変更されるピクセルが、画面ではなく、WIC ビットマップにあるという点です。リスト 5 の最後の部分では、元の画像ファイルをレンダー ターゲットに読み込んで、描画操作がこの画像に適用されるようにします。
リスト 5 WIC ビットマップに基づくレンダー ターゲットの作成
ComPtr<ID2D1Factory> d2dFactory;
ComPtr<ID2D1RenderTarget> wicRenderTarget;
Direct2DUtility::GetD2DFactory(&d2dFactory);
d2dFactory->CreateWicBitmapRenderTarget(
wicBitmap, D2D1::RenderTargetProperties(), &wicRenderTarget);
Direct2DUtility::LoadBitmapFromFile(
wicRenderTarget, m_imageInfo.fileName.c_str(), 0, 0, &m_bitmap);
リスト 6 に示す SimpleImage::Save メソッドの次の部分で、m_imageOperations ベクターに格納されていた描画操作を実行します。最初の部分では変換を作成し、画像に対して実行された回転がトリミングされた画像の中心に適用されるようにします。リスト 6 で重要なコードは最後の部分です。Save メソッドはインスタンス変数 m_currentRenderTarget を WIC ビットマップに基づくレンダー ターゲットに変更し、SimpleImage::DrawImage メソッドを呼び出します。これにより、描画操作が WIC レンダー ターゲットと WIC ビットマップの両方に適用されます。
リスト 6 WIC ビットマップへの描画操作の適用
// 現在のトリミングについて元のビットマップ四角形を取得する
D2D1_RECT_F originalBitmapRect = D2D1::RectF(
0, 0, Direct2DUtility::GetRectWidth(m_clipRect),
Direct2DUtility::GetRectHeight(m_clipRect));
// 画像を回転させる場合は、回転の中心点と回転させるレンダー ターゲットの中心を
// 必ず一致させる
if (false == m_isHorizontal)
{
float offsetX;
float offsetY;
if (width > height)
{
offsetX = (width - height) / 2;
offsetY = -offsetX;
}
else
{
offsetY = (height - width) / 2;
offsetX = - offsetY;
}
D2D1_MATRIX_3X2_F translation = D2D1::Matrix3x2F::Translation(offsetX, offsetY);
wicRenderTarget->SetTransform(translation);
}
// WIC のレンダー ターゲットを指すように、現在のレンダー ターゲットを更新する
m_currentRenderTarget = wicRenderTarget;
// 更新された画像を WIC レンダー ターゲットに描画する
wicRenderTarget->BeginDraw();
DrawImage(originalBitmapRect, m_clipRect, true);
wicRenderTarget->EndDraw();
Save メソッドの残りの部分では、元のファイルのバックアップ コピーを、AnnotatorBackup という名前のサブフォルダー内に作成し、Direct2DUtility::SaveBitmapToFile メソッドを呼び出して WIC ビットマップを保存します。
WIC エンコーダーの使用
画像データの書き込みは、画像データの読み取りに比べてやや複雑です。データを適切な形式でファイルに書き込むにはエンコーダーが必要ですが、大量のデータを書き込む可能性を考慮して、ピクセルおよびコピーする必要があるすべてのメタデータの両方について、データのブロック転送を使用するようにエンコーダーを初期化する必要があります。
エンコードの開始点として IWICBitmapEncoder インターフェイスを使用します。WIC API は一般的なすべてのファイル形式用の実装を提供します。エンコーダーを作成するには、必要なエンコーダーを識別する GUID を渡して IWICImagingFactory::CreateEncoder を呼び出します。IWICBitmapEncoder::Initialize メソッドの呼び出しによってエンコーダーを初期化し、データが保存されるファイルにストリームを提供します。これで、エンコーダーでさまざまな Set メソッドを呼び出して、パレットや画像のサムネイルなどのデータを書き込むことができるようになります。エンコーダーへの書き込みが完了し、ストリームおよび対象のファイルを閉じるときは、IWICBitmapEncoder::Commit メソッドを呼び出す必要があります。
実際のピクセル データは 1 つまたは複数のフレーム オブジェクトとして提供されるため、各フレームについて IWICBitmapEncoder::CreateNewFrame を呼び出して、特定のフレームの IWICBitmapFrameEncode インターフェイスを取得する必要があります。このインターフェイスには、画像のサイズ、解像度、ピクセル形式などのフレームのデータを設定する一連の Set メソッドが含まれています。実際のピクセル データを書き込むには、IWICBitmapFrameEncode::WritePixels メソッド (個々の走査線をバイトのバッファーと共に書き込む場合) または IWICBitmapFrameEncode::WriteSource メソッド (IWICBitmapSource オブジェクトからフレーム全体を書き込む場合) を呼び出します。フレームの書き込みが完了したら、IWICBitmapFrameEncode::Commit メソッドを呼び出して、ストリームへのフレームの書き込みが可能であることを示す必要があります。
前述のようにフレームにメタデータが含まれている場合は、 IWICBitmapFrameEncode::GetMetadataQueryWriter メソッドを呼び出すことにより、IWICMetadataQueryWriter オブジェクトを取得できます。ただし、既にメタデータを含むビットマップ ソース オブジェクトがある場合は、メタデータ ブロック リーダーおよびライターを使用して、メタデータをブロックとしてコピーできます。フレーム デコーダーは、IWICMetadataBlockReader インターフェイスを介して、ブロック メタデータの読み取りをサポートします。通常、このインターフェイスは直接使用するのではなく、メタデータ ブロック ライターを初期化するために使用します。フレーム エンコーダーは IWICMetadataBlockWriter インターフェイスを実装しており、IWICMetadataBlockWriter::InitializeFromBlockReader メソッドを呼び出してブロック リーダー オブジェクトを渡すことにより、ブロック ライターを初期化することができます。このメソッドは、フレームがコミットされるときに、すべてのメタデータを読み取って、メタデータを画像ファイル ストリームに書き込みます。
Annotator での WIC ビットマップの保存
Hilo の Common プロジェクトでは、エンコーダーを取得し、画像をディスクに保存する Direct2DUtility::SaveBitmapToFile メソッドが提供されます。このメソッドの最初の部分では、作成するファイルに関する情報を取得する必要があるため、まず IWICImagingFactory::CreateDecoderFromFilename を呼び出して元のファイルのデコーダーを取得します。このオブジェクトを通じて、SaveBitmapToFile メソッドは元の画像ファイルに含まれているフレーム数を特定することができます。Annotator で編集できるのは最初のフレームのみですが、画像ファイルには複数のフレームが含まれている場合があります。そのため、編集したファイルを保存すると、最初のフレームが Annotator で編集した画像になり、他のすべてのフレームは元のファイルから単純にコピーされます。
このメソッドの次の部分ではエンコーダーを作成します。このコードをリスト 7 に示します。ファクトリ オブジェクトを使用してエンコーダーを作成するには、エンコーダーの種類を指定する必要があり、SaveBitmapToFile メソッドはファイル拡張子からこの種類を特定します。
リスト 7 エンコーダーの作成
GUID containerFormat = GUID_ContainerFormatBmp;
ComPtr<IWICImagingFactory> factory;
ComPtr<IWICBitmapEncoder> encoder;
Direct2DUtility::GetWICFactory(&factory);
// 出力ファイルに使用するコンテナー形式を決定するファイル拡張子
std::wstring fileExtension(outputFilePath.substr(outputFilePath.find_last_of('.')));
// すべての文字を小文字に変換する
std::transform(fileExtension.begin(), fileExtension.end(), fileExtension.begin (), tolower);
// 既定値はビットマップ エンコード
if (fileExtension.compare(L".jpg") == 0 ||
fileExtension.compare(L".jpeg") == 0 ||
fileExtension.compare(L".jpe") == 0 ||
fileExtension.compare(L".jfif") == 0)
{
containerFormat = GUID_ContainerFormatJpeg;
}
else if (fileExtension.compare(L".tif") == 0 ||
fileExtension.compare(L".tiff") == 0)
{
containerFormat = GUID_ContainerFormatTiff;
}
// 他のコードは省略
factory->CreateEncoder(containerFormat, nullptr, &encoder);
エンコーダーを作成した後、データの書き込み先を指定するストリームを使用してエンコーダーを初期化します。リスト 8 に、この処理を行うコードを示します。ファクトリ オブジェクトを使用してストリームを作成し、出力ファイルへのパスを指定してストリームを初期化します。次に、このストリームを使用してエンコーダーオブジェクトを初期化します。
リスト 8 エンコーダーの初期化
ComPtr<IWICStream> stream;
// エンコーダー用のストリームを作成する
factory->CreateStream(&stream);
// 出力ファイルのパスを使用してストリームを初期化する
stream->InitializeFromFilename(outputFilePath.c_str(), GENERIC_WRITE);
// 画像ファイルに書き込むためのエンコーダーを作成する
encoder->Initialize(stream, WICBitmapEncoderNoCache);
この時点で 3 つのオブジェクトが存在します。元のデータを含むデコーダー、変更されたビットマップを含む WIC ビットマップ、およびデータの保存先を示すストリームを使用して初期化されたエンコーダーです。SaveBitmapToFile メソッドの最後の部分は、WIC ビットマップを最初のフレームとしてエンコーダーに書き込むためのものであり、元のファイルに複数のフレームが含まれている場合、他のすべてのフレームについては、デコーダーから読み取られたものがエンコーダーに書き込まれます。SaveBitmapToFile メソッドがフレームを書き込むときには、そのフレームのメタデータを書き込む必要があり、そのための最も簡単な方法はメタデータ ブロック リーダーおよびライターを使用することです。
リスト 9 に、フレームを書き込むループの最初の部分を示します。このコードの最初の部分で、指定されたフレームのフレーム デコーダーを取得し、エンコーダーで新しいフレームを作成します。次に、新しいフレームのサイズを設定します。最初のフレームの場合は、WIC ビットマップからサイズが取得されます。それ以外の場合、元のファイルのデコーダーから読み取られたフレームのサイズが取得されます。次に、このコードは、元のファイルのフレームのピクセル形式を読み取り、これを使用してフレーム エンコーダーを設定します。
リスト 9 エンコーダーを使用したフレームの作成
ComPtr<IWICMetadataBlockWriter> blockWriter;
ComPtr<IWICMetadataBlockReader> blockReader;
for (unsigned int i = 0; i < frameCount && SUCCEEDED(hr); i++)
{
// フレーム変数
ComPtr<IWICBitmapFrameDecode> frameDecode;
ComPtr<IWICBitmapFrameEncode> frameEncode;
// 画像のフレームを取得および作成する
decoder->GetFrame(i, &frameDecode);
encoder->CreateNewFrame(&frameEncode, nullptr);
// エンコーダーを初期化する
frameEncode->Initialize(NULL);
// サイズを取得および設定する
if (i == 0)
{
updatedBitmap->GetSize(&width, &height);
}
else
{
frameDecode->GetSize(&width, &height);
}
frameEncode->SetSize(width, height);
// ピクセル形式を設定する
frameDecode->GetPixelFormat(&pixelFormat);
frameEncode->SetPixelFormat(&pixelFormat);
ループの最後の部分では、リスト 10 に示されているように、フレームのデータを読み取り、エンコーダーを介してデータを書き込みます。最初に、このコードはデコーダーからメタデータ ブロック リーダーを取得し、これを使用してエンコーダーからメタデータ ブロック ライターを初期化し、フレームのメタデータを 1 つのアクションで書き込めるようにします。その後、IWICBitmapFrameEncode::WriteSource を呼び出して、WIC ビットマップまたはフレーム デコーダー オブジェクトを渡すことにより、フレームのピクセル データが 1 つのアクションで書き込まれます。ループの最後の部分はフレームをコミットするためのものです。すべてのフレームが書き込まれた時点で、IWICBitmapEncoder::Commit を呼び出して画像全体をファイルに書き込みます。
リスト 10 エンコーダーによるフレームの書き込み
// 保存先の形式と元の形式が同じであることを確認する
bool formatsEqual = false;
GUID srcFormat;
GUID destFormat;
decoder->GetContainerFormat(&srcFormat);
encoder->GetContainerFormat(&destFormat);
formatsEqual = (srcFormat == destFormat) ? true : false;
if (formatsEqual)
{
// メタデータ ブロック リーダー/ライターを使用してメタデータをコピーする
frameDecode->QueryInterface(&blockReader);
frameEncode->QueryInterface(&blockWriter);
if (nullptr != blockReader && nullptr != blockWriter)
{
blockWriter->InitializeFromBlockReader(blockReader);
}
}
if (i == 0)
{
// 更新されたビットマップを出力にコピーする
frameEncode->WriteSource(updatedBitmap, nullptr);
}
else
{
// 既存の画像を出力にコピーする
frameEncode->WriteSource(static_cast<IWICBitmapSource*> (frameDecode), nullptr);
}
// フレームをコミットする
frameEncode->Commit();
}
encoder->Commit();
まとめ
Windows 7 Imaging Component を使用すると、ファイル、リソース、およびストリームから標準的な画像形式の画像を読み込むことができます。WIC は、画像データを GDI または Direct2D で使用できる形式にデコードします。Hilo では、Browser アプリケーションと Annotator アプリケーションの両方で、画像の表示に WIC が使用され、Annotator アプリケーションでは写真の編集と保存にも使用されます。次の章では、オンラインでの画像の共有を実現する Hilo Share アプリケーションを紹介します。