2013/5/25 修正
|
// Bresenham の線分アルゴリズム // コンピュータ・グラフィックス 日本コンピュータ協会 P.443 〜 p.446 #include <windows.h> #include <stdio.h> #include <GdiPlus.h> #define MYWNDTITLE "Line" #define MYWNDCLASS "Line_CLASS" using namespace Gdiplus; GdiplusStartupInput gdiSI; ULONG_PTR gdiToken; HDC g_hDC; HWND g_hWnd; LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void Bre_Line(int x1, int y1, int x2, int y2, Color clr); void Bre_Line_Sub(int dx, int dy, int x1, int y1, int x2, int y2, Color clr, bool bSlantGently); void Bre_Write_Pixel(int x, int y, Color clr, bool bSlantGently); LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_CREATE: g_hWnd = hWnd; GdiplusStartup(&gdiToken,&gdiSI,NULL); // GDI+オブジェクト初期化 break; case WM_PAINT: { PAINTSTRUCT ps; g_hDC = BeginPaint(hWnd,&ps); Bre_Line(100, 100, 50, 200, Color(255,255,0,0)); Bre_Line(0, 0, 200, 200, Color(255,0,0,255)); EndPaint(hWnd,&ps); } break; case WM_CLOSE: DestroyWindow(hWnd); GdiplusShutdown(gdiToken); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; } // x1,y1:始点の座標、x2,y2:終点の座標、clr:色 void Bre_Line(int x1, int y1, int x2, int y2, Color clr) { int dx,dy; dx = abs(x2 - x1); dy = abs(y2 - y1); if(dx >= dy){ Bre_Line_Sub(dx, dy, x1, y1, x2, y2, clr, true); } else{ Bre_Line_Sub(dy, dx, y1, x1, y2, x2, clr, false); } }// Bre_Line void Bre_Line_Sub(int dx, int dy, int x1, int y1, int x2, int y2, Color clr, bool bSlantGently) { int d,x,y,xend,i1,i2,iy; i1 = 2 * dy; i2 = 2 * (dy - dx); d = i1 - dx; if((x1 <= x2 && y1 <= y2) || (x1 >= x2 && y1 >= y2)){ iy = 1; } else{ iy = -1; } if(x1 > x2){ x = x2; y = y2; xend = x1; } else{ x = x1; y = y1; xend = x2; } Bre_Write_Pixel(x, y, clr, bSlantGently); while(x < xend){ x ++; if(d < 0){ d += i1; } else{ y += iy; d += i2; } Bre_Write_Pixel(x, y, clr, bSlantGently); } }// Bre_Line_Sub void Bre_Write_Pixel(int x, int y, Color clr, bool bSlantGently) { int x2,y2; if(bSlantGently){ x2 = x; y2 = y; } else{ x2 = y; y2 = x; } Graphics g(g_hDC); SolidBrush brush(clr); Rect rect(x2, y2, 1, 1); g.FillRectangle(&brush, rect); }// Bre_Write_Pixel |
|
// Michener, Bresenham の円アルゴリズム // コンピュータ・グラフィックス 日本コンピュータ協会 P.452 〜 p.455 #include <windows.h> #include <stdio.h> #include <GdiPlus.h> #define MYWNDCLASS "Circle_CLASS" #define MYWNDTITLE "Circle" using namespace Gdiplus; GdiplusStartupInput gdiSI; ULONG_PTR gdiToken; HDC g_hDC; LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void Mich_Circle(POINT center, int radius, Color clr); void Circle_Points(POINT center, int radius, int x, int y, Color clr); void Write_Pixel(POINT center, int x, int y, Color clr); LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_CREATE: GdiplusStartup(&gdiToken,&gdiSI,NULL); // GDI+オブジェクト初期化 break; case WM_PAINT: PAINTSTRUCT ps; g_hDC = BeginPaint(hWnd,&ps); { int radius = 100; POINT center = {radius, radius}; Mich_Circle(center, radius, Color(255,255,0,0)); } EndPaint(hWnd,&ps); break; case WM_CLOSE: GdiplusShutdown(gdiToken); DestroyWindow(hWnd); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; }// WndProc // center:中心点、radius:半径、clr:色 void Mich_Circle(POINT center, int radius, Color clr) { center.x -= radius; center.y -= radius; int x,y,d; x = 0; y = radius; d = 3 - 2 * radius; while(x < y){ Circle_Points(center, radius, x, y, clr); if(d < 0){ d += 4 * x + 6; } else{ d += 4 * (x - y) + 10; y --; } x ++; } if(x == y){ Circle_Points(center, radius, x, y, clr); } }// Mich_Circle void Circle_Points(POINT center, int radius, int x, int y, Color clr) { x += radius; y += radius; int diameter = radius * 2; Write_Pixel(center, x, y, clr); Write_Pixel(center, y, x, clr); Write_Pixel(center, y, diameter-x, clr); Write_Pixel(center, x, diameter-y, clr); Write_Pixel(center, diameter-x, diameter-y, clr); Write_Pixel(center, diameter-y, diameter-x, clr); Write_Pixel(center, diameter-y, x, clr); Write_Pixel(center, diameter-x, y, clr); }// Circle_Points void Write_Pixel(POINT center, int x, int y, Color clr) { Graphics g(g_hDC); SolidBrush brush(clr); Rect rect(center.x+x, center.y+y, 1, 1); g.FillRectangle(&brush, rect); }// Write_Pixel |
コンピュータ・グラフィックス 日本コンピュータ協会 P.607 〜 p.611
中間階調化は、ディザ処理の1手法である。 ディザリングは他に、誤差拡散法などがある。 2×2 のパターンを使用する場合、2値画像では以下のように5階調を使える。 0 0 1 0 1 0 1 1 1 1 0 0 0 0 0 1 0 1 1 1 [size×size パターンの場合]
[2×2] [3×3] [4×4] [8×8] 0 2 6 8 4 0 8 2 10 0 32 8 40 2 34 10 42 3 1 1 0 3 12 4 14 6 48 16 56 24 50 18 58 26 5 2 7 3 11 1 9 12 44 4 36 14 46 6 38 15 7 13 5 60 28 52 20 62 30 54 22 3 35 11 43 1 33 9 41 51 19 59 27 49 17 57 25 15 47 7 39 13 45 5 37 63 31 55 23 61 29 53 21 // 8色、2×2行列のハーフトーニング void Halftone() { Color clr; BYTE r,g,b,c; float p,av; int size = 2; int pattern[4] = { 0, 2, 3, 1 }; for(UINT y=0;y<Bmp->GetHeight();y++){ for(UINT x=0;x<Bmp->GetWidth();x++){ Bmp->GetPixel(x, y , &clr); p = (float) (pattern[y%size*size+x%size]+1)*255/(size*size); r = clr.GetR(); g = clr.GetG(); b = clr.GetB(); if(p > r) r = 0; else r = 255; if(p > g) g = 0; else g = 255; if(p > b) b = 0; else b = 255; Bmp->SetPixel(x, y , Color(r,g,b)); } } } |
クリックした位置と同色で隣接している画素を塗り潰す。
Win32API には、ExtFloodFill() がある。 参考文献:高速塗りつぶし法(http://lee.phys.titech.ac.jp/~yasutake/PaintArea.html) |
// 領域塗り // コンピュータ・グラフィックス 日本コンピュータ協会 P.459 〜 p.461 #include <windows.h> #include <malloc.h> #include <GdiPlus.h> #define MYWNDCLASS "Class_GDI_Bucket" #define MYWNDTITLE "GDI_Bucket" #define SAFE_DELETE(p) if (p){delete p;p=NULL;} using namespace Gdiplus; GdiplusStartupInput gdiSI; ULONG_PTR gdiToken; Bitmap *Bmp; // 分岐情報を保持するスタック POINT *g_pt_stack = NULL; int g_num_stack = 0; LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); Status Bucket(Bitmap *Bmp, Point pos, Color clr_aft); bool Bucket_Sub(Bitmap *Bmp, int wdh, int hgt, ARGB rgb, Color clr_aft); POINT Bucket_Sub_x(Bitmap *Bmp, int wdh, int hgt, int xPos, int yPos, ARGB rgb, Color clr_aft); bool Check_StackSize(); void Read_Image(HWND hWnd, char *fname); int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSEX wc; HWND hWnd; // 親ウィンドウ ZeroMemory(&wc, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.lpfnWndProc = (WNDPROC)WndProc; wc.hInstance = hInst; wc.hIcon = LoadIcon(hInst, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)); wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wc.lpszMenuName = NULL; wc.lpszClassName = MYWNDCLASS; RegisterClassEx(&wc); hWnd = CreateWindowEx(WS_EX_CLIENTEDGE | WS_EX_ACCEPTFILES, MYWNDCLASS, MYWNDTITLE, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 480, 320, NULL, NULL, hInst, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }// WinMain() // 親ウィンドウ プロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: GdiplusStartup(&gdiToken,&gdiSI,NULL); // GDI+オブジェクト初期化 char s[256]; strcpy(s,MYWNDTITLE); strcat(s," - 画像ファイルをDrag&Dropしてください"); SendMessage(hWnd,WM_SETTEXT,0,(LPARAM)s); return 0; case WM_SHOWWINDOW: if(__argc > 1){ char fname[MAX_PATH]; strcpy(fname,__argv[1]); Read_Image(hWnd,fname); __argc = 0; } return 0; case WM_PAINT: HDC hDC; PAINTSTRUCT ps; hDC = BeginPaint(hWnd,&ps); if(Bmp){ Graphics g(hDC); g.DrawImage(Bmp, 0, 0, Bmp->GetWidth(), Bmp->GetHeight()); } EndPaint(hWnd,&ps); return 0; case WM_LBUTTONDOWN: if(Bmp){ Point pos; pos.X = LOWORD(lParam); pos.Y = HIWORD(lParam); // 座標posの色を黄色で塗りつぶす Bucket(Bmp, pos, Color(255,255,255,0)); InvalidateRect(hWnd,NULL,TRUE); } return 0; case WM_DROPFILES: HDROP hDrop; hDrop = (HDROP) wParam; char fname[MAX_PATH]; DragQueryFile(hDrop, 0, fname, sizeof(fname)); Read_Image(hWnd,fname); DragFinish(hDrop); InvalidateRect(hWnd,NULL,TRUE); return 0; case WM_CLOSE: SAFE_DELETE(Bmp); DestroyWindow(hWnd); GdiplusShutdown(gdiToken); PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, uMsg, wParam, lParam); }// WndProc // 塗りつぶし処理のサブルーチン // Bmp:元画像, pos:クリック座標, clr_aft:置き換える色 Status Bucket(Bitmap *Bmp, Point pos, Color clr_aft) { Status st = GenericError; int wdh = Bmp->GetWidth(); int hgt = Bmp->GetHeight(); Color clr_bfr; // 置き換えられる色 st = Bmp->GetPixel(pos.X, pos.Y, &clr_bfr); if(st != Ok){ MessageBox(NULL,"色を取得できません","",0); return st; } if(clr_bfr.GetValue() == clr_aft.GetValue()){ MessageBox(NULL,"同色です","",0); return st; } g_pt_stack = (LPPOINT) malloc(1024*sizeof(POINT)); g_num_stack = 0; g_pt_stack[g_num_stack].x = pos.X; g_pt_stack[g_num_stack].y = pos.Y; if(!Bucket_Sub(Bmp, wdh, hgt, clr_bfr.GetValue(), clr_aft)){ free(g_pt_stack); MessageBox (NULL,"メモリが不足しています","",0); return OutOfMemory; } free(g_pt_stack); return st; }// Bucket // 塗りつぶし範囲を求める bool Bucket_Sub(Bitmap *Bmp, int wdh, int hgt, ARGB rgb, Color clr_aft) { // flag1,flag2 は、1つ左のピクセルが塗り込み対象ではない場合で // カレントピクセルが塗り込み対象の場合には、スタックにカレント座標を積むためのフラグ // flag3 は、そのY座標でスタックに分岐座標を積んだかどうかのフラグ bool flag1,flag2,flag3; int x; int xPos,yPos; POINT pt; Color clr; while(g_num_stack >= 0){ // スタックが空になったら終了 flag3 = 1; while (flag3){ xPos = g_pt_stack[g_num_stack].x; yPos = g_pt_stack[g_num_stack].y; pt = Bucket_Sub_x(Bmp, wdh, hgt, xPos, yPos, rgb, clr_aft); flag1 = 0; flag2 = 0; flag3 = 0; for(x=pt.x; x<=pt.y; x++){ // 上向き if(yPos > 0){ Bmp->GetPixel(x, yPos-1, &clr); if(clr.GetValue() != rgb){ flag1 = 0; } else if(!flag1 && clr.GetValue() == rgb){ g_pt_stack[g_num_stack].x = x; g_pt_stack[g_num_stack].y = yPos-1; g_num_stack ++; if(!Check_StackSize()) return 0; flag1 = 1; flag3 = 1; } } // 下向き if(yPos < hgt - 1){ Bmp->GetPixel(x, yPos+1, &clr); if(clr.GetValue() != rgb){ flag2 = 0; } else if(!flag2 && clr.GetValue() == rgb){ g_pt_stack[g_num_stack].x = x; g_pt_stack[g_num_stack].y = yPos+1; g_num_stack ++; if(!Check_StackSize()) return 0; flag2 = 1; flag3 = 1; } } } g_num_stack --; if(g_num_stack < 0) break; } } return 1; }// Bucket_Sub // 点(xPos, yPos)の横方向のRGB(r,g,b)色で連続したピクセル値をclr_aftで塗り替える POINT Bucket_Sub_x(Bitmap *Bmp, int wdh, int hgt, int xPos, int yPos, ARGB rgb, Color clr_aft) { int x; POINT pt; Color clr; // 点(xPos, yPos)の左側を調べる x = 0; while (xPos >= x){ Bmp->GetPixel(xPos-x, yPos, &clr); if(clr.GetValue() == rgb) Bmp->SetPixel(xPos-x, yPos, clr_aft); else break; x ++; } pt.x = xPos - (x - 1); // 点(xPos, yPos)の右側を調べる x = 1; while (x < (wdh - xPos)){ Bmp->GetPixel(xPos+x, yPos, &clr); if(clr.GetValue() == rgb) Bmp->SetPixel(xPos+x, yPos, clr_aft); else break; x ++; } pt.y = xPos + (x - 1); return pt; }// Bucket_Sub_x // スタックを使い切ったら、スタックサイズを増やす bool Check_StackSize() { size_t size = _msize(g_pt_stack); if(g_num_stack * sizeof(POINT) >= size){ g_pt_stack = (POINT*)realloc(g_pt_stack, size+512*sizeof(POINT)); if(!g_pt_stack){ return 0; } } return 1; }// Check_StackSize // ファイルから画像を読み込む void Read_Image(HWND hWnd, char *fname) { WCHAR wfname[MAX_PATH]; MultiByteToWideChar(CP_ACP,0,fname,-1,wfname,sizeof(wfname)); SAFE_DELETE(Bmp); Bmp = Bitmap::FromFile(wfname,FALSE); char s[256]; strcpy(s,MYWNDTITLE); strcat(s," - 画像ファイルをクリックしてください"); SendMessage(hWnd,WM_SETTEXT,0,(LPARAM)s); }// Read_Image |
頂点座標が指定されている多角形を塗り潰す。
本来は、バケットと呼ばれるポインタとバケットソートで行うらしいが、代わりに、データ番号(配列の添え字)とクイックソートを使った。 多角形塗りは、スキャンラインアルゴリズムを使う。 これは、多角形の頂点リストのY座標の最小値(上図では1)から最大値(上図では10)までスキャンラインを動かして、スキャンラインと各辺の交点を順に直線で結ぶことで色を塗る。 上図の場合は、E2−E3間、E4−E5間において、それぞれのスキャンラインとの交点を結ぶ。 スキャンラインと各辺との交点は、 交点のX座標 = [辺の最小Y座標のX座標] + [辺の傾きの逆数の和] で求まる。 先ず、辺データ リストを作成する。 スキャンラインが下から上へ動くため、各辺データは総て、Y座標が低い方から高い方へのベクトルにする。 傾きが0の辺データはリストに加えない。 例えば、y == 3 のスキャンラインの場合、交点は、E1とE2の交点であるP1が2つと、E6との交点になるが、これでは適切に線を結ぶ事はできない。 スキャンラインが y == 4 の場合も同様である。 このような問題は、ある頂点において、隣接する2つの頂点のy座標が、その頂点のy座標よりも一方が低く、もう一方が高い場合に発生する。 この問題を解決するには、P1やP5の点を1つ無くせばいい。 そのためには、スキャンラインに最初に乗せるE2とE5のY座標を1つ上げればいい。 例えば、E2の場合は、点(2,4)と点P2を端点とする辺となる。 E5の場合は、点(10+(7-10)/(10-4),5)と点p4を端点とする辺となる。 [最小Y座標のX座標]と[スキャンラインと辺との交点のX座標]は、この例外を除き、同じ数値にし、[次のデータ番号]は空(から)を意味する-1を入れる。
次に、この辺データ リストを元に各辺のスキャンラインのY座標ごとに集計した辺テーブルを作成する。
スキャンラインのY座標を頂点リストのY座標の最小値(上図では1)から最大値(上図では10)までインクリメントして、以下の1〜5を繰り返す。
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// 多角形塗り // コンピュータ・グラフィックス J.D.FOLEY/A.VAN DAM著 日本コンピュータ協会 p.466 〜 p.470 #include <windows.h> #define MYWNDCLASS "PolygonPaintingCLASS" #define MYWNDTITLE "PolygonPainting" #define WIDTH 640 // ウィンドウの幅 #define HEIGHT 480 // ウィンドウの高さ #define WIDTHBYTES(bits) ((((bits) + 31)>>5)<<2) typedef struct{ int y_min; // 辺の最小Y座標 int y_max; // 辺の最大Y座標 int x_min; // 辺の最小Y座標のX座標 int x; // スキャンラインと辺の交点のX座標 double slant; // 辺の傾きの逆数 double slant_sum; // 辺の傾きの逆数の和 int next; // 次のデータ番号 }EDGE,*LPEDGE; typedef struct{ int index; // 最初のデータ番号 int last; // 最後のデータ番号 }ET,*LPET; HBITMAP ghBmpWork = NULL; BYTE *gpBitsWork = NULL; LRESULT CALLBACK ProcWnd(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void Create_Work_Dib(); int AETCmp(const EDGE *a, const EDGE *b); void Paint_Polygon(HDC hDC, POINT *pt, int pt_num); void Set_Data1(int idx_min, int idx_max, int y_min, int &data_ct, LPEDGE data, LPPOINT pos, LPET et); void Set_Data2(int idx_min, int idx_max, int y_min, int &data_ct, LPEDGE data, LPPOINT pos, LPET et); LRESULT CALLBACK ProcWnd(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_PAINT: { HDC hDC; PAINTSTRUCT ps; hDC = BeginPaint(hWnd,&ps); POINT pt[] = {{2,3},{2,9},{5,6},{7,10},{10,4},{6,1}}; // 多角形の頂点座標 int pt_num = sizeof(pt) / sizeof(POINT); // 頂点数 // 多角形のサイズを40倍にする for(int i=0;i<pt_num;i++){ pt[i].x *= 40; pt[i].y *= 40; } // 多角形塗り Create_Work_Dib(); if(ghBmpWork){ HDC hDCcp; HBITMAP hBmpPre; hDCcp = CreateCompatibleDC(hDC); hBmpPre = (HBITMAP) SelectObject(hDCcp, ghBmpWork); Paint_Polygon(hDCcp, pt, pt_num); BitBlt(hDC, 0, 0, WIDTH, HEIGHT, hDCcp, 0, 0, SRCCOPY); SelectObject(hDCcp, hBmpPre); DeleteDC(hDCcp); } DeleteObject(ghBmpWork); EndPaint(hWnd,&ps); } return 0; case WM_CLOSE: DestroyWindow(hWnd); PostQuitMessage(0); return 0; default: break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); }// ProcWnd // 作業用DIBの作成 void Create_Work_Dib() { BITMAPINFO bmi; ZeroMemory(&bmi, sizeof(bmi)); bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = WIDTH; bmi.bmiHeader.biHeight = HEIGHT; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 24; bmi.bmiHeader.biCompression = BI_RGB; ghBmpWork = (HBITMAP) CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (VOID **)&gpBitsWork, NULL, 0); // 作業用DIBの横幅を求める int stride = WIDTHBYTES(bmi.bmiHeader.biWidth * bmi.bmiHeader.biPlanes * bmi.bmiHeader.biBitCount); // 各行の余りバイト int rest = stride - bmi.bmiHeader.biWidth * (bmi.bmiHeader.biBitCount / 8); // 作業用DIBを白色で塗りつぶす int x,y; for(y=0; y<bmi.bmiHeader.biHeight; y++){ for(x=0; x<bmi.bmiHeader.biWidth; x++){ *(gpBitsWork ++) = 255; *(gpBitsWork ++) = 255; *(gpBitsWork ++) = 255; } for(x=0; x<rest; x++){ gpBitsWork += rest; } } }// Create_Work_Dib // クイックソートの比較関数 int AETCmp(const EDGE *a, const EDGE *b) { return (a->x - b->x); }// AETCmp // 多角形塗り // pt : 多角形の頂点座標 // pt_num : 配列 pt の要素数 void Paint_Polygon(HDC hDC, POINT *pt, int pt_num) { LPEDGE data = NULL; // 頂点リスト LPEDGE aet = NULL; // アクティブ辺テーブル(AET) LPET et = NULL; // 辺テーブル(ET) int i,j,y_min,y_max,y_num,prev,next,data_ct; // Y座標の最大値と最小値を求める y_min = pt[0].y; y_max = pt[0].y; for(i=1;i<pt_num;i++){ if(pt[i].y < y_min) y_min = pt[i].y; else if(pt[i].y > y_max) y_max = pt[i].y; } // 頂点リストのメモリ確保 data = (LPEDGE) malloc(sizeof(EDGE) * pt_num); if(!data){ MessageBox(GetFocus(), "メモリ確保エラー", MYWNDTITLE, MB_ICONWARNING); return; } ZeroMemory(data, sizeof(EDGE) * pt_num); for(i=0;i<pt_num;i++){ data[i].next = -1; } // AETのメモリ確保 aet = (LPEDGE) malloc(sizeof(EDGE) * pt_num); if(!aet){ MessageBox(GetFocus(), "メモリ確保エラー", MYWNDTITLE, MB_ICONWARNING); free(data); return; } ZeroMemory(aet, sizeof(EDGE) * pt_num); // ETの作成 y_num = y_max - y_min + 1; et = (LPET) malloc(sizeof(ET) * y_num); if(!et){ MessageBox(GetFocus(), "メモリ確保エラー", MYWNDTITLE, MB_ICONWARNING); free(data); free(aet); return; } for(i=0;i<y_num;i++){ et[i].index = -1; et[i].last = -1; } data_ct = 0; // data の要素数 for(i=0;i<pt_num;i++){ prev = i - 1; if(prev < 0){ prev = pt_num - 1; } next = i + 1; if(next >= pt_num){ next = 0; } if(pt[i].y < pt[prev].y && pt[i].y < pt[next].y){ Set_Data1(i, prev, y_min, data_ct, data, pt, et); Set_Data1(i, next, y_min, data_ct, data, pt, et); } else if(pt[i].y > pt[prev].y && pt[i].y < pt[next].y){ Set_Data2(i, next, y_min, data_ct, data, pt, et); } else if(pt[i].y > pt[next].y && pt[i].y < pt[prev].y){ Set_Data2(i, prev, y_min, data_ct, data, pt, et); } else if(pt[i].y == pt[prev].y && pt[i].y < pt[next].y){ Set_Data1(i, next, y_min, data_ct, data, pt, et); } else if(pt[i].y == pt[next].y && pt[i].y < pt[prev].y){ Set_Data1(i, prev, y_min, data_ct, data, pt, et); } else if(pt[i].y == pt[next].y && pt[i].y == pt[prev].y){ Set_Data1(i, i, y_min, data_ct, data, pt, et); } } // スキャン・ライン・アルゴリズムによる塗り潰し int aet_ct = 0; // AETの要素数 for(i=y_min;i<=y_max;i++){ int et_idx = i - y_min; if(et[et_idx].index != -1){ aet[aet_ct ++] = data[et[et_idx].index]; int next = data[et[et_idx].index].next; while(next != -1){ aet[aet_ct ++] = data[next]; next = data[next].next; } } // AET を x について昇順にソート qsort(aet, aet_ct, sizeof(EDGE), (int (*)(const void*, const void*))AETCmp); // スキャンラインの描画 int ct = 0; while(ct < aet_ct - 1){ COLORREF color = RGB(0, 0, 255); // 青色 for(j=aet[ct].x;j<=aet[ct+1].x;j++){ SetPixel(hDC, j ,i, color); } ct += 2; } // i == y_max の要素を AET から削除 for(j=aet_ct-1;j>=0;j--){ if(i == aet[j].y_max){ if(j < aet_ct-1){ memmove(aet+j, aet+(j+1), (aet_ct-(j+1))*sizeof(EDGE)); } aet_ct --; } } // AET の各 x を次のスキャンラインでの値に更新 for(j=0;j<aet_ct;j++){ aet[j].x = (int)(aet[j].x_min + aet[j].slant_sum); aet[j].slant_sum += aet[j].slant; } } free(data); data = NULL; free(aet); aet = NULL; free(et); et = NULL; }// Paint_Polygon // 頂点リスト設定1 void Set_Data1(int idx_min, int idx_max, int y_min, int &data_ct, LPEDGE data, LPPOINT pos, LPET et) { int et_idx = pos[idx_min].y - y_min; if(et[et_idx].last == -1) et[et_idx].index = data_ct; else data[et[et_idx].last].next = data_ct; et[et_idx].last = data_ct; data[data_ct].y_min = pos[idx_min].y; data[data_ct].y_max = pos[idx_max].y; data[data_ct].x_min = pos[idx_min].x; data[data_ct].x = data[data_ct].x_min; data[data_ct].slant = (double)(pos[idx_max].x - pos[idx_min].x) / (pos[idx_max].y - pos[idx_min].y); data[data_ct].slant_sum = data[data_ct].slant; data_ct ++; }// Set_Data1 // 頂点リスト設定2 void Set_Data2(int idx_min, int idx_max, int y_min, int &data_ct, LPEDGE data, LPPOINT pos, LPET et) { int et_idx = pos[idx_min].y - y_min + 1; if(et[et_idx].last == -1) et[et_idx].index = data_ct; else data[et[et_idx].last].next = data_ct; et[et_idx].last = data_ct; double slant = (double)(pos[idx_max].x - pos[idx_min].x) / (pos[idx_max].y - pos[idx_min].y); data[data_ct].y_min = pos[idx_min].y; data[data_ct].y_max = pos[idx_max].y; data[data_ct].x_min = pos[idx_min].x; data[data_ct].x = (int)(pos[idx_min].x + slant); data[data_ct].slant = slant; data[data_ct].slant_sum = data[data_ct].slant + slant; data_ct ++; }// Set_Data2 |
多角形塗りで、線分のピクセルに占める領域の比率を透明度とすると、アンチエイリアスをかけることが出来る。
以下のコードでは、多角形塗りと被る部分は省略。
|
// アンチエイリアジング多角形 // コンピュータ・グラフィックス J.D.FOLEY/A.VAN DAM著 日本コンピュータ協会 // 多角形塗り(p.466 〜 p.470) // アンチエイリアス(P.470 〜 p.471) #include <windows.h> #define MYWNDCLASS "GDI_AntialiasCLASS" #define MYWNDTITLE "GDI_Antialias" typedef struct{ BYTE red; // 赤 BYTE green; // 緑 BYTE blue; // 蒼 }COLOR; typedef struct{ int y_min; // 辺の最小Y座標 int y_max; // 辺の最大Y座標 int x_min; // 辺の最小Y座標のX座標 int x; // スキャンラインと辺の交点のX座標 double d; // 辺のそのピクセルに占める領域の比率 double slant; // 辺の傾きの逆数 double slant_sum; // 辺の傾きの逆数の和 int next; // 次のデータ番号 }EDGE,*LPEDGE; // 多角形塗り // pt : 多角形の頂点座標 // pt_num : 配列 pt の要素数 void Paint_Polygon(HDC hDC, POINT *pt, int pt_num) { LPEDGE data = NULL; // 頂点リスト LPEDGE aet = NULL; // アクティブ辺テーブル(AET) LPET et = NULL; // 辺テーブル(ET) int i,j,y_min,y_max,y_num,prev,next,data_ct; COLOR color_brush = { 0, 0, 255}; // 塗りつぶし色(青) COLOR color_back = {255, 255, 255}; // 背景色(白) COLOR color_dst; // アンチエイリアス色 // Y座標の最大値と最小値を求める y_min = pt[0].y; y_max = pt[0].y; for(i=1;i<pt_num;i++){ if(pt[i].y < y_min) y_min = pt[i].y; else if(pt[i].y > y_max) y_max = pt[i].y; } // 頂点リストのメモリ確保 data = (LPEDGE) malloc(sizeof(EDGE) * pt_num); if(!data){ MessageBox(GetFocus(), "メモリ確保エラー", MYWNDTITLE, MB_ICONWARNING); return; } ZeroMemory(data, sizeof(EDGE) * pt_num); for(i=0;i<pt_num;i++){ data[i].next = -1; } // AETのメモリ確保 aet = (LPEDGE) malloc(sizeof(EDGE) * pt_num); if(!aet){ MessageBox(GetFocus(), "メモリ確保エラー", MYWNDTITLE, MB_ICONWARNING); free(data); return; } ZeroMemory(aet, sizeof(EDGE) * pt_num); // ETの作成 y_num = y_max - y_min + 1; et = (LPET) malloc(sizeof(ET) * y_num); if(!et){ MessageBox(GetFocus(), "メモリ確保エラー", MYWNDTITLE, MB_ICONWARNING); free(data); free(aet); return; } for(i=0;i<y_num;i++){ et[i].index = -1; et[i].last = -1; } data_ct = 0; // data の要素数 for(i=0;i<pt_num;i++){ prev = i - 1; if(prev < 0){ prev = pt_num - 1; } next = i + 1; if(next >= pt_num){ next = 0; } if(pt[i].y < pt[prev].y && pt[i].y < pt[next].y){ Set_Data1(i, prev, y_min, data_ct, data, pt, et); Set_Data1(i, next, y_min, data_ct, data, pt, et); } else if(pt[i].y > pt[prev].y && pt[i].y < pt[next].y){ Set_Data2(i, next, y_min, data_ct, data, pt, et); } else if(pt[i].y > pt[next].y && pt[i].y < pt[prev].y){ Set_Data2(i, prev, y_min, data_ct, data, pt, et); } else if(pt[i].y == pt[prev].y && pt[i].y < pt[next].y){ Set_Data1(i, next, y_min, data_ct, data, pt, et); } else if(pt[i].y == pt[next].y && pt[i].y < pt[prev].y){ Set_Data1(i, prev, y_min, data_ct, data, pt, et); } else if(pt[i].y == pt[next].y && pt[i].y == pt[prev].y){ Set_Data1(i, i, y_min, data_ct, data, pt, et); } } // スキャン・ライン・アルゴリズムによる塗り潰し int aet_ct = 0; // AETの要素数 for(i=y_min;i<=y_max;i++){ int et_idx = i - y_min; if(et[et_idx].index != -1){ aet[aet_ct ++] = data[et[et_idx].index]; int next = data[et[et_idx].index].next; while(next != -1){ aet[aet_ct ++] = data[next]; next = data[next].next; } } // AET を x について昇順にソート qsort(aet, aet_ct, sizeof(EDGE), (int (*)(const void*, const void*))AETCmp); // スキャンラインの描画 int ct = 0; while(ct < aet_ct - 1){ double d2; d2 = 1 - aet[ct].d; color_dst.red = (BYTE)(color_back.red * aet[ct].d + color_brush.red * d2); color_dst.green = (BYTE)(color_back.green * aet[ct].d + color_brush.green * d2); color_dst.blue = (BYTE)(color_back.blue * aet[ct].d + color_brush.blue * d2); SetPixel(hDC, aet[ct].x , i, RGB(color_dst.red, color_dst.green, color_dst.blue)); d2 = 1 - aet[ct+1].d; color_dst.red = (BYTE)(color_back.red * d2 + color_brush.red * aet[ct+1].d); color_dst.green = (BYTE)(color_back.green * d2 + color_brush.green * aet[ct+1].d); color_dst.blue = (BYTE)(color_back.blue * d2 + color_brush.blue * aet[ct+1].d); SetPixel(hDC, aet[ct+1].x , i, RGB(color_dst.red, color_dst.green, color_dst.blue)); for(j=aet[ct].x+1;j<aet[ct+1].x;j++){ SetPixel(hDC, j ,i, RGB(color_brush.red, color_brush.green, color_brush.blue)); } ct += 2; } // i == y_max の要素を AET から削除 for(j=aet_ct-1;j>=0;j--){ if(i == aet[j].y_max){ if(j < aet_ct-1){ memmove(aet+j, aet+(j+1), (aet_ct-(j+1))*sizeof(EDGE)); } aet_ct --; } } // AET の各 x を次のスキャンラインでの値に更新 for(j=0;j<aet_ct;j++){ double sum = aet[j].x_min + aet[j].slant_sum; aet[j].x = (int) sum; aet[j].d = sum - aet[j].x; aet[j].slant_sum += aet[j].slant; } } free(data); data = NULL; free(aet); aet = NULL; free(et); et = NULL; }// Paint_Polygon |
|
// Cohen-Sutherland クリッピング・アルゴリズム // コンピュータ・グラフィックス J.D.FOLEY/A.VAN DAM著 日本コンピュータ協会 p.152 〜 p.155 #include <windows.h> #define MYWNDCLASS "ClippingLineCLASS" #define MYWNDTITLE "ClippingLine" typedef struct{ POINT pt1,pt2; // 線分の両端点の座標 WORD outcode1; // pt1 の外コード フラグ WORD outcode2; // pt2 の外コード フラグ }LINE,*LPLINE; LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void Cohen_CilpLine(HDC hDC, LINE *line, RECT rc); void Set_OutCodes(POINT pt, RECT rc, WORD *outcode); bool Reject_Check(WORD outcode1, WORD outcode2); bool Accept_Check(WORD outcode1, WORD outcode2); void Swap_Point(LINE *line); LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_PAINT: HDC hDC; PAINTSTRUCT ps; hDC = BeginPaint(hWnd,&ps); { LINE line; static RECT rc = {200,100,500,300}; Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom); ZeroMemory(&line, sizeof(line)); line.pt1.x = 100; line.pt1.y = 200; line.pt2.x = 600; line.pt2.y = 0; Cohen_CilpLine(hDC, &line, rc); } EndPaint(hWnd,&ps); return 0; case WM_CLOSE: DestroyWindow(hWnd); PostQuitMessage(0); return 0; default: break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); }// WndProc // Cohen-Sutherland クリッピング・アルゴリズム void Cohen_CilpLine(HDC hDC, LINE *line, RECT rc) { bool bAccept,bReject,bDone; bAccept = false; bReject = false; bDone = false; do{ Set_OutCodes(line->pt1, rc, &line->outcode1); Set_OutCodes(line->pt2, rc, &line->outcode2); bReject = Reject_Check(line->outcode1, line->outcode2); if(bReject){ bDone = true; } else{ bAccept = Accept_Check(line->outcode1, line->outcode2); if(bAccept){ bDone = true; } else{ if(!line->outcode1){ Swap_Point(line); } if(line->outcode1 & 0x1000){ line->pt1.x += (line->pt2.x - line->pt1.x) * (rc.bottom - line->pt1.y) / (line->pt2.y - line->pt1.y); line->pt1.y = rc.bottom; } else if(line->outcode1 & 0x0100){ line->pt1.x += (line->pt2.x - line->pt1.x) * (rc.top - line->pt1.y) / (line->pt2.y - line->pt1.y); line->pt1.y = rc.top; } else if(line->outcode1 & 0x0010){ line->pt1.y += (line->pt2.y - line->pt1.y) * (rc.right - line->pt1.x) / (line->pt2.x - line->pt1.x); line->pt1.x = rc.right; } else if(line->outcode1 & 0x0001){ line->pt1.y += (line->pt2.y - line->pt1.y) * (rc.left - line->pt1.x) / (line->pt2.x - line->pt1.x); line->pt1.x = rc.left; } } } }while(!bDone); if(bAccept){ POINT pt[2] = {{line->pt1.x,line->pt1.y}, {line->pt2.x,line->pt2.y}}; Polyline(hDC, pt, 2); } }// Cohen_CilpLine // 外コードの設定 void Set_OutCodes(POINT pt, RECT rc, WORD *outcode) { *outcode = 0; if(pt.x < rc.left){ *outcode |= 0x0001; } if(pt.x > rc.right){ *outcode |= 0x0010; } if(pt.y < rc.top){ *outcode |= 0x0100; } if(pt.y > rc.bottom){ *outcode |= 0x1000; } }// Set_OutCodes // 明らかな拒否テスト // 戻り値 : true→拒否、false→その他 bool Reject_Check(WORD outcode1, WORD outcode2) { if(outcode1 & outcode2){ return true; } return false; }// Reject_Check // 明らかな受け入れテスト // 戻り値 : true→受け入れ、false→その他 bool Accept_Check(WORD outcode1, WORD outcode2) { if(!outcode1 && !outcode2){ return true; } return false; }// Accept_Check // 端点の交換 void Swap_Point(LINE *line) { POINT pt; WORD outcode; pt = line->pt2; outcode = line->outcode2; line->pt2 = line->pt1; line->outcode2 = line->outcode1; line->pt1 = pt; line->outcode1 = outcode; }// Swap_Point |
|
// Sutherland-Hodgman クリッピング・アルゴリズム // 〔ただし、(外→外)の線分がクリップ領域を横切る場合には対応していない〕 // コンピュータ・グラフィックス J.D.FOLEY/A.VAN DAM著 日本コンピュータ協会 p.462 〜 p.464 #include <windows.h> #include <malloc.h> #define MYWNDCLASS "ClippingPolygonCLASS" #define MYWNDTITLE "ClippingPolygon" // 浮動小数点数を四捨五入して整数にする inline int Round(float val) { return (int)(val + 0.5f); }// Round typedef struct{ POINT pt1,pt2; // 線分の両端点の座標 }EDGE,*LPEDGE; typedef struct{ POINT pt1,pt2; // 線分の両端点の座標 POINT ptMin,ptMax;// 線分の座標の最小値と最大値 float slant; // 線分の傾き float height; // 直線とY軸との交点のY座標 }EDGE2,*LPEDGE2; typedef struct{ POINT pt; // 座標 short flagIS; // 交点フラグ(0:交点以外, 1:外→内, 2:内→外) long idxEdge;// 交点が属するクリップ領域の辺の番号 }POINTIS,*LPPOINTIS; LRESULT CALLBACK ProcWnd(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); bool Is_Inside(POINT ptSrc, EDGE2 *eBoundary, int eBoundary_num); void Set_DestPoint(POINT ptSrc, POINTIS *ptDst, int *ptDst_num); void Set_PtMinMax(LPPOINT ptMin, LPPOINT ptMax, POINT pt1, POINT pt2); void Set_DestPoint(POINTIS ptisSrc, POINTIS *ptDst, int *ptDst_num, short flagIS); POINTIS Get_IntersectPoint(POINT s, POINT p, EDGE2 *eBoundary, int eBoundary_num); int Get_LengthSegment(EDGE2 *eBoundary, int b_idx, POINTIS *ptDst, int d_idx, bool bClockwiseBoundary); void S_H_Clipping(HDC hDC, POINT *ptSrc, int ptSrc_num, EDGE2 *eBoundary, int eBoundary_num, bool bClockwiseBoundary); bool Is_Clockwise(POINT *pVtx, int vtx_num); LRESULT CALLBACK ProcWnd(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_PAINT: HDC hDC; PAINTSTRUCT ps; hDC = BeginPaint(hWnd,&ps); { // 多角形の頂点座標 // (右回り) static POINT ptSrc[] = {{600,200},{450,200},{450,350},{250,350}, {250,200},{100,200},{350,25}}; // static POINT ptSrc[] = {{100,50},{100,200},{300,200},{300,350}, // {50,350},{50,25},{450,25},{450,200}}; // (左回り) // static POINT ptSrc[] = {{600,200},{350,25},{100,200},{250,200}, // {250,350},{450,350},{450,200}}; // static POINT ptSrc[] = {{100,50},{450,200},{450,25},{50,25}, // {50,350},{300,350},{300,200},{100,200}}; static RECT rcBoundary = {200,100,500,300}; // クリップ領域 int ptSrc_num = sizeof(ptSrc) / sizeof(POINT); // 多角形の頂点数 int i; // 多角形の描画 for(i=0;i<ptSrc_num;i++){ int next = i + 1; if(next >= ptSrc_num){ next = 0; } POINT pt[2] = {{ptSrc[i].x, ptSrc[i].y}, {ptSrc[next].x, ptSrc[next].y}}; Polyline(hDC, pt, 2); } // クリップ領域の頂点座標 // (右回り) POINT ptBoundary[] = {{rcBoundary.left, rcBoundary.top}, {rcBoundary.right, rcBoundary.top}, {rcBoundary.right, rcBoundary.bottom}, {rcBoundary.left, rcBoundary.bottom}}; /* // (左回り) POINT ptBoundary[] = {{rcBoundary.left, rcBoundary.top}, {rcBoundary.left, rcBoundary.bottom}, {rcBoundary.right, rcBoundary.bottom}, {rcBoundary.right, rcBoundary.top}}; */ int ptBoundary_num = sizeof(ptBoundary) / sizeof(POINT); // クリップ領域は右回りか? bool bClockwiseBoundary = Is_Clockwise(ptBoundary, ptBoundary_num); // クリップ領域を構成する線分 LPEDGE2 eBoundary = (LPEDGE2) malloc(ptBoundary_num * sizeof(EDGE2)); POINT ptBd1,ptBd2; for(i=0;i<ptBoundary_num;i++){ int next = i + 1; if(next >= ptBoundary_num){ next = 0; } if(bClockwiseBoundary){ ptBd1 = ptBoundary[i]; ptBd2 = ptBoundary[next]; } else{ ptBd1 = ptBoundary[next]; ptBd2 = ptBoundary[i]; } eBoundary[i].pt1 = ptBd1; eBoundary[i].pt2 = ptBd2; if(eBoundary[i].pt2.x != eBoundary[i].pt1.x){ eBoundary[i].slant = (float)(eBoundary[i].pt2.y - eBoundary[i].pt1.y) / (eBoundary[i].pt2.x - eBoundary[i].pt1.x); eBoundary[i].height = (float)eBoundary[i].pt1.y - eBoundary[i].slant * eBoundary[i].pt1.x; } else{ eBoundary[i].slant = 0; eBoundary[i].height = 0; } Set_PtMinMax(&eBoundary[i].ptMin, &eBoundary[i].ptMax, eBoundary[i].pt1, eBoundary[i].pt2); } int eBoundary_num = ptBoundary_num; // クリップ領域の描画 for(i=0;i<eBoundary_num;i++){ POINT pt[2] = {{eBoundary[i].pt1.x, eBoundary[i].pt1.y}, {eBoundary[i].pt2.x, eBoundary[i].pt2.y}}; Polyline(hDC, pt, 2); } // 多角形のクリッピング S_H_Clipping(hDC, ptSrc, ptSrc_num, eBoundary, eBoundary_num, bClockwiseBoundary); free(eBoundary); } EndPaint(hWnd,&ps); return 0; case WM_CLOSE: DestroyWindow(hWnd); PostQuitMessage(0); return 0; default: break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); }// ProcWnd // 線分ptが辺eBoundaryの内側にあればtrueを返す bool Is_Inside(POINT ptSrc, EDGE2 *eBoundary, int eBoundary_num) { bool bIn = true; for(int i=0;i<eBoundary_num;i++){ POINT vecSrc = {{ptSrc.x - eBoundary[i].pt1.x}, {ptSrc.y - eBoundary[i].pt1.y}}; POINT vecBoundary = {{eBoundary[i].pt2.x - eBoundary[i].pt1.x}, {eBoundary[i].pt2.y - eBoundary[i].pt1.y}}; int cross_z = vecSrc.x * vecBoundary.y - vecSrc.y * vecBoundary.x; if(cross_z >= 0){ // 外積のZ成分が負ではない場合 bIn = false; } } return bIn; }// Is_Inside // 出力データの設定 void Set_DestPoint(POINT ptSrc, POINTIS *ptDst, int *ptDst_num) { ptDst[*ptDst_num].pt = ptSrc; ptDst[*ptDst_num].idxEdge = 0; ptDst[*ptDst_num].flagIS = 0; (*ptDst_num) ++; }// Set_DestPoint // 出力データの設定(オーバーロード) void Set_DestPoint(POINTIS ptisSrc, POINTIS *ptDst, int *ptDst_num, short flagIS) { ptDst[*ptDst_num].pt = ptisSrc.pt; ptDst[*ptDst_num].idxEdge = ptisSrc.idxEdge; ptDst[*ptDst_num].flagIS = flagIS; (*ptDst_num) ++; }// Set_DestPoint // 座標の最小値と最大値を設定する void Set_PtMinMax(LPPOINT ptMin, LPPOINT ptMax, POINT pt1, POINT pt2) { if(pt1.x < pt2.x){ ptMin->x = pt1.x; ptMax->x = pt2.x; } else{ ptMin->x = pt2.x; ptMax->x = pt1.x; } if(pt1.y < pt2.y){ ptMin->y = pt1.y; ptMax->y = pt2.y; } else{ ptMin->y = pt2.y; ptMax->y = pt1.y; } }// Set_PtMinMax // クリップ領域と多角形の辺の交点を求める POINTIS Get_IntersectPoint(POINT s, POINT p, EDGE2 *eBoundary, int eBoundary_num) { POINTIS ptIS = {0, 0, 0, 0}; POINT ptMin,ptMax; float slant[2],height[2]; int x,y; for(int i=0;i<eBoundary_num;i++){ Set_PtMinMax(&ptMin, &ptMax, s, p); // ptMin と ptMax で形成される矩形は、2つの線分でそれぞれ形成される矩形の重なる部分とする if(ptMin.x < eBoundary[i].ptMin.x){ ptMin.x = eBoundary[i].ptMin.x; } if(ptMin.y < eBoundary[i].ptMin.y){ ptMin.y = eBoundary[i].ptMin.y; } if(ptMax.x > eBoundary[i].ptMax.x){ ptMax.x = eBoundary[i].ptMax.x; } if(ptMax.y > eBoundary[i].ptMax.y){ ptMax.y = eBoundary[i].ptMax.y; } if(p.x != s.x && eBoundary[i].pt2.x != eBoundary[i].pt1.x){ slant[0] = (float)(p.y - s.y) / (p.x - s.x); slant[1] = eBoundary[i].slant; height[0] = (float)s.y - slant[0] * s.x; height[1] = eBoundary[i].height; if(slant[0] != slant[1]){ float fx = (height[1] - height[0]) / (slant[0] - slant[1]); x = Round(fx); y = Round(slant[1] * fx + height[1]); if(x >= ptMin.x && x <= ptMax.x && y >= ptMin.y && y <= ptMax.y){ ptIS.pt.x = x; ptIS.pt.y = y; ptIS.idxEdge = i; break; } } } else if(p.x != s.x && eBoundary[i].pt2.x == eBoundary[i].pt1.x){ slant[0] = (float)(p.y - s.y) / (p.x - s.x); height[0] = (float)s.y - slant[0] * s.x; x = eBoundary[i].pt1.x; y = Round(slant[0] * eBoundary[i].pt1.x + height[0]); if(x >= ptMin.x && x <= ptMax.x && y >= ptMin.y && y <= ptMax.y){ ptIS.pt.x = x; ptIS.pt.y = y; ptIS.idxEdge = i; break; } } else if(p.x == s.x && eBoundary[i].pt2.x != eBoundary[i].pt1.x){ slant[1] = eBoundary[i].slant; height[1] = eBoundary[i].height; x = s.x; y = Round(slant[1] * s.x + height[1]); if(x >= ptMin.x && x <= ptMax.x && y >= ptMin.y && y <= ptMax.y){ ptIS.pt.x = x; ptIS.pt.y = y; ptIS.idxEdge = i; break; } } } return ptIS; }// Get_IntersectPoint // 線分の長さ(ただし、計算速度重視の比較用で正確な長さではない)を返す int Get_LengthSegment(EDGE2 *eBoundary, int b_idx, POINTIS *ptDst, int d_idx, bool bClockwiseBoundary) { POINT ptBd; if(bClockwiseBoundary){ ptBd = eBoundary[b_idx].pt1; } else{ ptBd = eBoundary[b_idx].pt2; } return (abs(ptBd.x - ptDst[d_idx].pt.x) + abs(ptBd.y - ptDst[d_idx].pt.y)); }// Get_LengthSegment // Sutherland-Hodgman クリッピング・アルゴリズム void S_H_Clipping(HDC hDC, POINT *ptSrc, int ptSrc_num, EDGE2 *eBoundary, int eBoundary_num, bool bClockwiseBoundary) { POINT p,s; POINTIS is; int i; int ptDst_num; // ptDstの要素数 LPPOINTIS ptDst = (LPPOINTIS) malloc(0 * sizeof(POINTIS));// 出力する頂点座標 ptDst_num = 0; s = ptSrc[ptSrc_num-1]; for(i=0;i<ptSrc_num;i++){ // バッファサイズが足りなくなったら追加 size_t size = _msize(ptDst); if((UINT)(ptDst_num + 2) > size / sizeof(POINTIS)){ ptDst = (LPPOINTIS) realloc(ptDst, (ptDst_num + 2) * sizeof(POINTIS)); } // 多角形の各辺の両端点が、クリップ領域の内か外かの4つの場合分け // (外→内、内→内、内→外、外→外)で出力頂点を求める p = ptSrc[i]; if(Is_Inside(p, eBoundary, eBoundary_num)){ if(Is_Inside(s, eBoundary, eBoundary_num)){ // 内→内 Set_DestPoint(p, ptDst, &ptDst_num); } else{ // 外→内 is = Get_IntersectPoint(s, p, eBoundary, eBoundary_num); Set_DestPoint(is, ptDst, &ptDst_num, 1); Set_DestPoint(p, ptDst, &ptDst_num); } } else{ if(Is_Inside(s, eBoundary, eBoundary_num)){ // 内→外 is = Get_IntersectPoint(s, p, eBoundary, eBoundary_num); Set_DestPoint(is, ptDst, &ptDst_num, 2); } } s = p; } // 交点のアウトとインをつなぐ線分を追加 LPEDGE eDst = (LPEDGE) malloc(ptDst_num * sizeof(EDGE)); // 出力する線分 ZeroMemory(eDst, ptDst_num * sizeof(EDGE)); int eDst_num = ptDst_num; // eDstの要素数 int out_in_index; // 現在の(外→内)交点の ptDst の要素番号 int idx_b_out; // 現在の(内→外)交点の eBoundary の要素番号 int idx_b_in; // 現在の(外→内)交点の eBoundary の要素番号 int k,ct; int sz = 1; // ct の増分 bool bClockwiseSrc = Is_Clockwise(ptSrc, ptSrc_num); if((!bClockwiseSrc && bClockwiseBoundary) || (bClockwiseSrc && !bClockwiseBoundary)){ sz = -1; } int eDst_idx = 0; for(i=0;i<ptDst_num;i++){ int next = i + 1; if(next >= ptDst_num){ next = 0; } if(ptDst[i].flagIS == 2){ // (内→外)の時 eDst_num --; // 後で余分に加算するため、予め引いておく // 最も近い(外→内)交点を求める out_in_index = -1; int j2,dst_i,dst_k,dst_k_min; int j_start = ptDst[i].idxEdge; int j_end; if(sz == 1){ j_end = j_start + eBoundary_num; } else{ j_end = j_start - eBoundary_num; } dst_i = Get_LengthSegment(eBoundary, j_start, ptDst, i, bClockwiseBoundary); ct = j_start; do{ j2 = ct; if(j2 >= eBoundary_num){ j2 -= eBoundary_num; } if(j2 <= -1){ j2 += eBoundary_num; } dst_k_min = 0x7fffffff; // 最上位ビットは符号、Win32 の int は long for(k=0;k<ptDst_num;k++){ if(ptDst[k].idxEdge == j2 && ptDst[k].flagIS == 1){ // (外→内)の時 dst_k = Get_LengthSegment(eBoundary, j2, ptDst, k, bClockwiseBoundary); int dst1,dst2; if(sz == 1){ dst1 = dst_i; dst2 = dst_k; } else{ dst1 = dst_k; dst2 = dst_i; } if((ct == j_start && dst1 < dst2) || ct != j_start){// ct を j2 にしない if(dst_k < dst_k_min){ dst_k_min = dst_k; out_in_index = k; } } } } if(out_in_index != -1){ break; } ct += sz; }while(ct != j_end); // 最初の辺から一周して最初の辺まで if(out_in_index == -1){ SendMessage(GetFocus(), WM_SETTEXT, 0, (LPARAM)"(外→内)交点がありません"); break; } // (内→外)から(外→内)への線分を追加する idx_b_out = ptDst[i].idxEdge; idx_b_in = ptDst[out_in_index].idxEdge; ct = idx_b_out; do{ if(ct >= eBoundary_num){ ct -= eBoundary_num; } if(ct <= -1){ ct += eBoundary_num; } if(ct == idx_b_in){ // 最後の場合 eDst_num ++; eDst = (LPEDGE) realloc(eDst, eDst_num * sizeof(EDGE)); if(ct == idx_b_out){ // 最初と最後が同じ場合 eDst[eDst_idx].pt1 = ptDst[i].pt; } else{ eDst[eDst_idx].pt1 = eDst[eDst_idx-1].pt2; } eDst[eDst_idx].pt2 = ptDst[out_in_index].pt; eDst_idx ++; break; } // 最後以外は、クリップ領域の辺を(外→内)交点まで回る eDst_num ++; eDst = (LPEDGE) realloc(eDst, eDst_num * sizeof(EDGE)); int pre_eDst_idx = eDst_idx - 1; if(pre_eDst_idx < 0){ // 最初の場合 eDst[eDst_idx].pt1 = ptDst[i].pt; } else{ eDst[eDst_idx].pt1 = eDst[pre_eDst_idx].pt2; } if(bClockwiseSrc){ eDst[eDst_idx].pt2 = eBoundary[ct].pt2; } else{ eDst[eDst_idx].pt2 = eBoundary[ct].pt1; } eDst_idx ++; ct += sz; }while((ct + sz) != idx_b_in); } else{ // (内→外)以外の時 eDst[eDst_idx].pt1 = ptDst[i].pt; eDst[eDst_idx].pt2 = ptDst[next].pt; eDst_idx ++; } } // クリップされた多角形の描画 HPEN hPen,hPenOld; hPen = CreatePen(PS_SOLID, 1, RGB(255,0,0)); hPenOld = (HPEN) SelectObject(hDC, hPen); for(i=0;i<eDst_num;i++){ POINT pt[2] = {{eDst[i].pt1.x, eDst[i].pt1.y}, {eDst[i].pt2.x, eDst[i].pt2.y}}; Polyline(hDC, pt, 2); } SelectObject(hDC, hPenOld); DeleteObject(hPen); // メモリの解放 free(ptDst); free(eDst); }// S_H_Clipping // 多角形が右回りの場合は、true を返す // pVtx : 頂点座標 // vtx_num : 頂点の数(pVtxの要素数) bool Is_Clockwise(POINT *pVtx, int vtx_num) { // 凸(Convex)多角形の外積のZ成分(つまり法線ベクトルの向き)が負の場合は、 // その多角形は左回り、正の場合は右回り。 // 凹多角形にも対応させる時は下のような工夫が必要。辺が交差する多角形には対応できない。 int i,y_max_idx = 0; int y_max = pVtx[0].y; for(i=1;i<vtx_num;i++){ if(pVtx[i].y > y_max){ y_max = pVtx[i].y; y_max_idx = i; } } int idx[3]; idx[0] = y_max_idx - 1; if(idx[0] < 0){ idx[0] = vtx_num - 1; } idx[1] = y_max_idx; idx[2] = y_max_idx + 1; if(idx[2] >= vtx_num){ idx[2] = 0; } POINT vec1 = {pVtx[idx[1]].x - pVtx[idx[0]].x, pVtx[idx[1]].y - pVtx[idx[0]].y}; POINT vec2 = {pVtx[idx[2]].x - pVtx[idx[1]].x, pVtx[idx[2]].y - pVtx[idx[1]].y}; int cross_z = vec1.x * vec2.y - vec1.y * vec2.x; // 外積のZ成分 if(cross_z > 0){ return true; } return false; }// Is_Clockwise |
領域塗りを利用すると、マジックワンドのアルゴリズムを作成できる。
以下は、自作で、輪郭幅が1で袋小路がある場合には対応していない。 輪郭点を総てパスに入れているが、同じ傾きが並んでいる場合は、間引く事もできるはずである。 WinMain() は領域塗りと同じ。 Search_NextPt() は、再帰呼び出しで置き換え可能だが、再帰スタックのサイズには制限があるため、置き換えないほうが良い。 再帰スタックサイズは、コンパイルオプションで設定できる。 マジックワンドの手順は、
|
#include <windows.h> #include <malloc.h> #include <GdiPlus.h> #pragma comment(lib, "gdiplus.lib") #define MYWNDCLASS "Class_GDI_MagicWand" #define MYWNDTITLE "GDI_MagicWand" typedef struct{ POINT pos; bool flag; }RETPOS,*LPRETPOS; using namespace Gdiplus; GdiplusStartupInput gdiSI; ULONG_PTR gdiToken; Bitmap *Bmp = NULL; GraphicsPath *Path = NULL; const POINT g_pt1[9] = {{0,1},{-1,1},{-1,0},{-1,-1}, {0,-1},{1,-1},{1,0},{1,1},{0,1}}; // 8近傍の相対座標 // マジックワンドの座標データ typedef struct{ Point *pt; // 座標 int num; // ptの数 }MGC,*LPMGC; MGC g_mgc1; // 分岐情報を保持するスタック POINT *g_pt_stack = NULL; int g_num_stack = 0; LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); Status Magic_Wand(Bitmap *Bmp, GraphicsPath *path, Point pos); Point Search_FirstPt(BYTE *pxl, int wdh, int hgt, int x, int y); RETPOS Search_NextPt(BYTE *pxl, int wdh, int hgt, int x, int y); bool Bucket_Sub(Bitmap *Bmp, BYTE *pxl1, int wdh, int hgt, ARGB rgb); POINT Bucket_Sub_x(Bitmap *Bmp, BYTE *pxl1, int wdh, int hgt, int xPos, int yPos, ARGB rgb); bool Compare_Argb(Color clr, ARGB rgb); bool Check_StackSize(); bool Check_MagicMemorySize(); void Set_SafePos(Point *pt, int wdh, int hgt); void Read_Image(HWND hWnd, char *fname); // 親ウィンドウ プロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: GdiplusStartup(&gdiToken,&gdiSI,NULL); // GDI+オブジェクト初期化 char s[256]; strcpy(s,MYWNDTITLE); strcat(s," - 画像ファイルをDrag&Dropしてください"); SendMessage(hWnd,WM_SETTEXT,0,(LPARAM)s); return 0; case WM_SHOWWINDOW: if(__argc > 1){ char fname[MAX_PATH]; strcpy(fname,__argv[1]); Read_Image(hWnd,fname); __argc = 0; } return 0; case WM_PAINT: // 描画 HDC hDC; PAINTSTRUCT ps; hDC = BeginPaint(hWnd,&ps); if(Bmp){ Graphics g(hDC); g.DrawImage(Bmp, 0, 0, Bmp->GetWidth(), Bmp->GetHeight()); if(Path){ g.DrawPath(&Pen(Color(255,0,0),2), Path); // パスは赤色 } } EndPaint(hWnd,&ps); return 0; case WM_LBUTTONDOWN: // クリックでパスを作成 if(Bmp && Path){ Point pos; pos.X = LOWORD(lParam); pos.Y = HIWORD(lParam); // 座標posの色と違う色をパスに入れる Magic_Wand(Bmp, Path, pos); InvalidateRect(hWnd,NULL,FALSE); } return 0; case WM_RBUTTONDOWN: // 右クリックでパスをクリア if(Path){ Path->Reset(); } InvalidateRect(hWnd,NULL,TRUE); return 0; case WM_DROPFILES: // ウィンドウ内にファイルをドロップした時 HDROP hDrop; hDrop = (HDROP) wParam; char fname[MAX_PATH]; DragQueryFile(hDrop, 0, fname, sizeof(fname)); Read_Image(hWnd,fname); DragFinish(hDrop); InvalidateRect(hWnd,NULL,TRUE); return 0; case WM_CLOSE: if(Bmp){ delete Bmp; Bmp = NULL; } if(Path){ delete Path; Path = NULL; } if(g_mgc1.pt){ free(g_mgc1.pt); g_mgc1.pt = NULL; } if(g_pt_stack){ free(g_pt_stack); g_pt_stack = NULL; } DestroyWindow(hWnd); GdiplusShutdown(gdiToken); PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, uMsg, wParam, lParam); }// WndProc // マジックワンド Status Magic_Wand(Bitmap *Bmp, GraphicsPath *path, Point pos) { Status st = GenericError; if(!Bmp || !path){ MessageBox(GetFocus(),"画像、またはパスがありません","",MB_OK); return st; } int x,y,ab1; int wdh = Bmp->GetWidth() + 2; int hgt = Bmp->GetHeight() + 2; BYTE *pxl1 = NULL; // 同色と輪郭のピクセルのフラグ Color clr_bfr; // クリック座標の色 st = Bmp->GetPixel(pos.X, pos.Y, &clr_bfr); if(st != Ok){ MessageBox(GetFocus(),"色を取得できません","",MB_OK); return st; } Bitmap Bmp2(wdh, hgt, PixelFormat32bppARGB); Bmp2.SetResolution(Bmp->GetHorizontalResolution(), Bmp->GetVerticalResolution()); // 解像度の設定 Graphics gBmp(&Bmp2); gBmp.DrawRectangle(&Pen(clr_bfr, 1), 0, 0, wdh - 1, hgt - 1); gBmp.DrawImage(Bmp, 1, 1); // 右と下に 1 px ずつずらす pxl1 = new BYTE [wdh*hgt]; ZeroMemory(pxl1, wdh*hgt); if(g_pt_stack){ free(g_pt_stack); g_pt_stack = NULL; } g_pt_stack = (LPPOINT) malloc(1024*sizeof(POINT)); g_num_stack = 0; g_pt_stack[g_num_stack].x = pos.X + 1; // 最初にずらしたから、+1 する g_pt_stack[g_num_stack].y = pos.Y + 1; // clr_bfr と 同色のピクセルにフラグを設定する if(!Bucket_Sub(&Bmp2, pxl1, wdh, hgt, clr_bfr.GetValue())){ if(pxl1){ delete [] pxl1; pxl1 = NULL; } if(g_pt_stack){ free(g_pt_stack); g_pt_stack = NULL; } MessageBox(GetFocus(),"メモリが不足しています","",MB_OK); return OutOfMemory; } if(g_pt_stack){ free(g_pt_stack); g_pt_stack = NULL; } // マスクを反転 for(y=0;y<hgt;y++){ for(x=0;x<wdh;x++){ ab1 = wdh*y+x; if(pxl1[ab1]) pxl1[ab1] = 0; else pxl1[ab1] = 1; } } // 領域の輪郭を pxl1 に設定 int i,x2,y2; const POINT pt[4] = {{-1,0},{0,-1},{1,0},{0,1}}; // 4近傍の相対座標 for(y=1;y<hgt-1;y++){ for(x=1;x<wdh-1;x++){ ab1 = wdh*y+x; if(pxl1[ab1]){ for(i=0;i<4;i++){ x2 = x + pt[i].x; y2 = y + pt[i].y; if(pxl1[wdh*y2+x2] == 0){ pxl1[ab1] = 2; // 輪郭点は 2 にする break; } } } } } // クリックした座標から8方向に開始座標を探索し、輪郭の座標を並び順に g_mgc1 に設定 Point pos_first; pos_first = Search_FirstPt(pxl1, wdh, hgt, pos.X + 1, pos.Y + 1); if(pos_first.X >= 0 && pos_first.Y >= 0){ g_mgc1.num = 0; if(g_mgc1.pt){ free(g_mgc1.pt); g_mgc1.pt = NULL; } int mem_size = 1024 * sizeof(Point); g_mgc1.pt = (Point*) malloc(mem_size); RETPOS rp = {pos_first.X, pos_first.Y, 0}; do{ if(Check_MagicMemorySize()){ rp = Search_NextPt(pxl1, wdh, hgt, rp.pos.x, rp.pos.y); } }while(rp.flag == true); if(g_mgc1.num > 0){ path->StartFigure(); path->AddLines(g_mgc1.pt, g_mgc1.num); path->CloseFigure(); } } if(g_mgc1.pt){ free(g_mgc1.pt); g_mgc1.pt = NULL; } if(pxl1){ delete [] pxl1; pxl1 = NULL; } return st; }// Magic_Wand Point Search_FirstPt(BYTE *pxl, int wdh, int hgt, int x, int y) { Point pt2(-1,-1); int i,x2,y2; for(i=0;i<8;i++){ bool flag_loop_out = false; x2 = x; y2 = y; do{ x2 += g_pt1[i].x; y2 += g_pt1[i].y; if(x2 >= 1 && y2 >= 1 && x2 < wdh -1 && y2 < hgt - 1){ if(pxl[wdh*y2+x2] == 2){ flag_loop_out = true; // 見つかったら終わり pt2.X = x2; pt2.Y = y2; } } else{ if( i == 0 && y2 >= hgt - 1 || i == 1 && x2 < 1 && y2 >= hgt - 1 || i == 2 && x2 < 1 || i == 3 && x2 < 1 && y2 < 1 || i == 4 && y2 < 1 || i == 5 && x2 >= wdh -1 && y2 < 1 || i == 6 && x2 >= wdh -1 || i == 7 && x2 >= wdh -1 && y2 >= hgt - 1) { flag_loop_out = true; // 見つからなかったら終わり } } }while(!flag_loop_out); } return pt2; }// Search_FirstPt // 輪郭線の次の点を探す RETPOS Search_NextPt(BYTE *pxl, int wdh, int hgt, int x, int y) { int i,x2,y2,x3,y3; RETPOS rp = {0, 0, 0}; for(i=0;i<8;i++){ x2 = x + g_pt1[i].x; y2 = y + g_pt1[i].y; x3 = x + g_pt1[i+1].x; y3 = y + g_pt1[i+1].y; if(pxl[wdh*y2+x2] == 2 && pxl[wdh*y3+x3] == 0){ g_mgc1.pt[g_mgc1.num].X = x - 1; // 最初にずらしたから、-1 する g_mgc1.pt[g_mgc1.num].Y = y - 1; Set_SafePos(&g_mgc1.pt[g_mgc1.num], wdh-3, hgt-3); g_mgc1.num ++; pxl[wdh*y2+x2] = 1; // 座標を g_mgc1 に入れた輪郭点は消去 rp.flag = true; rp.pos.x = x2; rp.pos.y = y2; break; } } return rp; }// Search_NextPt // 塗りつぶし範囲を求める bool Bucket_Sub(Bitmap *Bmp, BYTE *pxl1, int wdh, int hgt, ARGB rgb) { // pxl1は、塗りこみ対象の座標にフラグを立てるマスク // flagCは、同じ色である場合にTRUEにするフラグ // flag1,flag2 は、1つ左のピクセルが塗り込み対象ではない場合で // カレントピクセルが塗り込み対象の場合には、スタックにカレント座標を積むためのフラグ // flag3 は、スタックに分岐座標を積んだかどうかのフラグ bool flag1,flag2,flag3,flagC; int x; int xPos,yPos; POINT pt; Color clr; while(g_num_stack >= 0){ // スタックが空になったら終了 flag3 = 1; while (flag3){ xPos = g_pt_stack[g_num_stack].x; yPos = g_pt_stack[g_num_stack].y; pt = Bucket_Sub_x(Bmp, pxl1, wdh, hgt, xPos, yPos, rgb); flag1 = 0; flag2 = 0; flag3 = 0; for(x=pt.x; x<=pt.y; x++){ // 上向き if(yPos > 0){ Bmp->GetPixel(x, yPos-1, &clr); flagC = Compare_Argb(clr, rgb); if(pxl1[wdh*(yPos-1)+x] || !flagC){ flag1 = 0; } else if(!pxl1[wdh*(yPos-1)+x] && !flag1 && flagC){ g_pt_stack[g_num_stack].x = x; g_pt_stack[g_num_stack].y = yPos-1; g_num_stack ++; if(!Check_StackSize()) return 0; flag1 = 1; flag3 = 1; } } // 下向き if(yPos < hgt - 1){ Bmp->GetPixel(x, yPos+1, &clr); flagC = Compare_Argb(clr, rgb); if(pxl1[wdh*(yPos+1)+x] || !flagC){ flag2 = 0; } else if(!pxl1[wdh*(yPos+1)+x] && !flag2 && flagC){ g_pt_stack[g_num_stack].x = x; g_pt_stack[g_num_stack].y = yPos+1; g_num_stack ++; if(!Check_StackSize()) return 0; flag2 = 1; flag3 = 1; } } } g_num_stack --; if(g_num_stack < 0) break; } } return 1; }// Bucket_Sub // 点(xPos, yPos)の横方向のRGB(r,g,b)色で連続したピクセル値をclr_aftで塗り替える POINT Bucket_Sub_x(Bitmap *Bmp, BYTE *pxl1, int wdh, int hgt, int xPos, int yPos, ARGB rgb) { int x; POINT pt; Color clr; // 点(xPos, yPos)の左側を調べる x = 0; while (xPos >= x){ Bmp->GetPixel(xPos-x, yPos, &clr); if(Compare_Argb(clr, rgb)) pxl1[wdh*yPos+xPos-x] = 1; else break; x ++; } pt.x = xPos - (x - 1); // 点(xPos, yPos)の右側を調べる x = 1; while (x < (wdh - xPos)){ Bmp->GetPixel(xPos+x, yPos, &clr); if(Compare_Argb(clr, rgb)) pxl1[wdh*yPos+xPos+x] = 1; else break; x ++; } pt.y = xPos + (x - 1); return pt; }// Bucket_Sub_x // 塗り込み値の判定 bool Compare_Argb(Color clr, ARGB rgb) { bool flag = 0; if( clr.GetA() == 255 && clr.GetValue() == rgb || clr.GetA() < 255 && clr.GetA() == (rgb >> 24) ){ flag = 1; } return flag; }// Compare_Argb // スタックを使い切ったら、スタックサイズを増やす bool Check_StackSize() { size_t size = _msize(g_pt_stack); if(g_num_stack * sizeof(POINT) >= size){ g_pt_stack = (POINT*)realloc(g_pt_stack, size+512*sizeof(POINT)); if(!g_pt_stack){ return 0; } } return 1; }// Check_StackSize // マジックワンドのメモリーを使い切ったら、サイズを増やす bool Check_MagicMemorySize() { size_t size = _msize(g_mgc1.pt); if(g_mgc1.num * sizeof(POINT) >= size){ g_mgc1.pt = (Point*)realloc(g_mgc1.pt, size+512*sizeof(Point)); if(!g_mgc1.pt){ return 0; } } return 1; }// Check_MagicMemorySize // 画像内に収まる座標に設定 void Set_SafePos(Point *pt, int wdh, int hgt) { if(pt->X < 0) pt->X = 0; if(pt->X > wdh) pt->X = wdh; if(pt->Y < 0) pt->Y = 0; if(pt->Y > hgt) pt->Y = hgt; }// Set_SafePos // ファイルから画像を読み込む void Read_Image(HWND hWnd, char *fname) { WCHAR wfname[MAX_PATH]; MultiByteToWideChar(CP_ACP,0,fname,-1,wfname,sizeof(wfname)); if(Bmp){ delete Bmp; Bmp = NULL; } Bmp = Bitmap::FromFile(wfname,FALSE); if(Path){ delete Path; Path = NULL; } Path = new GraphicsPath(); char s[256]; strcpy(s,MYWNDTITLE); strcat(s," - 画像をクリックしてください"); SendMessage(hWnd,WM_SETTEXT,0,(LPARAM)s); }// Read_Image |
メディアンカットは、フルカラーをインデックスカラーに変換するアルゴリズムの1つである。
手順(256色の場合)は、
色種列挙の時は、メモリ節約のため、RGB各色の下位3ビットを除去している。 メディアンカットはディザがないと厳しい。ディザありのコードは、PicTer を参照。 参考文献: 減色方法について(http://www1.kamakuranet.ne.jp/smo/proctalk/talk7.htm) Study of Color Image Quantization(http://www.ynk.ic.kanagawa-it.ac.jp/~kasuga/gentei.html) |
#include <windows.h> #include <stdio.h> #include ".\\include\\GdiPlus.h" #pragma comment(lib,"GdiPlus.lib") #define MYWNDCLASS "MEDIANCUT_CLASS" #define MYWNDTITLE "MEDIANCUT" #define WIDTHBYTES(width, bpp) ((((width * bpp) + 31) >> 5) << 2) #define SAFE_DELETE_ARRAY(p) if (p){delete[]p;p=NULL;} #define SAFE_DELETE_ARRAY_BYTE(p) if (p){delete[](LPBYTE)p;p=NULL;} #define FREE(p) {free(p);p=NULL;} using namespace Gdiplus; GdiplusStartupInput gdiSI; ULONG_PTR gdiToken; char fname[MAX_PATH] = ""; // 読み込みファイル名 typedef struct{ BYTE R; BYTE G; BYTE B; }BRGB,*LPBRGB; typedef struct{ WORD Val; WORD Num; }DATA,*LPDATA; typedef struct{ BYTE min[3],max[3],len[3]; int data_len; DATA *pData; }MEDIAN,*LPMEDIAN; MEDIAN g_mdn[256]; // 3つの整数の内、最大の整数を返す inline int max3(int a, int b, int c) { int max = a; if(b > max){ max = b; } if(c > max){ max = c; } return max; }// max3 // 3つの整数の内、最大の整数となる番号を返す inline int max3n(int a, int b, int c) { int max = a; int index = 0; if(b > max){ max = b; index = 1; } if(c > max){ max = c; index = 2; } return index; }// max3n LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); int GetEncoderClsid(const WCHAR* format, CLSID* pClsid); void Load_Image(HWND hWnd); Status Median_Cut(HWND hWnd, Bitmap *Bmp_R, UINT wdh, UINT hgt); void Set_MdnInfo(int index); int DATAcmpR(const DATA *a, const DATA *b); int DATAcmpG(const DATA *a, const DATA *b); int DATAcmpB(const DATA *a, const DATA *b); DWORD Get_Pixel_All(BYTE *pixels1, DATA *pData, UINT wdh, UINT hgt, UINT stride1); void Set_IdxClrPx(int bpp, BYTE *pixels, UINT stride, int x, int y, int val); int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASSEX wc; HWND hWnd; // 親ウィンドウ ZeroMemory(&wc, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.lpfnWndProc = (WNDPROC)WndProc; wc.hInstance = hInst; wc.hIcon = LoadIcon(hInst, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)); wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wc.lpszMenuName = NULL; wc.lpszClassName = MYWNDCLASS; RegisterClassEx(&wc); hWnd = CreateWindowEx(WS_EX_CLIENTEDGE | WS_EX_ACCEPTFILES, MYWNDCLASS, MYWNDTITLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 480, 320, NULL, NULL, hInst, NULL); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }// WinMain // 親ウィンドウ プロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: GdiplusStartup(&gdiToken,&gdiSI,NULL); // GDI+オブジェクト初期化 break; case WM_SIZE: if(__argc > 1){ // コマンドライン引数があるとき if(!strcmp(fname, "")){ strcpy(fname,__argv[1]); Load_Image(hWnd); // 画像の読み込み } } break; case WM_DROPFILES: // ドロップファイルを受け取る HDROP hDrop; hDrop = (HDROP) wParam; DragQueryFile(hDrop, 0, fname, sizeof(fname)); Load_Image(hWnd); DragFinish(hDrop); break; case WM_CLOSE: DestroyWindow(hWnd); GdiplusShutdown(gdiToken); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; }// CALLBACK WndProc // ファイルから画像を読み込む void Load_Image(HWND hWnd) { WCHAR wfname[MAX_PATH]; Bitmap *Bmp_R = NULL; UINT wdh,hgt; MultiByteToWideChar(CP_ACP,0,fname,-1,wfname,sizeof(wfname)); Bmp_R = Bitmap::FromFile(wfname,FALSE); wdh = Bmp_R->GetWidth(); hgt = Bmp_R->GetHeight(); Median_Cut(hWnd, Bmp_R, wdh, hgt); if(Bmp_R){ delete Bmp_R; Bmp_R = NULL; } } // フルカラーから256色インデックスを作成する Status Median_Cut(HWND hWnd, Bitmap *Bmp_R, UINT wdh, UINT hgt) { Status st; BitmapData bitmapData; Rect rect(0, 0, wdh, hgt); int i,j; int nColors; // パレットの色の総数(最終インデックス数) BYTE *pixels2 = NULL; // *pixels1とpal_256の対応表(最終画素情報) DWORD pal_256[256]; // パレットの色データ(最終インデックス情報) UINT bpp = 8; // カラーデプス(1 or 4 or 8) UINT index = 1 << bpp; // 色深度から色数を求める // Bmp_Rを32bitフォーマットの読み込み専用ビットマップ領域としてロックする st = Bmp_R->LockBits(&rect, ImageLockModeRead, PixelFormat32bppARGB, &bitmapData); if (st == Ok) { BYTE *pixels1 = NULL; // 32bpp色画素情報 int pos; UINT x,y; UINT stride1,stride2; // 読み込みデータと保存データの4byte境界幅 if (bitmapData.Stride > 0) pixels1 = (BYTE*) bitmapData.Scan0; else pixels1 = (BYTE*) bitmapData.Scan0 + (bitmapData.Stride)*(bitmapData.Height-1); stride1 = abs(bitmapData.Stride); // 保存用4byte境界幅の計算 stride2 = WIDTHBYTES(wdh, bpp); pixels2 = new BYTE [stride2 * hgt]; BRGB bRGB[256]; // pal_256 の元となるデータ ZeroMemory(g_mdn, sizeof(g_mdn)); g_mdn[0].pData = (DATA*) malloc(wdh * hgt * sizeof(DATA)); ZeroMemory(g_mdn[0].pData, wdh * hgt * sizeof(DATA)); // 画像で使用されている色の種類を総てg_mdn[0].pDataに入れる g_mdn[0].data_len = Get_Pixel_All(pixels1, g_mdn[0].pData, wdh, hgt, stride1); // データ情報初期化 Set_MdnInfo(0); // メディアン カット UINT now = 0; // 半分に分割するデータの g_mdn 番号 UINT next = 1; // 分割したデータを入れる g_mdn 番号 while(next < index){ int v_max = 0; for(i=0; i<(int)next; i++){ int v_max2 = max3(g_mdn[i].len[0], g_mdn[i].len[1], g_mdn[i].len[2]); // 最大の色範囲を求める if(v_max2 > v_max){ v_max = v_max2; now = i; } } int n_max = max3n(g_mdn[now].len[0], g_mdn[now].len[1], g_mdn[now].len[2]); // 範囲が最大の色番号を求める int len_mid1 = g_mdn[now].data_len / 2; // 分割した時の一方のデータの最初の要素番号 int len_mid2 = len_mid1 - 1; // 分割した時のもう一方のデータの最後の要素番号 if(len_mid1 > 0){ switch(n_max) { case 0: qsort(g_mdn[now].pData, g_mdn[now].data_len, sizeof(DATA), (int (*)(const void*, const void*))DATAcmpB); break; case 1: qsort(g_mdn[now].pData, g_mdn[now].data_len, sizeof(DATA), (int (*)(const void*, const void*))DATAcmpG); break; case 2: qsort(g_mdn[now].pData, g_mdn[now].data_len, sizeof(DATA), (int (*)(const void*, const void*))DATAcmpR); break; } g_mdn[next].data_len = g_mdn[now].data_len - len_mid1; g_mdn[next].pData = (DATA*) malloc(g_mdn[next].data_len * sizeof(DATA)); memcpy(g_mdn[next].pData, g_mdn[now].pData + len_mid1, g_mdn[next].data_len * sizeof(DATA)); g_mdn[now].data_len = len_mid2 + 1; g_mdn[now].pData = (DATA*) realloc(g_mdn[now].pData, g_mdn[now].data_len * sizeof(DATA)); Set_MdnInfo(now); Set_MdnInfo(next); next ++; } else{ break; } } nColors = next; // パレットの色数 // パレット色作成(透明度は0xffにする。色は配列内の色の平均値とする) int sum_r,sum_g,sum_b,sum_num; for(i=0; i<(int)nColors; i++){ for(j=0; j<3; j++){ g_mdn[i].min[j] <<= 3; g_mdn[i].max[j] <<= 3; } sum_b = 0; sum_g = 0; sum_r = 0; sum_num = 0; for(j=0; j<g_mdn[i].data_len; j++){ sum_b += (((g_mdn[i].pData[j].Val >> 10) & 0x1f) * g_mdn[i].pData[j].Num); sum_g += (((g_mdn[i].pData[j].Val >> 5) & 0x1f) * g_mdn[i].pData[j].Num); sum_r += (( g_mdn[i].pData[j].Val & 0x1f) * g_mdn[i].pData[j].Num); sum_num += g_mdn[i].pData[j].Num; } bRGB[i].B = (BYTE)((sum_b << 3) / sum_num); bRGB[i].G = (BYTE)((sum_g << 3) / sum_num); bRGB[i].R = (BYTE)((sum_r << 3) / sum_num); pal_256[i] = 0xff000000 | bRGB[i].R << 16 | bRGB[i].G << 8 | bRGB[i].B; } // メモリ解放 for(i=0; i<256; i++){ FREE(g_mdn[i].pData); } // 各ピクセルの近似色をパレットから探し、色番号を入れる int p_num; BYTE *pixels3 = NULL; BYTE pxl[3]; pixels3 = new BYTE [stride1 * hgt]; memcpy(pixels3, pixels1, stride1*hgt*sizeof(BYTE)); for(y=0; y<hgt; y++){ for(x=0; x<wdh; x++){ pos = stride1*y+x*4; for(i=0; i<3; i++){ pxl[i] = pixels3[pos+i] & 0xf8; } p_num = 0; for(i=0; i<(int)nColors; i++){ if( pxl[0] >= g_mdn[i].min[0] && pxl[0] <= g_mdn[i].max[0] && pxl[1] >= g_mdn[i].min[1] && pxl[1] <= g_mdn[i].max[1] && pxl[2] >= g_mdn[i].min[2] && pxl[2] <= g_mdn[i].max[2]) { p_num = i; break; } } Set_IdxClrPx(bpp, pixels2, stride2, x, y, p_num); } } SAFE_DELETE_ARRAY(pixels3); st = Bmp_R->UnlockBits(&bitmapData); if(st != Ok){ MessageBox (GetFocus(),"Bmp_Rをロック解除できません","",MB_OK); SAFE_DELETE_ARRAY(pixels2); return st; } } else{ MessageBox (GetFocus(),"Bmp_Rをロックできません","",MB_OK); return st; } if(st == Ok){ PixelFormat format; if(bpp == 1) format = PixelFormat1bppIndexed; else if(bpp == 4) format = PixelFormat4bppIndexed; else format = PixelFormat8bppIndexed; Bitmap Bmp_W(wdh, hgt, format); DWORD dwSizeColorPalette; dwSizeColorPalette = sizeof(ColorPalette); dwSizeColorPalette += sizeof(ARGB)*(nColors-1); ColorPalette *ppal = (ColorPalette *)new BYTE[dwSizeColorPalette]; if (ppal == NULL){ MessageBox (GetFocus(), "メモリが不足しています", "", MB_OK); SAFE_DELETE_ARRAY(pixels2); return OutOfMemory; } ZeroMemory(ppal, dwSizeColorPalette); ppal->Flags = 0; ppal->Count = nColors; for (i = 0; i < nColors; i++) { Color clr(pal_256[i]); ppal->Entries[i] = clr.GetValue(); } st = Bmp_W.SetPalette(ppal); if (st != Ok) { MessageBox (GetFocus(),"パレットを作成できません","",MB_OK); SAFE_DELETE_ARRAY_BYTE(ppal); SAFE_DELETE_ARRAY(pixels2); return st; } st = Bmp_W.LockBits(&rect, ImageLockModeWrite, Bmp_W.GetPixelFormat(), &bitmapData ); if (st == Ok) { BYTE *pixels = NULL; if (bitmapData.Stride > 0) pixels = (BYTE*) bitmapData.Scan0; else pixels = (BYTE*) bitmapData.Scan0 + bitmapData.Stride*(hgt-1); UINT stride = abs(bitmapData.Stride); memcpy(pixels, pixels2, stride*hgt*sizeof(BYTE)); st = Bmp_W.UnlockBits(&bitmapData); } if (st != Ok){ MessageBox (GetFocus(),"Bmp_Wをロック又はロック解除できません","",MB_OK); } HDC hDC = GetDC(hWnd); Graphics MyGraphics(hDC); MyGraphics.DrawImage(&Bmp_W, 0, 0, wdh, hgt); ReleaseDC(hWnd, hDC); SAFE_DELETE_ARRAY_BYTE(ppal); } SAFE_DELETE_ARRAY(pixels2); return st; }// Median_Cut // 配列をRGBそれぞれについて、最小値、最大値、長さを求める void Set_MdnInfo(int index) { if(g_mdn[index].pData){ int i,r,g,b,min_r,min_g,min_b,max_r,max_g,max_b,len_r,len_g,len_b; min_r = 255; min_g = 255; min_b = 255; max_r = 0; max_g = 0; max_b = 0; for(i=0; i<g_mdn[index].data_len; i++){ r = g_mdn[index].pData[i].Val & 0x1f; if(min_r > r) min_r = r; if(max_r < r) max_r = r; g = (g_mdn[index].pData[i].Val >> 5) & 0x1f; if(min_g > g) min_g = g; if(max_g < g) max_g = g; b = (g_mdn[index].pData[i].Val >> 10) & 0x1f; if(min_b > b) min_b = b; if(max_b < b) max_b = b; } len_r = max_r - min_r; len_g = max_g - min_g; len_b = max_b - min_b; g_mdn[index].min[0] = min_b; g_mdn[index].min[1] = min_g; g_mdn[index].min[2] = min_r; g_mdn[index].max[0] = max_b; g_mdn[index].max[1] = max_g; g_mdn[index].max[2] = max_r; g_mdn[index].len[0] = len_b; g_mdn[index].len[1] = len_g; g_mdn[index].len[2] = len_r; } }// Set_MdnInfo // クイックソートの比較関数(昇順・赤) int DATAcmpR(const DATA *a, const DATA *b) { return ((a->Val & 0x1f) - (b->Val & 0x1f)); } // クイックソートの比較関数(昇順・緑) int DATAcmpG(const DATA *a, const DATA *b) { return (((a->Val >> 5) & 0x1f) - ((b->Val >> 5) & 0x1f)); } // クイックソートの比較関数(昇順・青) int DATAcmpB(const DATA *a, const DATA *b) { return (((a->Val >> 10) & 0x1f) - ((b->Val >> 10) & 0x1f)); } // 画像で使用される全ての色種を取り出す // pixel1 : 画像のピクセル値 // pData : 取り出した色種データを格納する構造体 // stride1 : 4byte境界の横幅 // 戻り値 : 画素数 DWORD Get_Pixel_All(BYTE *pixels1, DATA *pData, UINT wdh, UINT hgt, UINT stride1) { if(!pixels1 || !pData){ return 0; } UINT i,x,y,flag,pos; DWORD k,ct; BYTE pxl[3]; int n_max = (1 << (sizeof(WORD) * 8)) - 1; ct = 0; for(y=0; y<hgt; y++){ for(x=0; x<wdh; x++){ pos = stride1*y+x*4; for(i=0;i<3;i++){ pxl[i] = pixels1[pos+i] >> 3; } flag = 0; for(k=0; k<ct; k++){ if(((pData[k].Val >> 10) & 0x1f) == pxl[0] && ((pData[k].Val >> 5) & 0x1f) == pxl[1] && ( pData[k].Val & 0x1f) == pxl[2]) { flag = 1; if(pData[k].Num < n_max){ pData[k].Num ++; } break; } } if(flag == 0){ pData[ct].Val = (pxl[0] << 10) | (pxl[1] << 5) | pxl[2]; pData[ct].Num ++; ct ++; } } } return ct; }// Get_Pixel_All // 1,4,8bitのカラーデプス別にピクセル値を設定する void Set_IdxClrPx(int bpp, BYTE *pixels, UINT stride, int x, int y, int val) { if(bpp == 1){ if(x % 8 == 1) pixels[stride*y+x/8] |= ((val & 1) << 6); else if(x % 8 == 2) pixels[stride*y+x/8] |= ((val & 1) << 5); else if(x % 8 == 3) pixels[stride*y+x/8] |= ((val & 1) << 4); else if(x % 8 == 4) pixels[stride*y+x/8] |= ((val & 1) << 3); else if(x % 8 == 5) pixels[stride*y+x/8] |= ((val & 1) << 2); else if(x % 8 == 6) pixels[stride*y+x/8] |= ((val & 1) << 1); else if(x % 8 == 7) pixels[stride*y+x/8] |= val & 1; else pixels[stride*y+x/8] = val << 7; } else if(bpp == 4){ if(x % 2) pixels[stride*y+x/2] |= val & 0xf; else pixels[stride*y+x/2] = val << 4; } else{ pixels[stride*y+x] = (BYTE) val; } }// Set_IdxClrPx |
|
|