■ダブルバッファリングでちらつきの無い描画

グラフなどを画面に直接描画すると描画されていく様がチラチラと見えます。
しかもウィンドウを画面の外に持っていったりすると描画処理が遅くてフリーズしたみたいになってしまいます。

どうしてもCPUで描画するとフェッチ→デコード→実行→ライトバックのサイクルに縛られて少しずつしかグラフィックスにメモリ転送できません。
そこで、先にビットマップをメモリ上に作って置いてメモリをグラフィックスに直接DMA転送する超速い描画のダブルバッファリングという方法をやりたいと思います。

WM_PAINTで処理しているBitBlt関数がそのDMA転送する超速い正体です。
ただ、WM_PAINTメッセージが来る前にWM_ERASEBKGNDメッセージで背景が塗りつぶされるため、
WM_ERASEBKGNDメッセージを処理したことにしてOSに処理させないようにしないとチラチラします。

まあ、いちいち説明するよりもコード見せたほうが話しが早いと言うことで。
テキトウなダイアログのリソースを挿入すると動きます。
#include <windows.h>
#include "resource.h"

BOOL CALLBACK DlgProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hCurInst,HINSTANCE hPrevInst,
					LPSTR lpsCmdLine,int nCmdShow ){

	DialogBox(hCurInst,MAKEINTRESOURCE(IDD_DIALOG1),NULL,(DLGPROC)DlgProc);
	return 0;
}


HBITMAP Bitmap;
HDC BitmapDC;

//ダイアログプロシージャー
BOOL CALLBACK DlgProc(HWND hDlg,UINT msg,WPARAM wp,LPARAM lp){
	switch (msg){
		case WM_PAINT:

			//ビットマップがあったら画面に転送
			if(BitmapDC){
				PAINTSTRUCT ps;
				HDC hdc=BeginPaint(hDlg,&ps);
				BitBlt(hdc,0,0,100,100,BitmapDC,0,0,SRCCOPY );//100*100を転送
				EndPaint(hDlg,&ps);
			}
			break;
		case WM_ERASEBKGND://背景の描画を処理したことにする。
			return TRUE;

		case WM_COMMAND:
				{
					//ビットマップがあったら破棄する
					if(BitmapDC){
						DeleteDC(BitmapDC);
						DeleteObject(Bitmap);
						BitmapDC=NULL;
					}
					//ビットマップの作成
					HDC hdc=GetDC(hDlg);
					BitmapDC=CreateCompatibleDC(hdc);
					Bitmap=CreateCompatibleBitmap(hdc,100,100);//ビットマップを100*100で作成
					SelectObject(BitmapDC,Bitmap);
					ReleaseDC(hDlg,hdc);

					//ビットマップの描画の開始
					HBRUSH brush = CreateSolidBrush(RGB(0,0,255));//ブラシの作成
					SelectObject(BitmapDC,brush);//ブラシの適用
					Rectangle(BitmapDC,0,0,100,100);//四角に塗りつぶし
					SetPixel(BitmapDC,10,10,RGB(0xFF,0,0));//点を打つ
					DeleteObject(brush);//ブラシの破棄

					//再描画
					InvalidateRect(hDlg, NULL,1);
					UpdateWindow(hDlg);
				}
			break;
		case WM_CLOSE:
			EndDialog(hDlg,WM_CLOSE);
		break;
	}
	return FALSE;
}



■縮小拡大して描画

ビットマップを縮小拡大して描画するには、
StretchBlt関数を使います、でもたぶんいらない処理してるせいでBitBltよりも遅いです。
BitBltより二つ引数が多くなっています、増えた二つがコピー元の幅と高さです。
コピー先の高さと幅のバランスがおかしいと画像が縦に伸びたり横に伸びたりします。


ビットマップを拡大縮小してウィンドウの真ん中あたりに表示するサンプル
テキトウなダイアログのリソースを挿入すると動きます。
ダイアログはサイズ変更枠をつかいました。

#include <windows.h>
#include "resource.h"

BOOL CALLBACK DlgProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hCurInst,HINSTANCE hPrevInst,
					LPSTR lpsCmdLine,int nCmdShow ){

	DialogBox(hCurInst,MAKEINTRESOURCE(IDD_DIALOG1),NULL,(DLGPROC)DlgProc);
	return 0;
}


HBITMAP Bitmap;
HDC BitmapDC;
int Width=500;
int Height=500;

//ダイアログプロシージャー
BOOL CALLBACK DlgProc(HWND hDlg,UINT msg,WPARAM wp,LPARAM lp){
	switch (msg){
		case WM_SHOWWINDOW:
			{
				//ビットマップがあったら破棄する
				if(BitmapDC){
					DeleteDC(BitmapDC);
					DeleteObject(Bitmap);
					BitmapDC=NULL;
				}
				//ビットマップの作成
				HDC hdc=GetDC(hDlg);
				BitmapDC=CreateCompatibleDC(hdc);
				Bitmap=CreateCompatibleBitmap(hdc,Width,Height);//ビットマップを作成
				SelectObject(BitmapDC,Bitmap);
				ReleaseDC(hDlg,hdc);
				//ビットマップの描画の開始
				HBRUSH brush = CreateSolidBrush(RGB(0,0,255));//ブラシの作成
				SelectObject(BitmapDC,brush);//ブラシの適用
				Rectangle(BitmapDC,0,0,100,100);//四角に塗りつぶし
				SetPixel(BitmapDC,10,10,RGB(0xFF,0,0));//点を打つ
				DeleteObject(brush);//ブラシの破棄
			}
			break;
		case WM_PAINT:
			{
				RECT crc;
				GetClientRect(hDlg,&crc);

				int w=(crc.right-crc.left);
				int h=(crc.bottom-crc.top);

				PAINTSTRUCT ps;
				HDC hdc=BeginPaint(hDlg,&ps);
				//背景をぬりつぶす
				HBRUSH brush = CreateSolidBrush(RGB(255,255,255));//ブラシの作成
				SelectObject(hdc,brush);//ブラシの適用
				Rectangle(hdc,0,0,w,h);//四角に塗りつぶし
				DeleteObject(brush);//ブラシの破棄

				//ビットマップがあったら画面に転送
				if(BitmapDC){
					double pos;
					if(((double)h/(double)Height)<((double)w/(double)Width)){
						pos=(double)h/(double)Height;
					}else{
						pos=(double)w/(double)Width;
					}

					int _h=(int)((double)Height*pos);
					int _w=(int)((double)Width*pos);

					StretchBlt(hdc,(w-_w)/2,(h-_h)/2,_w,_h,BitmapDC,0,0,Width,Height,SRCCOPY );
				}
				EndPaint(hDlg,&ps);
			}
			break;
		case WM_SIZE://サイズ変更されたら描画
			InvalidateRect(hDlg, NULL,1);
			UpdateWindow(hDlg);
			break;
		case WM_ERASEBKGND://背景の描画を処理したことにする。
			return TRUE;
		case WM_CLOSE:
			EndDialog(hDlg,WM_CLOSE);
		break;
	}
	return FALSE;
}

実行してウィンドウのサイズを変更するとビットマップが常に真ん中あたりに表示されるはずです。





■ファイルからビットマップを取り込んで表示する

上のプログラムをちょっと変更してウィンドウにビットマップファイルをドラッグアンドドロップすると表示するようにします。
テキトウなダイアログのリソースを挿入すると動きます。
ダイアログはサイズ変更枠をつかいました。
ダイアログのリソースはドラッグアンドドロップを許可にチェックを入れました。←じゃないとドラッグが出来ないもんで。
#include <windows.h>
#include "resource.h"

BOOL CALLBACK DlgProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hCurInst,HINSTANCE hPrevInst,
					LPSTR lpsCmdLine,int nCmdShow ){

	DialogBox(hCurInst,MAKEINTRESOURCE(IDD_DIALOG1),NULL,(DLGPROC)DlgProc);
	return 0;
}


HBITMAP Bitmap;
HDC BitmapDC;
char FileName[1024];
int Width;
int Height;


//ダイアログプロシージャー
BOOL CALLBACK DlgProc(HWND hDlg,UINT msg,WPARAM wp,LPARAM lp){
	switch (msg){
		case WM_DROPFILES:
			//ビットマップがあったら破棄する
			if(BitmapDC){
				DeleteDC(BitmapDC);
				DeleteObject(Bitmap);
				BitmapDC=NULL;
			}
			DragQueryFile((HDROP)wp,0,FileName,1024);//ドラッグされた0個目です。
			Bitmap=(HBITMAP)LoadImage( NULL, FileName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE );
			if(Bitmap){
				HDC hdc=GetDC(hDlg);
				BitmapDC=CreateCompatibleDC(hdc);
				SelectObject(BitmapDC,Bitmap);
				ReleaseDC(hDlg,hdc);
				//ビットマップの幅と高さを調べる
				BITMAP	pos;
				GetObject(Bitmap, sizeof(BITMAP), &pos);
				Width=pos.bmWidth;
				Height=pos.bmHeight;
			}
			InvalidateRect(hDlg, NULL,1);
			UpdateWindow(hDlg);
			break;
		case WM_PAINT:
			{
				RECT crc;
				GetClientRect(hDlg,&crc);

				int w=(crc.right-crc.left);
				int h=(crc.bottom-crc.top);

				PAINTSTRUCT ps;
				HDC hdc=BeginPaint(hDlg,&ps);
				//背景をぬりつぶす
				HBRUSH brush = CreateSolidBrush(RGB(255,255,255));//ブラシの作成
				SelectObject(hdc,brush);//ブラシの適用
				Rectangle(hdc,0,0,w,h);//四角に塗りつぶし
				DeleteObject(brush);//ブラシの破棄

				//ビットマップがあったら画面に転送
				if(BitmapDC){
					double pos;
					if(((double)h/(double)Height)<((double)w/(double)Width)){
						pos=(double)h/(double)Height;
					}else{
						pos=(double)w/(double)Width;
					}

					int _h=(int)((double)Height*pos);
					int _w=(int)((double)Width*pos);

					StretchBlt(hdc,(w-_w)/2,(h-_h)/2,_w,_h,BitmapDC,0,0,Width,Height,SRCCOPY );
				}
				EndPaint(hDlg,&ps);
			}
			break;
		case WM_SIZE://サイズ変更されたら描画
			InvalidateRect(hDlg, NULL,1);
			UpdateWindow(hDlg);
			break;
		case WM_ERASEBKGND://背景の描画を処理したことにする。
			return TRUE;
		case WM_CLOSE:
			EndDialog(hDlg,WM_CLOSE);
		break;
	}
	return FALSE;
}

実行してビットマップファイルをドラッグアンドドロップするとウィンドウに表示されます。
もちろんウィンドウサイズを変更すると拡大縮小されますよ。



▲トップページ > Windows と C++