一.实验目的
1.设计和开发一个小型的二维绘图系统
2.通过对计算机图形学中 DDA、中点、Bresenham 算法、椭圆以及 Bezier 曲线等
生成算法的理解,完成直线、椭圆、圆的实现;
3.通过图形软件的设计开发和上机实验,巩固所学图形学基本知识
4.掌握交互式图形系统的设计方法
5.熟悉并掌握有关图形图象编程语言、工具和类库的使用。
二.实验环境
Windows 7
Microsoft Visual C++ 6.0 以及 MFC
三.实验内容
1.绘制直线
(1)DDA 算法的原理
DDA 算法是根据直线的微分方程来计算Δx或Δy生成直线的扫描转换算法。
在一个坐标轴上以单位间隔对线段取样, 以决定另一个坐标轴方向上最靠近理
想线段的整数值。
设(x0, y0)为直线段的始点, (x1, y1)为直线段的终点, 且端点坐标均为整
数, 则直线的微分方程为
y
1
x
1
设|k|≤1, 则有
0
y
x
0
y
x
k
yi+1=kxi+1+b=k(xi+Δx)+b=yi+kΔx
上式表明, 若Δx=1, 则当 x每递增 1 时, y递增 k。 扫描转换开始时, 取直
线始点(x0, y0)作为初始坐标。
(2)中点法
算法的原理:
为了讨论的方便, 假定直线的斜率在 0~1 之间, 其它情况参照下述讨论进
行处理。 假设直线的起点和终点分别为(x0, y0)和(x1, y1), 则直线方程为
F(x, y)=ax+by+c=0
其中, a=y0-y1, b=x1-x0, c=x0y1-x1y0。对于直线上的点, F(x,y)=0; 对于直线上 方的
点, F(x,y)>0; 而对于直线下方的点, F(x,y)<0。 如图 3.1 所示, 若直线在 x 方向
上增加一个单位, 则在 y 方向上的增量只能在 0 和 1 之间。 假设横坐标为 xP 的
各像素点中最佳逼近于理想直线的像素为(xP,yP), 用实心小圆表示。 那么, 下一
个与直线最近的像素只能是正右方的 P1(xP+1,yP)或右上方的 P2(xP+1, yP+1)两者
之一, 用空心小圆表示。 我们用 P1 和 P2 的中点 M(xP+1, yP+0.5)与理想直线的位
置关系来判定。
中点画线示意图
(3)Bresenham 算法
算法的原理:
Bresenham 画线算法与中点画线法有相似之处, 也是通过在每列像素中确定
与理想直线最近的像素来进行直线的扫描转换的。 为了讨论的方便,不妨也假定
直线的斜率在 0~1 之间。 如图所示, 过各行、 各列像素中心构造一组虚拟网
格线, 按直线从起点到终点的顺序计算直线与各垂直网格线的交点, 然后确定该
列像素中与该交点最近的像素。
2.圆
算法的原理:
为了讨论的方便, 我们考虑中心在原点, 半径为 R 的圆的第二个八分圆弧,
圆的其它部分可通过一系列的简单的反射变换得到。 也就是讨论如何从(0, R)到
(R /
)顺时针确定最佳逼近于该圆弧的像素序列。
,R/
2
2
中心在原点, 半径为 R 的圆的方程为
x2+y2=R2
若令 F(x,y)=x2+y2-R2, 则上述方程为
F(x, y)=0
如图所示, 假定 x 坐标为 xP 的像素中最佳逼近理想圆弧的为 P(xP, yP), 那么,
下一个像素只能是正右方的 P1(xP+1, yP)或右下方的 P2(xP+1, yP-1)两者之一。 引
入 P1 和 P2 的中点 M(xP+1, yP-0.5), 当 M 在圆内时, 应取 P1(xP+1, yP)为下一个像
素, 否则, 应取 P2(xP+1, yP-1)为下一个像素。 为此, 构造判别式
d=F(M)=F(xP+1, yP-0.5)=(xP+1)2+(yP-0.5)2-R2
中点画圆法
若 d<0, 则应取 P1(xP+1, yP)为下一个像素, 而且再下一个像素的判别
式为
d′=F(xP+2, yP-0.5)=(xP+2)2+(yP-0.5)2-R2
=d+2xP+3
而 d≥0, 则应取 P2(xP+1, yP-1)为下一个像素, 而且再下一个像素
判别式为
d′=F(xP+2, yP-1.5)=(xP+2)2+(yP-1.5)2-R2
由于第一个像素是(0,R), 因而 d 的初始值为
d0=F(1, R-0.5)=1.25-R
=d+2(xP-yP)+5
3.椭圆
算法的原理:
中点画圆法可以推广到一般二次曲线的生成, 下面以中心在原点的标准椭
圆的扫描转换为例说明。 设椭 圆的方程为
F(x,y)=b2x2+a2y2-a2b2=0
其中, a 为沿 x 轴方向的长半轴长度, b 为 y 轴方向的短半轴长度, a、b 均为整数。
不失一般性, 我们只讨论第一象限椭圆弧的生成。需要注意的是, 在处理这段椭
圆时, 必须以弧上斜率为-1 的点(即法向量两个分量相等的点)作为分界把它分为
上部分和下部分, 如图所示。
第一象限的椭圆弧
该椭圆上一点(x, y)处的法向量为
,(
yxN
)
F
x
i
F
y
j
2
2
xib
2
2
yia
其中, i 和 j 分别为沿 x 轴和 y 轴方向的单位向量。 从图 3.6 可看出, 在上
分, 法向量的 y 分量更大, 而在下部分, 法向量的 x 分量更大, 因而, 在上部
若当前最佳逼近理想椭圆弧的像素(xP,yP)满足下列不等式
b2(xP+1)<a2(yP-0.5)
而确定的下一个像素不满足上述不等式, 则表明椭圆弧从上部分转入下部分。
在上部分, 假设横坐标为 xP 的像素中与椭圆弧更接近点是(xP, yP), 那么下一对候
选像素的中点是(xP+1, yP-0.5)。 因此判别式为
d1=F(xP+1, yP-0.5)=b2(xP+1)2+a2(yP-0.5)2-a2b2
若 d1<0, 中点在椭圆内, 则应取正右方像素, 且判别式应更新为
d′1=F(xP+2, yP-0.5)=b2(xP+2)2+a2(yP-0.5)2-a2b2
=d1+b2(2xP+3)
当 d1≥0, 中点在椭圆之外, 这时应取右下方像素, 并且更新判别式为
d′1=F(xP+2, yP-1.5)=b2(xP+2)2+a2(yP-1.5)2-a2b2
=d1+b2(2xP+3)+a2(-2yP+2)
由于弧起点为(0, b), 因此, 第一中点是(1, b-0.5), 对应的判别式是
d10=F(1, b-0.5)=b2+a2(b-0.5)2-a2b2
=b2+a2(-b+0.25)
在下部分, 应改为从正下方和右下方两个像素中选择下一像素。 如果在上部分
所选择的最后一像素是(xP, yP), 则下部分的中点判别式 d2 的初始值为
d20=F(xP+0.5, yP-1)=b2(xP+0.5)2+a2(yP-1)2-a2b2
d2 在正下方向与右下方向的增量计算与上部分类似, 这里不
重复论述。下部分弧的终止条件是 y=0。
直线,矩形,椭圆,Bezier 曲线,橡皮筋线以及颜色源程序代码如下:
// ZUOYEView.cpp : implementation of the CZUOYEView class
//
#include "stdafx.h"
#include "ZUOYE.h"
#include "ZUOYEDoc.h"
#include "ZUOYEView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CZUOYEView
IMPLEMENT_DYNCREATE(CZUOYEView, CView)
BEGIN_MESSAGE_MAP(CZUOYEView, CView)
//{{AFX_MSG_MAP(CZUOYEView)
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_COMMAND(IDR_Line, OnLine)
ON_UPDATE_COMMAND_UI(IDR_Line, OnUpdateLine)
ON_COMMAND(IDR_Rectangle, OnRectangle)
ON_UPDATE_COMMAND_UI(IDR_Rectangle, OnUpdateRectangle)
ON_COMMAND(IDR_Red, OnRed)
ON_UPDATE_COMMAND_UI(IDR_Red, OnUpdateRed)
ON_COMMAND(IDR_Adjust, OnAdjust)
ON_UPDATE_COMMAND_UI(IDR_Adjust, OnUpdateAdjust)
ON_COMMAND(IDR_Circle, OnCircle)
ON_UPDATE_COMMAND_UI(IDR_Circle, OnUpdateCircle)
ON_COMMAND(IDR_BLSELINE, OnBlseline)
ON_UPDATE_COMMAND_UI(IDR_BLSELINE, OnUpdateBlseline)
ON_WM_MOUSEMOVE()
ON_COMMAND(IDR_ONLine, OnONLine)
ON_UPDATE_COMMAND_UI(IDR_ONLine, OnUpdateONLine)
ON_COMMAND(IDR_Clean, OnClean)
ON_UPDATE_COMMAND_UI(IDR_Clean, OnUpdateClean)
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW,
CView::OnFilePrintPreview)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CZUOYEView construction/destruction
CZUOYEView::CZUOYEView()
{
// TODO: add construction code here
JW=0;
m_fstart=m_fend=0;
m_fbLine=m_fbRect=m_fbe=Selecting=m_fbflag=m_fbflag1=m_fbflag2=m_fbfl
ag3=0;
m_fgreen=m_fwhite=m_fred=m_fblue=m_fblack=0;
m_fblseLine=0;
m_fbclean=0;
m_bONLine=0;
M_f=m_fb=0;
Flag=0;
}
CZUOYEView::~CZUOYEView()
{
}
BOOL CZUOYEView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
//
the CREATESTRUCT cs
return CView::PreCreateWindow(cs);
}
/////////////////////////////////////////////////////////////////////////////
// CZUOYEView drawing
void CZUOYEView::OnDraw(CDC* pDC)
{
CZUOYEDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
}
/////////////////////////////////////////////////////////////////////////////
// CZUOYEView printing
BOOL CZUOYEView::OnPreparePrinting(CPrintInfo* pInfo)
{