ホーム » Windows (ページ 4)
「Windows」カテゴリーアーカイブ
高 DPI VC 「高い DPI 認識」
引き続きいろいろとやっているが…
異なる DPI での対応は簡単ではなさそう.
VC のプロパティで「モニターごと高い DPI 認識」としてビルドしていたが,モニタ間を移動するとうまくない.
ちゃんと対応すれば良いのだろうが,簡単ではない.
そのため exe のプロパティで「システム(拡張)」で良いと思っていたが,幾つかのバグ?(未対応)がある.
VC のプロパティで「高い DPI 認識」にしてビルドしたものの方がうまく機能している様な気がする.
左から VC 10 ,14 ,10 ,14 としたもの.
タスクマネージャで見ると,VC 10 exe は「システム」として表示される.
左側のモニタの範囲は次の様になる.
-1920 130 0 1210 100% 1920 1080 「非対応」や「モニタごと」の場合
-3840 260 0 2420 200% 3840 2160 「システム」とした exe の場合
右側は ( 0 , 0 ) – ( 3840 , 2160 ) .
GDI スケーリングの動作として,次の記述があった.
アプリケーションが 100% (96 DPI) の倍数ではないディスプレイで実行されている場合、ベクター グラフィックスとテキストは、ディスプレイの倍率より 100% 高い最初の整数倍にレンダリングされます。たとえば、アプリケーションが 225% の縮尺のディスプレイ上にある場合、ベクター グラフィックスとビットマップは 300% でレンダリングされます。その後、DWM はレンダリングされたコンテンツを 225% のスケールに縮小します。この場合、スケールダウンによりアプリケーションのあいまいさが目立ちますが、100%レンダリングされたコンテンツを単純にスケールアップするよりも見栄えが良くなります。
Improving the high-DPI experience in GDI based Desktop Apps
How to build high DPI aware native Windows desktop applications
2023/05/12
「システム(拡張)」で幾つか変な動作があったが,「高い DPI 認識」でビルドしたものであれば良さそう.
C++ 戻り値の異なる関数 template
先日からやっている .ini やレジストリにアクセスする関数.
MFC の CWinApp::GetProfileString , CWinApp::WriteProfileString にあたる部分は目途がついた.
それで,それらを呼出す部分.前に作成したものもそうだったが,型ごとにクラスの関数を定義していた.
これらをもう少し簡単にできないかと…
set の方は,特に難しい所はない(::To_tstring は,文字列に変換する関数として用意している).
template <class T> bool INI_set (LPCTSTR sec,LPCTSTR ent,const T& val)
{
tstring ini = ::INI_get_module_ini() ;
tstring str = ::To_tstring(val) ;
return ::INI_set(ini.c_str(),sec,ent,str.c_str()) ;
}
get の場合,文字列から変数に変換する方法をどうするか?
例えば,atoi や atof ,他にも 4 つの整数の文字列を RECT に変換するなど.
検索すると template で可能みたい だが…
よくわからなかったので,簡単な方法にした.
inline RECT To_RECT (LPCTSTR str)
{
RECT rect = { 0 } ;
{
v_tstring str_ary = ::String_SplitSpace(str,_T(" ,\t\r\n")) ;
if (0 < str_ary.size()) { rect.left = ::ttoi4(str_ary[0]) ; }
if (1 < str_ary.size()) { rect.top = ::ttoi4(str_ary[1]) ; }
if (2 < str_ary.size()) { rect.right = ::ttoi4(str_ary[2]) ; }
if (3 < str_ary.size()) { rect.bottom = ::ttoi4(str_ary[3]) ; }
}
return rect ;
}
inline bool string_to (LPCTSTR str,RECT* rect_) { *rect_ = ::To_RECT (str) ; return true ; }
inline bool string_to (LPCTSTR str,POINT* point) { *point = ::To_POINT(str) ; return true ; }
inline bool string_to (LPCTSTR str,SIZE* size_) { *size_ = ::To_SIZE (str) ; return true ; }
それぞれの型に合わせた関数を呼べるようになったので template に.
template <class T> T INI_get (LPCTSTR sec,LPCTSTR ent,const T& def)
{
tstring ini = ::INI_get_module_ini() ;
tstring dst = ::To_tstring(def) ;
tstring str = ::INI_get(ini.c_str(),sec,ent,dst.c_str()) ;
T val ;
::string_to(str.c_str(),&val) ;
return val ;
}
これで,次の様な使い方ができる様になる.
{
POINT point = ::POINT_set( 10, 20) ;
SIZE size_ = :: SIZE_set( 1111, 525) ;
RECT rect_ = :: RECT_set(point,size_) ;
{
::INI_set(_T("test"),_T("rect_"),rect_) ;
::INI_set(_T("test"),_T("point"),point) ;
::INI_set(_T("test"),_T("size_"),size_) ;
std::tout << ::To_tstring(::INI_get(_T("test"),_T("rect_"),rect_)) << std::endl ;
std::tout << ::To_tstring(::INI_get(_T("test"),_T("point"),point)) << std::endl ;
std::tout << ::To_tstring(::INI_get(_T("test"),_T("size_"),size_)) << std::endl ;
}
}
RegOpenKeyEx REGSAM
レジストリアクセスのコードを書き直していて,::RegOpenKeyEx の samDesired を調べてみた.
KEY_READ (0x20019) 0010 0000 0000 0001 1001
KEY_WRITE (0x20006) 0010 0000 0000 0000 0110
KEY_EXECUTE (0x20019) 0010 0000 0000 0001 1001
KEY_QUERY_VALUE (0x0001) 0000 0000 0000 0001
KEY_SET_VALUE (0x0002) 0000 0000 0000 0010
KEY_CREATE_SUB_KEY (0x0004) 0000 0000 0000 0100
KEY_ENUMERATE_SUB_KEYS (0x0008) 0000 0000 0000 1000
KEY_NOTIFY (0x0010) 0000 0000 0001 0000
KEY_CREATE_LINK (0x0020) 0000 0000 0010 0000
KEY_WOW64_64KEY (0x0100) 0000 0001 0000 0000
KEY_WOW64_32KEY (0x0200) 0000 0010 0000 0000
KEY_ALL_ACCESS (0xF003F) 1111 0000 0000 0011 1111
読み込み時,KEY_READ の方が速いなどはあるのか?それともファイルアクセスなどと同じ?
GetMonitorInfo
2002/08 に,マルチディスプレイ対応のコードを書いている.
今回,高 DPI 対応やディスプレイ位置が変わった時などのためもう一度…
ChatGPT で.
そのままでは VC 6 ではうまくビルドできなかったので,VC 8 で.
printf を使用しているので #include <cstdio> が必要.
そのまま実行すると 1920×1080 となる.
高 DPI スケール設定を「アプリケーション」とすると,3840×2160 .
2023/04/14
以前のコードを見ると,プライマリのみの情報は ::SystemParametersInfo などを使用している.
これで求められる SPI_GETWORKAREA は,::GetMonitorInfo で戻されるものと同じ?
システム 0 61 1920 1080
アプリケーション 0 122 3840 2160
::GetSystemMetrics(SM_?VIRTUALSCREEN) は,-1920 0 .
::GetSystemMetrics(SM_C?VIRTUALSCREEN) は,上をずらしているので少し異なる.
システム 3840 1210
アプリケーション 5760 2160
https://learn.microsoft.com/ja-jp/windows/win32/gdi/the-virtual-screen
MFC タイトルバーの変更
ダイアログベースであれば C…Dlg::OnInitDialog() に次の様なコードを追加.
{
tstring str_title = ::GetWindowText(this->GetSafeHwnd()) ;
str_title+= _T(" ") + ::Get_ModuleVersion() + ::Get_BuildStrMSC() ;
SetWindowText(str_title.c_str()) ;
}
SDI や MDI の場合は,
MainFrm.h に OnUpdateFrameTitle を追加.
virtual void OnUpdateFrameTitle (BOOL bAddToTitle);
MainFrm.cpp に次の様な OnUpdateFrameTitle を追加.
// SDI
void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
{
CFrameWnd::OnUpdateFrameTitle(bAddToTitle) ;
{
tstring str_title = ::GetWindowText(this->GetSafeHwnd()) ;
str_title+= _T(" ") + ::Get_ModuleVersion() + ::Get_BuildStrMSC() ;
SetWindowText(str_title.c_str()) ;
}
}
// MDI
void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
{
CMDIFrameWnd::OnUpdateFrameTitle(bAddToTitle) ;
{
tstring str_title = ::GetWindowText(this->GetSafeHwnd()) ;
str_title = ::MDI_Add_VerBuildStr(str_title.c_str()) ;
SetWindowText(str_title.c_str()) ;
}
}
高 DPI CToolBar のリサイズ
CToolBar を使用した 自前のコード .
CDialogBar や CStatusBar はスケーリングしてくれるのに,CToolBar は対応しない?
以前は設定などで変更可能なコードにしていたが,DPI を求める方法に.
if (newExtendSize == CSize(0,0)) {
double dpi_s = ::GetDPI_scale(toolBar->GetSafeHwnd()) ; // ::GetDpiForWindow()/96.
if (dpi_s > 1) {
CToolBarCtrl& tbCtrl = toolBar->GetToolBarCtrl() ;
CSize orgSize = tbCtrl.GetButtonSize() ;
CSize newSize = orgSize ;
newSize.cx = int(orgSize.cx*dpi_s) ;
newSize.cy = int(orgSize.cy*dpi_s) ;
newExtendSize.cx = newSize.cx - orgSize.cx ;
newExtendSize.cy = newSize.cy - orgSize.cy ;
resizeType = 'R' ;
}
}
あとは既存コードの,ビットマップのリサイズと CToolBar::SetSizes を呼出せば良さそう.
CMFCToolBar は DPI を正しく処理しているみたいで,その部分のコード.
…\VC\…\atlmfc\src\mfc\afxtoolbar.cpp の CMFCToolBar::LoadToolBarEx .
まだ幾つか問題はあるが,今回の対応部分としてはこれで良さそう.
更にツールバー上のコンボボックスのスケーリングは次の様にした.
{
int width = 50 ;
{
double dpi_s = 1. ;
dpi_s = ::GetDPI_scale(AfxGetMainWnd()->GetSafeHwnd()) ;
dpi_s = ::GetDPI_scale( m_wndToolBar.GetSafeHwnd()) ;
dpi_s = ::GetDPI_scale( this->GetSafeHwnd()) ;
if (dpi_s > 1.) {
width = int(width*dpi_s) ;
}
}
m_wndToolBar.SetButtonInfo (19,ID_COMBO_ANGLE,TBBS_SEPARATOR,width) ;
CRect rectCombo ;
m_wndToolBar.GetItemRect (19,&rectCombo) ;
rectCombo.top = 1 ;
rectCombo.bottom = rectCombo.top + 300 ;
if (!m_ComboAngle.Create(CBS_DROPDOWN | WS_VSCROLL | WS_VISIBLE,rectCombo,&m_wndToolBar,ID_COMBO_ANGLE)) {
TRACE0("Failed to ComboBox\n");
return -1;
}
m_ComboAngle.SetFont(m_wndToolBar.GetFont()) ;
m_ComboAngle.AddString(_T(" 0.")) ;
m_ComboAngle.AddString(_T("10.")) ;
// ...
m_ComboAngle.AddString(_T("90.")) ;
}
高 DPI テスト exe
先日の exe をテストしていると…
高 DPI をテストするために,次の様なコードの exe を作成.
void CT_aesDlg::OnDropFiles(HDROP hDropInfo)
{
v_tstring drop_files = ::DropFilesTo(hDropInfo) ;
for (size_t index=0 ; index<drop_files.size() ; index++) {
tstring drop_file = drop_files[index] ;
tstring ext = ::Path_GetExtLow(drop_file) ;
if (ext != _T("exe")) { continue ; }
{
S_Exec se ;
se.SetFile(drop_file.c_str()) ;
se.Execute() ;
}
}
CDialog::OnDropFiles(hDropInfo);
}
この exe に,他の exe をドロップして起動すると,「高 DPI スケール設定」が引き継がれる.
これらの設定は,次の所に持っている?
HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
A ~ HIGHDPIAWARE
S ~ DPIUNAWARE
E ~ GDIDPISCALING DPIUNAWARE
それぞれを実行すると表示が異なることはわかるが…
左から,「システム」,「システム(拡張)」,「アプリケーション」.
「システム」と「システム(拡張)」を区別する方法がわからない.
また「システム(拡張)」.exe で,何かの情報の取得が違っていて 表示が正しくない ものと思う.
::GetDeviceCaps の情報の表示は dc.DrawText(str,rect,DT_LEFT) としている.
この時,表示するフォントを指定していないため,「アプリケーション」では小さくなってしまう.
CFont::CreatePointFont などを呼ぶことで対応可能.
tcsncpy_s Buffer is too small
先日更新した ツール をテストしていると,フォントによりアプリケーションエラー?となってしまう.
デバッガで追いかけると,
—————————
Microsoft Visual C++ Runtime Library
—————————
Debug Assertion Failed!
Program: c:\Temp\i_Tools\TToPA\Debug.120\TToPA.exe
File: f:\dd\vctools\crt\crtw32\h\tcsncpy_s.inl
Line: 62
Expression: (L"Buffer is too small" && 0)
For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.
(Press Retry to debug the application)
—————————
中止(A) 再試行(R) 無視(I)
—————————
CRichEditCtrl を使用した CHARFORMATW の szFaceName の指定がうまくなかった.
::TcsNCpy(cf.szFaceName,LF_FACESIZE-1,faceName,LF_FACESIZE-1) ;
::TcsNCpy は,幾つかの環境でビルドできる様にしたもので,今回の場合は ::tcsncpy_s と同様もの.
faceName に与えたものが L"Bahnschrift Light SemiCondensed" で,コピー先のバッファが足りないため.
正しくは,
::TcsNCpy(cf.szFaceName,LF_FACESIZE-0,faceName,LF_FACESIZE-1) ;
これらの動作は,LOGFONT の時にもよく利用する.
既存のコードを検索してみると,LF_FACESIZE を正しく指定できていた.
::TcsCpy(lf.lfFaceName,LF_FACESIZE,tstring(faceName).substr(0,LF_FACESIZE-1).c_str()) ;
与えるデータは LF_FACESIZE-1 としている.
GetGlyphOutline ::PolyPolygon
求めたアウトラインをウィンドウに表示できる様な変換のコードを書いたので,その動作のテスト.
{
HWND hWnd = ::GetConsoleHwnd() ;
RECT rect = ::GetClientRect(hWnd) ;
POINT point= ::RECT_center(rect) ;
int ch = ::RECT_width(rect)/str.length()*10/7 ;
{
HDC hDC = ::GetDC(hWnd) ;
{
#define face_A _T("Arial")
#define face_U _T("Meiryo UI")
#define face_W _T("Wingdings")
{
::ClearWindow(hWnd) ;
int ry = -30 ;
tstring face_name = face_U ;
vv_PLF vv_plf= ::GetTTOutline(hDC,point,false,str.c_str(),face_name.c_str(),ch,0,0,ry,FW_DONTCARE) ;
v_Vd2A v_v2a = ::ToVd2A(vv_plf) ;
::PolyPolygon(hDC,v_v2a) ;
}
}
::ReleaseDC(hWnd,hDC) ;
}
}
::GetTTOutline の中では,文字の傾き,文字列の回転,Y の反転,表示位置の移動を行っている.
更に三角形に分割.
vv_PLF vv_plf= ::GetTTOutline(hDC,point,false,str.c_str(),face_name.c_str(),ch,0,0,ry,FW_DONTCARE) ;
// v_Vd2A v_v2a = ::ToVd2A(vv_plf) ;
v_Vd2A v_v2a = ::PLF_triangulation(vv_plf) ;
ShowWindow SW_HIDE SW_SHOW
ダイアログに「ピクチャーコントロール」を追加して,それの再描画.
以前よくやっていた方法.
void CFOutLineDlg::OnHeight()
{
if (m_CountSpin.m_hWnd == NULL) { return ; }
UpdateData(TRUE) ;
InvalidateRect(NULL) ;
}
これだと描画対象以外のコントロールも再描画するのでちらついてしまう.
一度 ShowWindow で SW_HIDE して SW_SHOW することで,対象がコントロールだけになる.
void CFOutLineDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
if (m_CountSpin.m_hWnd == NULL) { return ; }
::FitWindow(this,&m_Image,5,TRUE,FALSE) ;
m_Image.ShowWindow(SW_HIDE) ;
m_Image.ShowWindow(SW_SHOW) ;
}
GetGlyphOutline ポリゴン化
まだまだだが…
文字ごとのポリラインを取得できる様になったので,穴の処理と組み合わせてポリゴン化.
文字によっては,穴の処理でクロスしてしまうことがある.
https://itl.mish.work/Iwao/Doc/algo/div_cnvx/div_23_4.html
https://itl.mish.work/Iwao/Doc/algo/div_cnvx/div_23_5.html
GetGlyphOutline ポリラインの配列に
「MS ゴシック」と「MS Pゴシック」などの幅が,思っていたものと違った.
v_PLF TTPOLYGON_to (LPTTPOLYGONHEADER buf,const DWORD rSize)
{
vv_LQC vv_lqc = ::to_LQC(buf,rSize) ; // buf を座標の配列に
v_PLF v_plf = ::LQC_to(vv_lqc) ; // ポリラインに変換
return v_plf ;
}
v_PLF GetTTOutline (HDC hDC,const UINT chr,GLYPHMETRICS* gm,const UINT format=GGO_NATIVE)
{
v_PLF v_plf ;
{
MAT2 mat2 = { {0, 1}, {0, 0}, {0, 0}, {0, 1} } ;
DWORD dwSize = ::GetGlyphOutline(hDC,chr, format, gm, 0, NULL, &mat2 ) ;
if (dwSize != GDI_ERROR) {
v_char buffer(dwSize) ;
GLYPHMETRICS gm2 = { 0 } ;
::GetGlyphOutline(hDC, chr, format, &gm2, buffer.size(), &buffer[0], &mat2) ;
v_plf = TTPOLYGON_to((LPTTPOLYGONHEADER)&buffer[0],dwSize) ;
}
}
return v_plf ;
}
vv_PLF GetTTOutline (HDC hDC,LPCTSTR str,const bool tate,const UINT format=GGO_NATIVE)
{
vv_PLF vv_plf ;
double lastX = 0 ; // left
double lastY = 0 ; // top tate
int ch = 0 ;
int cw = 0 ;
{ // chracter size
TEXTMETRIC tm = { 0 } ;
::GetTextMetrics(hDC,&tm) ;
ch = tm.tmHeight ;
cw = tm.tmAveCharWidth ;
}
#ifdef _UNICODE
LPCTSTR strP = str ;
#else
unsigned char* strP = (unsigned char*)str ;
#endif
for (size_t mbCount=0 ; mbCount<_tcsclen(str) && *strP!='\0' ; mbCount++) {
UINT chr = 0 ;
#ifdef _UNICODE
{
chr = UINT(*strP) ;
strP++ ;
}
#else
{
chr = _mbsnextc(strP) ;
strP= _mbsinc(strP) ;
}
#endif
if (chr == _T('\r')) { lastX = 0 ; continue ; }
if (chr == _T('\n')) { lastY-= ch ; continue ; }
if (chr == _T('\t')) { lastX+= cw ; continue ; }
{
GLYPHMETRICS gm = { 0 } ;
v_PLF v_plf = ::GetTTOutline(hDC,chr,&gm,format) ;
if (tate) { lastY -= (double(gm.gmBlackBoxY)+gm.gmCellIncX)/2 ; }
else { }
Vd2 last(lastX,lastY) ;
for (size_t index=0 ; index<v_plf.size() ; index++) {
PLF plf = v_plf[index] ;
Vd2A v2a = ::V2_Translate(::ToVd2A(::ToVd3A(plf)),last) ;
v_plf[index] = PLF(PLF::line,::ToVd4A(::ToVd3A(v2a))) ;
}
vv_plf.push_back(v_plf) ;
if (tate) { }
else { lastX += gm.gmCellIncX ; }
}
}
return vv_plf ;
}
Q243285 の動作の確認
先日からやっている GetGlyphOutline .その Q243285 の動作を確認方法.
Q243285: HOWTO: Draw TrueType Glyph Outlines
その中の ::DrawT2Outline の動作を確認しようとコンソール AP でやってみたが,うまく表示されない.
コードを見ればわかるが,::PolyBezier に与えられている pt の y がマイナスの範囲になっている.
以前書いたコードで emf に出力するものがある.また,それの HWND への再生も.
それで,HDC に対して直接出力するのではなく,一度 emf に出力して,それを表示することに.
#include <clocale>
#include <iostream>
#include <fstream>
#include <Windows.h>
#include "Q243285.hxx"
#include "con_wnd.hxx"
#include "E_MF.hxx"
LOGFONT GetLogFont(
LPCTSTR faceName
, const bool tate
, const int ch
, const int cw = 0
, const double rx = 0
, const double ry = 0
, const int weight = FW_DONTCARE
)
{
LOGFONT lf = { 0 } ;
::GetObject(::GetStockObject(SYSTEM_FONT),sizeof(lf),&lf) ;
if (tate) { lf.lfHeight = (cw!=0)?cw:ch ; lf.lfWidth = ch/2 ; }
else { lf.lfHeight = ch ; lf.lfWidth = cw ; }
if (tate) { lf.lfEscapement = int((rx+ry-90)*10) ; lf.lfOrientation= int((ry-90)*10) ; }
else { lf.lfEscapement = int(rx*10) ; lf.lfOrientation= int(ry*10) ; }
lf.lfWeight = weight ;
::TcsCpy(lf.lfFaceName,LF_FACESIZE,tstring(faceName).substr(0,LF_FACESIZE-1).c_str()) ;
return lf ;
}
tstring Get_TMP_path (void)
{
tstring tmp_path ;
size_t size = MAX_PATH ;
tmp_path.resize(size,0) ;
::GetTempPath(DWORD(size),&tmp_path[0]) ;
tmp_path = tstring(tmp_path.c_str()) ;
return tmp_path ;
}
bool DrawOutline(HDC hDC,int xStart,int yStart,LPCTSTR str)
{
#ifdef _UNICODE
LPCTSTR strP = str ;
#else
unsigned char* strP = (unsigned char*)str ;
#endif
// tstring tmp_path = ::Get_i_Tools_tmp_date() ;
tstring tmp_path = ::Get_TMP_path() ;
tstring emf_name ;
for (size_t mbCount=0 ; mbCount<_tcsclen(str) && *strP!='\0' ; mbCount++) {
UINT chr = 0 ;
#ifdef _UNICODE
{
chr = UINT(*strP) ;
strP++ ;
}
#else
{
chr = _mbsnextc(strP) ;
strP = _mbsinc(strP) ;
}
#endif
{
tstring tcs ;
{
if (chr < 0xff) {
tcs += TCHAR(chr) ;
}
else {
tstring buf ; buf.resize(255,0) ;
::_itot(chr,&buf[0],16) ;
tcs = buf.c_str() ;
}
std::tout << tcs << std::endl ;
}
{
emf_name = tmp_path + tcs + _T(".emf") ;
}
}
{
E_MetaF em(emf_name.c_str()) ;
HDC eDC = em.Get_HDC() ;
{
MAT2 mat2 = { {0, 1}, {0, 0}, {0, 0}, {0, 1} };
GLYPHMETRICS gm = { 0 };
DWORD dwSize = ::GetGlyphOutline(hDC,chr, GGO_NATIVE, &gm, 0, NULL, &mat2);
if (dwSize != GDI_ERROR) {
std::vector<char> buffer(dwSize);
GLYPHMETRICS gm2 = { 0 };
::GetGlyphOutline(hDC, chr, GGO_NATIVE, &gm2, buffer.size(), &buffer[0], &mat2);
::DrawT2Outline (eDC,(LPTTPOLYGONHEADER)&buffer[0],dwSize) ;
}
}
}
{
E_MetaF::Play(::GetConsoleHwnd(),emf_name.c_str()) ;
}
}
::TextOut(hDC,xStart,yStart+150,str,_tcslen(str)) ;
return true ;
}
bool test (c_tstring& str)
{
std::terr << str << std::endl ;
{
HWND hWnd = ::GetConsoleHwnd () ;
{
HDC hDC = ::GetDC(hWnd) ;
{
#define face_A _T("Arial")
#define face_M _T("Meiryo UI")
#define face_W _T("Wingdings")
LOGFONT lf = ::GetLogFont(face_M,false,100) ;
HFONT hFont = ::CreateFontIndirect(&lf) ;
HFONT oFont = (HFONT)::SelectObject(hDC,hFont) ;
::DrawOutline(hDC,200,200,str.c_str()) ;
::SelectObject(hDC,oFont) ;
::DeleteObject(hFont) ;
}
::ReleaseDC(hWnd,hDC) ;
}
}
return true ;
}
GetGlyphOutline TTPOLYCURVE
TTPOLYCURVE の TT_PRIM_LINE と TT_PRIM_QSPLINE は昨日までのコードでできそう.
TT_PRIM_CSPLINE についてもコードはわかりそうだが,実際のデータがちょっとわからない.
Q243285 の ::DrawT2Outline では,処理はしていないみたい.
::MakeBezierFromQBSpline では Quadratic から Cubic にしている.
12: 2次ベジェ曲線を3次に変換する方法
2次ベジェ曲線から3次ベジェ曲線への変換
今まで使っていたコードの TT_PRIM_CSPLINE 部分も同様で,特に問題はなかったようにも思う.
曲線の種類と Vd2A(2D の double の配列)を持つ「LQC クラス」を用意するか.
その配列 v_LQC が 1つの TTPOLYGONHEADER となる?
‘1’,’2′,’3′ などは 1つの TTPOLYGONHEADER .’A’ や ‘b’ は 2つ,’B’ や ‘8’ は 3つとなる.
1つの文字を表現するために v_LQC の配列の vv_LQC を用意.
ここまでで ::GetGlyphOutline の出力を vv_LQC として変換できる.
この vv_LQC をポリラインに変換して,v_PLF(複数のポリラインなどを保持可能)に登録する.
これで文字列を vv_PLF(そのままでは輪郭,先日の穴の処理をしてポリゴンに)として保持できる.
2023/03/17
TT_PRIM_CSPLINE に関して
GetGlyphOutline で uFormat に GGO_NATIVE ではなく GGO_BEZIER を指定.
すると,制御点が 2 つのデータが返される.
ポリラインに変換するコードは次の様なもの?
Vd2A LQC_3_to (const LQC& lqc)
{
Vd2A pts ;
if (lqc.size() > 0) {
Vd2 q0(lqc[0]) ;
Vd2 q1(lqc[0]) ;
Vd2 q2(lqc[0]) ;
Vd2 q3(lqc[0]) ;
for (size_t index=0 ; index+3<lqc.size() ; index++) {
if (index == 0) { q0 = lqc[index+0] ; }
q1 = lqc[index+1] ;
q2 = lqc[index+2] ;
q3 = lqc[index+3] ;
Vd2A va = ::bezier(q0,q1,q2,q3) ;
pts.insert(pts.end(),va.begin(),va.end()) ;
q0 = q3 ;
}
}
return pts ;
}
GetGlyphOutline の続き
TTPOLYCURVE は,ポリラインはそのまま扱えるが,ベジェ曲線の場合はそれなりの処理が必要.
TT_PRIM_LINE , TT_PRIM_QSPLINE , TT_PRIM_CSPLINE
それで ChatGPT に Bezier と問合わせると,
2次ベジェ曲線
B(t) = (1-t)^2 * P0 + 2t(1-t) * P1 + t^2 * P2
3次ベジェ曲線
B(t) = (1-t)^3 * P0 + 3t(1-t)^2 * P1 + 3t^2(1-t) * P2 + t^3 * P3
w0 から w3 を求める部分の挿入位置が違っていたが,ほぼそのままのコード.
C++ でのコードは次の様なもの.5 年位前に作成した 3 次ベジェ曲線 に合わせたコード.
Vd3A bezier (const Vd3& q0,const Vd3& q1,const Vd3& q2,const long div_c)
{
Vd3A vd3a ;
for (long index=0 ; index<=div_c ; index++) {
double t = 1./div_c*index ;
double b0 = (1.-t)*(1.-t) ; // (1-t)^2
double b1 = 2.*t *(1.-t) ; // 2 * (1-t) * t
double b2 = t*t ; // t^2
double px = (b0*q0.x) + (b1*q1.x) + (b2*q2.x) ;
double py = (b0*q0.y) + (b1*q1.y) + (b2*q2.y) ;
double pz = (b0*q0.z) + (b1*q1.z) + (b2*q2.z) ;
vd3a.push_back(Vd3(px,py,pz)) ;
}
return vd3a ;
}
2023/03/14
最初,1 つの TT_PRIM_QSPLINE が次の様になっている場合の始点,終点と制御点の与え方がわからなかった.
(8,0) (5,0) (1,4) (1,8) … ‘O’ の左下,見やすい様に整数にしている.
::bezier を 2 回呼出す.それぞれの値は次の様に指定する.
(8,0) (5,0) (3,2)
(3,2) (1,4) (1,8)
(3,2) は (5,0) と (1,4) の中点.
まだうまくない部分はあるが,何とか.
GetGlyphOutline の再調査
今動作しているコードを見ると,20 年以上前に書き始めたもの.
UNICODE 対応などの変更はあるが,基本的な動作はほとんど変わっていない.
MFC や古い自前のコードに依存している部分があるので,書き直そうかと…
その当時,どこを参考にしたのかわからない.一番古い部分は 1998 になっている.
テスト用に作成したプロジェクトのスケルトンの作成も 1997/04 のものがある.
次の所を参考にしたものと思うが,内容がだいぶ変わっている.
Q243285: HOWTO: Draw TrueType Glyph Outlines
::GetGlyphOutline の呼び出し方はそれほど難しくないが,返された TTPOLYGONHEADER などが難解.
当時はあまり理解できず,単純にポリラインに変換(QSpline2Polyline)していた.
今回検索すると,TTPOLYGONHEADER と TTPOLYCURVE などが詳しく書かれている所があった.
GetGlyphOutlineからアウトラインデータをもらおう
文字アウトラインの取得
Q87115: HOWTO: GetGlyphOutline() Native Buffer Format
2023/03/13
ここ のコードの結果を,2D データとしてダンプ.
{
tstring tmp_path ;
{
size_t size = MAX_PATH ;
tmp_path.resize(size,0) ;
::GetTempPath(DWORD(size),&tmp_path[0]) ;
tmp_path = tstring(tmp_path.c_str()) ;
}
tstring out_ipl = tmp_path ;
{
// UINT chr = character ;
if (chr < 0xff) {
out_ipl += TCHAR(chr) ;
}
else {
tstring buf ; buf.resize(255,0) ;
::_itot(chr,&buf[0],16) ;
out_ipl += buf.c_str() ;
}
out_ipl += _T(".ipl") ;
}
std::tout << out_ipl << std::endl ;
std::ofstream ipl_file(out_ipl.c_str()) ;
for (size_t r_index=0 ; r_index<result.size() ; r_index++) {
std::vector <LINE> vd = result[r_index] ;
for (size_t v_index=0 ; v_index<vd.size() ; v_index++) {
LINE d = vd[v_index] ;
ipl_file << _T("L") << std::endl ;
for (size_t p_index=0 ; p_index<d.points.size() ; p_index++) {
Vec2 p = d.points[p_index] ;
ipl_file << p.x << _T(",") << p.y << std::endl ;
}
ipl_file << std::endl ;
}
}
ipl_file.close() ;
}
%TEMP%\A.ipl は次の様になる.
L
36.5313,22
26.3125,50.2344
16.125,22
L
44.4219,0
38.6875,16
13.9375,16
8.20313,0
1,0
22.0469,57
30.9844,57
52,0
ipl の形式 f_ipl.hxx
個人的に作成した次のツールで表示可能.
https://jml.mish.work/index.php/i-tools/i3dx.html
コンソール AP で DC の使用
ChatGPT で「Windows コンソールアプリで DC を使用する方法」と入力してみた.
すると,使えそうなコードが表示された.
#include <Windows.h>
#include "i_define.hxx"
#include "con_wnd.hxx"
int main() {
// 1. コンソールウィンドウのハンドルを取得する
HWND consoleWindow = ::GetConsoleWindow();
// 2. コンソールウィンドウのDCを取得する
HDC consoleDC = GetDC(consoleWindow);
// 3. DCを使用して描画する
TextOut(consoleDC, 10, 100, _T("Hello, World!"), _tcslen(_T("Hello, World!")));
// 4. DCを解放する
ReleaseDC(consoleWindow, consoleDC);
return 0;
}
そのままではエラーになる部分があったので,少し修正している.
コンソール AP のウィンドウで GDI が使えるのは知らなかった.
BMP を表示するコード.
{
// ...
tstring dib_name = file_name ;
if (::File_IsNothing(dib_name)) { return false ; }
i_DIB dib = ::DIB_Load(dib_name.c_str()) ;
{
HWND hWnd = ::GetConsoleWindow() ;
{
RECT rect = { 0 } ;
::GetWindowRect(hWnd,&rect) ;
HDC hDC = ::GetDC(hWnd) ;
::DrawDIB(hDC,rect,dib) ;
::ReleaseDC(hWnd,hDC) ;
}
}
// ...
}
{
HWND hWnd = ::GetConsoleHwnd () ;
D_I_E die ;
die.SetDocPath(dib_name.c_str()) ;
die.Draw(hWnd) ;
}
error C2678: 二項演算子 ‘=’ :
ある処理を関数にしてコンパイルすると,
--------------------構成: VFDiv_1 - Win32 Debug--------------------
コンパイル中...
VFDiv_1V.cpp
c:\program files (x86)\microsoft visual studio\vc98\include\algorithm(232) : error C2678: 二項演算子 '=' : 型 'const struct Vector2<double>' の左オペランドを扱う演算子は定義されていません。(または変換できません)(新しい動作; ヘルプを参照)
c:\program files (x86)\microsoft visual studio\vc98\include\algorithm(228) : コンパイルされたクラスのテンプレートのインスタンス化 'void __cdecl std::_Iter_swap(const struct Vector2<double> *,const struct Vector2<double> *,struct Vector2<double> *)' の参照を確認してください
c:\program files (x86)\microsoft visual studio\vc98\include\algorithm(232) : error C2678: 二項演算子 '=' : 型 'const struct Vector2<double>' の左オペランドを扱う演算子は定義されていません。(または変換できません)(新しい動作; ヘルプを参照)
c:\program files (x86)\microsoft visual studio\vc98\include\algorithm(228) : コンパイルされたクラスのテンプレートのインスタンス化 'void __cdecl std::_Iter_swap(const struct Vector2<double> *,const struct Vector2<double> *,struct Vector2<double> *)' の参照を確認してください
cl.exe の実行エラー
VFDiv_1.exe - エラー 2、警告 0
対象が ‘const struct Vector2<double>’ となっているので,わかり辛かった.
次の関数の,’const Vd2A& v2a’ を std::reverse(v2a.begin(),v2a.end()) として使用しているのが原因.
PLF_face_l PLF_triangulation (const Vd2A& v2a)
{
// ...
std::reverse(v2a.begin(),v2a.end()) ;
// ...
}
Win7 Windows Update で 80072EFE
先日仮想マシンにセットアップした Win7 環境の Windows Update でエラー 80072EFE .
.NET Framework を使用しているものを実行すると,.NET … を要求され,そのセットアップに失敗してしまう.
「Win7 Windows Update 80072EFE」で検索すると…
Windows7 をインストール後、Windows Update が 80072EFE エラーでできない場合の対処方法
Windows Updateのエラー 8007000Eの対処はKB3138612で解決(Windows7)
2021年版:Windows 7 を久しぶりに更新する時の注意
KB3138612 を手動でインストールする必要があるみたい.
https://www.catalog.update.microsoft.com/Search.aspx?q=KB3138612
環境は x64 なので「Windows 7 for x64-Based Systems … (KB3138612)」をダウンロードしてインストール.
仮想マシンの再起動後 Windows Update に入ると,167 個の更新があった.
VirtualBox 7.0 に更新
Win10 と Win11 の ViurtalBox を 6.1.40 から 7.0.4 に更新した.
4K 環境になってから,いろいろと表示でうまくない部分があった.
Win10 環境は,Win11 の仮想マシンを除いては問題なさそう.
一度 Win11 仮想マシンを「除去」して,作成し直してみたが,うまく動作しない.
起動中の「お待ちください」の表示で止まってしまう.
仮想マシンの「設定」-「ディスプレイ」の「3Dアクセラレーションを有効化」にチェックして止まらなくなった.
どこかに設定があるものと思うが,OpenGL での表示がうまくされない.
Win11 環境は,LAN 関係のエラーでうまく起動しない.
6.1.40 に戻したがうまく起動しなくなってしまった.
「ネットワークアダプターを有効化」のチェックを外せば起動するが,これだとあまり使い物にならない.
ASUSTOR NAS でも同様のエラー になったことがあるので,設定ではないのか?
もう一度 7.0.4 にしてみたが,変わらず.
VM Name: Win10-VS6
Failed to open/create the internal network ‘HostInterfaceNetworking-VirtualBox Host-Only Ethernet Adapter’ (VERR_SUPDRV_COMPONENT_NOT_FOUND).
Failed to attach the network LUN (VERR_SUPDRV_COMPONENT_NOT_FOUND).
Result Code:
E_FAIL (0X80004005)
Component:
ConsoleWrap
Interface:
IConsole {6ac83d89-6ee7-4e33-8ae6-b257b2e81be8}
アンインストールやデバイスの削除を行って,再度インストールしたら少し動作する様になった.
デバイスマネージャーで VirtualBox Host-Only Ethernet Adapter がうまくなかったので,そこが原因だったか?
まだうまく動作しない仮想マシンもあるが,あとは個別の設定なので何とかなるかな?
2023/06/05
Win11 環境で 7.0.8 にあげて,LAN 関係で同じ様な現象.
PC を再起動して,うまく動作する様になった.