吉林大学图形学实验课作业边标记填充多边形
2013-10-27 20:08
776 查看
吉林大学图形学实验课作业边标记填充多边形
作业要求:编写应用程序,采用鼠标输入顶点的方法确定待填充多边形;实现边标志算法完成对多边形的填充,要求完成使用自己学号的后四位数字对多边形内部进行填充。完成效果如下图所示:
注:因为没有安装vc,所以直接用windowsapi编写的。
思路: 1,获取鼠标点击坐标,保存到POINT数组pt[]中;
2,计算包含多边形的最小矩形,保存到xMin,xMax,yMin,yMax;
3,在矩阵中,用TextOut函数重复输出学号,使其充满矩阵如图所示
4,边标记法反填充矩形与多边形之间的部分。
实现:
1.按下鼠标左键,画点;松开鼠标左键,链接当前点与上一个点,画线。考虑到windows程序绘图特点,将绘画部分放在WM_PAINT消息中,WM_LBUTTONUP消息只执行InvalidateRect(hwnd,NULL,FALSE)将整个客户区无效化,并保留之前的绘图。
case WM_LBUTTONDOWN: pt[pCount].x=LOWORD(lParam); pt[pCount].y=HIWORD(lParam); hdc=GetDC(hwnd); SetPixel(hdc,LOWORD(lParam),HIWORD(lParam),0); ReleaseDC(hwnd,hdc); pCount++; break; case WM_LBUTTONUP: InvalidateRect(hwnd,NULL,FALSE); break; case WM_PAINT: hdc=BeginPaint(hwnd,&ps); for(int i=0; i<=pCount-2; i++) { MoveToEx(hdc,pt[i].x,pt[i].y,NULL); LineTo(hdc,pt[i+1].x,pt[i+1].y); } EndPaint(hwnd,&ps); break;
2.要画出多边形,最后的鼠标点击点,应该是pt[0],考虑到不容易点到初始点,编写函数bool isPoint_180(int x,int y,POINT pt[],int pCount)来智能化判断点击点是否是初始点,函数原型如下,点击点只要和初始点相距不超过3个像素即算点击了原始点。最后在绘图最后一个点时,也要稍作修改。
bool isPoint_1(int x,int y,LPARAM lParam) { int x1=LOWORD(lParam); int y1=HIWORD(lParam); if((x1-x)*(x1-x)+(y1-y)*(y1-y)<=9) return 1; return 0; }
3. 如果绘图完成后,再点击任意位置,清空屏幕,下一次点击为重新绘图。用bool变量isP_1记录,初始值为0,点击原点后值为1,最后一次点击时,计算并记录最小包含矩形并使isP_1为0,WM_LBUTTONDOWN完整代码如下。
case WM_LBUTTONDOWN: if(isP_1==1) { isP_1=0; pCount=0; InvalidateRect(hwnd,NULL,TRUE); break; } if(pCount>=3&&isPoint_1(pX0,pY0,lParam)) { xMin=2000; xMax=-1; yMin=2000; yMax=-1; for(int i=0; i<pCount; i++) { if(pt[i].x>xMax) xMax=pt[i].x; if(pt[i].x<xMin) xMin=pt[i].x; if(pt[i].y>yMax) yMax=pt[i].y; if(pt[i].y<yMin) yMin=pt[i].y; } isP_1=1; break; } else { pt[pCount].x=LOWORD(lParam); pt[pCount].y=HIWORD(lParam); if(pCount==0) { pX0=pt[pCount].x; pY0=pt[pCount].y; } hdc=GetDC(hwnd); SetPixel(hdc,LOWORD(lParam),HIWORD(lParam),0); ReleaseDC(hwnd,hdc); pCount++; break; }[code]
注:InvalidateRect(hwnd,NULL,TRUE);与上一个不同,第三个参数为TRUE,使客户区无效化的同时,清除原有绘图,即清屏。
4.点击初始点后的绘图分两步,第一步填充学号,因为Textout函数输出后原有线条会被覆盖,因为后续的变标志算法需要,第二步重绘多边形。
//第一步:cyChar为一个字符的高度,cxChar为字符平均宽度 for(int i=0; i<=(yMax-yMin)/cyChar; i++) for(int j=0; j<=(xMax-xMin)/(5*cxChar); j++) { int y=i*cyChar+yMin; int x=j*5*cxChar+xMin; SetTextColor(ps.hdc, RGB(255, 100, 0));//设置文本颜色 TextOut(hdc,x,y,str,lstrlen(str));
注:后来发现,Textout输出会在一行的末尾超出矩形范围,于是尝试手动设置无效区域:
ps.rcPaint.left=xMin;
ps.rcPaint.right=xMax;
ps.rcPaint.top=yMin;
ps.rcPaint.bottom=yMax;
但是后来发现还是超出了,一时找不到解决办法,想了想后续可以通过扩展矩形来解决这个问题,也就没有深究了。
//第二步:最后一点要多画一条线 for(int i=0; i<=pCount-2; i++) { MoveToEx(hdc,pt[i].x,pt[i].y,NULL); LineTo(hdc,pt[i+1].x,pt[i+1].y); } MoveToEx(hdc,pt[pCount-1].x,pt[pCount-1].y,NULL); LineTo(hdc,pt[0].x,pt[0].y);
5.传说中的边标记算法,不解释,书上有,很简单也很多余。
难点在于这种情况:
A点与B点对于程序中state的处理显然是不同的,对于A点,state需要取反,但是B点则需要忽略,以下代码解决这个问题:
bool isPoint_180(int x,int y,POINT pt[],int pCount) { for(int i=0;i<pCount;i++) if(pt[i].x==x&&pt[i].y==y) { if(i==pCount-1&&(pt[0].y-pt[i].y)*(pt[i-1].y-pt[i].y)>0) return 1; else if(i==0&&(pt[i+1].y-pt[i].y)*(pt[pCount-1].y-pt[i].y)>0) return 1; else if(i!=pCount-1&&i!=0&&(pt[i+1].y-pt[i].y)*(pt[i-1].y-pt[i].y)>0) return 1; else return 0; } return 0; }
如果是B点则返回1,否则则返回0;对于点(x,y),在pt[]中扫描,如果(x,y)是多边形的点分3中情况判断是否是 B点,分别是第1个点pt[0],第pCount个点pt[pCount-1],只需当前点的前一个点和后一个点在同一侧则是B点否则是A点。WM_PAINT消息中加入以下代码:
COLORREF color; hdc=GetDC(hwnd); for(int y=yMin-5;y<=yMax+20;y++)//矩形扩展 { bool nPstate=0; for(int x=xMin-5;x<xMax+50;x++)//矩形扩展 { color=GetPixel(hdc,x,y); if(nPstate==0&&color==RGB(255,100,0)) SetPixel(hdc,x,y,RGB(255,255,255)); else if(color==RGB(0,0,0)) if(!isPoint_180(x,y,pt,pCount)) nPstate=! nPstate; } } ReleaseDC(hwnd,hdc);
6. 但是还没有结束,因为会发生如图所示的情况呢:
细心观察不难发现,当直线斜率小于1或大于-1时,在一条扫描线上会出现相连的几个点,当相连的点数为偶数时,恰巧碰对了,但是是奇数时,结果显然不是我们想要的,解决方法,用一个变量pPstate记录前一个点是否是边上点。如果前一个点是边上点,则忽略nPstate的反转。
hdc=GetDC(hwnd); for(int y=yMin-5;y<=yMax+20;y++) { bool nPstate=0; bool pPstate=0; for(int x=xMin-5;x<xMax+50;x++) { color=GetPixel(hdc,x,y); if(nPstate==0&&color==RGB(255,100,0)) { SetPixel(hdc,x,y,RGB(255,255,255)); pPstate=0; } else if(color==RGB(0,0,0)) { if(pPstate==0&&!isPoint_180(x,y,pt,pCount)) nPstate=!nPstate; pPstate=1; } else pPstate=0; } } ReleaseDC(hwnd,hdc);
7.人生总有一些事情让你无从预料,就像这个程序还是没有结束,因为它还是有bug,上图:
比如这个:
再如这个:
那么这个呢:
现在知道为什么说那个课本是简单而多余的了吧。
第一种bug和前面的多个连续点类似,当有两个相连的点时,这种边标记算法,根本就无法直接判断是斜率小于1或大于-1的直线上的两点还是两条斜率很大的线连在一起。解决方法:写一个函数判断当前一个点是边上点时,当前点所在边是否和前一个点所在边是同一条边。
第二种bug是因为两条边斜率绝对值很大,以至于两条边重合为一条边。解决方法:写一个函数,判断当前点是否同时在两条边上。
第三种bug是因为相交点的出现,而之前的扫描点函数isPoint_180()忽略了这种点的出现。解决方法:同第二种,因为本质上也是一个点同时在两条边上。
涉及的函数有一下几个:
//判断点(x,y)是否是多边形顶点 bool onPoint(int x,int y,POINT pt[],int pCount) { for(int i=0;i<pCount;i++) if(pt[i].x==x&&pt[i].y==y) return 1; //(x,y)是多边形顶点 return 0; } //判断点(x,y)是否在线段a,b上 bool pOnline(int x,int y,POINT a,POINT b)//三角形两边之和大于第三边 { double lac = sqrt((a.x - x)*(a.x - x) + (a.y - y)*(a.y - y)); double lbc = sqrt((b.x - x)*(b.x - x) + (b.y - y)*(b.y - y)); double lab = sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y)); if(lac+lbc<lab+0.05&&x>=min(a.x,b.x)&&x<=max(a.x,b.x)&&y>=min(a.y,b.y)&&y<=max(a.y,b.y)) return 1; return 0; } //当前点(x,y)与前一个点(x-1,y)是否在在同一条边上 bool bug_1(int x,int y, POINT pt[],int pCount) { int line=-1; for(int i=0;i<pCount-1;i++) if(pOnline(x,y,pt[i],pt[i+1])) { line=i; break; } if(line==-1&&pOnline(x,y,pt[pCount-1],pt[0]) )line=pCount-1; if((line!=pCount-1&&pOnline(x-1,y,pt[line],pt[line+1]))||(line==pCount-1&&pOnline(x-1,y,pt[line],pt[0]))) return 1;//当前点与前一个点在同一条边上 return 0; } //判断点(x,y)是否是交点 int bug_2(int x,int y,POINT pt[],int pCount) { int num=0; for(int i=0;i<pCount-1;i++) if(pOnline(x,y,pt[i],pt[i+1])) num++; if(pOnline(x,y,pt[pCount-1],pt[0])) num++; if(num==1) return 0; //不是交点 else if (num/2==0) return 2; //当前点是偶数个边的交点 else return 1;//当前点时奇数个边的交点 }
现在情况分为两大类:
1. 前一个点不是边上点即pPstate==0
当当前点是B点时nPaste不取反,当当前点是交点且是偶数条边的交点时nPaste不取反,否则nPsate取反。
2. 前一点是边上点即pPstate==1
有5中情况,当前点是A点,当前点是B点,当前点是交点,当前点和前一个点在同一条边上,当前点和前一个点不在同一条边上。
完整代码如下:
if(pPstate==0) { if(isPoint_180(x,y,pt,pCount)); else if(!onPoint(x,y,pt,pCount)&&bug_2(x,y,pt,pCount)==2); else nPstate=!nPstate; } else { if(isPoint_180(x,y,pt,pCount)); else if(!onPoint(x,y,pt,pCount)&&bug_2(x,y,pt,pCount)==2); else if(!onPoint(x,y,pt,pCount)&&bug_2(x,y,pt,pCount)==0&&bug_1(x,y,pt,pCount)); else nPstate=!nPstate; }
附上可以运行的完整代码:
#include <windows.h>
#include <math.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("Connect") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Connect-the-Points Mouse Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
bool isPoint_1(int x,int y,LPARAM lParam) { int x1=LOWORD(lParam); int y1=HIWORD(lParam); if((x1-x)*(x1-x)+(y1-y)*(y1-y)<=9) return 1; return 0; }
bool isPoint_180(int x,int y,POINT pt[],int pCount) { for(int i=0;i<pCount;i++) if(pt[i].x==x&&pt[i].y==y) { if(i==pCount-1&&(pt[0].y-pt[i].y)*(pt[i-1].y-pt[i].y)>0) return 1; else if(i==0&&(pt[i+1].y-pt[i].y)*(pt[pCount-1].y-pt[i].y)>0) return 1; else if(i!=pCount-1&&i!=0&&(pt[i+1].y-pt[i].y)*(pt[i-1].y-pt[i].y)>0) return 1; else return 0; } return 0; }bool onPoint(int x,int y,POINT pt[],int pCount)
{
for(int i=0;i<pCount;i++)
if(pt[i].x==x&&pt[i].y==y)
return 1; //(x,y)是多边形顶点
return 0;
}
bool pOnline(int x,int y,POINT a,POINT b)//三角形两边之和大于第三边
{
double lac = sqrt((a.x - x)*(a.x - x) + (a.y - y)*(a.y - y));
double lbc = sqrt((b.x - x)*(b.x - x) + (b.y - y)*(b.y - y));
double lab = sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
if(lac + lbc < lab + 0.05&&x>=min(a.x,b.x)&&x<=max(a.x,b.x)&&y>=min(a.y,b.y)&&y<=max(a.y,b.y)) return 1;//点(x,y)在线段a,b上
return 0;
}
bool bug_1(int x,int y, POINT pt[],int pCount)
{
int line=-1;
for(int i=0;i<pCount-1;i++)
if(pOnline(x,y,pt[i],pt[i+1]))
{
line=i;
break;
}
if(line==-1&&pOnline(x,y,pt[pCount-1],pt[0]) )line=pCount-1;
if((line!=pCount-1&&pOnline(x-1,y,pt[line],pt[line+1]))||(line==pCount-1&&pOnline(x-1,y,pt[line],pt[0]))) return 1;//当前点与前一个点在同一条边上
return 0;
}
int bug_2(int x,int y,POINT pt[],int pCount)
{
int num=0;
for(int i=0;i<pCount-1;i++)
if(pOnline(x,y,pt[i],pt[i+1])) num++;
if(pOnline(x,y,pt[pCount-1],pt[0])) num++;
if(num==1) return 0; //不是交点
else if (num/2==0) return 2; //当前点是偶数个边的交点
else return 1;//当前点时奇数个边的交点
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
static int cxChar,cyChar;
static int pX0,pY0;
static int pCount=0;
static bool isP_1=0;
static POINT pt[100];
TCHAR *str="0213";
static int xMin;
static int xMax;
static int yMin;
static int yMax;
PAINTSTRUCT ps;
TEXTMETRIC tm;
switch (message) /* handle the messages */
{
case WM_CREATE:
hdc=GetDC(hwnd);
GetTextMetrics(hdc,&tm);
cxChar=tm.tmAveCharWidth;
cyChar=tm.tmHeight+tm.tmExternalLeading;
ReleaseDC(hwnd,hdc);
break;
case WM_LBUTTONDOWN:
if(isP_1==1)
{
isP_1=0;
pCount=0;
InvalidateRect(hwnd,NULL,TRUE);
break;
}
if(pCount>=3&&isPoint_1(pX0,pY0,lParam))
{
xMin=2000;
xMax=-1;
yMin=2000;
yMax=-1;
for(int i=0; i<pCount; i++)
{
if(pt[i].x>xMax) xMax=pt[i].x;
if(pt[i].x<xMin) xMin=pt[i].x;
if(pt[i].y>yMax) yMax=pt[i].y;
if(pt[i].y<yMin) yMin=pt[i].y;
}
isP_1=1;
break;
}
else
{
pt[pCount].x=LOWORD(lParam);
pt[pCount].y=HIWORD(lParam);
if(pCount==0)
{
pX0=pt[pCount].x;
pY0=pt[pCount].y;
}
hdc=GetDC(hwnd);
SetPixel(hdc,LOWORD(lParam),HIWORD(lParam),0);
ReleaseDC(hwnd,hdc);
pCount++;
break;
}
case WM_LBUTTONUP:
InvalidateRect(hwnd,NULL,FALSE);
break;
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
if(isP_1)
{
ps.rcPaint.left=xMin;
ps.rcPaint.right=xMax;
ps.rcPaint.top=yMin;
ps.rcPaint.bottom=yMax;
for(int i=0; i<=(yMax-yMin)/cyChar; i++)
for(int j=0; j<=(xMax-xMin)/(5*cxChar); j++)
{
int y=i*cyChar+yMin;
int x=j*5*cxChar+xMin;
SetTextColor(ps.hdc, RGB(255, 100, 0));//设置文本颜色
TextOut(hdc,x,y,str,lstrlen(str));
}
for(int i=0; i<=pCount-2; i++)
{
MoveToEx(hdc,pt[i].x,pt[i].y,NULL);
LineTo(hdc,pt[i+1].x,pt[i+1].y);
}
MoveToEx(hdc,pt[pCount-1].x,pt[pCount-1].y,NULL);
LineTo(hdc,pt[0].x,pt[0].y);
COLORREF color;
hdc=GetDC(hwnd);
for(int y=yMin-5;y<=yMax+20;y++)
{
bool nPstate=0;
bool pPstate=0;
for(int x=xMin-5;x<xMax+50;x++)
{
color=GetPixel(hdc,x,y);
if(nPstate==0&&color==RGB(255,100,0))
{
SetPixel(hdc,x,y,RGB(255,255,255));
pPstate=0;
}
else if(color==RGB(0,0,0))
{
if(pPstate==0) { if(isPoint_180(x,y,pt,pCount)); else if(!onPoint(x,y,pt,pCount)&&bug_2(x,y,pt,pCount)==2); else nPstate=!nPstate; } else { if(isPoint_180(x,y,pt,pCount)); else if(!onPoint(x,y,pt,pCount)&&bug_2(x,y,pt,pCount)==2); else if(!onPoint(x,y,pt,pCount)&&bug_2(x,y,pt,pCount)==0&&bug_1(x,y,pt,pCount)); else nPstate=!nPstate; }pPstate=1;
}
else pPstate=0;
}
}
ReleaseDC(hwnd,hdc);
}
else if(pCount>=2)
{
for(int i=0; i<=pCount-2; i++)
{
MoveToEx(hdc,pt[i].x,pt[i].y,NULL);
LineTo(hdc,pt[i+1].x,pt[i+1].y);
}
}
EndPaint(hwnd,&ps);
break;
case WM_DESTROY:
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
break;
default: /* for messages that we don't deal with */
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
反馈:
如果运行几次上面的代码,会发现,还是偶尔的有“抽丝”的现象存在。问题出在哪呢,事实上,这是所谓的边标记算法本身就具有的漏洞。回头看下代码,我是怎么实现判断一个点是否在一条线段上的。为了简单考虑,假设用比较斜率的方法实现。如图所示点c是a,b线段上的点,但是线段a,c和线段b,c的斜率却相差很大。
a,b线段的斜率为0.5,b,c斜率却为1;
无论是比较斜率还是三角形边的方法,都是理论的,在像素处理上本来就存在误差,(本来是用比较斜率的方法的,后来发现抽的丝太多,为了结果好看点,就改成三角形两边大于第三边了,本来以为应该对的,结果还是有抽丝现象)而这些误差在越靠近多边形顶点的地方越明显,这也是所有的抽丝错误点基本上在顶点旁边的原因,所有说所谓的边标记算法,本身就具有的严重的漏洞,当然也不是没有解决办法,可以使用画线算法重新画边,如果经过点,则可以判断点在边上。但是一个简单的算法,却有如此复杂的实现方法。如果真的去画线拟合,其时间复杂度度也是一个填充算法不可以接受的。所有我也懒得去实现了(被恶心到了)。
再续:
上课的时候闲着无聊,又无意中发现关于判断点在线上的方法,类似与画线算法,却又不需要画线,如图a,b,c 3点
只需算出a,b线段的方程,将c点x坐标带入方程,算出y,若c的y坐标近似等于y,则可以认为c点在线段ab上。当然若ab斜率绝对值大于1,则只需交换x,y即可。
这时候又突然想起,如果是这样的话,我为什么不干脆在填充之前,把边都扫描一下,把相交点什么的都算出来呢,想到这,又突然记起来书上好像有用一个二维数组,于是我回去又翻了翻书。。。发现,边标记就是这么做的。
之前是简单翻了下书,了解了边标记的思想,然后想当然的认为,边标记的话,只需要获取像素点,如果是黑色(边的颜色)不就说明是边了么。想不到,我天真了。但是,在理想的情况下,我的思想还是对的,而且还是个捷径,虽然速度很慢,编写很麻烦,哈哈。当然,不过不管怎么样,通过这些研究,好歹也对于计算机图形达到了一个新的认识,塞翁失马,又焉知非福呢。
废话不多说,先上代码,或许看到现在才到重点,寻找答案的同学都咬牙切齿了。
#include <windows.h>
#include <math.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("Connect") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Connect-the-Points Mouse Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
bool isPoint_1(int x,int y,LPARAM lParam) { int x1=LOWORD(lParam); int y1=HIWORD(lParam); if((x1-x)*(x1-x)+(y1-y)*(y1-y)<=9) return 1; return 0; }
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
static int cxChar,cyChar;
static int pX0,pY0;
static int pCount=0;
static bool isP_1=0;
static POINT pt[100];
TCHAR *str="0213";
static int xMin;
static int xMax;
static int yMin;
static int yMax;
PAINTSTRUCT ps;
TEXTMETRIC tm;
static bool mask[1366][768]={{0}};
switch (message) /* handle the messages */
{
case WM_CREATE:
hdc=GetDC(hwnd);
GetTextMetrics(hdc,&tm);
cxChar=tm.tmAveCharWidth;
cyChar=tm.tmHeight+tm.tmExternalLeading;
ReleaseDC(hwnd,hdc);
break;
case WM_LBUTTONDOWN:
if(isP_1==1)
{
isP_1=0;
pCount=0;
InvalidateRect(hwnd,NULL,TRUE);
break;
}
if(pCount>=3&&isPoint_1(pX0,pY0,lParam))
{
xMin=2000;
xMax=-1;
yMin=2000;
yMax=-1;
for(int i=0; i<pCount; i++)
{
if(pt[i].x>xMax) xMax=pt[i].x;
if(pt[i].x<xMin) xMin=pt[i].x;
if(pt[i].y>yMax) yMax=pt[i].y;
if(pt[i].y<yMin) yMin=pt[i].y;
}
for(int i=0;i<1366;i++) for(int j=0;j<768;j++) mask[i][j]=0;if((pt[1].y-pt[0].y)*(pt[pCount-1].y-pt[0].y)<0) mask[pt[0].x][pt[0].y]=1; for(int i=1;i<pCount-1;i++) { if((pt[i+1].y-pt[i].y)*(pt[i-1].y-pt[i].y)<0) mask[pt[i].x][pt[i].y]=1; } if((pt[0].y-pt[pCount-1].y)*(pt[pCount-2].y-pt[pCount-1].y)<0) mask[pt[pCount-1].x][pt[pCount-1].y]=1;pt[pCount].x=pt[0].x;
pt[pCount].y=pt[0].y;
for(int i=0;i<pCount;i++)
{
double x;
if(pt[i].y<pt[i+1].y)
x=pt[i].x;
else
x=pt[i+1].x;
int a=pt[i+1].x-pt[i].x; int b=pt[i+1].y-pt[i].y; if(a<0) a-=1; else a+=1; if(b<0) b-=1; else b+=1; double dx=(double)a/(double)b;for(int y=min(pt[i].y,pt[i+1].y);y<=max(pt[i].y,pt[i+1].y);y++)
{
mask[(int)(x+0.5)][y]=!mask[(int)(x+0.5)][y];
x=x+dx;
}
}
isP_1=1;
break;
}
else
{
pt[pCount].x=LOWORD(lParam);
pt[pCount].y=HIWORD(lParam);
if(pCount==0)
{
pX0=pt[pCount].x;
pY0=pt[pCount].y;
}
hdc=GetDC(hwnd);
SetPixel(hdc,LOWORD(lParam),HIWORD(lParam),0);
ReleaseDC(hwnd,hdc);
pCount++;
break;
}
case WM_LBUTTONUP:
InvalidateRect(hwnd,NULL,FALSE);
break;
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
if(isP_1)
{
ps.rcPaint.left=xMin;
ps.rcPaint.right=xMax;
ps.rcPaint.top=yMin;
ps.rcPaint.bottom=yMax;
for(int i=0; i<=(yMax-yMin)/cyChar; i++)
for(int j=0; j<=(xMax-xMin)/(5*cxChar); j++)
{
int y=i*cyChar+yMin;
int x=j*5*cxChar+xMin;
SetTextColor(ps.hdc, RGB(255, 100, 0));//设置文本颜色
TextOut(hdc,x,y,str,lstrlen(str));
}
for(int i=0; i<=pCount-2; i++)
{
MoveToEx(hdc,pt[i].x,pt[i].y,NULL);
LineTo(hdc,pt[i+1].x,pt[i+1].y);
}
MoveToEx(hdc,pt[pCount-1].x,pt[pCount-1].y,NULL);
LineTo(hdc,pt[0].x,pt[0].y);
hdc=GetDC(hwnd);
for(int y=yMin-5;y<=yMax+20;y++)
{
bool nPstate=0;
for(int x=xMin-5;x<xMax+50;x++)
{
if(mask[x][y]) nPstate=!nPstate;
if(nPstate==0&&GetPixel(hdc,x,y)==RGB(255,100,0)) SetPixel(hdc,x,y,RGB(255,255,255));
}
}
ReleaseDC(hwnd,hdc);
}
else if(pCount>=2)
{
for(int i=0; i<=pCount-2; i++)
{
MoveToEx(hdc,pt[i].x,pt[i].y,NULL);
LineTo(hdc,pt[i+1].x,pt[i+1].y);
}
}
EndPaint(hwnd,&ps);
break;
case WM_DESTROY:
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
break;
default: /* for messages that we don't deal with */
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
显然,一但算法正确,代码也简短多了,如果你的程序有很多的if,else,那么clear吧,你需要新算法。
程序很好写,但是有几个细节还是花了我不少时间。
第一个是关于dx。
dx严格上并不等于书上写的(pt[i+1].x-pt[i].x)/(intb=pt[i+1].y-pt[i].y);
如图所示
B | |||
A |
所以dx应该等于(pt[i+1].x-pt[i].x+1)/(int b=pt[i+1].y-pt[i].y+1);
但是因为pt[i+1].x-pt[i].x, pt[i+1].y-pt[i].y有可能小于0,这时候就应该是-1,而不是+1了。所以有一下代码:
int a=pt[i+1].x-pt[i].x; int b=pt[i+1].y-pt[i].y; if(a<0) a-=1; else a+=1; if(b<0) b-=1; else b+=1; double dx=(double)a/(double)b;
注意double dx=(double)a/(double)b;的强式转换
第二个是之前所说的,多边形两种顶点的处理方式是不一样的,所以这里给A类顶点做了个预设,将mask设为1,
if((pt[1].y-pt[0].y)*(pt[pCount-1].y-pt[0].y)<0) mask[pt[0].x][pt[0].y]=1; for(int i=1;i<pCount-1;i++) { if((pt[i+1].y-pt[i].y)*(pt[i-1].y-pt[i].y)<0) mask[pt[i].x][pt[i].y]=1; } if((pt[0].y-pt[pCount-1].y)*(pt[pCount-2].y-pt[pCount-1].y)<0) mask[pt[pCount-1].x][pt[pCount-1].y]=1;
注意有3种情况。
第三个是mask数组的初始化,一定要注意。
for(int i=0;i<1366;i++) for(int j=0;j<768;j++) mask[i][j]=0;
第四个是:
if(nPstate==0&&GetPixel(hdc,x,y)==RGB(255,100,0)) SetPixel(hdc,x,y,RGB(255,255,255));
因为边标记,对于斜率小于一的边,可能每一个扫描线与边有多个交点,而标记的是最右边的点,如果没有GetPixel(hdc,x,y)==RGB(255,100,0)的判断,可能会出现将边线也填充成白色的情况。
不让过
下午上课,老是竟然不让过,说先填充字符后反填充这种方法不行,好吧,我改!
#include <windows.h>
#include <math.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("Connect") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Connect-the-Points Mouse Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
bool isPoint_1(int x,int y,LPARAM lParam) { int x1=LOWORD(lParam); int y1=HIWORD(lParam); if((x1-x)*(x1-x)+(y1-y)*(y1-y)<=9) return 1; return 0; }
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
static int pX0,pY0;
static int pCount=0;
static bool isP_1=0;
static POINT pt[100];
static int xMin;
static int xMax;
static int yMin;
static int yMax;
PAINTSTRUCT ps;
static bool mask[2000][1000]={{0}};
switch (message) /* handle the messages */
{
case WM_LBUTTONDOWN:
if(isP_1==1)
{
isP_1=0;
pCount=0;
InvalidateRect(hwnd,NULL,TRUE);
break;
}
if(pCount>=3&&isPoint_1(pX0,pY0,lParam))
{
xMin=2000;
xMax=-1;
yMin=2000;
yMax=-1;
for(int i=0; i<pCount; i++)
{
if(pt[i].x>xMax) xMax=pt[i].x;
if(pt[i].x<xMin) xMin=pt[i].x;
if(pt[i].y>yMax) yMax=pt[i].y;
if(pt[i].y<yMin) yMin=pt[i].y;
}
for(int i=0;i<2000;i++)
for(int j=0;j<1000;j++) mask[i][j]=0;
if((pt[1].y-pt[0].y)*(pt[pCount-1].y-pt[0].y)<0) mask[pt[0].x][pt[0].y]=1; for(int i=1;i<pCount-1;i++) { if((pt[i+1].y-pt[i].y)*(pt[i-1].y-pt[i].y)<0) mask[pt[i].x][pt[i].y]=1; } if((pt[0].y-pt[pCount-1].y)*(pt[pCount-2].y-pt[pCount-1].y)<0) mask[pt[pCount-1].x][pt[pCount-1].y]=1;pt[pCount].x=pt[0].x;
pt[pCount].y=pt[0].y;
for(int i=0;i<pCount;i++)
{
double x;
if(pt[i].y<pt[i+1].y)
x=pt[i].x;
else
x=pt[i+1].x;
int a=pt[i+1].x-pt[i].x; int b=pt[i+1].y-pt[i].y; if(a<0) a-=1; else a+=1; if(b<0) b-=1; else b+=1; double dx=(double)a/(double)b;for(int y=min(pt[i].y,pt[i+1].y);y<=max(pt[i].y,pt[i+1].y);y++)
{
mask[(int)(x+0.5)][y]=!mask[(int)(x+0.5)][y];
x=x+dx;
}
}
isP_1=1;
break;
}
else
{
pt[pCount].x=LOWORD(lParam);
pt[pCount].y=HIWORD(lParam);
if(pCount==0)
{
pX0=pt[pCount].x;
pY0=pt[pCount].y;
}
hdc=GetDC(hwnd);
SetPixel(hdc,LOWORD(lParam),HIWORD(lParam),0);
ReleaseDC(hwnd,hdc);
pCount++;
break;
}
case WM_LBUTTONUP:
InvalidateRect(hwnd,NULL,FALSE);
break;
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
if(isP_1)
{
for(int i=0; i<=pCount-2; i++)
{
MoveToEx(hdc,pt[i].x,pt[i].y,NULL);
LineTo(hdc,pt[i+1].x,pt[i+1].y);
}
MoveToEx(hdc,pt[pCount-1].x,pt[pCount-1].y,NULL);
LineTo(hdc,pt[0].x,pt[0].y);
hdc=GetDC(hwnd);
bool a[14][37]={ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0},
{0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0},
{0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0},
{0,1,1,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0},
{0,1,1,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0},
{0,1,1,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0},
{0,1,1,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0},
{0,1,1,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0},
{0,1,1,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0},
{0,0,1,1,1,1,0,0,0,1,1,1,1,1,1,0,0,0,0,1,1,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
for(int y=max(yMin-5,0);y<=yMax+20;y++)
{
bool nPstate=0;
for(int x=max(xMin-5,0);x<xMax+50;x++)
{
if(mask[x][y]) nPstate=!nPstate;
if(nPstate==1&&a[y%14][x%37]==1&&GetPixel(hdc,x,y)!=RGB(0,0,0))
SetPixel(hdc,x,y,RGB(255,100,0));
}
}
ReleaseDC(hwnd,hdc);
}
else if(pCount>=2)
{
for(int i=0; i<=pCount-2; i++)
{
MoveToEx(hdc,pt[i].x,pt[i].y,NULL);
LineTo(hdc,pt[i+1].x,pt[i+1].y);
}
}
EndPaint(hwnd,&ps);
break;
case WM_DESTROY:
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
break;
default: /* for messages that we don't deal with */
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
数组a中的0,1怎么设置呢,难道要一个一个的手打,当然不用,教你用下面的程序自动生成。
#include <windows.h> #include <iostream> using namespace std; /* Declare Windows procedure */ LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM); /* Make the class name into a global variable */ char szClassName[ ] = "CodeBlocksWindowsApp"; int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) { HWND hwnd; /* This is the handle for our window */ MSG messages; /* Here messages to the application are saved */ WNDCLASSEX wincl; /* Data structure for the windowclass */ /* The Window structure */ wincl.hInstance = hThisInstance; wincl.lpszClassName = szClassName; wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */ wincl.style = CS_DBLCLKS; /* Catch double-clicks */ wincl.cbSize = sizeof (WNDCLASSEX); /* Use default icon and mouse-pointer */ wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION); wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION); wincl.hCursor = LoadCursor (NULL, IDC_ARROW); wincl.lpszMenuName = NULL; /* No menu */ wincl.cbClsExtra = 0; /* No extra bytes after the window class */ wincl.cbWndExtra = 0; /* structure or the window instance */ /* Use Windows's default colour as the background of the window */ wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND; /* Register the window class, and if it fails quit the program */ if (!RegisterClassEx (&wincl)) return 0; /* The class is registered, let's create the program*/ hwnd = CreateWindowEx ( 0, /* Extended possibilites for variation */ szClassName, /* Classname */ "Code::Blocks Template Windows App", /* Title Text */ WS_OVERLAPPEDWINDOW, /* default window */ CW_USEDEFAULT, /* Windows decides the position */ CW_USEDEFAULT, /* where the window ends up on the screen */ 544, /* The programs width */ 375, /* and height in pixels */ HWND_DESKTOP, /* The window is a child-window to desktop */ NULL, /* No menu */ hThisInstance, /* Program Instance handler */ NULL /* No Window Creation data */ ); /* Make the window visible on the screen */ ShowWindow (hwnd, nCmdShow); /* Run the message loop. It will run until GetMessage() returns 0 */ while (GetMessage (&messages, NULL, 0, 0)) { /* Translate virtual-key messages into character messages */ TranslateMessage(&messages); /* Send message to WindowProcedure */ DispatchMessage(&messages); } /* The program return-value is 0 - The value that PostQuitMessage() gave */ return messages.wParam; } /* This function is called by the Windows function DispatchMessage() */ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; TCHAR *str="0213"; PAINTSTRUCT ps; switch (message) /* handle the messages */ { case WM_PAINT: hdc=BeginPaint(hwnd,&ps); SetTextColor(ps.hdc, RGB(255, 100, 0));//设置文本颜色 TextOut(hdc,0,0,str,lstrlen(str)); for(int y=0;y<15;y++) { for(int x=0;x<37;x++) { if(GetPixel(hdc,x,y)==RGB(255, 100, 0) )cout<<"1,"; else cout<<"0,"; } cout<<endl; } EndPaint(hwnd,&ps); case WM_DESTROY: PostQuitMessage (0); /* send a WM_QUIT to the message queue */ break; default: /* for messages that we don't deal with */ return DefWindowProc (hwnd, message, wParam, lParam); } return 0; }
然后将结果复制粘贴就搞定了。
相关文章推荐
- 图形学实验之多边形填充算法
- 计算机图形学(三)扫描线多边形填充算法讲解与源代码
- 计算机图形学作业 - 运用PyOpenGL使用区域填充的递归算法(种子填充)绘制“明”字
- 计算机图形学-多边形填充法
- 计算机图形学 学习笔记(三):多边形的区域填充算法,反走样算法
- 计算机图形学 -- 光栅图形学扫描线填充多边形[转]
- 计算机图形学--多边形扫瞄转换与区域填充实现
- 计算机图形学(二)输出图元_10_多边形填充区_5_平面方程
- 多边形填充实验
- 操作系统(实验二)作业调度
- 算法系列之十二:多边形区域填充算法--递归种子填充算法
- 第二章实验作业2-7题
- C++第四次实验——作业
- C++第2次实验作业项目2:本月有几天
- MATLAB实验作业答案
- 作业:计数器仿真实验
- 算法系列之十二:多边形区域填充算法--递归种子填充算法 .
- 11 11 实验二 作业调度模拟实验
- C++第五次上机实验其他作业