007-09(1)  前へ←  ホームへ  →次へ  007-10(1)

No.007 - 09-2  円描画(2)


ブレゼンハム とミッチェナーの一般的なソースより、もう少しだけ精度を高くしたアルゴリズムを使った円描画です。
速度も落としません。一応自前ルーチンなので他のサイト・書籍には載ってません、あしからず。


・ブレゼンハム
(Bresenham) とミッチェナー(Michener)のアルゴリズムの問題

前回のまとめでも書きましたが
ブレゼンハムとミッチェナーのアルゴリズムには少し問題があります。

1.引数に半径を指定しているので、奇数の直径を指定出来ない。
    大きな円ならわかりませんが、小さな円なら露骨になります。

2.実は精度が良くない。
    精度が高くて遅いシンプルなルーチンと書いて、
    同じ中心、同じ半径でブレゼンハムとミッチェナー描画するとピッタリ重ならないのがわかります。

・ブレゼンハムはなぜ精度が悪い?

中点分岐法に限っていえば、ブレゼンハムは中点をピクセル判定の材料にしていますが、
円描画は2乗演算する都合上、中点で計算した誤差は「誤差の中点」にならないということです。
つまり中点から見て、yy-1 の座標は等誤差の距離にないのです。
それゆえ、真円よりすこし小さめな円になります。



1.円描画ルーチン


・なにはともあれ高精度ルーチンの用意

前回のサンプルソースから。
シンプルで、とても基本に忠実で遅い関数です。これをベースとします。
シンプル円描画2  (値のより近いピクセルを選択する方式)

// シンプル円描画2(値のより近いピクセルを選択する方式)
void ReallySimpleCircle2 (HDC hdc, LONG radius, POINT center, COLORREF col){
LONG cx = 0, cy=radius;
double xLimit = sqrt ((double) (radius * radius) / 2); // 45度→r*√2
double d1, d2;

for (cx=0; cx <= xLimit ; cx++){
d1 = (cx * cx + cy * cy) - radius * radius;
d2 = (cx * cx +(cy-1)*(cy-1)) - radius * radius;
if (abs(d1)>abs(d2)) cy--;
SetPixel (hdc, cx + center.x, cy + center.y, col); // 45-90 度の間
SetPixel (hdc, cx + center.x, -cy + center.y, col); // 270-315 度の間
SetPixel (hdc, -cx + center.x, cy + center.y, col); // 90-135 度の間
SetPixel (hdc, -cx + center.x, -cy + center.y, col); // 225-270 度の間
SetPixel (hdc, cy + center.x, cx + center.y, col); // 0-45 度の間
SetPixel (hdc, cy + center.x, -cx + center.y, col); // 315-360 度の間
SetPixel (hdc, -cy + center.x, cx + center.y, col); // 135-180 度の間
SetPixel (hdc, -cy + center.x, -cx + center.y, col); // 180-225 度の間
}
}





・引数を半径→直径にする

上記のアルゴリズムでは引数に半径を指定しているのに
できあがる円は奇数の直径という「おいおい、直径≠2*半径なの?」と思わずツッコミの余地満載な仕様です。
前回のミッチェナー、ブレゼンハムのソースも同様です。
(ただし、ミッチェナー、ブレゼンハムは飽くまで8分円のアルゴリズムなので、ミッチェナー、ブレゼンハムが悪いのでは無いです。)

まず、これを改善します。
「直径≠2*半径」なのは中心点の反対側にピクセルを置く都合上の問題ですが、
それには奇数直径と、偶数直径のときの中心点を考えます。
直径4〜5ピクセルの例を見てみます。
直径4ピクセル
直径5ピクセル 直径4ピクセル

直径5ピクセルの中心は、座標(2, 2) でキマリですが、
直径4ピクセルの中心は、座標(1, 1) なのか、(2, 2) なのか、はっきりしません。

では直径2ピクセルの例を見てみます。
直径2ピクセル
直径2ピクセル

中心は座標(0, 0) か、座標(1, 1) ですが、
アナログに人間感覚で決めちゃうなら、「少なくとも(1, 1)じゃないんじゃない?」、ということで
直径2ピクセルの中心は座標(0, 0)です。

したがって 直径4ピクセルの中心は、座標(1, 1)です。
直径4ピクセル
正方向になら(center + r) でピクセル座標がでて丁度いいです。
(これは当サイトの考え方ですので、また別の考え方なら(2,2)になることもあります)

さて中心点の反対側のピクセル「鏡像ピクセル」を置く都合上の問題です。
直径5ピクセルのとき 半径=2、中心(2, 2) なので、(4, 2) の反対側は(0, 2) で、直径ピッタリです。
直径4ピクセルのとき 半径=2、中心(1, 1) なので、(3, 1) の反対側は(-1, 1) で、直径オーバーです。

ということで 直径偶数のとき、鏡像ピクセルを置こうとするとハミでるので、鏡像用の中心点を考えます。
鏡像は、1ピクセルのオーバーなので
中心座標が(x, y)なら
鏡像用の中心座標は(x+1, y+1)です。



また引数を半径→直径にかえたことで計算式も少し変えます。
radius → diameteter/2 に変えたあと、割り算の部分も消します。
具体的には、d1, d2 を4倍してます。
cy の初期値は割り算入ったままでダイジョブです。というか入ってた方がいいです。


引数を半径→直径にする

void NewCircleAlgorithm (HDC hdc, LONG diameter, POINT center, COLORREF col){
LONG cx = 0, cy=diameter/2+1;
double d1, d2;
POINT mirror_center = center;
if ((diameter&1) ==0){
mirror_center.x++;
mirror_center.y++;
}

for (cx=0; cx <= cy ; cx++){
d1 = 4*(cx * cx + cy * cy) - diameter * diameter;
d2 = 4*(cx * cx + (cy-1) * (cy-1)) - diameter * diameter;
if (abs(d1)>abs(d2)) cy--;

SetPixel (hdc, cy + center.x, cx + center.y, col); // 0-45 度の間
SetPixel (hdc, cx + center.x, cy + center.y, col); // 45-90 度の間

SetPixel (hdc, -cx + mirror_center.x, cy + center.y, col); // 90-135 度の間
SetPixel (hdc, -cy + mirror_center.x, cx + center.y, col); // 135-180 度の間

SetPixel (hdc, -cy + mirror_center.x, -cx + mirror_center.y, col); // 180-225 度の間
SetPixel (hdc, -cx + mirror_center.x, -cy + mirror_center.y, col); // 225-270 度の間

SetPixel (hdc, cx + center.x, -cy + mirror_center.y, col); // 270-315 度の間
SetPixel (hdc, cy + center.x, -cx + mirror_center.y, col); // 315-360 度の間
}
}



・メインループの計算式の最適化
  (初期段階)

まず
if (abs(d1)>abs(d2)) cy--;
の abs(d1)>abs(d2) の比較部分ですが、

d1が円の外側、d2が円の内側なので
d1>0、d2<0

従って
d1>-d2
から
d1+d2>0


ここで
d = d1+d2;
とおいて置きます。


d = 4 (x 2  + y 2 ) - diameter 2  +  4 (x 2 + (y-1) 2 ) - diameter 2
   = 8x 2  + 8y 2 - 8y +4 - 2diameter 2

d は明らかに2の倍数であり if (d>0) 〜 でしか判定しないので2で割ります。
d = 4x 2  + 4y 2  -4y +2 - diameter 2



x が1増えると
4x 2    →   4(x+1) 2   = 4x 2  +8x +4
から
d 8x +4 増えます


同様に y が1減るとき
4y 2  -4y   →   4(y-1) 2  -4(y-1)  = 4y 2  -12y +8
から
d -8y +8 増えます


また d の初期値
d = 4x0 2  + 4y0 2 - 4y0 +2 - diameter 2
   = 4y0 2 - 4y0 +2 - diameter 2



メインループの計算式の最適化(初期段階)

// 直径引数
void NewCircleAlgorithm (HDC hdc, LONG diameter, POINT center, COLORREF col){
        LONG    cx = 0, cy=diameter/2+1;
        double d;
        d = -diameter*diameter + 4*cy*cy -4*cy +2;
        POINT mirror_center = center;
        if ((diameter&1) ==0){
                mirror_center.x++;
                mirror_center.y++;
        }

        for (cx=0; cx <= cy ; cx++){
                if (d>0) {
                        d += -8*cy+8;
                        cy--;
                }
                SetPixel (hdc,  cy + center.x,  cx + center.y, col);            // 0-45         度の間
                SetPixel (hdc,  cx + center.x,  cy + center.y, col);            // 45-90        度の間

                SetPixel (hdc, -cx + mirror_center.x,  cy + center.y, col);     // 90-135       度の間
                SetPixel (hdc, -cy + mirror_center.x,  cx + center.y, col);     // 135-180      度の間

                SetPixel (hdc, -cy + mirror_center.x, -cx + mirror_center.y, col);      // 180-225      度の間
                SetPixel (hdc, -cx + mirror_center.x, -cy + mirror_center.y, col);      // 225-270      度の間

                SetPixel (hdc,  cx + center.x, -cy + mirror_center.y, col);     // 270-315      度の間
                SetPixel (hdc,  cy + center.x, -cx + mirror_center.y, col);     // 315-360      度の間

                d += 8*cx+4;
        }
}



・メインループの計算式の最適化
  (最終段階)


通常 d の増分は
x が1増えるとき  →  d += 8x +4
y が1減るとき    →  d += -8y +8

dx = 8x +4
dy = 8y +8

とおくと
x が1増えるとき  →  d += dx;  dx += 8
y が1減るとき    →  d += dy;  dy += 8

円描画  (メインループ完成形)

// 直径引数
void NewCircleAlgorithm (HDC hdc, LONG diameter, POINT center, COLORREF col){
    LONG    cx = 0, cy=diameter/2+1;
    double d;
    d = -diameter*diameter + 4*cy*cy -4*cy +2;
    int dx, dy;
    dx = 4;
    dy = -8*cy+8;
    POINT mirror_center = center;
    if ((diameter&1) ==0){
        mirror_center.x++;
        mirror_center.y++;
    }

    for (cx=0; cx <= cy ; cx++){
        if (d>0) {
            d += dy;
            dy += 8;
            cy--;
        }
        SetPixel (hdc,  cy + center.x,  cx + center.y, col);        // 0-45     度の間
        SetPixel (hdc,  cx + center.x,  cy + center.y, col);        // 45-90    度の間

        SetPixel (hdc, -cx + mirror_center.x,  cy + center.y, col); // 90-135   度の間
        SetPixel (hdc, -cy + mirror_center.x,  cx + center.y, col); // 135-180  度の間

        SetPixel (hdc, -cy + mirror_center.x, -cx + mirror_center.y, col);  // 180-225  度の間
        SetPixel (hdc, -cx + mirror_center.x, -cy + mirror_center.y, col);  // 225-270  度の間

        SetPixel (hdc,  cx + center.x, -cy + mirror_center.y, col); // 270-315  度の間
        SetPixel (hdc,  cy + center.x, -cx + mirror_center.y, col); // 315-360  度の間

        d += dx;
        dx+=8;
    }
}




2.範囲補正、クリッピング


・範囲補正、クリッピング・・・をする前に


お気づきのかたも多いと思いますが、円のクリッピングは大変です。
上記の1回のループで8ピクセル同時描画のままでは範囲補正できません。

点対称で描画してる都合上、
片方は画面からハミでてんのに、片方はハミでてないとき範囲補正しようがないのです。
なので
1回のループで8ピクセル同時描画 → 8回のループで1ピクセルずつ描画です。
若干非効率な気がしますが、
1ピクセルずつ if (x>=0 && x<width && y<0 && y<height) 〜
なんてやるわけに行かないのでしゃーないのです。

また1回のループ → 8回のループで増える演算は
    if (d>0) { d += dy; dy += 8; cy--; } と、d += dx; dx+=8;
    と初期値の設定
だけなので我慢しましょう。



各円弧で描画

// 直径引数
void NewCircleAlgorithm (HDC hdc, LONG diameter, POINT center, COLORREF col, RECT *rc){
    long    cx = 0, cy=diameter/2+1, *px, *py;
    long dx, dy, x_sign, y_sign, num_eigth;
    double d;
    d = -diameter*diameter + 4*cy*cy -4*cy +2;
    dx = 4;
    dy = -8*cy+8;
    POINT mirror_center = center, now_center;
    if ((diameter&1) ==0){
        mirror_center.x++;
        mirror_center.y++;
    }

    // メインループ
    for(num_eigth=0; num_eigth <8 ; num_eigth++){
        /*
            \2|1/       [0] 0-45度の間      [1] 45-90度の間
            3\|/0       [2] 90-135度の間    [3] 135-180度の間
          ------※------
            4/|\7       [4] 180-225度の間   [5] 225-270度の間
            /5|6\       [6] 270-315度の間   [7] 315-360度の間
        */
        if (num_eigth<4){
                now_center.y = center.y; y_sign=1; }            // 0,1,2,3
        else{    now_center.y = mirror_center.y; y_sign=-1; }    // 4,5,6,7
        if ((num_eigth%6)<=1){
                now_center.x = center.x;  x_sign=1; }            // 0,1,6,7
        else {    now_center.x = mirror_center.x; x_sign=-1; }    // 2,3,4,5
        if ((num_eigth%4)%3){
                px = &cx; py = &cy; }    // 0,3,4,7
        else {    px = &cy; py = &cx; }    // 1,2,5,6

        cy=diameter/2+1;
        cx=0;

        // d 値
        d = -diameter*diameter + 4*cx*cx + 4*cy*cy -4*cy +2;
        dx = 4;
        dy = -8*cy+8;

        for (cx=0;cx <= cy;cx++){
            if (d>0) {
                d += dy;
                dy += 8;
                cy--;
            }
            SetPixel (hdc, (*px)*x_sign + now_center.x, (*py)*y_sign + now_center.y, col);    // 描画

            d += dx;
            dx+=8;
        }
    }
}

このソースの
        if (num_eigth<4){
                now_center.y = center.y; y_sign=1; }            // 0,1,2,3
        else{   now_center.y = mirror_center.y; y_sign=-1; }    // 4,5,6,7
        if ((num_eigth%6)<=1){
                now_center.x = center.x;  x_sign=1; }           // 0,1,6,7
        else {  now_center.x = mirror_center.x; x_sign=-1; }    // 2,3,4,5
        if ((num_eigth%4)%3){
                px = &cx; py = &cy; }   // 0,3,4,7
        else {  px = &cy; py = &cx; }   // 1,2,5,6
となってる部分は
if (num_eigth==0) {・・・}
if (num_eigth==1) {・・・}
if (num_eigth==2) {・・・}
・・・

とした方が実行速度は速いのですが、さすがにそれは小学生でもできるコードなので、
敢えてこんなコードです。






・範囲補正、クリッピング処理の考え方



さて、本題のクリッピングです。

右図を見て下さい。
A点の座標を調べます。
右図のA点の座標は(x1,y1)ですが、
使いたい値は中心点からみた仮想座標なので、

A点の便宜座標は
(center.x-x1, center.y-y1)

では矩形でクリッピングすることを考えます。
A点の y 座標はわかるので、y 座標から x 座標を求め、得られたxy座標を使ってクリッピングします。

y 座標から x 座標を求めるとき平方根を使います。

平方根を求めるのに小数点はいらないので
自前の整数版ニュートン法で平方根を取得します。
整数版ニュートン法
root_i(int x){
    int s=1, s2=1;
    do {
        s=(x/s+s)/2;
        s2=s+1;
        if (s*s<=x && x<s2*s2) break;
    } while(1);
    return s;
}

ニュートン法で得られる平方根は
n <= x < n+1
となるような n なので n と n+1 どちらの誤差のほうが小さいか調べて選択します。
これで座標は得られました。



さて、座標が得られたら、
最大のネック、8分円においての初期座標・終了座標を設定します。

右図を見て下さい。
0,3,4,7 の y座標は 計算上は x座標です。
このへんがネックです。

例えば上辺の座標を計算しても
1, 2は y 座標の初期座標となり、
0, 3は x 座標の終了座標となり、
4, 7は x 座標の初期座標となり、
5, 6は y 座標の終了座標となります。

これをそれぞれ下辺、右辺、左辺についてもやるわけです。
相当やっかいです。





この範囲補正方法はいくつか種類があるんですが、
筆者が考えた一番カンタン(と思われる方法)がこちらです。

まず上辺の場合で考えます。
上辺はy座標がいくつの値のときに、
どの部分と交わり、どの範囲に影響するかというのを調べた時に
こんな表ができます
範囲 交わる部分 交点の種別 完全非描画となる部分
√2 <y 1,2 y 座標の初期座標 なし
0<y<√2 0,3 x 座標の終了座標 1,2
-√2 <y< 0 4,7 x 座標の初期座標 0,1,2,3
y < -√2 5,6 y 座標の終了座標 0,1,2,3,4,7

また左辺の場合も考えます。
範囲 交わる部分 交点の種別 完全非描画となる部分
√2 <y 3,4 y 座標の初期座標 なし
0<y<√2 2,5 x 座標の終了座標 3,4
-√2 <y< 0 1,6 x 座標の初期座標 2,3,4,5
y < -√2 7,0 y 座標の終了座標 1,2,3,4,5,6
とまあこういうカンジで下辺、右辺も考えると各範囲々々で、
ある数字の並びの法則が見えてきます。

上辺→左辺→下辺→右辺の並びでやると
√2 <y の範囲なら 1,2 → 3,4 → 5,6 → 7,0 …[i*2+1]、[(i*2+2)%8]
0<y<√2 の範囲なら 0,3 → 2,5 → 4,7 → 6,1 …[i*2]、[(i*2+3)%8]
-√2 <y< 0 の範囲なら 4,7 → 6,1 → 0,3 → 2,5 …[(i*2+4)%8]、[(i*2+7)%8]
y < -√2 の範囲なら 5,6 → 7,0 → 1,2 → 3,4 …[(i*2+5)%8]、[(i*2+6)%8]

コレを使います。
コレ思いつくのに1週間かかったよ。







矩形のク リッ ピング補正部分のみ

    for(int li=0;li<4;li++){
        if (li==0) { cy = center.y-(rc->bottom-1);  y_sign=-1;  }
        if (li==1) { cy = mirror_center.x-rc->left; y_sign=1;       }
        if (li==2) { cy = mirror_center.y-rc->top;  y_sign=1;   }
        if (li==3) { cy = center.x-(rc->right-1);   y_sign=-1;  }

        if (abs(cy)>=(diameter/2)) {
            if (((li==0 || li==3) && cy<0) || ((li==1 || li==2) && cy>0)) continue; // 円は範囲内
            else return ;   // 円は完全に範囲外
        }

        tmp = diameter*diameter -4*cy*cy;
        cx = root_i(tmp/4);   // n=tmp/4; if (tmp%4) n++;
        tmp -= 4*cx*cx;
        if (abs(tmp)>=abs(tmp -8*cx-4)) cx++;

        /*
            \2|1/       [0] 0-45度の間      [1] 45-90度の間
            3\|/0       [2] 90-135度の間    [3] 135-180度の間
          ------※------
            4/|\7       [4] 225-270度の間   [5] 180-225度の間
            /5|6\       [6] 270-315度の間   [7] 315-360度の間
        */
        if (cy*y_sign>r_root2){
            // 1,2 → 3,4 → 5,6 → 7,0
            start_po[li*2+1].y = start_po[(li*2+2)%8].y = abs(cy);
            start_po[li*2+1].x = start_po[(li*2+2)%8].x = abs(cx);
        }
        else {
            start_po[li*2+1].y = start_po[(li*2+2)%8].y = 0;        // 範囲外指定
            start_po[li*2+1].x = start_po[(li*2+2)%8].x = diameter; // 範囲外指定
            if (cy*y_sign<=r_root2 && cy*y_sign>0){
                // 範囲外指定 … 1,2 → 3,4 → 5,6 → 7,0
                // 0,3 → 2,5 → 4,7 → 6,1
                end_po[li*2].y = end_po[(li*2+3)%8].y = abs(cx);
                end_po[li*2].x = end_po[(li*2+3)%8].x = abs(cy);
            }
            else {
                start_po[li*2].y = start_po[(li*2+3)%8].y = 0;
                start_po[li*2].x = start_po[(li*2+3)%8].x = diameter;
                if (cy*y_sign<=0 && cy*y_sign>-r_root2){
                    // 範囲外指定 … 0,3 → 2,5 → 4,7 → 6,1
                    // 4,7 → 6,1 → 0,3 → 2,5
                    start_po[(li*2+4)%8].y = start_po[(li*2+7)%8].y = abs(cx);
                    start_po[(li*2+4)%8].x = start_po[(li*2+7)%8].x = abs(cy);
                }
                else {
                    start_po[(li*2+4)%8].y = start_po[(li*2+7)%8].y = 0;
                    start_po[(li*2+4)%8].x = start_po[(li*2+7)%8].x = diameter;
                //  if (cy<-r_root2 && cy>(diameter/2)) ←確定済み
                    // 範囲外指定 … 4,7 → 6,1 → 0,3 → 2,5
                    // 5,6 → 7,0 → 1,2 → 3,4
                    end_po[(li*2+5)%8].y = end_po[(li*2+6)%8].y = abs(cy);
                    end_po[(li*2+5)%8].x = end_po[(li*2+6)%8].x = abs(cx);
                }
            }
        }
    }

これで範囲補正のカタチができました。
ここからちょこっとコード修正加えます。


・範囲補正、クリッピング処理の完成


上記の範囲補正では、
単純に上辺、左辺、右辺、下辺からハミでていたら即そのハミでた分を代入していたので
2辺以上に対し、ハミでている場合も想定して、一度補正した値を
再補正するときに、初期値を小さくしたり終了値を大きくしたりしないようにします。

またd や、dx, dyの初期値も本来の演算方法から決めてやります。
で、完成です。

こういう場合ね



クリッピング(完成形)


// 整数版ニュートン法
root_i(int x){
    int s=1, s2=1;
    do {
        s=(x/s+s)/2;
        s2=s+1;
        if (s*s<=x && x<s2*s2) break;
    } while(1);
    return s;
}

// 直径引数
void NewCircleAlgorithm (HDC hdc, LONG diameter, POINT center, COLORREF col, RECT *rc){
    long cx = 0, cy=diameter/2, *px, *py, tmp;
    long dx, dy, x_sign, y_sign, num_eigth, r_root2;
    double d;
    r_root2 = (diameter>3)? root_i(diameter*diameter/8) :1;
    tmp = r_root2*r_root2*8-diameter*diameter;
    if (abs(tmp)>abs(tmp+8*(2*r_root2+1))) r_root2++;   // r*√2の近似値

    d = -diameter*diameter + 4*cy*cy -4*cy +2;
    dx = 4;
    dy = -8*cy+8;
    POINT mirror_center = center, now_center, start_po[8], end_po[8];
    if ((diameter&1) ==0){
        mirror_center.x++;
        mirror_center.y++;
    }

    // クリッピング
    POINT *p_po1, *p_po2;
    for(num_eigth=0; num_eigth <8; num_eigth++){
        start_po[num_eigth].y=diameter/2;
        start_po[num_eigth].x=0;
        end_po[num_eigth].x=end_po[num_eigth].y=r_root2;
    }

    for(int li=0;li<4;li++){
        if (li==0) { cy = center.y-(rc->bottom-1);  y_sign=-1;  }
        if (li==1) { cy = mirror_center.x-rc->left; y_sign=1;       }
        if (li==2) { cy = mirror_center.y-rc->top;  y_sign=1;   }
        if (li==3) { cy = center.x-(rc->right-1);   y_sign=-1;  }

        if (abs(cy)>=(diameter/2)) {
            if (((li==0 || li==3) && cy<0) || ((li==1 || li==2) && cy>0)) continue; // 円は範囲内
            else return ;   // 円は完全に範囲外
        }

        tmp = diameter*diameter -4*cy*cy;
        cx = root_i(tmp/4); // n=tmp/4; if (tmp%4) n++;
        tmp -= 4*cx*cx;
        if (abs(tmp)>=abs(tmp -8*cx-4)) cx++;

        /*
            \2|1/       [0] 0-45度の間      [1] 45-90度の間
            3\|/0       [2] 90-135度の間    [3] 135-180度の間
          ------※------
            4/|\7       [4] 225-270度の間   [5] 180-225度の間
            /5|6\       [6] 270-315度の間   [7] 315-360度の間
        */
        if (cy*y_sign>r_root2){
            // 1,2 → 3,4 → 5,6 → 7,0
            if (start_po[li*2+1].x<abs(cx)) {
                start_po[li*2+1].y = abs(cy);
                start_po[li*2+1].x = abs(cx);
            }
            if (start_po[(li*2+2)%8].x<abs(cx)) {
                start_po[(li*2+2)%8].y = abs(cy);
                start_po[(li*2+2)%8].x = abs(cx);
            }
        }
        else {
            start_po[li*2+1].y = start_po[(li*2+2)%8].y = 0;        // 範囲外指定
            start_po[li*2+1].x = start_po[(li*2+2)%8].x = diameter; // 範囲外指定
            if (cy*y_sign<=r_root2 && cy*y_sign>0){
                // 範囲外指定 … 1,2 → 3,4 → 5,6 → 7,0
                // 0,3 → 2,5 → 4,7 → 6,1
                if (end_po[li*2].x>abs(cy)) {
                    end_po[li*2].y = abs(cx);
                    end_po[li*2].x = abs(cy);
                }
                if (end_po[(li*2+3)%8].x>abs(cy)) {
                    end_po[(li*2+3)%8].y = abs(cx);
                    end_po[(li*2+3)%8].x = abs(cy);
                }
            }
            else {
                start_po[li*2].y = start_po[(li*2+3)%8].y = 0;
                start_po[li*2].x = start_po[(li*2+3)%8].x = diameter;
                if (cy*y_sign<=0 && cy*y_sign>-r_root2){
                    // 範囲外指定 … 0,3 → 2,5 → 4,7 → 6,1
                    // 4,7 → 6,1 → 0,3 → 2,5
                    if (start_po[(li*2+4)%8].x<abs(cy)) {
                        start_po[(li*2+4)%8].y = abs(cx);
                        start_po[(li*2+4)%8].x = abs(cy);
                    }
                    if (start_po[(li*2+7)%8].x<abs(cy)) {
                        start_po[(li*2+7)%8].y = abs(cx);
                        start_po[(li*2+7)%8].x = abs(cy);
                    }
                }
                else {
                    start_po[(li*2+4)%8].y = start_po[(li*2+7)%8].y = 0;
                    start_po[(li*2+4)%8].x = start_po[(li*2+7)%8].x = diameter;
                //  if (cy<-r_root2 && cy>(diameter/2)) ←確定済み
                    // 範囲外指定 … 4,7 → 6,1 → 0,3 → 2,5
                    // 5,6 → 7,0 → 1,2 → 3,4
                    if (end_po[(li*2+5)%8].x>abs(cx)) {
                        end_po[(li*2+5)%8].y = abs(cy);
                        end_po[(li*2+5)%8].x = abs(cx);
                    }
                    if (end_po[(li*2+6)%8].x>abs(cx)) {
                        end_po[(li*2+6)%8].y = abs(cy);
                        end_po[(li*2+6)%8].x = abs(cx);
                    }
                }
            }
        }
    }

    // メインループ
    for(num_eigth=0; num_eigth<8 ; num_eigth++){
        /*
            \2|1/       [0] 0-45度の間      [1] 45-90度の間
            3\|/0       [2] 90-135度の間    [3] 135-180度の間
          ------※------
            4/|\7       [4] 180-225度の間   [5] 225-270度の間
            /5|6\       [6] 270-315度の間   [7] 315-360度の間
        */
        if (num_eigth<4){
                now_center.y = center.y; y_sign=1; }            // 0,1,2,3
        else{   now_center.y = mirror_center.y; y_sign=-1; }    // 4,5,6,7
        if ((num_eigth%6)<=1){
                now_center.x = center.x;  x_sign=1; }           // 0,1,6,7
        else {  now_center.x = mirror_center.x; x_sign=-1; }    // 2,3,4,5
        if ((num_eigth%4)%3){
                px = &cx; py = &cy; }   // 0,3,4,7
        else {  px = &cy; py = &cx; }   // 1,2,5,6

        // 初期値
        cy=start_po[num_eigth].y;
        cx=start_po[num_eigth].x;

        // d 値
        d = 4*cx*cx+4*cy*cy -4*cy+2 - diameter * diameter;
        dx = 8*cx+4;
        dy = -8*cy+8;

        // 描画ループ
        for (;cx<=end_po[num_eigth].x;cx++){
            if (d>0) {
                d += dy;
                dy+=8;
                cy--;
            }
            SetPixel (hdc, (*px)*x_sign + now_center.x, (*py)*y_sign + now_center.y, col);  // 描画

            d += dx;
            dx+=8;
        }
    }
}








サンプル解説 

下記サンプルソースは6つのファイルから成っています

ピクセル反復子や、パレット(色テーブル)クラスを定義するヘッダの gt_img_parts.h と、
定数を定義している resource_09_2.h と、
画像クラスを定義している gt_img_09_2.h
リソーススクリプトの gt_img_09_2.rc
BMPファイルを読み書きするgt_img_bmp_io.cpp
メインファイルの gt_img_09_2.cpp です。

gt_img_parts.hファ イルは007-02のものと全く同じものを使いま す。ここには書きませんのでご注意ください。
gt_img_bmp_io.cppファ イルは007-04のモノの中身は同じものを 使います。ここには書きませんのでご注意ください。
#include "gt_img_04.h"  を   #include "gt_img_09_2.h"   に変えてください)


マウスでドラッグするとそれを半径とした円が描けます。




1.
マウスでドラッグします
2.
それを半径とした円ができます
3.
いろいろな円が描けます。




gt_img_09_2.rc
// 円描画

#include "resource_09_2.h"

//リソーススクリプト
GT_DIB_MENU MENU {
    POPUP "ファイル" {
        POPUP "作成" {
            MENUITEM "1 bit" , IDM_MAKE_DIB_1
            MENUITEM "4 bit" , IDM_MAKE_DIB_4
            MENUITEM "8 bit" , IDM_MAKE_DIB_8
            MENUITEM "16 bit", IDM_MAKE_DIB_16
            MENUITEM "24 bit", IDM_MAKE_DIB_24
            MENUITEM "32 bit", IDM_MAKE_DIB_32
        }
        MENUITEM "ひらく",        IDM_OPEN
        MENUITEM "保存",        IDM_SAVE_DIB
    }
    MENUITEM "色",                IDM_COL
}





resource_09_2.h

#define IDM_MAKE_DIB_0        1000
#define IDM_MAKE_DIB_1        1001
#define IDM_MAKE_DIB_4        1004
#define IDM_MAKE_DIB_8        1008
#define IDM_MAKE_DIB_16        1016
#define IDM_MAKE_DIB_24        1024
#define IDM_MAKE_DIB_32        1032

#define IDM_LOAD_DIB        2001
#define IDM_SAVE_DIB        2002
#define IDM_OPEN            2003

#define IDM_COL                3000




gt_img_09_2.h

// 円描画

#include <stdio.h>
#include <windows.h>
#include "resource_09_2.h"
#include "gt_img_parts.h"

// ======== GT_IMG ==================================
class GT_IMG {
    public :
    GT_COL_TABLE m_col_table;
    unsigned long *m_col_mask;
    char *m_alpha;

    int m_width, m_height, m_byte_width, m_bit_count;
    BYTE *m_pbits;

    GT_IMG();
    virtual ~GT_IMG();

    // 作成と破棄
    virtual destroy();
    virtual create(int cx, int cy, int a_bit_count, int a_color_size=0);

    // ピクセル描画
    set_pixel(int x, int y, DWORD col);
    DWORD get_pixel(int x, int y);

    // 直線描画
    draw_line(int sx, int sy, int ex, int ey, BYTE r, BYTE g, BYTE b, int rate);
    template <int d_bit> void draw_line_pixput(int sx, int sy, int ex, int ey, BYTE r, BYTE g, BYTE b, int rate);
    adjust_lining_limit(int *p_start, int *p_end, int sx, int sy, int ex, int ey, int *p_cx, int *p_cy);

    // 円描画
    draw_circle(int center_x, int center_y, int diameter, BYTE r, BYTE g, BYTE b, int rate);
    template <int d_bit> draw_circle_pixput(int center_x, int center_y, int diameter, BYTE r, BYTE g, BYTE b, int rate);

    // アルファ値
    is_alpha(){ return (m_alpha!=NULL); }

    // カラー
    set_col_mask(UINT r, UINT g, UINT b);
    set_pal_col(BYTE index, BYTE r, BYTE g, BYTE b);

    // 読込
    load_dib(char *filename);
    load_dib_rle4(HANDLE hfile);    // 4bit ランレングス圧縮
    load_dib_rle8(HANDLE hfile);    // 8bit ランレングス圧縮

    // 書込み
    save_dib(char *filename, int rle_compress_flag=0);
    save_dib_rle4(HANDLE hfile);    // 4bit ランレングス圧縮
    save_dib_rle8(HANDLE hfile);    // 8bit ランレングス圧縮
};

// 直線描画
GT_IMG::draw_circle(int center_x, int center_y, int diameter, BYTE r, BYTE g, BYTE b, int rate){
    if (!m_pbits) return -1;

    // 描画
    switch(m_bit_count){
    case 1 : draw_circle_pixput<1> (center_x, center_y, diameter, r, g, b, rate);     break;
    case 4 : draw_circle_pixput<4> (center_x, center_y, diameter, r, g, b, rate);     break;
    case 8 : draw_circle_pixput<8> (center_x, center_y, diameter, r, g, b, rate);     break;
    case 16: draw_circle_pixput<16>(center_x, center_y, diameter, r, g, b, rate);     break;
    case 24: draw_circle_pixput<24>(center_x, center_y, diameter, r, g, b, rate);     break;
    case 32: draw_circle_pixput<32>(center_x, center_y, diameter, r, g, b, rate);     break;
    }
    return 0;
}
// 整数版ニュートン法
root(double x){
    double s=1, s2=1;
    if (x<=0) return 1;
    do { s2=s; s=(x/s+s)/2; } while(s2!=s);
    return s;
}
// 整数版ニュートン法
root_i(int x){
    int s=1, s2=1;
    if (x<=0) return 1;
    do {
        s2=s; s=(x/s+s)/2;
        s2=s+1;
        if (s*s<=x && x<s2*s2) break;
    } while(1);
    return s;
}
// 円描画のインライン・テンプレート
template <int d_bit>
GT_IMG::draw_circle_pixput(int center_x, int center_y, int diameter, BYTE r, BYTE g, BYTE b, int rate){
    PIXEL_ITR<d_bit,0> d_pix;
    if (rate<0) rate=0;
    if (rate>=255) rate=255;
    d_pix.init(m_pbits, m_alpha, m_byte_width, m_width, m_height, &m_col_table);
    POINT center;
    center.x=center_x;
    center.y=center_y;

    long cx = 0, cy=diameter/2, *px, *py, tmp;
    long dx, dy, x_sign, y_sign, num_eigth, r_root2;
    double d;
    r_root2 = (diameter>3)? root(diameter*diameter/8) :1;
    tmp = r_root2*r_root2*8-diameter*diameter;
    if (abs(tmp)>abs(tmp+8*(2*r_root2+1))) r_root2++;    // r*√2の近似値

    d = -diameter*diameter + 4*cy*cy -4*cy +2;
    dx = 4;
    dy = -8*cy+8;
    POINT mirror_center = center, now_center, start_po[8], end_po[8];
    if ((diameter&1) ==0){
        mirror_center.x++;
        mirror_center.y++;
    }

    // クリッピング
    POINT *p_po1, *p_po2;
    for(num_eigth=0; num_eigth <8; num_eigth++){
        start_po[num_eigth].y=diameter/2;
        start_po[num_eigth].x=0;
        end_po[num_eigth].x=end_po[num_eigth].y=r_root2;
    }

    for(int li=0;li<4;li++){
        if (li==0) { cy = center.y-(m_height-1);    y_sign=-1;    }
        if (li==1) { cy = mirror_center.x;    y_sign=1;        }
        if (li==2) { cy = mirror_center.y;    y_sign=1;    }
        if (li==3) { cy = center.x-(m_width-1);    y_sign=-1;    }

        if (abs(cy)>=(diameter/2)) {
            if (((li==0 || li==3) && cy<0) || ((li==1 || li==2) && cy>0)) continue;    // 円は範囲内
            else return -1;    // 円は完全に範囲外
        }

        tmp = diameter*diameter -4*cy*cy;
        cx = root_i(tmp/4);    // n=tmp/4;    if (tmp%4) n++;
        tmp -= 4*cx*cx;
        if (abs(tmp)>=abs(tmp -8*cx-4)) cx++;

        /*
            \2|1/       [0] 0-45度の間      [1] 45-90度の間
            3\|/0       [2] 90-135度の間    [3] 135-180度の間
          ------※------
            4/|\7       [4] 225-270度の間   [5] 180-225度の間
            /5|6\       [6] 270-315度の間   [7] 315-360度の間
        */
        if (cy*y_sign>r_root2){
            // 1,2 → 3,4 → 5,6 → 7,0
            if (start_po[li*2+1].x<abs(cx)) {
                start_po[li*2+1].y = abs(cy);
                start_po[li*2+1].x = abs(cx);
            }
            if (start_po[(li*2+2)%8].x<abs(cx)) {
                start_po[(li*2+2)%8].y = abs(cy);
                start_po[(li*2+2)%8].x = abs(cx);
            }
        }
        else {
            start_po[li*2+1].y = start_po[(li*2+2)%8].y = 0;        // 範囲外指定
            start_po[li*2+1].x = start_po[(li*2+2)%8].x = diameter;    // 範囲外指定
            if (cy*y_sign<=r_root2 && cy*y_sign>0){
                // 範囲外指定 … 1,2 → 3,4 → 5,6 → 7,0
                // 0,3 → 2,5 → 4,7 → 6,1
                if (end_po[li*2].x>abs(cy)) {
                    end_po[li*2].y = abs(cx);
                    end_po[li*2].x = abs(cy);
                }
                if (end_po[(li*2+3)%8].x>abs(cy)) {
                    end_po[(li*2+3)%8].y = abs(cx);
                    end_po[(li*2+3)%8].x = abs(cy);
                }
            }
            else {
                start_po[li*2].y = start_po[(li*2+3)%8].y = 0;
                start_po[li*2].x = start_po[(li*2+3)%8].x = diameter;
                if (cy*y_sign<=0 && cy*y_sign>-r_root2){
                    // 範囲外指定 … 0,3 → 2,5 → 4,7 → 6,1
                    // 4,7 → 6,1 → 0,3 → 2,5
                    if (start_po[(li*2+4)%8].x<abs(cy)) {
                        start_po[(li*2+4)%8].y = abs(cx);
                        start_po[(li*2+4)%8].x = abs(cy);
                    }
                    if (start_po[(li*2+7)%8].x<abs(cy)) {
                        start_po[(li*2+7)%8].y = abs(cx);
                        start_po[(li*2+7)%8].x = abs(cy);
                    }
                }
                else {
                    start_po[(li*2+4)%8].y = start_po[(li*2+7)%8].y = 0;
                    start_po[(li*2+4)%8].x = start_po[(li*2+7)%8].x = diameter;
                //    if (cy<-r_root2 && cy>(diameter/2)) ←確定済み
                    // 範囲外指定 … 4,7 → 6,1 → 0,3 → 2,5
                    // 5,6 → 7,0 → 1,2 → 3,4
                    if (end_po[(li*2+5)%8].x>abs(cx)) {
                        end_po[(li*2+5)%8].y = abs(cy);
                        end_po[(li*2+5)%8].x = abs(cx);
                    }
                    if (end_po[(li*2+6)%8].x>abs(cx)) {
                        end_po[(li*2+6)%8].y = abs(cy);
                        end_po[(li*2+6)%8].x = abs(cx);
                    }
                }
            }
        }
    }

    // メインループ
    for(num_eigth=0; num_eigth<8 ; num_eigth++){
        /*
            \2|1/       [0] 0-45度の間      [1] 45-90度の間
            3\|/0       [2] 90-135度の間    [3] 135-180度の間
          ------※------
            4/|\7       [4] 180-225度の間   [5] 225-270度の間
            /5|6\       [6] 270-315度の間   [7] 315-360度の間
        */
        if (num_eigth<4){
                now_center.y = center.y; y_sign=1; }            // 0,1,2,3
        else{    now_center.y = mirror_center.y; y_sign=-1; }    // 4,5,6,7
        if ((num_eigth%6)<=1){
                now_center.x = center.x;  x_sign=1; }            // 0,1,6,7
        else {    now_center.x = mirror_center.x; x_sign=-1; }    // 2,3,4,5
        if ((num_eigth%4)%3){
                px = &cx; py = &cy; }    // 0,3,4,7
        else {    px = &cy; py = &cx; }    // 1,2,5,6

        // 初期値
        cy=start_po[num_eigth].y;
        cx=start_po[num_eigth].x;

        // d 値
        d = 4*cx*cx+4*cy*cy -4*cy+2 - diameter * diameter;
        dx = 8*cx+4;
        dy = -8*cy+8;

        // 描画ループ
        for (;cx<=end_po[num_eigth].x;cx++){
            if (d>0) {
                d += dy;
                dy+=8;
                cy--;
            }
            d_pix.set_pos((*px)*x_sign + now_center.x, (*py)*y_sign + now_center.y);
            d_pix.set_col(r, g, b, rate);    // 描画

            d += dx;
            dx+=8;
        }
    }
    return 0;
}
// 直線描画
GT_IMG::draw_line(int sx, int sy, int ex, int ey, BYTE r, BYTE g, BYTE b, int rate){
    if (!m_pbits) return -1;
    if (sx==ex && sy==ey) return -1;

    // 描画
    switch(m_bit_count){
    case 1 : draw_line_pixput<1> (sx, sy, ex, ey, r, g, b, rate);     break;
    case 4 : draw_line_pixput<4> (sx, sy, ex, ey, r, g, b, rate);     break;
    case 8 : draw_line_pixput<8> (sx, sy, ex, ey, r, g, b, rate);     break;
    case 16: draw_line_pixput<16>(sx, sy, ex, ey, r, g, b, rate);     break;
    case 24: draw_line_pixput<24>(sx, sy, ex, ey, r, g, b, rate);     break;
    case 32: draw_line_pixput<32>(sx, sy, ex, ey, r, g, b, rate);     break;
    }
    return 0;
}

// 直線描画のインライン・テンプレート(RGB版)
template <int d_bit> void
GT_IMG::draw_line_pixput(int sx, int sy, int ex, int ey, BYTE r, BYTE g, BYTE b, int rate){
    int cx, cy, pos_x, pos_y, i, lim, start, end;
    PIXEL_ITR<d_bit,0> d_pix;
    if (rate<0) rate=0;
    if (rate>=255) rate=255;
    d_pix.init(m_pbits, m_alpha, m_byte_width, m_width, m_height, &m_col_table);
    int col_index;
    double e;
    int *p_co1, *p_co2, ddis1, ddis2, dn1, dn2;
    if (adjust_lining_limit(&start, &end, sx, sy, ex, ey, &cx, &cy)<0) return;

    int base_axis = (abs(cx)>abs(cy))? 'x' : 'y';
    p_co1 = (base_axis=='x')? &pos_x : &pos_y;
    p_co2 = (base_axis=='x')? &pos_y : &pos_x;
    ddis1 = (base_axis=='x')? 2*cx : 2*cy;
    ddis2 = (base_axis=='x')? 2*cy : 2*cx;
    dn1 = dn2 = 1;
    if (ddis1<0) { dn1=-1; ddis1 = -ddis1; }
    if (ddis2<0) { dn2=-1; ddis2 = -ddis2; }
    pos_x = sx;
    pos_y = sy;

    // 初期座標補正
    *p_co1 += dn1* start;    // 基軸座標
    *p_co2 += dn2* (start*ddis2+ddis1/2) /ddis1;
    e = (ddis1/2 + start*ddis2)%ddis1;

    if (d_bit<8){    // (パレット版)
        col_index = m_col_table.get_col_index<d_bit>(r*rate/255, g*rate/255, b*rate/255);
        for(i=start;i<=end;i++){
            d_pix.set_pos(pos_x, pos_y);
            d_pix.set_col(col_index);
            (*p_co1)+=dn1;
            e += ddis2;
            if (e>=ddis1){
                e -= ddis1;
                (*p_co2)+=dn2;
            }
        }
    }
    else {    // (RGB版)
        for(i=start;i<=end;i++){
            d_pix.set_pos(pos_x, pos_y);
            d_pix.set_col(r, g, b, rate);
            (*p_co1)+=dn1;
            e += ddis2;
            if (e>=ddis1){
                e -= ddis1;
                (*p_co2)+=dn2;
            }
        }
    }
    return ;
};

// 直線描画 補正
GT_IMG::adjust_lining_limit(int *p_start, int *p_end, int sx, int sy, int ex, int ey, int *p_cx, int *p_cy){
    int img_lim, img_lim_other, base_lim, other_lim;
    int *p_res, base_co, other_co, tmp, cx, cy;
    cx=*p_cx=ex-sx;
    cy=*p_cy=ey-sy;
    int base_axis = (abs(cx)>abs(cy))? 'x' : 'y';
    *p_start = *p_end = 0;

    if (!cx && !cy) return -1;
    // x,yの長いほうの座標軸をベースにする
    if (base_axis=='x'){    // x 座標ベース
        img_lim = m_width;
        img_lim_other = m_height;
        base_lim = cx;
        other_lim = cy;
    }
    else{    // y 座標ベース
        img_lim = m_height;
        img_lim_other = m_width;
        base_lim = cy;
        other_lim = cx;
    }

    for(int i=0;i<2;i++){
        if (i==0) {
            p_res = p_start;
            base_co  = (base_axis=='x')? sx : sy;
            other_co = (base_axis=='x')? sy : sx;
        }
        else if (i==1) {
            p_res = p_end;
            base_co  = (base_axis=='x')? ex : ey;
            other_co = (base_axis=='x')? ey : ex;
            other_lim = -other_lim;
        }

        // x,yの長いほうの座標軸で判定
        if (base_co<0)     *p_res=-base_co;
        if (base_co>=img_lim) *p_res= base_co-img_lim+1;

        // x,yの短いほうの座標軸で判定
        if (!other_lim) continue;
        tmp = other_co + ((*p_res) * (2*other_lim) + abs(base_lim))/(2*base_lim);

        int aim_co;
        if (tmp<0) { aim_co = -other_co; }
        else if (tmp>=img_lim_other) { aim_co = other_co-img_lim_other+1; }
        else continue;    // 範囲内

        tmp = aim_co*2*abs(base_lim)-abs(base_lim);
        *p_res = tmp/abs(2*other_lim);
        if (i==0 && tmp%abs(2*other_lim)) (*p_res)++;
        if (i==1) (*p_res)++;
    }
    *p_end = abs(base_lim) - *p_end;
    return 0;
}

// コンストラクタ
GT_IMG::GT_IMG(){
    m_col_mask =NULL;
    m_alpha =NULL;
    m_width = m_height = m_byte_width = m_bit_count =0;
    m_pbits =NULL;
}
// デストラクタ
GT_IMG::~GT_IMG(){
    destroy();
}
// GIMG 破棄
GT_IMG::destroy(){
    m_width=0;
    m_height=0;
    m_byte_width=0;
    m_bit_count=0;
    if (m_pbits) delete[] m_pbits;
    m_pbits = NULL;
    if (m_col_mask) delete[] m_col_mask;
    m_col_mask = NULL;
    m_col_table.destroy();
    return 0;
};
// GIMG 作成
GT_IMG::create(int cx, int cy, int a_bit_count, int a_color_size){
    int i, palette_len, info_size;
    destroy();    // ファイルが開けたら既存データ破棄

    if (cx<=0 || cy<=0) return -1;
    if (a_bit_count!=1 && a_bit_count!=4 && a_bit_count!=8
        && a_bit_count!=16 && a_bit_count!=24 && a_bit_count!=32) return -1;

    if (a_bit_count<=8 && a_color_size==0)  palette_len=1<<a_bit_count;    // 8bit以下でパレット色数指定無しなら
    else palette_len=a_color_size;

    // パレット・メモリ確保
    m_col_table.make(palette_len);

    /* バッファの1ラインの長さを計算 */
    m_byte_width=4*((cx*a_bit_count+31)/32);
    m_pbits = new BYTE[m_byte_width*cy];
    ZeroMemory(m_pbits, m_byte_width*cy);

    if (!m_pbits) return -1;
    m_width= cx;
    m_height=cy;
    m_bit_count=a_bit_count;

    // パレット自動配色
    // 適当な基本色
    RGBTRIPLE rgb[8]={
        {0,0,0},{255, 255, 255}, {0,0,255}, {255,0,0}, {0,255,0}, {255, 255,0}, {255,0,255},{0,255,255}
    };

    for(i=0;i<min(palette_len, 8);i++){
        m_col_table.add_col(rgb[i].rgbtRed, rgb[i].rgbtGreen, rgb[i].rgbtBlue);
    }

    return 0;
}
// パレットに色設定
inline GT_IMG::set_pal_col(BYTE index, BYTE r, BYTE g, BYTE b){
    if (m_col_table.m_cols_len<=index) return 0;
    m_col_table.set_col(index, r, g, b);
    return 0;
}
// カラーマスク設定
GT_IMG::set_col_mask(UINT r, UINT g, UINT b){
    if (!m_col_mask) m_col_mask = new unsigned long[3];
    m_col_mask[0] = r;
    m_col_mask[1] = g;
    m_col_mask[2] = b;
}
// 1点ピクセル取得
DWORD GT_IMG::get_pixel(int x, int y){
    if (!m_pbits) return 0;
    if (x<0 || x>=m_width || y<0 || y>=m_height) return 0;
    BYTE *ppix;
    ppix=m_pbits+(m_height-y-1)*m_byte_width + (x*m_bit_count>>3);

    switch(m_bit_count){
    case 1: return 0x01 & (*ppix >> (7-(x&7)));
    case 4: return 0x0f & (*ppix >> ((x&1)?0:4));
    case 8: return *ppix ;
    case 16: return *(WORD*)ppix;
    case 24: return 0x00FFffFF & *(DWORD*)ppix;
    case 32: return *(DWORD*)ppix ;
    }
    return 0;
}
// 1点ピクセル書きこみ
GT_IMG::set_pixel(int x, int y, DWORD col){
    if (!m_pbits) return 0;
    if (x<0 || x>=m_width || y<0 || y>=m_height) return 0;
    BYTE *ppix;
    ppix=m_pbits+(m_height-y-1)*m_byte_width + (x*m_bit_count>>3);

    switch(m_bit_count){
    case 1: *ppix &= ~(1         <<(7-(x&7)));
            *ppix |= ((bool)col) <<(7-(x&7));
            break;
    case 4: *ppix &= 0x0f << ((x&1)?4:0);
            *ppix |= (0x0f & col)  << ((x&1)?0:4);
            break;
    case 8: *ppix = (BYTE)col;                            break;
    case 16: *(WORD*)ppix = (WORD)col;                    break;
    case 24: *(RGBTRIPLE*)ppix = *(RGBTRIPLE*)&col;        break;
    case 32: *(DWORD*)ppix = *(DWORD*)&col;                break;
    }
    return 0;
}

// ======== GT_DIB ==================================
class GT_DIB : public GT_IMG{
    public :
    HDC m_hdc;
    HBITMAP m_hbmp;

    GT_DIB();
    ~GT_DIB();

    // DC 適用
    apply_dc(HDC hdc);
    apply_dc(HWND hwnd);

    // 作成と破棄
    create(int cx, int cy, int a_bit_count, int a_color_size=0);
    virtual destroy();

    // カラー
    apply_palette_all();
    set_pal_col(BYTE index, BYTE r, BYTE g, BYTE b);

};
// コンストラクタ
GT_DIB::GT_DIB(){
    m_hdc = NULL;
    m_hbmp = NULL;
}
// デストラクタ
GT_DIB::~GT_DIB(){
    destroy();
}
// HDCパレット設定(全て)
GT_DIB::apply_palette_all(){
    return m_col_table.apply_palette_all(m_hdc);
}
// パレットに色設定
inline GT_DIB::set_pal_col(BYTE index, BYTE r, BYTE g, BYTE b){
    return m_col_table.set_pal_col(m_hdc, index, r, g, b);
}

// DC有効化
GT_DIB::apply_dc(HDC hdc){
    if (m_hdc) { DeleteDC(m_hdc);    m_hdc=NULL; }
    m_hdc = CreateCompatibleDC(hdc);
    SelectObject(m_hdc, m_hbmp);
    return 0;
}
// DC有効化(HWND hwnd)
GT_DIB::apply_dc(HWND hwnd){
    if (m_hdc)        { DeleteDC(m_hdc);    m_hdc=NULL; }
    HDC hwnd_hdc = GetDC(hwnd);
    m_hdc = CreateCompatibleDC(hwnd_hdc);
    SelectObject(m_hdc, m_hbmp);
    ReleaseDC(hwnd, hwnd_hdc);
    return 0;
}

// 破棄
GT_DIB::destroy(){
    if (m_hdc)        { DeleteDC(m_hdc);    m_hdc=NULL; }
    if (m_hbmp)        { DeleteObject(m_hbmp); m_hbmp=NULL; }
//    if (m_pbits) delete[] m_pbits;
    m_pbits = NULL;
    m_width=0;
    m_height=0;
    m_byte_width=0;
    m_bit_count=0;
    if (m_col_mask) delete[] m_col_mask;
    m_col_mask = NULL;
    m_col_table.destroy();
    return 0;
}
// 作成
GT_DIB::create(int cx, int cy, int a_bit_count, int a_color_size){
    BITMAPINFO *bmp_info;
    int i, palette_entry, info_size;
    destroy();    // 作成済みメモリを破棄する

    if (cx<=0 || cy<=0) return -1;
    if (a_bit_count!=1 && a_bit_count!=4 && a_bit_count!=8
        && a_bit_count!=16 && a_bit_count!=24 && a_bit_count!=32) return -1;

    palette_entry=1;
    if        (a_color_size!=0) palette_entry=a_color_size;
    else if (a_bit_count<=8)  palette_entry=1<<a_bit_count;    // 8bit以下でパレット色数指定無しなら

    info_size=sizeof(BITMAPINFOHEADER) + (palette_entry-1)*sizeof(RGBQUAD)+1;
    bmp_info = (BITMAPINFO*)malloc(info_size);
    if (!bmp_info) return -1;
    ZeroMemory(bmp_info, info_size);

    // 適当な基本色
    RGBTRIPLE rgb[8]={
        {0,0,0},{255, 255, 255}, {0,0,255}, {255,0,0}, {0,255,0}, {255, 255,0}, {255,0,255},{0,255,255}
    };

    m_col_table.make(palette_entry);
    for(i=0;i<min(palette_entry, 8);i++){
        bmp_info->bmiColors[i].rgbBlue    = rgb[i].rgbtBlue;
        bmp_info->bmiColors[i].rgbGreen    = rgb[i].rgbtGreen;
        bmp_info->bmiColors[i].rgbRed    = rgb[i].rgbtRed;
        bmp_info->bmiColors[i].rgbReserved = 0;
        m_col_table.add_col(rgb[i].rgbtRed, rgb[i].rgbtGreen, rgb[i].rgbtBlue);
    }

    /* BITMAPINFO構造体設定 */
    bmp_info->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
    bmp_info->bmiHeader.biWidth=cx;
    bmp_info->bmiHeader.biHeight=cy;
    bmp_info->bmiHeader.biPlanes=1;
    bmp_info->bmiHeader.biBitCount=a_bit_count;
    bmp_info->bmiHeader.biCompression=BI_RGB;
    bmp_info->bmiHeader.biSizeImage=0;
    bmp_info->bmiHeader.biXPelsPerMeter=0;
    bmp_info->bmiHeader.biYPelsPerMeter=0;
    bmp_info->bmiHeader.biClrUsed=palette_entry;
    bmp_info->bmiHeader.biClrImportant=0;

    m_hbmp = CreateDIBSection(NULL, bmp_info, DIB_RGB_COLORS, (VOID**)&m_pbits, NULL, 0);
    if (!m_hbmp) {
        free(bmp_info);
        return -1;
    }
    m_width= cx;
    m_height=cy;
    m_bit_count=a_bit_count;

    /* バッファの1ラインの長さを計算 */
    m_byte_width=4*((m_width*m_bit_count+31)/32);
    ZeroMemory(m_pbits, m_byte_width*m_height);

    free(bmp_info);
    return 0;
}






gt_img_09_2.cpp

// 円描画

#include "gt_img_09_2.h"
#include <math.h>

GT_DIB gt_dib_1;
DWORD col=0xffffff;
static OPENFILENAME ofn = {0};
static char filename[MAX_PATH];
POINT start_po, end_po;

CHOOSECOLOR cc = {0};
DWORD col_costom[8] = {0};
BYTE r=255, g=255, b=255;
COLORREF color=0xffffff;
int col_index=0;

// メニュー
on_command(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp){
    switch(LOWORD(wp)) {
    case IDM_MAKE_DIB_0:
    case IDM_MAKE_DIB_1:
    case IDM_MAKE_DIB_4:
    case IDM_MAKE_DIB_8:
    case IDM_MAKE_DIB_16:
    case IDM_MAKE_DIB_24:
    case IDM_MAKE_DIB_32:
        gt_dib_1.create(201, 151, LOWORD(wp)-IDM_MAKE_DIB_0, 0);
        gt_dib_1.apply_dc(hwnd);
        InvalidateRect(hwnd, NULL, 0);
        break;
    case IDM_OPEN:
        ofn.Flags = OFN_FILEMUSTEXIST;
        if (!GetOpenFileName(&ofn)) break;
        gt_dib_1.load_dib(filename);
        gt_dib_1.apply_dc(hwnd);
        gt_dib_1.apply_palette_all();
        InvalidateRect(hwnd, NULL, 1);
        break;
    case IDM_SAVE_DIB:
        ofn.Flags = 0;
        if (!GetSaveFileName(&ofn)) break;
        gt_dib_1.save_dib(filename);
        break;
    case IDM_COL:
        cc.lStructSize    = sizeof (CHOOSECOLOR);
        cc.hwndOwner    = hwnd;
        cc.rgbResult    = color;
        cc.lpCustColors    = col_costom;
        cc.Flags    = CC_FULLOPEN | CC_RGBINIT;
        if (!ChooseColor(&cc)) break;
        color = cc.rgbResult;
        r = GetRValue(color);
        g = GetGValue(color);
        b = GetBValue(color);

        // パレット
        col_index = 0;
        switch(gt_dib_1.m_bit_count) {
        case 1 : gt_dib_1.m_col_table.get_col_index<1>(r,g,b); break;
        case 4 : gt_dib_1.m_col_table.get_col_index<4>(r,g,b); break;
        case 8 : gt_dib_1.m_col_table.get_col_index<8>(r,g,b); break;
        }
        gt_dib_1.apply_palette_all();
        break;
    }
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
    HDC hdc;
    PAINTSTRUCT ps;
    static POINT mou_po, po_array[4];
    static int mou_down=0;
    static char s[50];
    RECT rc;
    int x,y;
    HRGN hrgn, hrgn2;

    switch (msg) {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        if (gt_dib_1.m_hdc){
            BitBlt(    hdc, 20, 20, gt_dib_1.m_width, gt_dib_1.m_height, gt_dib_1.m_hdc, 0, 0, SRCCOPY);
        }
        sprintf(s, "   ヨコ %d X タテ %d  %dビット",
                gt_dib_1.m_width, gt_dib_1.m_height, gt_dib_1.m_bit_count);
        GetClientRect(hwnd , &rc);
        rc.top=gt_dib_1.m_height+20;
        DrawText(hdc, s, -1, &rc, DT_WORDBREAK );

        EndPaint(hwnd, &ps);
        break;
    case WM_COMMAND:
        on_command(hwnd, msg, wp, lp);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_LBUTTONDOWN:
        if (!mou_down){
            GetCursorPos(&start_po);
            ScreenToClient(hwnd, &start_po);
            mou_down=1;
        }
        break;
    case WM_LBUTTONUP:
        if (mou_down){
            mou_down=0;
            GetCursorPos(&end_po);
            ScreenToClient(hwnd, &end_po);
            x = end_po.x-start_po.x;
            y = end_po.y-start_po.y;
            gt_dib_1.draw_circle(start_po.x-20, start_po.y-20, 2*sqrt(x*x+y*y), r, g, b, 255);
            InvalidateRect(hwnd, NULL, 1);
        }
        mou_down=0;
        break;
    case WM_MOUSEMOVE:
        if (mou_down){
            hdc=GetDC(hwnd);
            GetClientRect(hwnd , &rc);
            if (mou_down==2){
                po_array[0] = po_array[3] = start_po;
                po_array[1] = po_array[2] = mou_po;
                po_array[2].x++;    po_array[3].x++;
                po_array[2].y++;    po_array[3].y++;
                hrgn = CreatePolygonRgn(po_array , 4 , WINDING);
                SelectObject(hdc, hrgn);       
                PatBlt(hdc,  0, 0, rc.right, rc.bottom, PATINVERT);
                DeleteObject(hrgn);
            }
            mou_down=2;

            GetCursorPos(&mou_po);
            ScreenToClient(hwnd, &mou_po);
            po_array[0] = po_array[3] = start_po;
            po_array[1] = po_array[2] = mou_po;
            po_array[2].x++;    po_array[3].x++;
            po_array[2].y++;    po_array[3].y++;
            hrgn2 = CreatePolygonRgn(po_array , 4 , WINDING);
            SelectObject(hdc, hrgn2);       
            PatBlt(hdc,  0, 0, rc.right, rc.bottom, PATINVERT);
            DeleteObject(hrgn2);
            ReleaseDC(hwnd, hdc);
        }
        break;
    case WM_CREATE:
        ofn.lStructSize = sizeof (OPENFILENAME);
        ofn.hwndOwner = hwnd;
        ofn.lpstrFilter =    
                    TEXT("bmp files {*.bmp}\0*.bmp\0")
                    TEXT("All files {*.*}\0*.*\0\0");
        ofn.lpstrCustomFilter = NULL;
        ofn.nMaxCustFilter = 0;
        ofn.nFilterIndex = 0;
        ofn.lpstrFile = filename;
        ofn.nMaxFile = MAX_PATH;
        ofn.Flags = OFN_FILEMUSTEXIST;

        gt_dib_1.create(201, 151, 24, 0);
        gt_dib_1.apply_dc(hwnd);
        break;
    }
    return DefWindowProc(hwnd, msg, wp, lp);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,     PSTR lpCmdLine, int nCmdShow) {
    HWND hwnd;
    MSG msg;
    WNDCLASS winc;

    winc.style         = CS_HREDRAW | CS_VREDRAW;
    winc.lpfnWndProc    = WndProc;
    winc.cbClsExtra    = winc.cbWndExtra    = 0;
    winc.hInstance        = hInstance;
    winc.hIcon        = LoadIcon(NULL, IDI_APPLICATION);
    winc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    winc.hbrBackground    = (HBRUSH)GetStockObject(WHITE_BRUSH);
    winc.lpszMenuName    = TEXT("GT_DIB_MENU");
    winc.lpszClassName    = TEXT("GT_IMG");

    if (!RegisterClass(&winc)) return 1;

    hwnd = CreateWindow(
            TEXT("GT_IMG"), TEXT("円描画"), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
            CW_USEDEFAULT, CW_USEDEFAULT, 300, 250,
            NULL, NULL, hInstance, NULL
    );

    if (hwnd == NULL) return 1;

    while (GetMessage(&msg, NULL, 0, 0 )) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}


 007-09(1)  前へ←  ホームへ  →次へ  007-10(1)