今天yogurt想要和你們分享一個你們在玩電腦時常常會用到的一個功能「窗口裁剪」的C語言編程實現方法~~相信用過QQ截屏或者其餘截屏軟件的盆友都知道截屏就是對一個圖形或者圖案用一個矩形框或者圓形框框起來,只保留框內的內容,而框外的內容自動捨去。那麼它是怎麼實現的呢?今天就讓美麗可愛善良機智的yogurt來告訴你這個神奇的東東吧!算法
===================================yogurt小課堂開課啦===================================編程
首先講一下程序中用到的算法--Cohen-Sutherland端點編碼算法。咱們的每個點在存儲時除了要存儲它的xy座標,還要存儲一個編碼key,編碼key由四個0/1的數字組成,0表示在窗口某邊內,1表示在窗口某邊外,如key=0101,表示該點在窗口左邊界和下邊界以外,以下圖:ide
而後對待截取的直線,進行判斷,若直線在窗口內(直線兩端點的key都是0000)則簡取,若直線兩端點都在窗口外的同一側(兩個端點的key有同一位都是1)則簡棄,對於其餘狀況(直線穿過窗口、直線未穿過窗口可是跨越三個界域)則進行較爲複雜的判斷,可是每次判斷以前必須確保第一個點p1在窗口外,以便計算交點。獲得交點以後用交點替代p1,繼續重複進行待截取直線的判斷,直到能夠簡取或者簡棄爲止。測試
至於複雜多邊形的裁剪,咱們要考慮到由p1到p2是由外-->內,or由外-->外,or由內-->內,or由內-->外四種狀況,每種狀況對於點的處理是不一樣的。(如下是yogurt本身想到的算法,恩,就叫它「標記數判斷法」吧O(∩_∩)O~)編碼
咱們用a來進行特殊判斷,當多邊形某條邊由內-->外時,記a爲1。如果外-->外,就將a++,且判斷若a==3,則證實該點的相鄰兩條邊都在窗口外面(都是外-->外),則該點是捨去的且沒有替代此點位置的點,將a從新置爲2(表明這條線是外-->外,方便下一條線進行判斷)。如果外-->內,判斷若a==1,證實上一條邊正好是從內-->外,則相對於被裁剪多邊形來講,窗口內增長一個端點,而後把a置爲0。以下圖:spa
====================================好,同窗們,下課啦===================================設計
接下來是Yogurt的我的炫技時間(*^__^*) 嘻嘻……3d
首先咱們看看簡單的線段在窗口內的裁剪,上代碼:code
1 // caijian.cpp : 定義控制檯應用程序的入口點。 2 // 3 4 #include "stdafx.h" 5 #include"Graph.h" 6 7 typedef struct Key 8 { 9 int d3; 10 int d2; 11 int d1; 12 int d0; 13 }key; 14 15 typedef struct Point 16 { 17 int x; 18 int y; 19 key c; 20 }point; 21 22 key code(point p, int xw1, int xwr, int ywb, int ywt); 23 point asso(point p1, point p2,int xw1, int xwr, int ywb, int ywt); 24 void drawline(point p1, point p2); 25 26 int _tmain(int argc, _TCHAR* argv[]) 27 { 28 point p1, p2; 29 int xmin , xmax , ymin , ymax ; 30 31 //printf("Please enter point one:"); 32 //scanf("%d,%d", &p1.x, &p1.y); 33 34 //printf("Please enter point two:"); 35 //scanf("%d,%d", &p2.x, &p2.y); 36 // 37 //printf("Please enter the four boundary(xmin,xmax,ymin,ymax):"); 38 //scanf("%d,%d,%d,%d", &xmin, &xmax, &ymin, &ymax); 39 40 41 /*測試數據*/ 42 p1.x = 3; 43 p1.y = 10; 44 p2.x = 100; 45 p2.y = 120; 46 xmin = 50; 47 xmax = 150; 48 ymin = 0; 49 ymax = 180; 50 51 52 setPenColor(RED); 53 drawline(p1, p2); 54 55 int x0 = int((xmin + xmax) / 2); 56 int y0 = int((ymin + ymax) / 2); 57 drawRectangle(x0,y0,xmax-xmin,ymax-ymin); 58 59 p1.c = code(p1, xmin, xmax, ymin, ymax); 60 p2.c = code(p2, xmin, xmax, ymin, ymax); 61 62 while (1) 63 { 64 if (((p1.c.d0 | p2.c.d0 )== 0) && ((p1.c.d1 | p2.c.d1) == 0)&& ((p1.c.d2 | p2.c.d2) == 0 )&&( (p1.c.d3 | p2.c.d3) == 0)) //簡取 65 break; 66 else if ((p1.c.d0 & p2.c.d0 == 1) || (p1.c.d1 & p2.c.d1 == 1 )|| (p1.c.d2 & p2.c.d2 == 1) || (p1.c.d3 & p2.c.d3 == 1 )) //簡棄 67 return 0; 68 else 69 { 70 //確保p1在窗口外 71 if ((p1.c.d0 == 0) && (p1.c.d1 == 0) && (p1.c.d2 == 0) && (p1.c.d3 == 0)) //若p1在窗口內 72 { 73 point p3; 74 p3 = p1; 75 p1 = p2; 76 p2 = p3; 77 } 78 79 point s = asso(p1, p2, xmin, xmax, ymin, ymax); //s爲直線段p1p2與窗口的交點 80 p1 = s; 81 } 82 } 83 84 setPenColor(GREEN); 85 drawline(p1, p2); 86 return 0; 87 } 88 89 key code(point p, int xmin, int xmax, int ymin, int ymax) 90 { 91 if (p.x < xmin) 92 p.c.d0 = 1; 93 else 94 p.c.d0 = 0; 95 96 if (p.x>xmax) 97 p.c.d1 = 1; 98 else 99 p.c.d1 = 0; 100 101 if (p.y < ymin) 102 p.c.d2 = 1; 103 else 104 p.c.d2 = 0; 105 106 if (p.y>ymax) 107 p.c.d3 = 1; 108 else 109 p.c.d3 = 0; 110 111 return p.c; 112 } 113 114 point asso(point p1, point p2, int xmin, int xmax, int ymin, int ymax) 115 { 116 double k = (p2.y - p1.y)*1.0 / (p2.x - p1.x); 117 118 point s; 119 120 if (p1.c.d0 == 1)//p1在左側 121 { 122 s.x = xmin; 123 s.y = p1.y + k*(xmin - p1.x); 124 125 if (s.y > ymax)//s應該在矩形上邊界 126 { 127 s.y = ymax; 128 s.x = p1.x + (ymax - p1.y) / k; 129 } 130 else if (s.y < ymin)//s應該在矩形下邊界 131 { 132 s.y = ymin; 133 s.x = p1.x + (ymin - p1.y) / k; 134 } 135 } 136 else if (p1.c.d1 == 1)//p1在右側 137 { 138 s.x = xmax; 139 s.y = p1.y + k*(xmax - p1.x); 140 141 if (s.y > ymax)//s應該在矩形上邊界 142 { 143 s.y = ymax; 144 s.x = p1.x + (ymax - p1.y) / k; 145 } 146 else if (s.y < ymin)//s應該在矩形下邊界 147 { 148 s.y = ymin; 149 s.x = p1.x + (ymin - p1.y) / k; 150 } 151 } 152 else if (p1.c.d2 == 1)//p1在正下側 153 { 154 s.y = ymin; 155 s.x = p1.x + (ymin - p1.y) / k; 156 } 157 else//p1在正上方 158 { 159 s.y = ymax; 160 s.x = p1.x + (ymax - p1.y) / k; 161 } 162 s.c = code(s, xmin, xmax, ymin, ymax); 163 164 return s; 165 } 166 167 void drawline(point p1, point p2) 168 { 169 moveTo(p1.x, p1.y); 170 lineTo(p2.x, p2.y); 171 }
結果如圖,爲了驗證裁剪算法的正確性,我不只畫了模擬窗口,還將待裁剪的線段也畫了出來,可看到該線段的紅色部分是被裁剪掉的,不在窗口內顯示,而黑色部分就是在窗口內顯示的部分啦~~blog
而後你說yogurt這個也太簡單了吧!有本事來個複雜的!yogurt傲嬌臉,好嘞!客官,我給您上一道大菜!(拭目以待哦~~mua~~)
1 // 多邊形裁剪.cpp : 定義控制檯應用程序的入口點。 2 // 3 4 #include "stdafx.h" 5 #include"Graph.h" 6 #define MAX_NUM 14 7 8 typedef struct Key 9 { 10 int d3; 11 int d2; 12 int d1; 13 int d0; 14 }key; 15 16 typedef struct Vertex 17 { 18 int x, y, table; 19 key code; 20 }vertex; 21 22 typedef struct Graph 23 { 24 vertex ver[MAX_NUM+1]; 25 int vexnum; 26 }graph;//存儲的是圖形邊界點(封閉凸圖形) 27 28 typedef struct WINDOW 29 { 30 int xmin; 31 int xmax; 32 int ymin; 33 int ymax; 34 }window; 35 36 graph readgraphics(char * filename); 37 void Tocode(vertex *point , window C); 38 void Totable(vertex *point, window C); //在可視域範圍內爲1,不然爲0. 39 graph clip(graph G,window C); 40 vertex newver(vertex a, vertex b, window C); 41 void draw(graph G); 42 43 int _tmain(int argc, _TCHAR* argv[]) 44 { 45 char filename[20] = "Graph.txt"; 46 graph G=readgraphics(filename); 47 48 setPenColor(GREEN); 49 draw(G); 50 51 window C; 52 /*printf("Please enter the four boundary(xmian,xmax,ymin,ymax):"); 53 scanf("%d,%d,%d,%d",&C.xmin,&C.xmax,&C.ymin,&C.ymax);*/ 54 55 C.xmin = -180; 56 C.xmax = 180; 57 C.ymin = -230; 58 C.ymax = 220; 59 60 for (int i = 1; i <= G.vexnum; i++) 61 { 62 Tocode(&(G.ver[i]), C); 63 Totable(&(G.ver[i]), C); 64 } 65 66 setPenColor(RED); 67 int x0 = int((C.xmin + C.xmax) / 2); 68 int y0 = int((C.ymin + C.ymax) / 2); 69 drawRectangle(x0, y0, C.xmax - C.xmin, C.ymax - C.ymin); 70 71 graph newG=clip(G,C); 72 73 draw(newG); 74 75 return 0; 76 } 77 78 graph readgraphics(char * filename) 79 { 80 graph G; 81 FILE * fp = fopen(filename, "r"); 82 if (fp) 83 { 84 fscanf(fp, "%d", &G.vexnum); 85 86 for (int i = 1; i <= G.vexnum; i++) 87 { 88 fscanf(fp,"%d,%d", &G.ver[i].x, &G.ver[i].y); 89 } 90 }fclose(fp); 91 92 return G; 93 } 94 95 void Tocode(vertex *point, window C) 96 { 97 98 if (point->x < C.xmin) 99 point->code.d0 = 1; 100 else 101 point->code.d0 = 0; 102 103 if (point->x>C.xmax) 104 point->code.d1 = 1; 105 else 106 point->code.d1 = 0; 107 108 if (point->y < C.ymin) 109 point->code.d2 = 1; 110 else 111 point->code.d2 = 0; 112 113 if (point->y>C.ymax) 114 point->code.d3 = 1; 115 else 116 point->code.d3 = 0; 117 118 } 119 120 graph clip(graph G,window C) 121 { 122 graph newG; 123 newG.vexnum = G.vexnum; 124 125 vertex ver; 126 127 int a = 0, b = 0; 128 129 for (int i = 1; i <= G.vexnum - 1; i++) //從第一個頂點-->下一個頂點,直到第num-1個頂點到第num個頂點爲止 130 { 131 if ((G.ver[i].table == 0) && (G.ver[i + 1].table == 1)) //從外到內 132 { 133 if (a == 1) //上次進行的是從內到外,此次外到內,newG相對於G來講增長一個點 134 { 135 b -= 1; 136 newG.vexnum++; 137 } 138 139 a = 0;//將a清零 140 141 ver = newver(G.ver[i], G.ver[i + 1], C); 142 newG.ver[i + 1 - b] = G.ver[i + 1]; 143 newG.ver[i - b] = ver; 144 } 145 else if ((G.ver[i].table == 1) && (G.ver[i + 1].table == 1)) //從內到內 146 { 147 newG.ver[i + 1 - b] = G.ver[i + 1]; 148 newG.ver[i - b] = G.ver[i]; 149 } 150 else if ((G.ver[i].table == 1) && (G.ver[i + 1].table == 0)) //從內到外 151 { 152 a += 1; 153 ver = newver(G.ver[i], G.ver[i + 1], C); 154 newG.ver[i - b] = G.ver[i]; 155 newG.ver[i + 1 - b] = ver; 156 } 157 else //從外到外 158 { 159 //進入時a爲2證實上一次就是從外到外 160 a += 1; 161 162 if (a == 3) //與該點相鄰的左右兩點都在外,則newG相對於G來講沒有該點(也沒有該點的替換點) 163 { 164 b += 1; 165 a = 2;//將a從新置爲1 166 newG.vexnum--; 167 } 168 } 169 } 170 //處理從最後一個點到第一個點 171 if ((G.ver[G.vexnum].table == 0) && (G.ver[1].table == 1)) //從外到內 172 { 173 ver = newver(G.ver[G.vexnum], G.ver[1], C); 174 newG.ver[G.vexnum + 1 - b] = ver; 175 newG.vexnum++; 176 } 177 else if ((G.ver[G.vexnum].table == 1) && (G.ver[1].table == 1)) //從內到內 178 ; 179 else if ((G.ver[G.vexnum].table == 1) && (G.ver[1].table == 0)) //從內到外,多一個點 180 { 181 ver = newver(G.ver[G.vexnum], G.ver[1], C); 182 newG.ver[G.vexnum + 1 - b] = ver; 183 newG.vexnum++; 184 } 185 else //從外到外 186 ; 187 188 return newG; 189 } 190 191 void Totable(vertex *point,window C) 192 { 193 if ((point->x < C.xmin) || (point->x>C.xmax) || (point->y<C.ymin) || (point->y>C.ymax)) //在窗口外 194 point->table = 0; 195 else 196 point->table = 1; 197 198 return ; 199 } 200 201 vertex newver(vertex p1, vertex p2, window C) 202 { 203 //確保p1在窗口外 204 if ((p1.code.d0 == 0) && (p1.code.d1 == 0) && (p1.code.d2 == 0) && (p1.code.d3 == 0)) //若p1在窗口內 205 { 206 vertex p3; 207 p3 = p1; 208 p1 = p2; 209 p2 = p3; 210 } 211 212 double k = (p2.y - p1.y)*1.0 / (p2.x - p1.x); 213 214 vertex s; 215 216 if (p1.code.d0 == 1)//p1在左側 217 { 218 s.x = C.xmin; 219 s.y = p1.y + k*(C.xmin - p1.x); 220 221 if (s.y > C.ymax)//s應該在矩形上邊界 222 { 223 s.y = C.ymax; 224 s.x = p1.x + (C.ymax - p1.y) / k; 225 } 226 else if (s.y < C.ymin)//s應該在矩形下邊界 227 { 228 s.y = C.ymin; 229 s.x = p1.x + (C.ymin - p1.y) / k; 230 } 231 } 232 else if (p1.code.d1 == 1)//p1在右側 233 { 234 s.x = C.xmax; 235 s.y = p1.y + k*(C.xmax - p1.x); 236 237 if (s.y > C.ymax)//s應該在矩形上邊界 238 { 239 s.y = C.ymax; 240 s.x = p1.x + (C.ymax - p1.y) / k; 241 } 242 else if (s.y < C.ymin)//s應該在矩形下邊界 243 { 244 s.y = C.ymin; 245 s.x = p1.x + (C.ymin - p1.y) / k; 246 } 247 } 248 else if (p1.code.d2 == 1)//p1在正下側 249 { 250 s.y = C.ymin; 251 s.x = p1.x + (C.ymin - p1.y) / k; 252 } 253 else//p1在正上方 254 { 255 s.y = C.ymax; 256 s.x = p1.x + (C.ymax - p1.y) / k; 257 } 258 Tocode(&s, C); 259 Totable(&s, C); 260 261 return s; 262 } 263 264 void draw(graph G) 265 { 266 moveTo(G.ver[1].x, G.ver[1].y); 267 268 for(int i=2;i<=G.vexnum;i++) 269 { 270 lineTo(G.ver[i].x,G.ver[i].y); 271 } 272 273 lineTo(G.ver[1].x,G.ver[1].y); 274 275 return; 276 }
接下來就是見證奇蹟的時刻啦!duang~~duang~~duang~~
一樣,爲了驗證算法的正確性,我設計的圖案(雖然很醜!)包含了全部增長端點數的可能性,在結果裏不只畫了模擬窗口,還畫出了待裁剪的原圖以及裁剪後在窗口內的圖案,圖中綠色部分是被窗口裁剪掉的,不會在窗口內顯示。
好啦,今天就講到這裏啦~~我們下次再見哦,( ^_^ )/~~拜拜