计算机图形学画多边形

更新时间:2023-07-29 13:56:01 阅读量: 实用文档 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

基本图形的生成

计算机图形学已成为计算机技术中发展最快的领域,计算机图形软件也相应得到快速发展。计算机绘图显示有屏幕显示、打印机打印图样和绘图机输出图样等方式,其中用屏幕显示图样是计算机绘图的重要内容。

计算机上常见的显示器为光栅图形显示器,光栅图形显示器可以看作像素的矩阵。像素是组成图形的基本元素,一般称为“点”。通过点亮一些像素,灭掉另一些像素,即在屏幕上产生图形。在光栅显示器上显示任何一种图形必须在显示器的相 应像素点上画上所需颜色,即具有一种或多种颜色的像素集合构成图形。确定最佳 接近图形的像素集合,并用指定属性写像素的过程称为图形的扫描转换或光栅化。 对于一维图形,在不考虑线宽时,用一个像素宽的直、曲线来显示图形。二维图形 的光栅化必须确定区域对应的像素集,并用指定的属性或图案进行显示,即区域 填充。

复杂的图形系统,都是由一些最基本的图形元素组成的。利用计算机编制图形软件时,编制基本图形元素是相当重要的,也是必需的。点是基本图形,本章主要讲述如何在指定的输出设备(如光栅图形显示器)上利用点构造其他基本二维几何图形(如点、直线、圆、椭圆、多边形域及字符串等)的算法与原理,并利用Visual C++编程实现这些算法。

1.1 直 线

数学上,理想的直线是由无数个点构成的集合,没有宽度。计算机绘制直线是在显示器所给定的有限个像素组成的矩阵中,确定最佳逼近该直线的一组像素,并且按扫描线顺序,对这些像素进行写操作,实现显示器绘制直线,即通常所说的直线的扫描转换,或称直线光栅化。

由于一图形中可能包含成千上万条直线,所以要求绘制直线的算法应尽可能地快。本节介绍一个像素宽直线的常用算法:数值微分法(DDA )、中点画线法、Bresenham 算法。

1.1.1 DDA (数值微分)算法

DDA 算法原理:如图1-1所示,已知过端点

000111(, ), (, )p x y p x y 的直线段01p p ;直线斜率为10

10y y k x x -=-,从x 的左端点0x 开始,向x 右端点步

进画线,步长=1(个像素),计算相应的y 坐标

y k x B =+;取像素点 [x , round (y )] 作为当前点的坐标。计算

1111i i y k x B k x B k x y k x ++=+=++?=+?,当

11i i x ,y y k +==+,即当x 每递增1,y 递增k (即直线

M

Q P 2

P 1 P =(x p , y p )

图1-2 中点画线法每步迭代涉

及的像素和中点示意图 Line: P 0(0,0)…P 1(5,2)

图1-1 DDA 方法扫描转换连接两点

斜率)。

注意:上述分析的算法仅适用于k ≤1的情形。在这种情况下,x 每增加1, y 最多增加1。当1k ≥时,必须把x ,y 地位互换,y 每增加1,x 相应增加1/k (请参阅后面的Visual C++程序)。

1.1.2 生成直线的中点画线法

中点画线法的基本原理如图1-2所示。在画直线段的过程中,当前像素点为P ,下一个像素点有两种选择,点P 1或P 2。M 为P 1与P 2中点,Q 为理想直线与X =X p +1垂线的交点。当M 在Q 的下方时,则P 2应为下一个像素点;当M 在Q 的上方时,应取P 1为下一点。

中点画线法的实现:令直线段为L [ p 0(x 0,y 0), p 1(x 1, y 1)],其方程式F (x , y )=ax +by +c =0。 其中,a =y 0–y 1, b =x 1–x 0, c =x 0y 1–x 1y 0;点与L 的关系如下。

在直线上,F (x , y )=0;

在直线上方,F (x , y )>0;

在直线下方,F (x , y )<0。

把M 代入F (x , y ),判断F 的符号,可知Q 点在中点M 的上方还是下方。为此构造判别式d =F (M )=F (x p +1, y p +0.5)=a (x p +1)+b (y p +0.5)+c 。

当d < 0,L (Q 点)在M 上方,取P 2为下一个像素。

当d > 0,L (Q 点)在M 下方,取P 1为下一个像素。

当d =0,选P 1或P 2均可,取P 1为下一个像素。

其中d 是x p , y p 的线性函数。

1.1.3 Bresenham 算法

Bresenham 算法是计算机图形学领域使用最广泛的直线扫描转换算法。由误差项符号决定下一个像素取右边点还是右上方点。

设直线从起点(x 1, y 1)到终点(x 2, y 2)。直线可表示为方程y = mx +b ,其中b =y 1–mx 1, m = (y 2–y 1)/(x 2–x 1)=d y /d x ;此处的讨论直线方向限于第一象限,如图1-3所示,当直线光栅化时,x 每次都增加1个单元,设x 像素为(x i ,y i )。下一

个像素的列坐标为x i +1,行坐标为y i 或者递增1为y i +1,由y 与y i 及y i +1的距离d 1及d 2的大小而定。计算公式为 y = m (x i + 1) + b

(1.1)

d 1 = y – y i (1.2) d 2=y i +1–y (1.3) 如果d 1–d 2>0,则y i +1=y i +1,否则y i +1=y i 。 式(1.1)、(1.2)、(1.3)代入d 1–d 2,再用d x 乘等式两边,并以P i =(d 1–d 2),d x 代入上述

等式,得 y i +1 y d 2 d 1 y 1 x i +1 x 1

图1-3 第一象限直线光栅化 Bresenham 算法

P i = 2x i d y–2y i d x+2d y+(2b–1)d x(1.4)

d1–d2是用以判断符号的误差。由于在第一象限,d x总大于0,所以P i仍旧可以用做判断符号的误差。P i+1为

P i+1 = P i+2d y–2(y i+1–y i)d x(1.5)

求误差的初值P1,可将x1、y1和b代入式(1.4)中的x i、y i,而得到

P1 = 2d y–d x

综述上面的推导,第一象限内的直线Bresenham算法思想如下:

(1)画点(x1, y1),d x=x2–x1,d y=y2–y1,计算误差初值P1=2d y–d x,i=1。

(2)求直线的下一点位置x i+1 = x i + 1,如果P i>0,则y i+1=y i+1,否则y i+1=y i。

(3)画点(x i+1, y i+1)。

(4)求下一个误差P i+1,如果P i>0,则P i+1=P i+2d y–2d x,否则P i+1=P i+2d y。

(5)i=i+1;如果i<d x+1则转步骤(2);否则结束操作。

1.1.4 程序设计

1.程序设计功能说明

为编程实现上述算法,本程序利用最基本的绘制元素(如点、直线等),绘制图形。如图1-4所示,为程序运行主界面,通过选择菜单及下拉菜单的各功能项分别完成各种对应算法的图形绘制。

图1-4 基本图形生成的程序运行界面

2.创建工程名称为“基本图形的生成”单文档应用程序框架

(1)启动VC,选择“文件”|“新建”菜单命令,并在弹出的新建对话框中单击“工程”标签。

(2)选择MFC AppWizard(exe),在“工程名称”编辑框中输入“基本图形的生成”作为工程名称,单击“确定”按钮,出现Step 1对话框。

(3)选择“单个文档”选项,单击“下一个”按钮,出现Step 2对话框。

(4)接受默认选项,单击“下一个”按钮,在出现的Step 3~Step 5对话框中,接受默认选项,单击“下一个”按钮。

(5)在Step 6对话框中单击“完成”按钮,即完成“基本图形的生成”应用程序的所有选项,随后出现工程信息对话框(记录以上步骤各选项选择情况),如图1-5所示,单击“确定”按钮,完成应用程序框架的创建。

图1-5 信息程序基本

3.编辑菜单资源

设计如图1-4所示的菜单项。在工作区的ResourceView标签中,单击Menu项左边“+”,然后双击其子项IDR_MAINFRAME,并根据表1-1中的定义编辑菜单资源。此时VC已自动建好程序框架,如图1-5所示。

表1-1菜单资源表

4.添加消息处理函数

利用ClassWizard(建立类向导)为应用程序添加与菜单项相关的消息处理函数,

ClassName栏中选择CMyView,根据表1-2建立如下的消息映射函数,ClassWizard会自动完成有关的函数声明。

表1-2菜单项的消息处理函数

5.程序结构代码,在CMyView.cpp文件中相应位置添加如下代码:

// DDA算法生成直线

void CMyView:: OnDdaline()

{

CDC* pDC=GetDC();//获得设备指针

int xa=100,ya=300,xb=300,yb=200,c=RGB(255,0,0);//定义直线的两端点,直线颜色

int x,y;

float dx, dy, k;

dx=(float)(xb-xa), dy=(float)(yb-ya);

k=dy/dx, y=ya;

if(abs(k)<1)

{

for (x=xa;x<=xb;x++)

{pDC->SetPixel (x,int(y+0.5),c);

y=y+k;}

}

if(abs(k)>=1)

{

for (y=ya;y<=yb;y++)

{pDC->SetPixel (int(x+0.5),y,c);

x=x+1/k;}

}

ReleaseDC(pDC);

}

说明:

(1)以上代码理论上通过定义直线的两端点,可得到任意端点之间的一直线,但由于一般屏幕坐标采用右手系坐标,屏幕上只有正的x, y值,屏幕坐标与窗口坐标之间转换知识请参考第3章。

(2)注意上述程序考虑到当k 1的情形x每增加1,y最多增加1;当k>1时,y每增加1,x相应增加1/k。在这个算法中,y与k用浮点数表示,而且每一步都要对y进行四舍五入后取整。

//中点算法生成直线

void CMyView::OnMidpointline()

{

CDC* pDC=GetDC();

int xa=300, ya=200, xb=450, yb=300,c=RGB(0,255,0);

float a, b, d1, d2, d, x, y;

a=ya-yb, b=xb-xa, d=2*a+b;

d1=2*a, d2=2* (a+b);

x=xa, y=ya;

pDC->SetPixel(x, y, c);

while (x<xb)

{ if (d<0) {x++, y++, d+=d2; }

else {x++, d+=d1;}

pDC->SetPixel(x, y, c);

}

ReleaseDC(pDC);

}

说明:

(1)其中d是x p, y p的线性函数。为了提高运算效率,程序中采用增量计算。具体算法如下:若当前像素处于d>0情况,则取正右方像素P1(x p+1, y p),判断下一个像素点的位置,应计算d1=F(x p+2, y p+0.5)=a(x p+2)+b(y p+0.5)=d+a;,其中增量为a。若d<0时,则取右上方像素P2(x p+1, y p+1)。再判断下一像素,则要计算d2= F(x p+2, y p+1.5)=a(x p+2)+b(y p+1.5)+c=d+a+b,增量为a+b。

(2)画线从(x0, y0)开始,d的初值d0=F(x0+1, y0+0.5)=F(x0, y0)+a+0.5b,因F(x0, y0)=0,则d0=a+0.5b。

(3)程序中只利用d的符号,d的增量都是整数,只是初始值包含小数,用2d代替d,使程序中仅包含整数的运算。

//Bresenham算法生成直线

void CMyView::OnBresenhamline()

{

CDC* pDC=GetDC();

int x1=100, y1=200, x2=350, y2=100,c=RGB(0,0,255);

int i,s1,s2,interchange;

float x,y,deltax,deltay,f,temp;

x=x1;

y=y1;

deltax=abs(x2-x1);

deltay=abs(y2-y1);

if(x2-x1>=0) s1=1; else s1=-1;

if(y2-y1>=0) s2=1; else s2=-1;

if(deltay>deltax){

temp=deltax;

deltax=deltay;

deltay=temp;

interchange=1;

}

else interchange=0; f=2*deltay-deltax; pDC->SetPixel(x,y,c); for(i=1;i<=deltax;i++){ if(f>=0){

if(interchange==1) x+=s1; else y+=s2;

pDC->SetPixel(x,y,c); f=f-2*deltax; } else{

if(interchange==1) y+=s2; else x+=s1; f=f+2*deltay; } } }

说明:

(1)以上程序已经考虑到所有象限直线的生成。 (2)Bresenham 算法的优点如下:

① 不必计算直线的斜率,因此不做除法。 ② 不用浮点数,只用整数。

③ 只做整数加减运算和乘2运算,而乘2运算可以用移位操作实现。 ④ Bresenham 算法的运算速度很快。 1.2 圆

给出圆心坐标(x c , y c )和半径r ,逐点画出一个圆周的公式有下列两种。 1.2.1 直角坐标法 直角坐标系的圆的方程为

222()()c c x x y y r -+-=

由上式导出:

=c y y

当x –x c 从–r 到r 做加1递增时,就可以求出对应的圆周点的y 坐标。但是这样求出的圆周上的点是不均匀的,| x –x c | 越大,对应生成圆周点之间的圆周距离也就越长。因此,所生成的圆

M

P 2 P 1

P =(x p , y p )

图1-6 中点画圆法示意图

y y i y y i –1

O x i x i +1 x

1d

2d

图1-7 确定y 的位置

不美观。

1.2.2 中点画圆法

如图1-6所示,函数为F(x, y)=x2+y2–R2的构造圆,圆上的点为F(x, y)=0,圆外的点F(x, y)>0,圆内的点F(x, y)<0,构造判别式:

d=F(M)=F(x p+1, y p–0.5)=(x p+1)2+(y p–0.5)2

若d<0,则应取P1为下一像素,而且下一像素的判别式为

d=F(x p+2, y p–0.5)= (x p+2)2+(y p–0.5)2–R2=d+2x p+3

若d≥0,则应取P2为下一像素,而且下一像素的判别式为

d=F(x p+2, y p–1.5)= (x p+2)2+(y p–1.5)2–R2=d+2(x p–y p)+5

我们讨论按顺时针方向生成第二个八分圆,则第一个像素是(0, R),判别式d的初始值为

d0=F(1, R–0.5)=1.25–R

1.2.3 圆的Bresenham算法

设圆的半径为r,先考虑圆心在(0, 0),从x=0、y=r开始的顺时针方向的1/8圆周的生成过程。在这种情况下,x每步增加1,从x=0开始,到x=y结束,即有x i+1 = x i + 1;相应的,y i+1则在两种可能中选择:y i+1=y i或者y i+1= y i–1。选择的原则是考察精确值y是靠近y i还是靠近y i–1(见图1-7),计算式为

y2= r2–(x i+1)2

d1 = y i2–y2 = y i2–r2+(x i+1)2

d2 = y2– (y i–1)2 = r2–(x i+1)2–(y i–1)2

令p i=d1–d2,并代入d1、d2,则有

p i = 2(x i+1)2 + y i2+ (y i–1)2–2r2(1.6)

p i称为误差。如果p i<0,则y i+1=y i,否则y i+1=y i–1。

p i的递归式为

p i+1 = p i + 4x i +6+2(y i2+1–y i2)–2(y i+1–y i) (1.7)

p i的初值由式(1.6)代入x i=0,y i=r,得

p1 = 3–2r(1.8)

根据上面的推导,圆周生成算法思想如下:

(1)求误差初值,p1=3–2r,i=1,画点(0, r)。

(2)求下一个光栅位置,其中x i+1=x i+1,如果p i<0则y i+1=y i,否则y i+1=y i–1。

(3)画点(x i+1, y i+1)。

(4)计算下一个误差,如果p i<0则p i+1=p i+4x i+6,否则p i+1=p i+4(x i–y i)+10。

(5)i=i+1,如果x=y则结束,否则返回步骤(2)。

程序设计步骤如下。

(1)创建应用程序框架,以上面建立的单文档程序框架为基础。

(2)编辑菜单资源。

在工作区的ResourceView标签中,单击Menu项左边“+”,然后双击其子项IDR_MAINFRAME,并根据表1-3中的定义添加编辑菜单资源。此时建好的菜单如图1-8

所示。

表1-3 菜单资源表

(3)添加消息处理函数。

利用ClassWizard (建立类向导)为应用程序添加与菜单项相关的消息处理函数,ClassName 栏中选择CMyView ,根据表1-4建立如下的消息映射函数,ClassWizard 会自动完成有关的函数声明。

表1-4 菜单项的消息处理函数

(4)程序结构代码,在CMyView.cpp 文件中的相应位置添加如下代码。

void CMyView::OnMidpointcircle() //中点算法绘制圆,如图1-9所示

{

// TODO: Add your command handler code here

CDC* pDC=GetDC();

int xc=300, yc=300, r=50, c=0;

int x,y;

float d;

x=0; y=r; d=1.25-r;

pDC->SetPixel ((xc+x),(yc+y),c);

pDC->SetPixel ((xc-x),(yc+y),c);

pDC->SetPixel ((xc+x),(yc-y),c);

pDC->SetPixel ((xc-x),(yc-y),c);

pDC->SetPixel ((xc+y),(yc+x),c);

pDC->SetPixel ((xc-y),(yc+x),c);

pDC->SetPixel ((xc+y),(yc-x),c);

pDC->SetPixel ((xc-y),(yc-x),c);

while(x<=y)

{ if(d<0) d+=2*x+3;

else { d+=2*(x-y)+5; y--;}

x++;

pDC->SetPixel ((xc+x),(yc+y),c);

pDC->SetPixel ((xc-x),(yc+y),c); 图1-9 中点算法绘制圆

图1-8 程序主菜单

pDC->SetPixel ((xc+x),(yc-y),c);

pDC->SetPixel ((xc-x),(yc-y),c);

pDC->SetPixel ((xc+y),(yc+x),c);

pDC->SetPixel ((xc-y),(yc+x),c);

pDC->SetPixel ((xc+y),(yc-x),c);

pDC->SetPixel ((xc-y),(yc-x),c);

}

}

void CMyView::OnBresenhamcircle() //// Bresenham算法绘制圆,如图1-10所示{

CDC* pDC=GetDC();

int xc=100, yc=100, radius=50, c=0;

int x=0,y=radius,p=3-2*radius;

while(x<y)

{

pDC->SetPixel(xc+x, yc+y, c);

图1-10 Bresenham算法绘制圆pDC->SetPixel(xc-x, yc+y, c);

pDC->SetPixel(xc+x, yc-y, c);

pDC->SetPixel(xc-x, yc-y, c);

pDC->SetPixel(xc+y, yc+x, c);

pDC->SetPixel(xc-y, yc+x, c);

pDC->SetPixel(xc+y, yc-x, c);

pDC->SetPixel(xc-y, yc-x, c);

if (p<0)

p=p+4*x+6;

else

{

p=p+4*(x-y)+10;

y-=1;

}

x+=1;

}

if(x==y)

pDC->SetPixel(xc+x, yc+y, c);

pDC->SetPixel(xc-x, yc+y, c);

pDC->SetPixel(xc+x, yc-y, c);

pDC->SetPixel(xc-x, yc-y, c);

pDC->SetPixel(xc+y, yc+x, c);

pDC->SetPixel(xc-y, yc+x, c);

pDC->SetPixel(xc+y, yc-x, c);

pDC->SetPixel(xc-y, yc-x, c);

}

1.3 椭圆扫描转换中点算法

下面讨论椭圆的扫描转换中点算法,设椭圆为中心在坐标原点的标准椭圆,其方 程为 F (x , y )=b 2x 2+a 2y 2–a 2b 2=0

(1)对于椭圆上的点,有F (x , y )=0;

(2)对于椭圆外的点,F (x , y )>0;

(3)对于椭圆内的点,F (x , y )<0。

以弧上斜率为–1的点作为分界将第一象限椭圆弧分为上下两部分(如图1-11所示)。 法向量:

22(,)i j 2i 2j F F N x y b x a y x y ??=+=+??

22(1)(0.5)i i b x a y +<- 而在下一个点,不等号改变方向,则说明椭圆弧从上部分转入下部分。 与中点绘制圆算法类似,一个像素确定后,在下面两个候选像素点的中点计算一个判别式的值,再根

据判别式符号确定离椭圆最近的点。先看椭圆弧的上半部分,具体算法如下: 假设横坐标为x p 的像素中与椭圆最近点为(x p , y p ),下一对候选像素的中点应为(x p +1,y p –0.5),判别式为

222222

1(1,0.5)(1)(0.5)p p p p d F x y b x a y a b =+-=++--

10d <,表明中点在椭圆内,应取正右方像素点,判别式变为

222222211(2,0.5)(2)(0.5)(23)p p p p p d F x y b x a y a b d b x '=+-=++--=++

若10d ≥,表明中点在椭圆外,应取右下方像素点,判别式变为

222222

1222

1(2, 1.5)(2)( 1.5) (23)(22)p p p p p p d F x y b x a y a b d b x a y '=+-=++--=+++-+

判别式1d 的初始条件确定。椭圆弧起点为(0, b ),第一个中点为(1,b – 0.5),对应判别式为 上半部分 下半部分 二分量相等的法向量

y x 图1-11 第一象限的椭圆弧

222222210(1,0.5)(0.5)(0.25)d F b b a b a b b a b =-=+--=+-+

在扫描转换椭圆的上半部分时,在每步迭代中需要比较法向量的两个分量来确定核实从上部分转到下半部分。在下半部分算法有些不同,要从正上方和右下方两个像素中选择下一个像素。在从上半部分转到下半部分时,还需要对下半部分的中点判别式进行初始化。即若上半部分所选择的最后一个像素点为(x p , y p ),则下半部分中点判别式应在(x p +0.5, y p –1)的点上计算。其在正下方与右下方的增量计算同上半部分。具体算法的实现请参考下面的程序设计。

程序设计步骤如下。

(1)创建应用程序框架,以上面建立的单文档程序框架为基础。

(2)编辑菜单资源。

在工作区的ResourceView 标签中,单击Menu 项左边“+”,然后双击其子项IDR_MAINFRAME ,并根据表1-5中的定义添加编辑菜单资源。此时建好的菜单如图1-12所示。

图1-12 程序主菜单

(3)添加消息处理函数。

利用ClassWizard (建立类向导)为应用程序添加与菜单项相关的消息处理函数,ClassName 栏中选择CMyView ,根据表1-6建立如下的消息映射函数,ClassWizard 会自动完成有关的函数声明。

表1-6

菜单项的消息处理函数

(4)程序结构代码如下:

void CMyView:: OnMidpointellispe () //中点算法绘制椭圆,如图1-13所示

{

CDC* pDC=GetDC();

int a=200,b=100,xc=300,yc=200,c=0;

int x,y;

double d1,d2;

x=0;y=b; d1=b*b+a*a*(-b+0.25); 图1-13 中点算法绘制椭圆

pDC->SetPixel(x+300,y+200,c);

pDC->SetPixel(-x+300,y+200,c);

pDC->SetPixel(x+300,-y+200,c);

pDC->SetPixel(-x+300,-y+200,c);

while(b*b*(x+1)<a*a*(y-0.5))

{

if(d1<0){

d1+=b*b*(2*x+3);

x++;}

else

{d1+=b*b*(2*x+3)+a*a*(-2*y+2);

x++;y--;

}

pDC->SetPixel(x+xc,y+yc,c);

pDC->SetPixel(-x+xc,y+yc,c);

pDC->SetPixel(x+xc,-y+yc,c);

pDC->SetPixel(-x+xc,-y+yc,c);

}

d2=sqrt(b*(x+0.5))+a*(y-1)-a*b;

while(y>0)

{

if(d2<0){

d2+=b*b*(2*x+2)+a*a*(-2*y+3);

x++;y--;}

else

{d2+=a*a*(-2*y+3);

y--;}

pDC->SetPixel(x+xc,y+yc,c);

pDC->SetPixel(-x+xc,y+yc,c);

pDC->SetPixel(x+xc,-y+yc,c);

pDC->SetPixel(-x+xc,-y+yc,c);

}

}

1.4 多边形的扫描转换与区域填充

在计算机图形学中,多边形有两种重要的表示方法:顶点表示和点阵表示。顶点表示是用多边形的顶点序列来表示多边形,特点直观、几何意义强、占内存少,易于进行几何变换,但由于它没有明确指出哪些像素在多边形内,故不能直接用于面着色。点阵表示

是用位于多边形内的像素集合来刻画多边形。这种表示丢失了许多几何信息,但便于帧缓冲器表示图形,是面着色所需要的图形表示形式。光栅图形的一个基本问题是把多边形的顶点表示转换为点阵表示。这种转换称为多边形的扫描转换。

1.4.1 多边形的扫描转换

多边形可分为凸多边形、凹多边形、含内环多边形。

(1)凸多边形:任意两顶点间的连线均在多边形内。

(2)凹多边形:任意两顶点间的连线有不在多边形内的部分。

(3)含内环多边形:多边形内包含有封闭多边形。

扫描线多边形区域填充算法是按扫描线顺序,计算扫描线与多边形的相交区间,再用要求的颜色显示这些区间的像素。区间的端点可以通过计算扫描线与多边形边界线的交点获得。对于一条扫描线,多边形的填充过程可以分为4个步骤。

(1)求交:计算扫描线与多边形各边的交点。

(2)排序:把所有交点按x 值递增顺序排序。

(3)配对:第一个与第二个,第三个与第四个等,每对交点代表扫描线与多边形的一个相交区间。

(4)填色:把相交区间内的像素置成多边形颜色,把相交区间外的像素置成背景色。 具体实现方法:为多边形的每一条边建立一边表;为了提高效率,在处理一条扫描线时,仅对与它相交的多边形的边进行求交运算。把与当前扫描线相交的边称为活性边,并把它们按与扫描线交点递增的顺序存放在一个链表中,称此链表为活性边表。另外使用增量法计算时,需要知道一条边何时不再与下一条扫描线相交,以便及时把它从扫描线循环中删除出去。为了方便活性边表的建立与更新,为每一条扫描线建立一个新边表(NET ),存放在该扫描线第一次出现的边。为使程序简单、易读,这里新边表的结构应保存其对应边如下信息:当前边的边号、边的较低端点(x min ,y min )与边的较高端点(x max ,y max )和从当前扫描线到下一条扫描线间x 的增量x 。

相邻扫描线间x 的增量x 的计算,假定当前扫描线与多边形某一条边的交点的X 坐标为x ,则下一条扫描线与该边的交点不要重计算,只要加一个增量x 。设该边的直线方程为ax +by +c =0;若y =y i ,x =x i ;则当y = y i +1时

111()i i i i b x b y c x a a ++=-?-=-

其中/x b a ?=-为常数。

扫描线与多边形顶点相交的处理方法如图1-14所示。

(1)扫描线与多边形相交的边分别处于扫描线的两侧,

则记为一个交点,如点P 5,P 6。

(2)扫描线与多边形相交的边分别处于扫描线同侧,且

y i <y i –1,y i <y i +1,则计两个交点(填色),如P 2,若y i >y i –1,

y i >y i +1,则计0个交点(不填色),如P 1。

(3)扫描线与多边形边界重合(当要区分边界和边界内

图1-14 扫描线与多边形相交,

特殊情况的处理

区域时需特殊处理),则计1个交点。

具体实现时,只需检查顶点的两条边的另外两个端点的y值。按这两个y值中大于交点y值的个数是0,1,2来决定。

算法步骤如下:

(1)初始化:构造边表。

(2)对边表进行排序,构造活性边表。

(3)对每条扫描线对应的活性边表中求交点。

(4)判断交点类型,并两两配对。

(5)对符合条件的交点之间用画线方式填充。

(6)下一条扫描线,直至满足扫描结束条件。

1.4.2 区域填充算法

这里的区域指已表示成点阵形式的填充图形,是像素的集合。区域有两种表示形式:内点表示和边界表示,如图1-15所示。内点表示,即区域内的所有像素有相同颜色;边界表示,即区域的边界点有相同颜色。区域填充指先将区域的一点赋予指定的颜色,然后将该颜色扩展到整个区域的过程。

区域填充算法要求区域是连通的。区域可分为4向连通区域和8向连通区域,如图1-16所示。4向连通区域指的是从区域上一点出发,可通过四个方向,即上、下、左、右移动的组合,在不越出区域的前提下,到达区域内的任意像素;8向连通区域指的是从区域内每一像素出发,可通过8个方向,即上、下、左、右、左上、右上、左下、右下这八个方向的移动的组合来到达。

图1-15 区域的内点表示和边界表示图1-16 4连通区域和8连通区域

1.区域填充的递归算法

上面讨论的多边形填充算法是按扫描线顺序进行的。种子填充算法则是假设在多边形内有一像素已知,由此出发利用连通性填充区域内的所有像素。一般采用多次递归方式。2.区域填充的扫描线算法

算法的基本过程如下:给定种子点(x,y),首先填充种子点所在扫描线上给定区域的一个区段,然后确定与这一区段相连通的上、下两条扫描线上位于给定区域内的区段,并依次保存下来。反复这个过程,直到填充结束。

区域填充的扫描线算法可由下列3个步骤实现。

(1)初始化:确定种子点元素(x,y)。

(2)判断种子点(x,y)是否满足非边界、非填充色的条件,若满足条件,以y作为当前扫描线沿当前扫描线向左、右两个方向填充,直到边界。

(3)确定新的种子点:检查与当前扫描线y上、下相邻的两条扫描线上的像素。若存在非边界、未填充的像素,则返回步骤(2)进行扫描填充。直至区域所有元素均为填充色,程序结束。

扫描线填充算法提高了区域填充的效率。

程序设计的步骤如下:

(1)创建应用程序框架,以上述单文档程序框架为基础,创建如图1-17所示应用程序界面。

(2)编辑菜单资源。

在工作区的ResourceView标签中,单击Menu项左边“+”,然后双击其子项IDR_MAINFRAME,并根据表1-7中的定义添加编辑菜单资源。此时建好的菜单如图1-18所示。

图1-17 程序界面

表1-7菜单资源表

图1-18 程序主菜单

(3)添加消息处理函数。

利用ClassWizard(建立类向导)为应用程序添加与菜单项相关的消息处理函数,ClassName栏中选择CMyView,根据表1-8建立如下的消息映射函数,ClassWizard会自动完成有关的函数声明。

表1-8菜单项的消息处理函数

(4)添加程序结构代码。

①在“基本图形的生成View.h”适当位置添加以下黑体字部分代码:

typedef struct //建立边表结构

{

int num, ymin,ymax;

float xmin,xmax,dx;

} Edge;

class CMyView : public CView

{

protected: // create from serialization only

public:

Cpoint ptset[7];

Edge edge[7],edge1[7],newedge[1];

}

②在OnDraw()函数中添加如下黑体字部分代码。

void CMyView::OnDraw(CDC* pDC)//绘制要填充的多边形

{

CMyDoc* pDoc = GetDocument();

ASSERT_V ALID(pDoc);

CPen newpen(PS_SOLID,1,RGB(255,0,0));

CPen *old=pDC->SelectObject(&newpen);

pDC->TextOut(20,20,"双击鼠标左键, 出现需填充的多边形, 点击相关功能菜单实现区域填充");

pDC->TextOut(20,50,"进行种子填充, 需用鼠标右键, 单击多边形内一点, 作为开始填充的种子点");

pDC->SelectObject(old);

}

③在菜单项的消息处理函数实体中添加以下黑体字部分代码。

void CMyView::OnScanfill() //扫描线算法进行多边形区域填充,如图1-19所示

{

CDC* pDC=GetDC();

CPen newpen(PS_SOLID,1,RGB(0,255,0));

CPen *old=pDC->SelectObject(&newpen);

int j,k,s=0;

int pmin,pmax;

for(int i=0;i<6;i++)//建立边表

{

edge[i].dx=(float)(spt[i+1].x-spt[i].

x)/(spt[i+1].y-spt[i].y);

图1-19 扫描线算法区域填充if(spt[i].y<=spt[i+1].y){

edge[i].num=i;

edge[i].ymin=spt[i].y;

edge[i].ymax=spt[i+1].y;

edge[i].xmin=(float)spt[i].x;

edge[i].xmax=(float)spt[i+1].x;

pmax=spt[i+1].y;

pmin=spt[i].y;

}

else{

edge[i].num=i;

edge[i].ymin=spt[i+1].y;

edge[i].ymax=spt[i].y;

edge[i].xmax=(float)spt[i].x;

edge[i].xmin=(float)spt[i+1].x;

pmax=spt[i].y;

pmin=spt[i+1].y;

}

}

for(int r=1;r<6;r++) //排序edge(yUpper,xIntersect)

{

for(int q=0;q<6-r;q++)

{

if(edge[q].ymin<edge[q+1].ymin)

{

newedge[0]=edge[q]; edge[q]=edge[q+1];

edge[q+1]=newedge[0];

}

}

}

for(int scan=pmax-1;scan>pmin+1;scan--)

{

int b=0;

k=s;

for(j=k;j<6;j++)

{

if((scan>edge[j].ymin)&&(scan<=edge[j].ymax))//判断与线段相交

{

if(scan==edge[j].ymax)

{

if(spt[edge[j].num+1].y<edge[j].ymax)

{

b++;

p[b]=(int)edge[j].xmax;

}

if(spt[edge[j].num-1].y<edge[j].ymax)

{

b++;

p[b]=(int)edge[j].xmax;

}

}

if((scan>edge[j].ymin)&&(scan<edge[j].ymax))

{

b++;

p[b]=(int)(edge[j].xmax+edge[j].dx*(scan-edge[j]. ymax));

}

}

//pDC->LineTo(spt[edge[0].num].x,spt[edge[0].num].y);

if(scan<=edge[j].ymin)//

s=j;

}

if(b>1)

{

for(int u=1;u<b;u++)

{

pDC->MoveTo(p[u]+3,scan);

u++;

本文来源:https://www.bwwdw.com/article/0tfm.html

微信扫码分享

《计算机图形学画多边形.doc》
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档
下载全文
范文搜索
下载文档
Top