VC2005 MFC です。
MM_ANISOTROPICのマッピングモードを覚えるため、
ダイアログベースの初期ソースに以下のようなテスト処理を加えました。
void CTestDlg::OnBnClickedButton1()
{
static int ratio = 200;
RedrawWindow();
CFont font;
font.CreatePointFont(220, _T(MS ゴシック), NULL);
CClientDC dc(this);
dc.SaveDC();
dc.SetMapMode(MM_ANISOTROPIC);
dc.SetWindowOrg(0, 0);
dc.SetWindowExt(100, 100);
dc.SetViewportOrg(0, 0);
dc.SetViewportExt(ratio, ratio);
dc.SelectObject(&font);
dc.DrawText(_T(1234567890ABCDEFGHIJ1234567890), CRect(0, 0, 0, 0),
DT_SINGLELINE | DT_NOCLIP);
dc.RestoreDC(-1);
ratio -= 5;
}
これで同じサイズのフォントのまま縮小表示できるのは理解したのですが、
縮小のされかたが均一ではなく、ときどき逆に大きくなったりします。
フォントサイズを別の値にすると、スムーズに縮小されていくこともあります。
これはMM_ANISOTROPICモードでフォントを縮小表示する際には
発生してしまう現象なのでしょうか?
どんなサイズのフォントでもスムーズに縮小できるものなのでしょうか?
>縮小のされかたが均一ではなく、ときどき逆に大きくなったりします。
実験してみました。
確かに提示のソースだと、一見大きくなったように
見える場合がありますね(ratio=195->190の時など)。
しかし、キャプチャして両者を比べると、1文字の大きさ自体は小さく
なっているようです。拡大しているのは「文字間」又は、文字の「論理幅」
のようですね。
んで、DrawText()をTextOut()に変えてみたところ症状は出なくなりました。
また、フォントフェースを「MS Pゴシック」にした場合は、
DrawText()の場合でも症状は軽減するようです。
整理すると
1.固定幅フォントで顕著
2.DrawText()系で発生する
ここから、疑わしい部分は
1.CreatePointFont()のクリップ精度や、出力精度に問題はないのか
2.DrawText()はクリップ不要なのに何か余計な計算をしていないか
等が気になるところですね。
> これはMM_ANISOTROPICモードでフォントを縮小表示する際には発生してしまう現象?
関係ありそうな事を羅列してみる。
・CreateFont() の 第 2 引数に 0 を指定した場合の仕様
・尺度を反映した結果、小数点以下の情報が発生した場合の数値丸めによる誤差
・DrawText() の仕様
DrawText() って↓な感じで文字描画を実現してるぽい(XP スクリーンデバイスの場合)
int DrawText(LPWSTR string, int count, int x) {
for (int i=0; i < count; ++i) {
TextOut(x, y, &string[i], 1);
x += dc.GetTextExtent(&string[i], 1).cx;
}
}
つまり、文字位置を一文字づつ更新している感じなんだけど、これやると”小数点以下の
丸めによる誤差”の影響をモロ受ける。
誤差については、尺度を変えた状態で↓コードを実行してみると実感できる。
LPWSTR string = L1234567890ABCDEFGHIJ1234567890;
for (int i=0, cx1=0; i < count; ++i) {
cx1 += dc.GetTextExtent(&string[i], 1).cx;
int cx2 = dc.GetTextExtent(string, i + 1).cx;
TRACE(_T(%d, %d\n), cx1, cx2);
}
> どんなサイズのフォントでもスムーズに縮小できるものなのでしょうか?
フォント作成時に文字幅も指定してやれば、文字列が描画される領域のアスペクト比はそ
れなりに維持できそうだけど…プロポーショナルフォントの場合は指定するワケにはいか
ないか…
仲澤@失業者さん、gakさん、試していただきありがとうございます。
自分のところでもratio=195->190のときに範囲が大きくなります。
また、TextOut()だと確かに大きくなることはありませんが、
縮小間隔は均一ではないように見えました。
もちろん、モニタ上でフォントを縮小表示するのには限界はあるでしょうし、
そのためにgakさんの言われるような誤差が発生しているのでしょうけど、
範囲が逆に広がって表示が切れてしまうくらいなら、
文字が多少重なってでも範囲内に収まってくれればよかったのですが、
これはDCのモードなどで設定できるものではありませんよね。
これはMM_ANISOTROPICで文字列を縮小表示するには
避けられない問題と諦めるしかないのでしょうか。
> 文字が多少重なってでも範囲内に収まってくれればよかったのですが
> 避けられない問題と諦めるしかないのでしょうか。
こういう物だと諦めるか、自力で何とかするかのどっちかだろうね。
自力でがんばるなら、一番問題となっているであろう
・尺度を 100% 以外に設定した際に発生する小数点以下の数値丸めによる誤差
を解決すれば良い。
具体的にどうすりゃ良いかというと、単純に小数点以下の情報が発生しない状況(=尺度
100%)で文字位置・サイズの計算を行えば良いかと。
CClientDC dc(this);
dc.SaveDC();
// 描画対象 DC の尺度設定
dc.SetMapMode(MM_ANISOTROPIC);
dc.SetWindowOrg(0, 0);
dc.SetWindowExt(100, 100);
dc.SetViewportOrg(0, 0);
dc.SetViewportExt(ratio, ratio);
// 属性用 DC 作成(この DC の尺度は 100% のまま)
CDC attr_dc;
attr_dc.CreateCompatibleDC(&dc);
attr_dc.SaveDC();
// ここで属性 DC セット
dc.SetAttribDC(attr_dc);
CFont font;
font.CreatePointFont(220, _T(MS ゴシック), NULL);
dc.SelectObject(&font);
const TCHAR * const string = _T(1234567890ABCDEFGHIJ1234567890);
int x = 0;
for (LPCTSTR p=string; *p != _T('\0'); ) {
LPCTSTR next = _tcsinc(p);
// x = dc.GetTextExtent(string, p - string).cx; // 毎回文字列先頭から計算
dc.TextOut(0 + x, 0, p, next - p);
x += dc.GetTextExtent(p, next - p).cx; // 尺度100%なのでコレで精度的に十分か
p = next;
}
dc.RestoreDC(-1);
>また、TextOut()だと確かに大きくなることはありませんが、
>縮小間隔は均一ではないように見えました。
そうですね。
あと、フォントに影響が関係ありそうなものとして、
グラフィックスモードがあります。
SetGraphicsMode( GM_ADVANCED)
も、試してみる価値があるかもしれません。
・・・本質的な違いが無い可能性もありますが。
あと、わざわざCreatePointFont()を使っているのに
CDCを与えないっていうのも、OS側から見ると反則なので
普通のCreateFont()にしましょう(笑)。
gakさん、仲澤@失業者さん、ご意見ありがとうございます。
> こういう物だと諦めるか、自力で何とかするかのどっちかだろうね。
既存のプログラムでは、DrawText()で
センタリングやDT_WORDBREAKやDT_CALCRECTを多用しているのですが、
やはりそれらの機能も移植する必要がありますよね。
とはいえ、DrawText()のままでは解決できなさそうなので、
自前で描画する関数を作ってしまうしか無いのかもしれません。
作業の優先度などを検討してみたいと思います。
> SetGraphicsMode( GM_ADVANCED)
この関数も試してみましたが、表示は変わりませんでした。
> あと、わざわざCreatePointFont()を使っているのに
> CDCを与えないっていうのも、OS側から見ると反則なので
> 普通のCreateFont()にしましょう(笑)。
はい、本番のプログラムではもちろんこのようにはなっていません。
どうも特定のフォントサイズで多発する感じで、
テストプログラムではとりあえず固定サイズで画面に出すだけなので、
スクリーンデバイスコンテキストをCreatePointFont()に使いました。
ご指摘ありがとうございます。