メタファイル
手順の記録
これまで、パレットやフォントなどビットマップに関連したグラフィックスを説明しました
ビットマップはピクセルごとの情報を格納したグラフィックスの高度な技術ですが
この他に、現代ではあまり目立ちませんがメタファイルというものもあります
ビットマップはディスプレイやプリンタを対象としたラスタデバイスであることがわかると思います
逆に、メタファイルとはプロッタを対象としたベクタデバイスという位置付けです
つまり、ピクセルごとのビットマップと違い、メタファイルは図形の手順を記します
まず、この章では Win16 時代の古いメタファイルについて説明します
ここで紹介するプログラムの方法は、今はもう推奨されない方法ですが
現在使われている拡張関数よりも単純なので、覚えやすいでしょう
メタファイルは HMETAFILE 型で表される GDI オブジェクトの一つです
メタファイルの生成には、最初に専用のデバイスコンテキストを作成し
このデバイスコンテキストに、いつもの様に GDI 関数を使って描画します
ただし、これはメタファイルに手順が記録されるだけでディスプレイなどには描画されません
メタファイルを作るには、最初に CreateMetaFile() 関数を使います
HDC CreateMetaFile(LPCTSTR lpszFile);
lpszFile にはメタファイルを記録するディスクファイル名を指定します
この引数に NULL を指定すれば、メタファイルはメモリ上に作成されます
関数が成功すればデバイスコンテキストのハンドルが、失敗すれば NULL が返ります
この関数が返したデバイスコンテキストに描画されれば
それらは物理デバイスに描画されることはなく、手順が記録されます
ディスクファイルを使った方法は後記するとして、今は引数に NULL を指定して
メモリにメタファイルを作成する方法をピックアップしましょう
メタファイルを閉じるには CloseMetaFile() 関数を使います
HMETAFILE CloseMetaFile(HDC hdc);
hdc にはデバイスコンテキストのハンドルを指定します
この値は CreateMetaFile() 関数が返したものでなければなりません
関数が成功すればメタファイルのハンドル、失敗すれば NULL が返ります
このメタファイルのハンドルさえ持っていれば、いつでも手順を再生できます
「手順の再生」であり、ビットマップの様に画像を保存しているわけではありません
メタファイルを再生するには PlayMetaFile() 関数を用います
BOOL PlayMetaFile(HDC hdc , HMETAFILE hmf);
hdc にはデバイスコンテキストのハンドルを
hmf にはメタファイルのハンドルを指定します
関数が成功すれば 0 以外、失敗すれば 0 が返ります
その後、メタファイルが不用になれば削除する必要があります
メタファイルをメモリから削除する時は DeleteMetaFile() を使います
BOOL DeleteMetaFile(HMETAFILE hmf);
hmf には破棄するメタファイルのハンドルを指定します
成功すれば 0 以外、失敗すれば 0 が返ります
#include <windows.h>
#define TITLE TEXT("Kitty on your lap")
LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
HDC hdc;
PAINTSTRUCT ps;
static HMETAFILE hMeta;
switch (msg) {
case WM_DESTROY:
DeleteMetaFile(hMeta);
PostQuitMessage(0);
return 0;
case WM_CREATE:
hdc = CreateMetaFile(NULL);
TextOut(hdc , 0 , 0 , TITLE , lstrlen(TITLE));
hMeta = CloseMetaFile(hdc);
case WM_PAINT:
hdc = BeginPaint(hWnd , &ps);
PlayMetaFile(hdc , hMeta);
EndPaint(hWnd , &ps);
return 0;
}
return DefWindowProc(hWnd , msg , wp , lp);
}
int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
PSTR lpCmdLine , int nCmdShow) {
HWND hWnd;
MSG msg;
WNDCLASS winc;
winc.style = CS_HREDRAW | CS_VREDRAW;
winc.lpfnWndProc = WndProc;
winc.cbClsExtra = winc.cbWndExtra = 0;
winc.hInstance = hInstance;
winc.hIcon = LoadIcon(NULL , IDI_APPLICATION);
winc.hCursor = LoadCursor(NULL , IDC_ARROW);
winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
winc.lpszMenuName = NULL;
winc.lpszClassName = TEXT("KITTY");
if (!RegisterClass(&winc)) return 1;
hWnd = CreateWindow(
TEXT("KITTY") , TITLE ,
WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
CW_USEDEFAULT , CW_USEDEFAULT ,
CW_USEDEFAULT , CW_USEDEFAULT ,
NULL , NULL , hInstance , NULL
);
if (hWnd == NULL) return 1;
while (GetMessage(&msg , NULL , 0 , 0 )) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
このプログラムは、ウィンドウのクライアント領域に文字列を出力するだけの
単純なプログラムですが、その手順は WM_PAINT ではなく WM_CREATE で作成され
WM_PAINT でそのメタフィアルを再生しています
メタファイルは、ビットマップ同様に補助記録装置に保存することが可能です
しかも、方法はビットマップよりも簡単で、CreateMetaFile() 関数で
メタファイルを保存するファイル名を指定するだけというお手軽さです
まず、メタファイルをディスクに作成してみましょう
次のような、ごく簡単なプログラムを用意してみました
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
PSTR lpCmdLine , int nCmdShow) {
HDC hdc;
if (lpCmdLine[0] == 0) {
MessageBox(NULL ,
TEXT("ファイル名を指定してください") , NULL , MB_OK);
return 1;
}
hdc = CreateMetaFile(lpCmdLine);
Ellipse(hdc , 0 , 0 , 200 , 100);
LineTo(hdc , 200 , 100);
CloseMetaFile(hdc);
return 0;
}
コマンドライン引数から、メタファイルの名前を指定します
すると、指定したパスに指定した名前のメタファイルが生成されたと思います
メタファイルの場合、ファイルの拡張子にこだわることはありませんが
*.WMF(Windows-format Meta File) と呼ばれることがあります
次に、以下のようなプログラムをつくってそのメタファイルを再生してみましょう
ディスクからメタファイルを取得するには、バイナリレベルで操作する必要はありません
(もちろん、バイナリレベルで取得して自分で解析したい方はご自由に…)
メタファイルの取得は GetMetaFile() 関数を使用します
HMETAFILE GetMetaFile(LPCTSTR lpszMetaFile);
lpszMetaFile には、読み込むメタファイルの名前を指定します
関数が成功すればメタファイルのハンドル、失敗すれば NULL が返ります
#include <windows.h>
#define TITLE TEXT("Kitty on your lap")
PSTR strFileName;
LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
HDC hdc;
PAINTSTRUCT ps;
HMETAFILE hMeta;
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
hdc = BeginPaint(hWnd , &ps);
if ((hMeta = GetMetaFile(strFileName))) {
PlayMetaFile(hdc , hMeta);
DeleteMetaFile(hMeta);
}
EndPaint(hWnd , &ps);
return 0;
}
return DefWindowProc(hWnd , msg , wp , lp);
}
int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
PSTR lpCmdLine , int nCmdShow) {
HWND hWnd;
MSG msg;
WNDCLASS winc;
strFileName = lpCmdLine;
winc.style = CS_HREDRAW | CS_VREDRAW;
winc.lpfnWndProc = WndProc;
winc.cbClsExtra = winc.cbWndExtra = 0;
winc.hInstance = hInstance;
winc.hIcon = LoadIcon(NULL , IDI_APPLICATION);
winc.hCursor = LoadCursor(NULL , IDC_ARROW);
winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
winc.lpszMenuName = NULL;
winc.lpszClassName = TEXT("KITTY");
if (!RegisterClass(&winc)) return 1;
hWnd = CreateWindow(
TEXT("KITTY") , TITLE ,
WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
CW_USEDEFAULT , CW_USEDEFAULT ,
CW_USEDEFAULT , CW_USEDEFAULT ,
NULL , NULL , hInstance , NULL
);
if (hWnd == NULL) return 1;
while (GetMessage(&msg , NULL , 0 , 0 )) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
最初の、メタファイルを作成するだけの小さなプログラムが作り出したファイルを
GetMetaFile() 関数を用いて読み込み、メタファイルのハンドルを取得して描画しています
メタファイルは手順を数値で表しているだけのバイナリファイルです
つまり、図形の情報を保存する場合はビットマップよりもはるかに効率的です
しかし、多くの場合ディスクに退避させるならばビットマップを使います
そうすることで、他社製のアプリケーションなどでも画像を扱えるからです
メタファイルのディスクへの保存の最大の理由は、メモリの節約にあるでしょう
巨大なメタファイルになれば、ディスクに一時保存したほうがメモリを有効に使えます
メタファイルと表示の問題点
メタファイルを再生するプログラムが、メタファイルを作っている場合は問題はないのですが
あるプログラムで作られたメタファイルを、異なるプログラムで表示する場合
マッピングモードなど、描画手順と座標だけでは情報が足りないことに気がつきます
外部に保存されているファイルのフォーマットについては今後触れるとして
最大の問題は、直接データを情報ヘッダ無しで渡すクリップボードでの転送です
別のプログラムで作られたプログラムは MM_TEXT 以外の論理単位かもしれません
だとすれば、その座標数値を MM_TEXT で実行すれば結果は間違ったものになります
そこで、マッピングモードと(論理的な)メタファイルのサイズを追加した
METAFILEPICT 構造体を用い、これをクリップボードに渡します
typedef struct tagMETAFILEPICT { // mfp
LONG mm;
LONG xExt;
LONG yExt;
HMETAFILE hMF;
} METAFILEPICT;
mm はマッピングモード、xExt は横幅、yExt は高さを表すメンバです
hMF には、転送目的のメタファイルのハンドルです
受け取り側は、単純にマッピングモードに mm を設定し
指定された幅と高さの情報を元にメタファイルを再生すれば良いのですが
実際はそう単純でもなく、デバイスコンテキストの属性によって結果が異なってしまいます
メタファイルを転送する側は、最初にウィンドウの原点の変更や
デバイスコンテキストの属性の設定などを意識して行う必要があります
異なるプログラムで実行される時、設定は保障されるものではありません
グラフィックスというレベルでは、メタファイルはあまり使えるものではありません
ネットワークで使えるものでもありませんし、他のコンピュータでも使えないどころか
他のプログラムと共有することも、ビットマップに比べてマイナーで不便です
しかし、例えばオーナー描画のコントロールや独自ウィンドウで
ユーザーインターフェイスを独自のデザインで描画するプログラムの場合
プログラムで直接描くよりは、メタファイルを読み込んで再生することによって
いわゆる「スキン」などに対応した柔軟な UI を実現することができるでしょう
CreateMetaFile()
HDC CreateMetaFile(LPCTSTR lpszFile);
メタファイルのデバイスコンテキストを作成します
lpsz - メタファイルのファイル名を指定します。メモリ保存の場合は NULL
戻り値 - デバイスコンテキストのハンドル。失敗すると NULL
CloseMetaFile()
HMETAFILE CloseMetaFile(HDC hdc);
メタファイルを閉じますデバイスコンテキストを解放します
同時に、ウィンドウズ-フォーマットメタファイルのハンドルを返します
hdc - デバイスコンテキストのハンドルを指定します
戻り値 - メタファイルのハンドル。失敗すると NULL
PlayMetaFile()
BOOL PlayMetaFile(HDC hdc , HMETAFILE hmf);
メタファイルを再生します
hdc - メタファイルの再生対象となるデバイスコンテキストを指定します
hmf - 再生するメタファイルを指定します
戻り値 - 成功すれば 0 以外。失敗すれば 0
DeleteMetaFile()
BOOL DeleteMetaFile(HMETAFILE hmf);
メタファイルを解放します
hmf - メタファイルのハンドルを指定します
戻り値 - 成功すれば 0 以外。失敗すれば 0
GetMetaFile()
HMETAFILE GetMetaFile(LPCTSTR lpszMetaFile);
指定したファイル名のメタファイルを読み込んでハンドルを返します
lpszMetaFile - 読み込むメタファイルが格納されたファイル名を指定します
戻り値 - メタファイルのハンドル。失敗すれば NULL