ただ表示するだけでは面白くありません。と、いうのもビットマップは 矩形なので、時計の背景色とビットマップの背景色が同じでないと みっともない状態となるからです。
さて、左の図を見てください。ビットマップは猫の顔です。その周りの色と
時計の背景色が同じになっています。
時計の背景色を変えても大丈夫です。
さらに今回は、アナログ時計の盤の色や針の色もユーザーが自由に 変更できるようにします。
これは、どのようにしているかというとMaskBlt関数を使って実現しています。
(注:この関数はWindows95/98では動作しません。)
BOOL MaskBlt( HDC hdcDest, // 結合先のデバイスコンテキストハンドル int nXDest, // 結合先左上隅の x 座標 int nYDest, // 結合先左上隅の y 座標 int nWidth, // 結合元と結合先の長方形の幅 int nHeight, // 結合元と結合先の長方形の高さ HDC hdcSrc, // 結合元のデバイスコンテキストのハンドル int nXSrc, // 結合元左上隅の x 座標 int nYSrc, // 結合元左上隅の y 座標 HBITMAP hbmMask, // ビットマスクのハンドル int xMask, // マスクビットマップのx方向のオフセット int yMask, // マスクビットマップのy方向のオフセット DWORD dwRop // ラスタオペレーションコード );一見面倒くさい関数のように見えますが、前半の引数はBitBltと同じです。BitBlt関数に ついては第26章を参照してください。
hbmMask以降がBitBlt関数には無い引数です。
hdcDestから、nYSrcまでは、BitBlt関数と同じと考えてください。
次にマスクとなるビットマップを用意します。これは、白黒画像でなくては行けません。 このマスクのビットマップハンドルがhbmMaskです。
(xMask, yMask)は、マスク画像のどの位置からマスクとして使うかを指定します。 普通は元画像に合わせてマスクを作るので(0, 0)となります。
最後のdwRopですが、これはMAKEROP4マクロを使って指定します。
DWORD MAKEROP4( DWORD fore, // フォアグラウンド・ラスターオペレーションコード DWORD back // バックグラウンド・ラスターオペレーションコード );さて、ここでいうフォアグラウンドとかバックグラウンドとは何でしょうか。
実はマスク画像でビットが1(つまり白)の部分がフォアグラウンドのラスターオペレーションコードが、ビットが0(つまり黒)の部分がバックグラウンドのラスターオペーれーションが適応されます。
説明するより、例題を見た方がわかりやすいと思います。
左の図が表示したい画像です(50 * 50 の大きさにしてあります)。背景の青の部分はマスクしたいところです。
上の図の青い部分を黒(ビット0)にして、猫の顔の部分を白(ビット1)にした
マスク画像です。猫の顔がフォアグラウンド、その他の部分がバックグラウンド
ということになります。
フォアグラウンドのところは、そのままコピーして使って欲しいのでSRCCOPYですね。 バックグラウンドは結合先の塗りつぶし色で塗って欲しいのでPATCOPYですね。 従ってMaskBlt関数の最後の引数は
MAKEROP4(SRCCOPY, PATCOPY)となります。
では、プログラムを見てみましょう。まず、上のような50*50のビットマップを2つ用意します。
リソース名をそれぞれ"CAT", "MASK"としました。
// clock05.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU BEGIN POPUP "ダミーです" BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "オプション(&O)" BEGIN MENUITEM "背景色(&B)...", IDM_BACKGROUND MENUITEM "文字色(&T)...", IDM_TEXT MENUITEM "文字盤の色(&P)...", IDM_PLATE MENUITEM "長針の色(&L)...", IDM_LONG MENUITEM "短針の色(&S)...", IDM_SHORT MENUITEM "秒針の色(&O)...", IDM_SECOND END END END ///////////////////////////////////////////////////////////////////////////// // // Bitmap // CAT BITMAP "bitmap1.bmp" MASK BITMAP "bmp00001.bmp"メニュー項目がいくつか増えました。
ビットマップリソースも2つ追加となりました。
// clock05.cpp #define hParentKey HKEY_CURRENT_USER #define lpszSubKey "Software\\Kumei\\Clock" #define PAI 3.14159 #define CLOCK_WIDTH 250 //時計全体のウィンドウ幅 #define CLOCK_HEIGHT 60 //高さ #define MYTIMER 1 //タイマーID #include <windows.h> #include <math.h> #include "resource.h" typedef struct MYDATA { COLORREF cr_bg; //背景色 COLORREF cr_txt; //文字色 COLORREF cr_plate; //文字盤の色 COLORREF cr_short; //短針の色 COLORREF cr_long; //長針の色 COLORREF cr_second; //秒針の色 int x; int y; } INIDATA; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); HFONT SetMyFont(LPCTSTR, int); BOOL GetMyColor(HWND, COLORREF *, COLORREF); void GetInitialSettings(INIDATA *); BOOL GetDataDWORD(char *, DWORD *); BOOL SetInitialSettings(INIDATA); BOOL SetDataDWORD(char *, DWORD); BOOL ShowClock(HDC, char *, INIDATA); BOOL GetHMS(char *, int *, int *, int *); BOOL ShowBitmap(HDC, INIDATA); char szClassName[] = "clock05"; //ウィンドウクラス char szAppName[] = "猫クロック"; //アプリケーション名 HINSTANCE hInst;MYDATA構造体のメンバが増えました。文字盤の色とか針の色を記憶させておくためです。
ShowClock関数の引数が増えました。これは、構造体を見て文字盤とか針を描画する色を 決めるためですね。
ShowBitmap関数が新たに増えました。これで猫の顔を描画します。
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; BOOL bRet; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { if (bRet == -1) { break; } else { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int)msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst;//インスタンス wc.hIcon = (HICON)LoadImage(NULL, MAKEINTRESOURCE(IDI_APPLICATION), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); wc.hCursor = (HCURSOR)LoadImage(NULL, MAKEINTRESOURCE(IDC_ARROW), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = (HICON)LoadImage(NULL, MAKEINTRESOURCE(IDI_APPLICATION), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるWindowsプログラミング", //タイトルバーにこの名前が表示されます WS_POPUP, //ウィンドウの種類 0, //X座標 0, //Y座標 CLOCK_WIDTH, //幅 CLOCK_HEIGHT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }これらの関数に変更はありません。
//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id, x, y; SYSTEMTIME st; PAINTSTRUCT ps; HDC hdc; HFONT hFont; SIZE s; HRGN hRgn, hRgn1, hRgn2, hRound1Rgn, hRound2Rgn, hRectRgn; HBRUSH hBrush; HMENU hMenu, hSubMenu; POINT pt; COLORREF cr; static INIDATA inidata; RECT rc; char szYobi[8]; static char szBuf[64], szBuf2[64]; //時刻表示用 switch (msg) { case WM_CREATE: GetInitialSettings(&inidata); MoveWindow(hWnd, inidata.x, inidata.y, CLOCK_WIDTH, CLOCK_HEIGHT, TRUE); SetTimer(hWnd, MYTIMER, 1000, NULL); hRgn = CreateRectRgn(0, 0, 1, 1); hRgn1 = CreateRectRgn(0, 0, 1, 1); hRgn2 = CreateRectRgn(0, 0,1, 1); hRound1Rgn = CreateEllipticRgn(0, 0, CLOCK_HEIGHT, CLOCK_HEIGHT); hRectRgn = CreateRectRgn(CLOCK_HEIGHT / 2, 0, CLOCK_WIDTH - CLOCK_HEIGHT / 2, CLOCK_HEIGHT); CombineRgn(hRgn1, hRound1Rgn, hRectRgn, RGN_OR); hRound2Rgn = CreateEllipticRgn(CLOCK_WIDTH - CLOCK_HEIGHT, 0, CLOCK_WIDTH, CLOCK_HEIGHT); CombineRgn(hRgn2, hRound2Rgn, hRectRgn, RGN_OR); CombineRgn(hRgn, hRgn1, hRgn2, RGN_OR); SetWindowRgn(hWnd, hRgn, TRUE); DeleteObject(hRound1Rgn); DeleteObject(hRound2Rgn); DeleteObject(hRectRgn); DeleteObject(hRgn1); DeleteObject(hRgn2); break; case WM_RBUTTONDOWN: pt.x = LOWORD(lp); pt.y = HIWORD(lp); hMenu = LoadMenu(hInst, "MYMENU"); hSubMenu = GetSubMenu(hMenu, 0); ClientToScreen(hWnd, &pt); TrackPopupMenu(hSubMenu, TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, NULL); DestroyMenu(hMenu); break; case WM_LBUTTONDOWN: PostMessage(hWnd, WM_NCLBUTTONDOWN, (WPARAM)HTCAPTION, lp); break; case WM_TIMER: if (wp != MYTIMER) return DefWindowProc(hWnd, msg, wp, lp); GetLocalTime(&st); wsprintf(szBuf, "%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond); switch (st.wDayOfWeek) { case 0: strcpy(szYobi, "Sun"); break; case 1: strcpy(szYobi, "Mon"); break; case 2: strcpy(szYobi, "Tue"); break; case 3: strcpy(szYobi, "Wed"); break; case 4: strcpy(szYobi, "Thu"); break; case 5: strcpy(szYobi, "Fri"); break; case 6: strcpy(szYobi, "Sat"); break; } wsprintf(szBuf2, "%d/%02d/%02d(%s)", st.wYear, st.wMonth, st.wDay, szYobi); InvalidateRect(hWnd, NULL, TRUE); break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); hBrush = CreateSolidBrush(inidata.cr_bg); SelectObject(hdc, hBrush); PatBlt(hdc, 0, 0, CLOCK_WIDTH, CLOCK_HEIGHT, PATCOPY); ShowBitmap(hdc, inidata); hFont = SetMyFont("MS ゴシック", 30); SelectObject(hdc, hFont); GetTextExtentPoint32(hdc, szBuf, (int)strlen(szBuf), &s); x = (CLOCK_WIDTH - s.cx) / 2 + 4; y = (CLOCK_HEIGHT - s.cy) / 2 + 10; SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, inidata.cr_txt); TextOut(hdc, x, y, szBuf, (int)strlen(szBuf)); DeleteObject(hFont); hFont = SetMyFont("MS ゴシック", 16); SelectObject(hdc, hFont); GetTextExtentPoint32(hdc, szBuf2, (int)strlen(szBuf2), &s); x = (CLOCK_WIDTH - s.cx) / 2 + 4; TextOut(hdc, x, 4, szBuf2, (int)strlen(szBuf2)); DeleteObject(hFont); DeleteObject(hBrush); ShowClock(hdc, szBuf, inidata); EndPaint(hWnd, &ps); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_BACKGROUND: if (GetMyColor(hWnd, &cr, inidata.cr_bg)) { inidata.cr_bg = cr; } break; case IDM_TEXT: if (GetMyColor(hWnd, &cr, inidata.cr_txt)) { inidata.cr_txt = cr; } break; case IDM_PLATE: if (GetMyColor(hWnd, &cr, inidata.cr_plate)) { inidata.cr_plate = cr; } break; case IDM_SHORT: if (GetMyColor(hWnd, &cr, inidata.cr_short)) { inidata.cr_short = cr; } break; case IDM_LONG: if (GetMyColor(hWnd, &cr, inidata.cr_long)) { inidata.cr_long = cr; } break; case IDM_SECOND: if (GetMyColor(hWnd, &cr, inidata.cr_second)) { inidata.cr_second = cr; } break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよろしいですか", szAppName, MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { GetWindowRect(hWnd, &rc); inidata.x = rc.left; inidata.y = rc.top; DestroyWindow(hWnd); } break; case WM_DESTROY: SetInitialSettings(inidata); KillTimer(hWnd, MYTIMER); PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }親ウィンドウのプロシージャです。
WM_CREATEメッセージが来た時にSetTimer関数を実行していますが、 間隔を500から1000ミリセコンドに変更しました。もちろん500のままでも構いません。
WM_PAINTメッセージが来た時に、文字(時刻・日付)を描画していますが、ビットマップの表示部分を考慮してフォントの大きさを少しだけ小さくしました。
PatBlt関数で時計の背景を塗りつぶした後Showbitmap関数を呼んで、ビットマップを 表示しています。このような順番だとShowBitmap関数の2番目の引数は不要です(後述)。
文字を表示する位置も少しずらしました。
先にも書いたようにShowClock関数の引数が増えています。
メニューから、IDM_PLATE, IDM_SHORT, IDM_LOG, IDM_SECONDが選択されたら それぞれ、GetMyColor関数を呼んで、選択された色を構造体に記録しています。
HFONT SetMyFont(LPCTSTR face, int h) { HFONT hFont; hFont = CreateFont(h, //フォント高さ 0, //文字幅 0, //テキストの角度 0, //ベースラインとx軸との角度 FW_REGULAR, //フォントの重さ(太さ) FALSE, //イタリック体 FALSE, //アンダーライン FALSE, //打ち消し線 SHIFTJIS_CHARSET, //文字セット OUT_DEFAULT_PRECIS, //出力精度 CLIP_DEFAULT_PRECIS,//クリッピング精度 PROOF_QUALITY, //出力品質 FIXED_PITCH | FF_MODERN,//ピッチとファミリー face); //書体名 return hFont; } BOOL GetMyColor(HWND hWnd, COLORREF *lpcr, COLORREF org_cr) { CHOOSECOLOR cc; static DWORD dwCustColors[16]; cc.lStructSize = sizeof(CHOOSECOLOR); cc.hwndOwner = hWnd; cc.lpCustColors = dwCustColors; cc.rgbResult = org_cr; cc.Flags = CC_RGBINIT; if (ChooseColor(&cc)) { *lpcr = cc.rgbResult; return TRUE; } return FALSE; }これらの関数に変更点はありません。
void GetInitialSettings(INIDATA *lpini) { DWORD dwData; if (GetDataDWORD("background-color", &dwData)) { lpini->cr_bg = (COLORREF)dwData; } else { lpini->cr_bg = RGB(0, 255, 255); } if (GetDataDWORD("text-color", &dwData)) { lpini->cr_txt = (COLORREF)dwData; } else { lpini->cr_txt = RGB(0, 0, 0); } if (GetDataDWORD("x", &dwData)) { lpini->x = (int)dwData; } else { lpini->x = 0; } if (GetDataDWORD("y", &dwData)) { lpini->y = (int)dwData; } else { lpini->y = 0; } if (GetDataDWORD("short-color", &dwData)) { lpini->cr_short = (COLORREF)dwData; } else { lpini->cr_short = RGB(0, 0, 255); } if (GetDataDWORD("long-color", &dwData)) { lpini->cr_long = (COLORREF)dwData; } else { lpini->cr_long = RGB(0, 0, 0); } if (GetDataDWORD("second-color", &dwData)) { lpini->cr_second = (COLORREF)dwData; } else { lpini->cr_second = RGB(255, 0, 0); } if (GetDataDWORD("plate-color", &dwData)) { lpini->cr_plate = (COLORREF)dwData; } else { lpini->cr_plate = RGB(0, 255, 255); } return; }色指定が増えた分、レジストリから読み出す項目も増えています。
BOOL GetDataDWORD(char *szName, DWORD *dwValue) { HKEY hKey; DWORD dwPosition; DWORD dwType = REG_DWORD; DWORD dwByte = 32; RegCreateKeyEx(hParentKey, lpszSubKey, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &dwPosition); if (RegQueryValueEx(hKey, szName, NULL, &dwType, (BYTE *)dwValue, &dwByte) != ERROR_SUCCESS) { RegCloseKey(hKey); return FALSE; } RegCloseKey(hKey); return TRUE; }この関数に変更はありません。
BOOL SetInitialSettings(INIDATA inidata) { SetDataDWORD("background-color", inidata.cr_bg); SetDataDWORD("text-color", inidata.cr_txt); SetDataDWORD("x", inidata.x); SetDataDWORD("y", inidata.y); SetDataDWORD("short-color", inidata.cr_short); SetDataDWORD("long-color", inidata.cr_long); SetDataDWORD("second-color", inidata.cr_second); SetDataDWORD("plate-color", inidata.cr_plate); return TRUE; }設定項目が増えた分だけレジストリに書き込む項目も増えています。
BOOL SetDataDWORD(char *szName, DWORD dwData) { HKEY hKey; DWORD dwPosition; RegCreateKeyEx(hParentKey, lpszSubKey, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &dwPosition); RegSetValueEx(hKey, szName, 0, REG_DWORD, (CONST BYTE *)&dwData, sizeof(DWORD)); RegCloseKey(hKey); return TRUE; }この関数に変更はありません。
BOOL ShowClock(HDC hdc, char *lpszBuf, INIDATA inidata) { HBRUSH hBrush; HPEN hPen; int h, m, s, x0, y0, x, y; int l = CLOCK_HEIGHT / 2; if (strcmp(lpszBuf, "") == 0) return FALSE; hBrush = CreateSolidBrush(inidata.cr_plate); SelectObject(hdc, hBrush); Ellipse(hdc, 1, 1, CLOCK_HEIGHT - 1, CLOCK_HEIGHT - 1); Ellipse(hdc, 4, 4, CLOCK_HEIGHT - 4, CLOCK_HEIGHT - 4); DeleteObject(hBrush); GetHMS(lpszBuf, &h, &m, &s); x0 = y0 = CLOCK_HEIGHT / 2; //短針 if (h > 11) h -= 12; hPen = CreatePen(PS_SOLID, 4, inidata.cr_short); SelectObject(hdc, hPen); MoveToEx(hdc, x0, y0, NULL); x = (int)(x0 + (l - 16) * sin(h * PAI / 6 + m * PAI / 360)); y = (int)(x0 - (l - 16) * cos(h * PAI / 6 + m * PAI / 360)); LineTo(hdc, x, y); DeleteObject(hPen); //長針 hPen = CreatePen(PS_SOLID, 2, inidata.cr_long); SelectObject(hdc, hPen); MoveToEx(hdc, x0, y0, NULL); x = (int)(x0 + (l - 10) * sin(m * PAI / 30 + s * PAI / 1800)); y = (int)(y0 - (l - 10) * cos(m * PAI / 30 + s * PAI / 1800)); LineTo(hdc, x, y); DeleteObject(hPen); //秒針 hPen = CreatePen(PS_SOLID, 1, inidata.cr_second); SelectObject(hdc, hPen); MoveToEx(hdc, x0, y0, NULL); x = (int)(x0 + (l - 5) * sin(s * PAI / 30)); y = (int)(y0 - (l - 5) * cos(s * PAI / 30)); LineTo(hdc, x, y); DeleteObject(hPen); return TRUE; }文字盤の色、針の色を引数からもらってきたinidataを見て描画しています。
BOOL GetHMS(char *lpszBuf, int *lpH, int *lpM, int *lpS) { char szTemp[64], *token, szSep[] = ":"; strcpy(szTemp, lpszBuf); token = strtok(szTemp, szSep); *lpH = atoi(token); token = strtok(NULL, szSep); *lpM = atoi(token); token = strtok(NULL, szSep); *lpS = atoi(token); return TRUE; }この関数に変更はありません。
BOOL ShowBitmap(HDC hdc, INIDATA inidata) { HBITMAP hBmp, hMask; BITMAP bmp_info; HDC hdc_mem; int wx,wy; HBRUSH hBrush; hBrush = CreateSolidBrush(inidata.cr_bg); SelectObject(hdc, hBrush); hBmp = (HBITMAP)LoadImage(hInst, "CAT", IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR); hMask = (HBITMAP)LoadImage(hInst, "MASK", IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR); if (hMask == NULL) { MessageBox(NULL, "Error", szAppName, MB_OK); return FALSE; } GetObject(hBmp, (int)sizeof(BITMAP), &bmp_info); wx = bmp_info.bmWidth; wy = bmp_info.bmHeight; hdc_mem = CreateCompatibleDC(hdc); SelectObject(hdc_mem, hBmp); MaskBlt(hdc, CLOCK_WIDTH - wx - 5, 5, wx, wy, hdc_mem, 0, 0, hMask, 0, 0, MAKEROP4(SRCCOPY, PATCOPY)); DeleteObject(hBmp); DeleteObject(hMask); DeleteObject(hBrush); DeleteDC(hdc_mem); return TRUE; }猫のビットマップを表示する関数です。
WM_PAINTメッセージのところでも少し書きましたが、このプログラムでは CreateSolidBrush関数で時計の背景と同じ論理ブラシを作る必要はありません。 時計の背景を塗りつぶしたすぐ後にこの関数が呼ばれているので、ブラシは 塗りつぶしの時と同じものです。
しかし、後々のことも考えて(文字盤などを描画した後でこの関数を呼んだ場合は この関数で論理ブラシを作成しないと文字盤を塗りつぶした色になってしまうので) この関数内で、再度時計の背景を塗りつぶしたブラシを作っています。
この関数が後々も、今の順番で呼ばれることがわかっていればブラシ作りの部分は 省略して、引数も減らして構いません。
MaskBlt関数の最後の引数(ラスタオペーれーション・コード)をいろいろ変えてどのようになるか 実験してみてください。
Update 05/Jan/2003 By Y.Kumei