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

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

作业要求:

编写应用程序,采用鼠标输入顶点的方法确定待填充多边形;实现边标志算法完成对多边形的填充,要求完成使用自己学号的后四位数字对多边形内部进行填充。完成效果如下图所示:

   

注:因为没有安装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,上图:

比如这个:

  • 6
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值