拡張メタファイルのサイズについて

[戻る]
Hiro 2010/02/09(火) 14:15:30
お世話になっております。
Windows7 + VisualStudio2005(MFC使用)の環境で開発を行っています。

現在、ワープロ系のアプリを作成しているのですが、クリップボードにある拡張メタフ
ァイル形式のデータをドキュメントに貼り付ける処理で問題が起きています。

問題というのは、取得した拡張メタファイルをドキュメント上に表示する時の
再生サイズです。

ENHMETAHEADER hdr;
GetEnhMetaFileHeader(hEmf, sizeof(ENHMETAHEADER), &hdr);

のようなコードで拡張メタファイルのヘッダー情報を取得することができます。
この中の「hdr.rclFrame」メンバにハイメトリック単位(HM)でのサイズが格納されて
います。このサイズを以下のような公式でピクセル単位に変換して
ドキュメント上に表示しています。

void HMtoPixelforEmf(CSize *psiz)
{
  HDC hDC = ::GetDC(NULL);
  int nWidthMM     = GetDeviceCaps(hDC, HORZSIZE);
  int nHeightMM    = GetDeviceCaps(hDC, VERTSIZE);
  int nWidthPixel  = GetDeviceCaps(hDC, HORZRES);
  int nHeightPixel = GetDeviceCaps(hDC, VERTRES);
  ::ReleaseDC(NULL, hDC);

  psiz->cx = psiz->cx * nWidthPixel / (nWidthMM * 100);
  psiz->cy = psiz->cy * nHeightPixel / (nHeightMM * 100);
}

これは、下記のサイトにも説明されている変換方法です。

・Win32 で拡張メタファイルを作成および再生する方法
http://support.microsoft.com/kb/145999/ja

このような変換でExcel2007の表などは正しいサイズで表示できました。

問題はWord2007やワードパッドの文書をコピーした時です。
上記の変換公式ではサイズが小さくなってしまうのです。

実はハイメトリックからピクセル単位への変換には以下のような公式もあります。

#define HIMETRIC_INCH   2540    // HIMETRIC units per inch

CDC::DPtoHIMETRIC(CSize *psiz) 
{
  cxPerInch = GetDeviceCaps(hDC, LOGPIXELSX);
  cyPerInch = GetDeviceCaps(hDC, LOGPIXELSY);

  psiz->cx = MulDiv(psiz->cx, cxPerInch, HIMETRIC_INCH);
  psiz->cy = MulDiv(psiz->yx, cyPerInch, HIMETRIC_INCH);
}

これはMFCのCDCクラスのDPtoHIMETRIC()で使用されている公式です。
こちらの変換式を使うとWord2007やワードパッドからの拡張メタファイルは
正常なサイズで表示されます。
しかし、Excel2007からの拡張メタファイルは逆にサイズが大きくなります。

どちらもハイメトリックからピクセル単位への変換ですが、
なぜか同じ値を変換しても結果が異なります。

つまり、Excel2007が作成した拡張メタファイルは
前者のHMtoPixelforEmf()でサイズ変換するとうまくいきますが、
後者のCDC::DPtoHIMETRIC()でサイズ変換するとうまくいきません。
Word2007やワードパッドが作成した拡張メタファイルの場合には、
まったく逆になります。

どのような場合に前者の変換を使って、どのような場合に後者の変換を使えばよいの
か、その判定方法がよく分かりません。
ENHMETAHEADERの中の情報で判断できるのでしょうか?

なにかご存じの方がいましたらアドバイスをお願い致します。

ちなみに、Excel2007にWord2007とワードパッドからの文書を貼り付けると、
ワードパッドからの方はサイズが小さくなりますが、
Word2007からの方は正しいサイズで貼り付きます。

仲澤@失業者 2010/02/09(火) 16:53:26
>どのような場合に前者の変換を使って、どのような場合に後者の変換を使えばよいの
>か、その判定方法がよく分かりません。

まず、
psiz->cx = psiz->cx * nWidthPixel / (nWidthMM * 100);  の方は
Cx'  = 絵の物理幅[0.01mm] * モニターのXドット数[pixl]
                                   / (モニターの物理幅[mm] * 100)
     = 絵の物理幅[mm] * モニターのXドット数[pixl] / (モニターの物理幅[mm]
     = 絵の物理幅[pixl]
ですね。

psiz->cx = MulDiv(psiz->cx, cxPerInch, HIMETRIC_INCH); の方は
Cx'  = 絵の物理幅[0.01mm] * 画面解像度[pixl/inch]
                                   / ( 25.4[mm] * 100)
     = 絵の物理幅[mm] * 画面解像度[pixl/inch] / 25.4[mm]
      // 25.4[mm]=1inchなので
     = 絵の物理幅[pixl]

となります。本来ならね。でも、

1.モニターの物理幅[mm]  GetDeviceCaps( dc.m_hDC, HORZSIZE)
                          は嘘っぱちの値が戻りますよね。自分のは320[mm]が
             戻ったが、実際に計ると395[mm]でした。

2.画面解像度[pixl/inch]   GetDeviceCaps( dc.m_hDC, LOGPIXELSX)
             も嘘っぱちの値が戻るりますよね。
             17inchでも19inchのモニターでも96[dpi]等が戻る(笑)

なので、両式とも正しい値は計算できません。参考にするだけにしましょう。
基準になるのは
「自分のアプリケーションのHDCが、ピクセルあたり何[mm]または、何[inch]で、
現在表示しているつもりなのか」
だけです。

仲澤@失業者 2010/02/09(火) 18:01:05
     = 絵の物理幅[pixl]
はまちがいですね
     = 画面上の絵の幅[pixl] = Cx'
に訂正します。 orz

Hiro 2010/02/10(水) 08:36:53
仲澤@失業者さん、
詳しい解説ありがとうございました。

仲澤さんが仰るように、確かに嘘っぱちの値が戻るんですよね。
自分もカキコした後で、いろいろ調べていて確認しました。
(GetDeviceCaps( dc.m_hDC, HORZSIZE)の値は実測値とは全然違いました)

結局、拡張メタファイルの作成側がどちらの方法でハイメトリックの値を
計算しているのかは分からないということですね。

私が実験した限りでは、標準メタファイルでは、画面解像度[pixl/inch]
を経由する方法でセットしているアプリがほとんどでした。
(あくまで私の周りにあるアプリの話ですが・・・)

拡張メタファイルの貼り付けに関して2種類のオプションを用意するか
標準メタファイル形式の貼り付けコマンドを追加するか・・・
検討してみたいと思います。

ありがとうございました。

subaru 2010/02/10(水) 14:32:40
ENHMETAHEADER の情報から計算する場合は
szlDevice と szlMillimeters の値を使用した方がよい気がします。

>私が実験した限りでは、標準メタファイルでは、画面解像度[pixl/inch]
>を経由する方法でセットしているアプリがほとんどでした。
>(あくまで私の周りにあるアプリの話ですが・・・)
クリップボードから標準メタファイル形式と拡張メタファイル形式の両方が
取得できる場合は再生サイズだけ標準メタファイルの方を優先して使うとか。

仲澤@失業者 2010/02/10(水) 16:32:43
>標準メタファイル形式の貼り付けコマンドを追加するか・・・

旧形式のメタファイルは、アルダス拡張形式(だったかな)が
ほとんどだと聞いています。
いまさら感がある上に無くなっちまった会社のローカルな
拡張形式がデファクトスタンダードになっちゃってます。
・・・まぁやってみてもいいかもしれませんが(vv;)。

Hiro 2010/02/14(日) 08:04:13
[[解決]]
subaru さん、
アドバイスありがとうございます。

> szlDevice と szlMillimeters の値を使用した方がよい気がします。

参照デバイスの値については、アプリによって解釈が異なるようです。
多くのアプリではモニタのサイズが設定されているのですが、
Word 2007では印刷用紙のサイズがセットされていました。
この値で計算すると、ものすごく大きなサイズで表示されてしまいます。


結局、みなさんのアドバイスを参考にして、
拡張メタファイルの貼り付けは、ダイアログを表示して2種類の変換方法に対する
プレビュー画面を表示する方法にしました。
これなら、ユーザーが好きな方を選択して貼り付けることができます。

いろいろとありがとうございました。

ロマ 2010/02/14(日) 16:58:47
興味があったので暇をみつけて試していましたが、
こっちが解決してしまったので、急ぎ中間報告します。

OleGetClipboardとGetClipboardDataを比較してみました。

ワードパッド内の文章をコピーすると
OleGetClipboardには拡張メタファイルはありませんでした。

エクセルのセルをコピーすると
METAFILEPICTのxExtはワードパッドのルーラーのサイズと同じでしたが、
拡張メタファイルのrclFrameとは異なりました。

ワードパッドの場合、コピーの結果はOleのオブジェクトだと思います。
WindowsはGetClipboardDataの中で拡張メタファイルを生成するのだと思います。
# この変換でmmが変わってしまうと邪推

まぁ、IViewObject2::GetExtent()を使う方が正確だと思います。

参考)
vc6付属MSDNてfreeloadを検索してDOCUMENT.CPP

ロマ 2010/02/14(日) 17:05:09
> # この変換でmmが変わってしまうと邪推

すみません、混乱してました。この行削除します。

古いスレッドには追加発言できません。