您的位置:首页 > 其它

吉林大学图形学实验课作业边标记填充多边形

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

A,B线段斜率是多少?(2-1)/(4-1)=1/3但是实际上显然斜率是1/2;

所以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;
}


然后将结果复制粘贴就搞定了。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: