吉林大学图形学实验课作业边标记填充多边形
作业要求:
编写应用程序,采用鼠标输入顶点的方法确定待填充多边形;实现边标志算法完成对多边形的填充,要求完成使用自己学号的后四位数字对多边形内部进行填充。完成效果如下图所示:
注:因为没有安装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; }
注: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,上图:
比如这个: