矩形窗口裁剪(以裁剪直線和複雜多邊形爲例)

    今天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 }
View Code

    結果如圖,爲了驗證裁剪算法的正確性,我不只畫了模擬窗口,還將待裁剪的線段也畫了出來,可看到該線段的紅色部分是被裁剪掉的,不在窗口內顯示,而黑色部分就是在窗口內顯示的部分啦~~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 }
View Code

 

    接下來就是見證奇蹟的時刻啦!duang~~duang~~duang~~

    一樣,爲了驗證算法的正確性,我設計的圖案(雖然很醜!)包含了全部增長端點數的可能性,在結果裏不只畫了模擬窗口,還畫出了待裁剪的原圖以及裁剪後在窗口內的圖案,圖中綠色部分是被窗口裁剪掉的,不會在窗口內顯示。

    好啦,今天就講到這裏啦~~我們下次再見哦,( ^_^ )/~~拜拜

相關文章
相關標籤/搜索