さて、このシリーズのトップはC++による独自塗潰し関数の紹介から始めましょう。
ご紹介するのは、20年以上前にツールバービットマップ作成、編集ツール(TBEditor.exe)を作った際に参考にさせていただいたFuzzy氏(注)の塗潰し関数のコードをアルゴリズムをそのままに書き直してみたものです。コメントを多く描いているので、詳細末尾にコードを載せますのではそちらを見てください。
注:webでFuzzy氏を探してみたのですが、もう何も引っ掛かりませんでした。残念です。
描画アルゴリズムは、
(1)初期設定として、描画始点(x, y)のy座標上、描画画面の左右を描画始点の領域色と同じ色のピクセル走査して、(lx, y)-(rx, y)という横線の情報をキュー(LINFO構造体配列Linfo)の先頭(Linfo_Top)に入れます。
(2)次にループに入り、キューの先頭からLINFO情報を取得して描画色(paint_color)で横線を描きます。
(3)その後、始点y座標の上(lx, y - 1)-(rx, y - 1)、下(lx, y + 1)-(rx, y + 1)についてScanLine関数で走査して、同じ領域色の横線情報をキューの末尾(Linfo_Bottom)に追加し、
(4)キューの先頭を進め(++Linfo_Top)て上記(2)に戻り、描画、走査を繰り返します。
(5)Linfo配列のポインターであるLinfo_TopとLinfo_Bottomが配列外に出てしまうと(== &Linfo[MAXSIZE])、また先頭に戻り、処理を続けます。
(6)描画処理はキューに描画情報が残る限り続きます。(Linfo_Top != Linfo_Bottom)
というものです。
オリジナルのコードはSetPixel関数(末尾のサンプルコードでは所要時間2.4秒)を使っていたのですが、LineTo関数にかえることで若干スピードアップ(同所要時間1.5秒)が図れましたが、矢張り赤ブラシでのExtFloddFillと比べると速さが全く違います。
ということで、このアルゴリズムは「ン~」と評価し、また他をあたってみます。
【Paint関数(含むScanLine関数)】
//////////////////////////////////////////////
// 同一領域色の横線情報による塗潰し
//描画画面の設定はg_Width - 1, g_Heightで行う
//////////////////////////////////////////////
#define MAXSIZE 1024 //バッファサイズは1024
//塗潰しを行う画面の外延(変更可能)
RECT PaintLimit = {0, 0, g_Width - 1, g_Height - 1};
//塗潰し横線情報構造体
typedef struct {
int Left_x; //領域左端のX座標
int Right_x; //領域右端のX座標
int Present_y; //領域のY座標
int Original_y; //元ラインのY座標
} LINFO, *PTLINFO;
//塗潰し情報配列(X軸上の塗潰し横線情報をY軸に沿ってキュー化)
LINFO Linfo[MAXSIZE];
//塗潰し情報配列の先頭、末尾ポインター
PTLINFO Linfo_Top, Linfo_Bottom;
//////////////////////////////////////
// 新規LINFO構造体にy座標の塗潰し横線
// 情報(lx <-- 領域色 --> rx)を記録/
/////////////////////////////////////
void ScanLine(HDC hDC, int lx, int rx, int y, int oy, COLORREF col) {
while(lx <= rx) {
//非領域色を飛ばす
while(lx < rx && GetPixel(hDC, lx, y) != col)
lx++;
//領域色が同一の横線(複数可)をLINFOに記録してゆく
if(GetPixel(hDC, lx, y) == col) {
Linfo_Bottom->Left_x = lx; //新規LINFOの左端を記録
//領域色を飛ばす
while(lx <= rx && GetPixel(hDC, lx, y) == col)
lx++;
Linfo_Bottom->Right_x = lx - 1; //新規LINFOの右端を記録
Linfo_Bottom->Present_y = y; //現在のy座標を記録
Linfo_Bottom->Original_y = oy; //現在のy座標を記録
if(++Linfo_Bottom == &Linfo[MAXSIZE]) //末尾を追加し、配列を超えたならば
Linfo_Bottom = Linfo; //先頭に戻す
}
//非領域色がない(lx == rx)場合終了
else
break;
}
}
////////////////////////////////////
//塗潰し横線情報配列をキューとして
//ScanLine関数で末尾に追加して行き、
//Paint関数で横線情報配列をキュー
//から取り出し、描画色で横線を引く
//ことにより、塗潰しを行う
////////////////////////////////////
void Paint(HDC hDC, int x, int y, COLORREF paint_color) {
//局所変数
int lx, rx, uy, dy, oy;
int Left_Rend, Right_Lend;
COLORREF col = GetPixel(hDC, x, y); //(x, y)の画素色を領域色として取得
//初期化
Linfo_Top = Linfo; //LINFO配列(横走査情報))先頭のポインター
Linfo_Bottom = Linfo + 1; //LINFO配列(横走査情報))末尾のポインター
Linfo_Top->Left_x = Linfo_Top->Right_x = x; //xに初期化
Linfo_Top->Present_y = Linfo_Top->Original_y = y; //yに初期化(始点判別条件)
//描画準備処理
HPEN hPen = CreatePen(PS_SOLID, 1, paint_color);
HPEN hOldPen = (HPEN)SelectObject(hDC, hPen);
//描画ループ
do {
lx = Linfo_Top->Left_x; //描画横線左端のX座標
rx = Linfo_Top->Right_x; //描画横線右端のX座標
uy = Linfo_Top->Present_y; //描画横線のy座標(上に走査する)
dy = Linfo_Top->Present_y; //描画横線のy座標(下に走査する)
oy = Linfo_Top->Original_y; //描画横線の元y座標
Left_Rend = lx - 1; //始点の左(左側走査の右端)
Right_Lend = rx + 1; //始点の右(右側走査の左端)
if(++Linfo_Top == &Linfo[MAXSIZE]) //Linfo_Topを一つ進め、配列を超えたならば
Linfo_Top = Linfo; //先頭に戻す
//領域色が描画色(処理済を含む)なら次のLINFO(塗りつぶし横線情報)にスキップする
if(GetPixel(hDC, lx, uy) == paint_color)
continue;
//右方向の境界を探す
while(rx < PaintLimit.right && GetPixel(hDC, rx + 1, uy) == col)
rx++;
//左方向の境界を探す
while(lx > PaintLimit.left && GetPixel(hDC, lx - 1, uy) == col)
lx--;
//lx - rxの線分を描画
MoveToEx(hDC, lx, uy, NULL);
LineTo(hDC, rx + 1, uy); //終点は描画されない為
//for(int i = lx; i <= rx; i++) //オリジナルはSetPixelによる処理
// SetPixel(hDC, i, uy, paint_color);
//現在の左右端(lx, rx)を使い、
if(--uy >= PaintLimit.top) { //一段上のラインを
//始点の横線の場合(初期化でuy == oy)
if(uy == oy) {
//始点の左(Left_Rend)、始点の右(Right_Lend)左右別々に走査する
ScanLine(hDC, lx, Left_Rend, uy, uy + 1, col);
ScanLine(hDC, Right_Lend, rx, uy, uy + 1, col);
}
else {
ScanLine(hDC, lx, rx, uy, uy + 1, col);
}
}
if(++dy < PaintLimit.bottom) { //一段下のラインを
//始点の横線の場合(初期化でuy == oy)
if(dy == oy) {
//始点の左(Left_Rend)、始点の右(Right_Lend)左右別々に走査する
ScanLine(hDC, lx, Left_Rend, dy, dy - 1, col);
ScanLine(hDC, Right_Lend, rx, dy, dy - 1, col);
}
else {
ScanLine(hDC, lx, rx, dy, dy - 1, col);
}
}
} while(Linfo_Top != Linfo_Bottom); //未処理の横線情報がある限りループ
//描画終了処理
SelectObject(hDC, hOldPen);
DeleteObject(hPen);
}
【テストプログラム-WM_PAINT部分のみ(赤-定番コード、緑-描画関係、青-コメント、橙-GDIオブジェクト関連)】
case WM_PAINT:
PAINTSTRUCT paint;
HDC hDC = BeginPaint(hWnd, &paint);
//ユーザー処理
HPEN hPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255)); //青のソリッドペン
HPEN hOldPen = (HPEN)SelectObject(hDC, hPen);
Rectangle(hDC, 10, 10, g_Width - 30, g_Height - 70);
Rectangle(hDC, 100, 100, g_Width - 120, g_Height - 160);
Rectangle(hDC, g_Width - 220, 120, g_Width - 140, g_Height - 180);
Ellipse(hDC, 120, 80, 200, g_Height - 180);
HBRUSH hBrush = CreateSolidBrush(RGB(255, 0, 0)); //赤のソリッドブラシ
HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);
if(g_PaintFlag) { //メニューまたはマウスクリックでg_PaintFlagが真となるように設定している
ExtFloodFill(hDC, 50, 50, GetPixel(hDC, 50, 50), FLOODFILLSURFACE); //この描画が実に速い
DWORD start = GetTickCount(); // スタート時間
Paint(hDC, 110, 110, RGB(0, 255, 0));
Paint(hDC, 150, 250, RGB(64, 128, 64));
Paint(hDC, g_Width - 200, 130, RGB(128, 64, 64));
Paint(hDC, 0, 0, RGB(0, 64, 64));
DWORD end = GetTickCount(); // 終了時間
char buff[MAX_PATH];
wsprintf(buff, "Time elapsed is %d miliseconds.", (end - start));
MessageBox(hWnd, buff, "CHeck time", MB_OK | MB_ICONINFORMATION);
}
SelectObject(hDC, hOldBrush);
DeleteObject(hBrush);
SelectObject(hDC, hOldPen);
DeleteObject(hPen); //DC、ペンやブラシなどを作ったら、必ず開放すること
EndPaint(hWnd, &paint); //BeginPaintで取得したhDCはReleaseDCで開放する必要はない。
break;







