トップ回答者
EMFが縦長になってしまう

質問
-
お世話になります。
開発環境はWinXPSP2+VC++6.0です。
下記コードを実行すると,領域が正方形のEMFが作製されるはずですが,やや古いWinXPのパソコン及びWin2000のパソコンではディスプレイの画素数が1280×1024ドット(SXGA)のときに縦長のEMFが作製されてしまうようです。また,このとき,下記コードのLineTo関数で書かれる対角線も,EMFの領域より小さめに描画されます。
比較的新しいWinXPのパソコンと,WinVistaのパソコンでは正方形のEMFが作製されます。
(また,比較的新しいWinXPのパソコンでSXGAのときは対角線がEMFの領域の大きさにぴったり重なります。)
このように,EMFが縦長に作製されてしまうかどうかを,あらかじめプログラムから認識する方法はないでしょうか。特に,WinXPの場合は上記のように仕様が混在しているので困っています。
Code SnippetCDC* pdc; //属性DCハンドルを拡張メタファイルオブジェクトに設定
pdc = GetDC(); //OCXのデバイスコンテキストのハンドルを取得
//作業用EMF出力サイズ(単位はHIMETRIC=1/100mm)
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = 10000;
rect.bottom = 10000;//作業用EMFファイル名決定
CString EMFName1;
EMFName1 = "Temp_EMF_1.emf";CMetaFileDC EmfDc;
//作業用EMF作成
if ( EmfDc.CreateEnhanced( pdc, EMFName1, &rect, NULL ) == NULL){ //作業用EMFの作成を開始
ReleaseDC(pdc);return;
}
EmfDc.MoveTo(0,0);
CSize sz;
sz.cx=10000;
sz.cy=10000;
pdc->HIMETRICtoLP(&sz);
EmfDc.LineTo(sz.cx,sz.cy);EmfDc.LineTo(0,sz.cy);
EmfDc.LineTo(sz.cx,0);
EmfDc.LineTo(0,0);
HENHMETAFILE hEMeta = EmfDc.CloseEnhanced();
DeleteEnhMetaFile(hEMeta); //拡張メタファイルのハンドルを削除
ReleaseDC(pdc);
回答
-
# 勉強し直してきました。
CMetaFileDC::CreateEnhanced で作られる領域が GetDeviceCaps の HORZSIZE と HORZRES (あるいは VERTSIZE と VERTRES) を使って物理領域を計算しているにも関わらず、CDC::HIMETRICtoLP が LOGPIXELSX (あるいは LOGPIXELSY) を使って論理座標→物理座標変換をしているのが原因だと思います。
正しいやり方は良く分からないのですが、CreateEnhanced に渡す領域を調整してやればうまくいくはずです。
とりあえずこんな感じで試してみてください。
Code Snippetconst int HIMETRIC_INCH = 2540;
int emfW = ::MulDiv(
10000, // 調整前の幅 (HIMETRIC)
dc.GetDeviceCaps(LOGPIXELSX) * dc.GetDeviceCaps(HORZSIZE) * 100,
HIMETRIC_INCH * dc.GetDeviceCaps(HORZRES));
int emfH = ::MulDiv(
10000, // 調整前の高さ (HIMETRIC)
dc.GetDeviceCaps(LOGPIXELSY) * dc.GetDeviceCaps(VERTSIZE) * 100,
HIMETRIC_INCH * dc.GetDeviceCaps(VERTRES));
CRect emfRect(0, 0, emfW, emfH);
CMetaFileDC emfDC;
emfDC.CreateEnhanced(&dc, _T("test.emf"), emfRect, NULL);
すべての返信
-
パソコンのモニタが 4:3 の比率になっている場合、
1280×1024ドットを表示しようとすると、縦方向のピクセルを少し詰めて無理やり 4:3 にしなければなりません。
(1ドットが正方形なら1280×960ドットになるため。)
このような(縦方向が詰まっている)画面に 10cm × 10cm の正方形を描画しようとすると、
縦方向のピクセル数の方が横方向よりも多めに必要になってしまいます。
このようにして作られたEMFを他のPCで表示すると、縦方向のピクセル数が多いために
縦長に表示されてしまいます。
もし、EmfDc.GetDeviceCaps(LOGPIXELSX) と EmfDc.GetDeviceCaps(LOGPIXELSY) の値が異なっているなら、
それを見て判断してしまってもいいかも知れませんが、EmfDc が画面の比率に依存しないような形になっているのが
本来の姿かと思います。 -
zakioさま,お返事ありがとうございます。
Win2000/XP(旧)のPCでは,仰るとおりEMFが縦長になります。
しかし,WinXP(新)/VistaのPCではEMFは正方形になってしまうのです。
なお,いずれも画素数は1280×1024ドットで,XP(旧)とXP(新),VistaのPCのモニタを交換しても結果は変わりません。
異なるOSで動作が異なるのならばGetVersionEx()で処理を切り分ける(縦長にされるときはrectを縦に短くする)のですが,
XPの場合はそれも叶わず困っております。
#なお,便宜上「XP(旧)/XP(新)」などと書いてありますが,どちらもプリインストールでXPSP2が入っていたPCです。
それと,説明が足りませんでしたが,最初の発言で挙げたコードを含むソースファイルを
WinXP(旧)のPCでコンパイルしてEXEファイルを作製し,このEXEファイルをそれぞれのPCで実行して
テスト結果を得ています。
#具体的には,MFC AppWizard(exe)からダイアログボックスを作製し,
#void CxxxDlg:: OnOK() 内のCDialog:: OnOK();の呼び出しの前に上記コードを追加しています。
#MFCはスタティックライブラリです。---共有DLLでやろうとするとVistaで「Dllが存在しないよエラー」になります。
特定のPCでEMFファイルを作製して,それを別のPCで見ているわけではありません。
#なお,あるPCで作ったEMFを他のPCで見るとどうなるかというテストもしましたが,仰るような型くずれはないようです。
ところで,私のPCではEmfDc.GetDeviceCaps(LOGPIXELSX) / EmfDc.GetDeviceCaps(LOGPIXELSY) を実行すると
Debugモードでは「Debug Assertion Failed!」になってしまいます。Releaceモードでは戻り値が共に0になります。
なにかおかしいのでしょうか。
pdc->GetDeviceCaps(LOGPIXELSX) / pdc->GetDeviceCaps(LOGPIXELSY)の戻り値は共に96です。
ただし,画面のプロパティから「DPI 設定」を変えると120またはカスタム設定値になります。
>EmfDc が画面の比率に依存しないような形になっているのが本来の姿かと思います。
pdc = GetDC() とEmfDc.CreateEnhanced( pdc, EMFName1, &rect, NULL )
が非常に気持ち悪いですよね。どちらも黙示的?にDCが取得されてしまう……。
# " : " と "O" をくっつけて書くと
という顔文字になってしまうので,12行目のOnOKの前にはスペースを入れています。
-
>Debugモードでは「Debug Assertion Failed!」になってしまいます。
m_hAttribDCがNULLに設定されているためですね。
APIの::GetDeviceCapsを使って
int lx = ::GetDeviceCaps(EmfDc.m_hDC, LOGPIXELSX);
とでもすれば良いようです。
たぶん、CMetaFileDCというクラスが昔のメタファイルと共用に
なっているための制限ではないかと思われます。
縦長の表示になる件は手持ちのモニターの関係で実験できません
でしたが、とりあえず1600x1200のもにたーを1280x1024に設定変更して
実験した結果は縦長にはなりません。
あと、画面のプロパティ設定で大きな文字(120dpi)に設定すると
正しい大きさが再現できませんでした。
この辺りはWindowsの動作がちょっと怪しいと思います。
(ダイアログのデザインが崩れます)
-
# 勉強し直してきました。
CMetaFileDC::CreateEnhanced で作られる領域が GetDeviceCaps の HORZSIZE と HORZRES (あるいは VERTSIZE と VERTRES) を使って物理領域を計算しているにも関わらず、CDC::HIMETRICtoLP が LOGPIXELSX (あるいは LOGPIXELSY) を使って論理座標→物理座標変換をしているのが原因だと思います。
正しいやり方は良く分からないのですが、CreateEnhanced に渡す領域を調整してやればうまくいくはずです。
とりあえずこんな感じで試してみてください。
Code Snippetconst int HIMETRIC_INCH = 2540;
int emfW = ::MulDiv(
10000, // 調整前の幅 (HIMETRIC)
dc.GetDeviceCaps(LOGPIXELSX) * dc.GetDeviceCaps(HORZSIZE) * 100,
HIMETRIC_INCH * dc.GetDeviceCaps(HORZRES));
int emfH = ::MulDiv(
10000, // 調整前の高さ (HIMETRIC)
dc.GetDeviceCaps(LOGPIXELSY) * dc.GetDeviceCaps(VERTSIZE) * 100,
HIMETRIC_INCH * dc.GetDeviceCaps(VERTRES));
CRect emfRect(0, 0, emfW, emfH);
CMetaFileDC emfDC;
emfDC.CreateEnhanced(&dc, _T("test.emf"), emfRect, NULL); -
>Tommaさま
ありがとうございます。お教えいただいたように,EMFの出力デバイス コンテキストm_hDCを使ってAPIを呼び出して
int lx = ::GetDeviceCaps(EmfDc.m_hDC, LOGPIXELSX);
のようにすることでEMFのデバイス情報を得ることが出来ました。
>zakioさま
ありがとうございます。わざわざお調べいただいたようで,感謝の念に耐えません。
ご教示いただいた方式で最初の発言で挙げたソースコードを修正したところ,
どのPCのどの解像度でも正方形のEMFが作製され,
LineTo命令で描画している線がその正方形の対角線となりました。
PCによって GetDeviceCaps(HORZSIZE) / GetDeviceCaps(VERTSIZE) の戻り値が違い,
モニタ画素数の設定どおりにGetDeviceCaps(HORZRES) / GetDeviceCaps(VERTREZ) の値が返り,
それによってCreateEnhanced に渡す領域の補正が正しく行われるのですね。
#GetDeviceCaps(LOGPIXELSX) / GetDeviceCaps(LOGPIXELSY) については
#いずれの場合も 96 / 96 で固定でした。
#画面のプロパティの「DPI設定」を変更するとこの値は変わりますが,そのテストは省略させていただきました。
勉強になりました&大いに助かりました!
#参考までに HORZSIZE×VERTSIZEの戻り値を表にしてみます。
#画素数設定 800× 600 1024× 768 1280×1024
#Win2000 320× 240 320× 240 320× 240
#WinXP-1 320× 240 320× 240 320× 240
#WinXP-2 340× 270 340× 270 340× 270
#WinXP-3 340× 255 340× 255 337× 270
#WinVista 282× 212 361× 271 452× 361
#
#Win2000/XP-1(旧)は,320× 240で固定です。
#WinXP-2は,340× 270で固定です。
#WinXP-3(新)はおかしな挙動をしていますね。タイプミスではありません。
#自分でも目を疑いましたが,3回テストを繰り返しても全く同じ挙動でした。
#結局,XPの場合はPCによって戻り値が変わるようです。
#WinVistaは,おおよそモニタの画素数に比例した値が返るようです。
#1台しかVistaのPCがなかったので,PCによって戻り値が変わるかどうかは分かりません。