No.007 - 09-2 円描画(2) |
ブレゼンハム とミッチェナーの一般的なソースより、もう少しだけ精度を高くしたアルゴリズムを使った円描画です。 速度も落としません。一応自前ルーチンなので他のサイト・書籍には載ってません、あしからず。 |
・ブレゼンハム(Bresenham) とミッチェナー(Michener)のアルゴリズムの問題 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
前回のまとめでも書きましたが ブレゼンハムとミッチェナーのアルゴリズムには少し問題があります。 1.引数に半径を指定しているので、奇数の直径を指定出来ない。 大きな円ならわかりませんが、小さな円なら露骨になります。 2.実は精度が良くない。 精度が高くて遅いシンプルなルーチンと書いて、 同じ中心、同じ半径でブレゼンハムとミッチェナー描画するとピッタリ重ならないのがわかります。 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
・ブレゼンハムはなぜ精度が悪い? |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
中点分岐法に限っていえば、ブレゼンハムは中点をピクセル判定の材料にしていますが、 円描画は2乗演算する都合上、中点で計算した誤差は「誤差の中点」にならないということです。 つまり中点から見て、y と y-1 の座標は等誤差の距離にないのです。 それゆえ、真円よりすこし小さめな円になります。 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1.円描画ルーチン | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
・なにはともあれ高精度ルーチンの用意 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
前回のサンプルソースから。 シンプルで、とても基本に忠実で遅い関数です。これをベースとします。
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
・引数を半径→直径にする |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
上記のアルゴリズムでは引数に半径を指定しているのに できあがる円は奇数の直径という「おいおい、直径≠2*半径なの?」と思わずツッコミの余地満載な仕様です。 前回のミッチェナー、ブレゼンハムのソースも同様です。 (ただし、ミッチェナー、ブレゼンハムは飽くまで8分円のアルゴリズムなので、ミッチェナー、ブレゼンハムが悪いのでは無いです。) まず、これを改善します。 「直径≠2*半径」なのは中心点の反対側にピクセルを置く都合上の問題ですが、 それには奇数直径と、偶数直径のときの中心点を考えます。 直径4〜5ピクセルの例を見てみます。
直径5ピクセルの中心は、座標(2, 2) でキマリですが、 直径4ピクセルの中心は、座標(1, 1) なのか、(2, 2) なのか、はっきりしません。 では直径2ピクセルの例を見てみます。
中心は座標(0, 0) か、座標(1, 1) ですが、 アナログに人間感覚で決めちゃうなら、「少なくとも(1, 1)じゃないんじゃない?」、ということで 直径2ピクセルの中心は座標(0, 0)です。 したがって 直径4ピクセルの中心は、座標(1, 1)です。
(これは当サイトの考え方ですので、また別の考え方なら(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 の初期値は割り算入ったままでダイジョブです。というか入ってた方がいいです。 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
・メインループの計算式の最適化 (初期段階) |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
まず 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 は明らかに2の倍数であり if (d>0) 〜 でしか判定しないので2で割ります。
x が1増えると
d は 8x +4 増えます 同様に y が1減るとき
d は -8y +8 増えます また d の初期値は
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
・メインループの計算式の最適化 (最終段階) |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
通常 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
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
2.範囲補正、クリッピング | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
・範囲補正、クリッピング・・・をする前に |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
・範囲補正、クリッピング処理の考え方 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
A点の y 座標はわかるので、y 座標から x 座標を求め、得られたxy座標を使ってクリッピングします。 y 座標から x 座標を求めるとき平方根を使います。 平方根を求めるのに小数点はいらないので 自前の整数版ニュートン法で平方根を取得します。
ニュートン法で得られる平方根は n <= x < n+1 となるような n なので n と n+1 どちらの誤差のほうが小さいか調べて選択します。 これで座標は得られました。
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ここからちょこっとコード修正加えます。 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
・範囲補正、クリッピング処理の完成 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
下記サンプルソースは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 です。
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
マウスでドラッグするとそれを半径とした円が描けます。 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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; } |