拡張メタファイル


Win32 のメタファイル

旧式のメタファイルはプログラム間で相互に図形の情報を渡すデータとしては
あまりにもデバイスの属性に依存しているので、使うのは難しいものです

そこで、Win32 では新たに拡張メタファイルを定義しました
このメタファイルは *.EMF(Enhanced MetaFile) という拡張子を持ち
ヘッダ情報を保有するため、メタファイルを描画するプログラムに情報を与えられます

拡張メタファイルを使うには CreateEnhMetaFile() を使います
これは、CreateMetaFile() 同様にメタファイル用のデバイスコンテキストを返します
HDC CreateEnhMetaFile(
	HDC hdcRef , LPCTSTR lpFilename ,
	CONST RECT *lpRect , LPCTSTR lpDescription
);
hdc には参照するデバイスコンテキストのハンドルを指定します
NULL を指定すれば現在の表示デバイスが参照されます

lpFilename は補助記録装置に保存する場合はファイル名を指定し
メモリベースで作成する場合は NULL を指定します
lpRect は画像の寸法を 0.01 ミリ単位で指定した RECT 構造体のポインタを指定します
この引数を NULL にすると、拡張メタファイルの最小の長方形が計算されます

lpDescription には拡張メタファイルを作成したアプリケーション名と
メタファイルの表題となる文字列をヌル文字で区切って指定します
文字列の最後は連続した2つのヌル文字で表します
関数が成功すれば、拡張メタファイル用のデバイスコンテキストのハンドルが
失敗すれば NULL が返ります

拡張メタファイルのハンドルを得るには CloseEnhMetaFile() を使います
同時に、拡張メタファイル用のデバイスコンテキストを解除します

HENHMETAFILE CloseEnhMetaFile(HDC hdc);

hdc にはデバイスコンテキストのハンドルを指定します
関数が成功すれば拡張メタファイルのハンドル、失敗すれば NULL が返ります

このメタファイルを再生するには PlayEnhMetaFile() を使います
この関数も、古いメタファイルよりも柔軟に情報を指定できます

BOOL PlayEnhMetaFile(HDC hdc , HENHMETAFILE hemf , CONST RECT *lpRect);

hdc には出力デバイスとなるデバイスコンテキストのハンドルを
hemf には拡張メタファイルのハンドルを指定します
lpRect には境界長方形の論理座標が格納された RECT 構造体へのポインタを指定します
この長方形に合わせてメタファイルが再生されます
関数が成功すれば 0 以外、失敗すれば 0 が返ります

そして、必要のなくなった格調メタファイルを削除するには
DeleteEnhMetaFile() 関数を用いてメモリから解放します

BOOL DeleteEnhMetaFile(HENHMETAFILE hemf);

hemf には拡張メタファイルのハンドルを指定します
関数が成功すると 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;
	RECT rect;
	static HENHMETAFILE hMeta;
	
	switch (msg) {
	case WM_DESTROY:
		DeleteEnhMetaFile(hMeta);
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		hdc = CreateEnhMetaFile(NULL , NULL , NULL , NULL);
		Ellipse(hdc , 0 , 0 , 200 , 100);
		hMeta = CloseEnhMetaFile(hdc);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);
		GetClientRect(hWnd , &rect);
		PlayEnhMetaFile(hdc , hMeta , &rect);
		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;
}
このプログラムを実行すると、クライアント領域のサイズに合わせて Ellipse() の円が描画されます
Ellipse() 関数で論理座標を定めているにもかかわらず、です

なぜならば、まず CreateEnhMetaFile() 関数で
拡張メタファイルの長方形をあらわす引数を NULL にしています
これによって、Windows は拡張メタファイルをの内容から最小の長方形を選択します
つまり、200 × 100 の長方形がこの拡張メタファイルのサイズです

そして、拡張メタファイルは再生時に拡大縮小ができるため
PlayEnhMetaFile() でクライアント領域のサイズに拡大しています
そのため、Ellipse() で指定した論理座標に関係なく描画されていたのです

メタファイルを補助記録装置に格納した場合も、旧式メタファイル同様に
GetEnhMetaFile() を使って読み込むことができます

HENHMETAFILE GetEnhMetaFile(LPCTSTR lpszMetaFile);

lpszMetaFile にはメタファイルのファイル名を指定します
成功すれば拡張メタファイルのハンドルが、失敗すれば NULL が返ります

拡張メタファイルは、ただ単純にプログラムがメタファイルをディスクに保存するだけではなく
この拡張メタファイルを他のプログラムでも使えるように、情報ヘッダが含まれています
この情報ヘッダを得るには GetEnhMetaFileHeader() を使います
UINT GetEnhMetaFileHeader(
	HENHMETAFILE hemf,
	UINT cbBuffer , LPENHMETAHEADER lpemh
);
hemf には拡張メタファイルのハンドルを指定します
cbBuffer には lpemh で指定するバッファのサイズを
lpemh には、ENHMETAHEADER 構造体へのポインタを指定します
この構造体にヘッダレコードが格納されます

最後の引数に NULL を指定すれば、ヘッダレコードのサイズが返ります
それ以外で、関数が成功すれば格納されたバイト数、失敗すれば 0 が返ります

拡張メタファイルのヘッダは ENHMETAHEADER 構造体で表されます
この構造体は次のように定義されています
typedef struct tagENHMETAHEADER { // enmh 
    DWORD iType; 
    DWORD nSize; 
    RECTL rclBounds; 
    RECTL rclFrame; 
    DWORD dSignature; 
    DWORD nVersion; 
    DWORD nBytes; 
    DWORD nRecords; 
    WORD  nHandles; 
    WORD  sReserved; 
    DWORD nDescription; 
    DWORD offDescription; 
    DWORD nPalEntries; 
    SIZEL szlDevice; 
    SIZEL szlMillimeters;
    DWORD cbPixelFormat;
    DWORD offPixelFormat;
    DWORD bOpenGL;
} ENHMETAHEADER;
iType は、レコードタイプを表すメンバで常に EMR_HEADER を示します
nSize は、この構造体のサイズをバイト単位で表します
rclBounds は、メタファイルの描画に必要な長方形を論理単位で表す RECTL 構造体
rclFrame は、rclBounds を 0.01 ミリ単位で表した RECTL 構造体です
RECTL 構造体は RECT 構造体とサイズ、メンバともにまったく同じです

dSignature は、常に ENHMETA_SIGNATURE という定数でなければなりません
この定数は "EMF" というキャラクタを表すダブルワードの数値になっています
nVersion はメタファイルのバージョンを表すもので、いまは常に 0x10000 を指定します

nBytes は拡張メタファイルのバイト単位のサイズ
nRecords は拡張メタファイルのレコード数を表しています
レコードとは、すなわち GDI 関数とヘッダ、そしてメタファイルの終了を表すデータの塊です

nHangles は拡張メタファイルの内部で非デフォルトのハンドルが用いられた時の
ハンドルテーブルが持つハンドルの数を表しています
sReserved は予約済みのメンバで、常に 0 です

nDescription は、拡張メタファイルの説明文が入っている配列の文字数です
この値が 0 を表すとき、記述が存在しないことを意味します
offDescription は、ENHMETAHEADER 構造体の先頭から数えて
拡張メタファイルの説明文が入っている配列までのオフセットを意味します
この値が 0 の時は、記述が存在しないことを表しています

nPalEntries は、拡張メタファイルのパレットエントリ数を意味します
szlDevice は、ピクセル単位で参照デバイスの解像度を表し
szlMillimeters は、参照デバイスこ解像度をミリ単位で表した数値です
SIZEL 構造体は SIZE 構造体と同じです

cbPixelFormat はピクセルフォーマットのサイズを
offPixelFOrmat はピクセルフォーマットのオフセットを表しています
bOpenGL は、OpenGL レコードが含まれていれば TURE、そうでなければ FALSE になります
OpenGL については、グラフィックス関連の分野になりますので、独自に学習してください
これら3つのメンバは、Windows 98 までには含まれてはいませんでした

ヘッダ情報のなかには、メタファイルの説明文の位置を示す情報がありますが
この説明文は GetEnhMetaFileDescription() 関数で取得できます
UINT GetEnhMetaFileDescription(
	HENHMETAFILE hemf,
	UINT cchBuffer,
	LPTSTR lpszDescription
);
hemf には拡張メタファイルのハンドルを
cchBuffer には文字列を受けるバッファのサイズを
lpszDescription には、文字列を受けるバッファのポインタを指定します

lpszDescription が NULL であれば文字列の長さが
そうでなければバッファにコピーされた文字数が返ります

これらの拡張メタファイルの実験を行うために
まずは、次のような小さなプログラムを作って拡張メタファイルをディスクに保存します
#include <windows.h>
#define META_TXT TEXT("TEST\0Kitty on your lap\0")

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 = CreateEnhMetaFile(NULL , lpCmdLine , NULL , META_TXT);
	Ellipse(hdc , 0 , 0 , 200 , 100);
	LineTo(hdc , 200 , 100);
	CloseEnhMetaFile(hdc);
	return 0;
}
このプログラムは、コマンドラインからファイル名を指定すると
そのファイル名で、ディスクに拡張メタファイルを保存します
この拡張メタファイルには、ファイルを作ったプログラムめいとファイルの情報が含まれます

次に、拡張メタファイルをディスクから読み込んで再生するプログラムを作ります
この時、プログラムはヘッダ情報を元にファイルの情報を取得し
最初のヌル文字を '-' に変換してウィンドウのタイトルバーに表示します
#include <windows.h>

PSTR strFileName;
ENHMETAHEADER enmhHeader;

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;
	HENHMETAFILE hMeta;
	PSTR strFileInfo;

	switch (msg) {
	case WM_DESTROY:
		free(strFileInfo);
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		if (!(hMeta = GetEnhMetaFile(strFileName))) return -1;
		GetEnhMetaFileHeader(hMeta , 
			sizeof (ENHMETAHEADER) , &enmhHeader);
		if (enmhHeader.nDescription == 0) return 0;

		strFileInfo = malloc(enmhHeader.nDescription);
		GetEnhMetaFileDescription(hMeta ,
			enmhHeader.nDescription , strFileInfo);
		strFileInfo[lstrlen(strFileInfo)] = '-';
		SetWindowText(hWnd , strFileInfo);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);
		GetClientRect(hWnd , &rect);
		hMeta = GetEnhMetaFile(strFileName);
		PlayEnhMetaFile(hdc , hMeta , &rect);
		DeleteEnhMetaFile(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") , TEXT("") ,
			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;
}


タイトルバーに注目してください
拡張メタファイルのヘッダの情報を元に、ファイルの情報を引き出していることが分かります

他のプログラムが作った拡張メタファイルでも
この情報ヘッダを用いれば、ある程度保証された範囲で元のように再生することができます
念のためもう一度繰り返しますが、メタファイルはビットマップではないので
ピクセルごとの画像を保存しているわけではなく、図を描画するための手順を記しています
そのため、PlayEnhMetaFile() でこれを解析して情報を元にそれを再現しているのです
速度重視の場合は、メモリデバイスコンテキストに描画してビットマップ化するとよいでしょう

さらに、拡張メタファイルはビットマップを含むことも可能です
ビットマップに関しては、情報だけではなくピクセルビットもデータとして保存します
ファイルに保存した場合は BITMAPINFOHEADER から DIB として
拡張メタファイルのディスクファイルの内部に保存されます


CreateEnhMetaFile()

HDC CreateEnhMetaFile(
	HDC hdcRef , LPCTSTR lpFilename ,
	CONST RECT *lpRect , LPCTSTR lpDescription
);
拡張メタファイル用のデバイスコンテキストを返します

hdcRef - 参照するデバイスコンテキストを指定します
lpFilename - メタファイルを格納するファイル名を指定します
lpRect - 0.01 ミリ単位で図面のサイズを指定します
lpDescription - アプリケーション名と表題をヌル文字で区切って指定します

戻り値 - 拡張メタファイル用のDCのハンドル。失敗すれば NULL

CloseEnhMetaFile()

HENHMETAFILE CloseEnhMetaFile(HDC hdc);

拡張メタファイル用のデバイスコンテキストを解除し
メタファイルのハンドルを返します

hdc - デバイスコンテキストのハンドルを指定します

戻り値 - 張メタファイルのハンドル。失敗すれば NULL

PlayEnhMetaFile()

BOOL PlayEnhMetaFile(HDC hdc , HENHMETAFILE hemf , CONST RECT *lpRect);

拡張メタファイルを再生します

hdc - 出力デバイスとなるデバイスコンテキストのハンドルを指定します
hemf - は拡張メタファイルのハンドルを指定します
lpRect - 境界長方形を論理座標で表した RECT 構造体へのポインタを指定します

戻り値 - 成功すれば 0 以外。失敗すれば 0 が返ります

DeleteEnhMetaFile()

BOOL DeleteEnhMetaFile(HENHMETAFILE hemf);

拡張メタファイルを破棄します

hemf - 拡張メタファイルのハンドルを指定します

戻り値 - 成功すると 0 以外。失敗すると 0

GetEnhMetaFile()

HENHMETAFILE GetEnhMetaFile(LPCTSTR lpszMetaFile);

ディスク上ファイルから、拡張メタファイルを読み込みます

lpszMetaFile - メタファイルのファイル名を指定します

戻り値 - 拡張メタファイルのハンドル。失敗すれば NULL

GetEnhMetaFileheader()

UINT GetEnhMetaFileHeader(
	HENHMETAFILE hemf,
	UINT cbBuffer , LPENHMETAHEADER lpemh
);
拡張メタファイルの情報ヘッダを取得します
lpemh に NULL を指定すると、ヘッダレコードのサイズを返します

hemf - 拡張メタファイルのハンドルを指定します
cbBuffer - lpemh で指定するバッファのサイズを指定します
lpemh - ヘッダを格納する ENHMETAHEADER 構造体へのポインタを指定します

戻り値 - それ以外ならば格納されたバイト数、失敗すれば 0

GetEnhMetaFileDescription()

UINT GetEnhMetaFileDescription(
	HENHMETAFILE hemf,
	UINT cchBuffer,
	LPTSTR lpszDescription
);
メタファイルの情報を表す文字列を取得します
lpszDescription が NULL であれば文字列の長さが返ります

hemf - 拡張メタファイルのハンドルを指定します
cchBuffer - 文字列を受けるバッファのサイズを指定します
lpszDescription - 文字列を受けるバッファのポインタを指定します

戻り値 - バッファにコピーされた文字数



前のページへ戻る次のページへ