一、实验目的
1、进一步熟悉CDC图形程序库;
2、掌握直线的中点Bresenham生成算法、数值微分直线生成算法;
3、掌握圆的中点Bresenham生成算法;
4、掌握椭圆的中点Bresenham生成算法。
二、实验性质
验证性
三、实验要求
1、认真阅读本次实验的目的,清楚本次实验要求和实验内容;
2、能够根据实验指导书的要求,独立完成相关的内容;
3、根据直线的中点Bresenham生成算法和数值微分生成算法,完成任意直线的绘制。
4、根据圆的中点Bresenham生成算法思想和圆的对称性,完成圆的绘制;
5、根据椭圆的中点Bresenham生成算法思想和椭圆的对称性, 完成椭圆的绘制。
四、实验内容
1、根据中点Bresenham直线生成算法思想,完成任意斜率直线的绘制。
(1) 创建一个新的MFC项目,如下图所示。
(2) 在view.h头文件中添加起点p0和终点p1,以及基于中点Bresenham的直线绘制函数void DrawLine_MB(CDC *pDC, CPoint p0, CPoint p1);
protected:
CPoint p0; // 定义直线的起点
CPoint p1; // 定义直线的终点
void DrawLine_MB(CDC *pDC, CPoint p0, CPoint p1);
//void DrawLine_DDA(CDC *pDC, CPoint p0, CPoint p1);
(3) 在菜单“项目”中点击“类向导”,如下图所示。
(4) 在弹出的“MFC类向导”对话框中,在“类名”中选择“CView”类,在“消息”选项卡中选择“WM_LBUTTONDOWN”,然后点击“添加处理程序(A)…”,如下图所示。
(5) 类似地,在“MFC类向导”对话框中,在“类名”中选择“CView”类,在“消息”选项卡中选择“WM_LBUTTONUP”,然后点击“添加处理程序(A)…”。
(6) 在CView.cpp文件中void CExp2View::OnLButtonDown(UINT nFlags, CPoint point)函数添加代码,获得直线的起点坐标。
void CExp2View::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CRect rect; //定义矩形
GetClientRect(&rect); //获得客户区矩形的大小
p0.x=point.x; //将当前鼠标按下的点作为起点坐标
p0.y=point.y;
p0.x=p0.x-rect.Width()/2; //设备坐标系向自定义坐标系转换
p0.y=rect.Height()/2-p0.y;
CView::OnLButtonDown(nFlags, point);
}
(7) 在CView.cpp文件中void CExp2View::OnLButtonUP(UINT nFlags, CPoint point)函数添加代码,获得直线的终点坐标,并绘制直线。
void CExp2View::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
p1.x=point.x; //将当前释放鼠标的点作为终点
p1.y=point.y;
CRect rect; //定义矩形
GetClientRect(&rect); //获得客户区矩形的大小
CDC *pDC=GetDC(); //定义设备上下文指针
pDC->SetMapMode(MM_ANISOTROPIC); //自定义坐标系
pDC->SetWindowExt(rect.Width(),rect.Height()); //设置窗口比例
pDC->SetViewportExt(rect.Width(),-rect.Height()); //设置视区x轴水平向右,y轴垂直向上
pDC->SetViewportOrg(rect.Width()/2,rect.Height()/2); //设置客户区中心为坐标系原点
p1.x=p1.x-rect.Width()/2; //原始设备坐标系向自定义坐标系转换
p1.y=rect.Height()/2-p1.y;
DrawLine_MB(pDC, p0, p1); //采用中点Bresenham直线生成算法绘制直线
ReleaseDC(pDC);
CView::OnLButtonUp(nFlags, point);
}
(8) 在CView.cpp文件中void CExp2View::DrawLine_MB(CDC *pDC, CPoint p0, CPoint p1)函数中添加代码,实现中点Bresenham直线生成算法。
void Ctest1View::DrawLine_MB(CDC* pDC, CPoint p0, CPoint p1)
{
CPoint t, p;
int dx = p1.x - p0.x;
int dy = p1.y - p0.y;
double dc = 1 / (double)dx;
if (abs(p0.x - p1.x) == 0)//绘制垂直线
{
if (p0.y > p1.y) //交换顶点,使得起始点低于终点
{
t = p0; p0 = p1; p1 = t;
}
for (p = p0; p.y < p1.y; p.y++)
{
double r, g, b;
r = (1 - (p.y - p0.y)), g = 0, b = (p.y - p0.y);
pDC->SetPixelV(p, RGB(r * 255, g * 255, b * 255));
//pDC->SetPixelV(p.x, p.y, RGB(0, 0, 0));
}
}
else
{
double k, d;
k = double(p1.y - p0.y) / double(p1.x - p0.x);
if (0.0 <= k && k <= 1.0) //绘制0<=k<=1 的直线
{
if (p0.x > p1.x) //交换顶点
{
t = p0; p0 = p1; p1 = t;
}
d = 0.5 - k; //初始中点误差值
for (p = p0; p.x < p1.x; p.x++)
{
//请在这里添加代码,描绘直线上的像素点
double r, g, b;
r = (1 - (p.x - p0.x) * dc), g = 0, b = (p.x - p0.x) * dc;
pDC->SetPixelV(p, RGB(r * 255, g * 255, b * 255));
//pDC->SetPixelV(p.x, p.y, RGB(0, 0, 0));
if (d < 0) {
p.y++;
d++;
}
else {
d = d - k;
}
}
}
else if (k > 1.0) //绘制k>1 的直线
{
if (p0.y > p1.y) //交换顶点
{
t = p0; p0 = p1; p1 = t;
}
d = 1 - 0.5 * k; //初始中点误差值
for (p = p0; p.y < p1.y; p.y++)
{
//请在这里添加代码,描绘直线上的像素点
double r, g, b;
r = (1 - (p.x - p0.x) * dc), g = 0, b = (p.x - p0.x) * dc;
pDC->SetPixelV(p, RGB(r * 255, g * 255, b * 255));
//pDC->SetPixelV(p.x, p.y, RGB(0, 0, 0));
if (d < 0) {
d = d + 1;
}
else {
p.x++;
d = d + 1 - k;
}
}
}
else if (k >= -1.0 && k < 0.0)//绘制-1<=k<0的直线
{
if (p0.x > p1.x) //交换顶点
{
t = p0; p0 = p1; p1 = t;
}
d = -0.5 - k;
for (p = p0; p.x < p1.x; p.x++)
{ //描绘直线上的像素点
double r, g, b;
r = (1 - (p.x - p0.x) * dc), g = 0, b = (p.x - p0.x) * dc;
pDC->SetPixelV(p, RGB(r * 255, g * 255, b * 255));
//pDC->SetPixelV(p.x, p.y, RGB(0, 0, 0));
if (d > 0)
{
p.y--;
d -= 1 + k;
}
else
d -= k;
}
}
else if (k < -1.0)//绘制k<-1的直线
{
if (p0.y < p1.y) //交换顶点
{
t = p0; p0 = p1; p1 = t;
}
d = -1 - 0.5 * k;
for (p = p0; p.y > p1.y; p.y--)
{ //描绘直线上的像素点
double r, g, b;
r = (1 - (p.x - p0.x) * dc), g = 0, b = (p.x - p0.x) * dc;
pDC->SetPixelV(p, RGB(r * 255, g * 255, b * 255));
//pDC->SetPixelV(p.x, p.y, RGB(0, 0, 0));
if (d < 0)
{
p.x++;
d -= 1 + k;
}
else
d -= 1;
}
}
}
p0 = p1;
}
(9)运行该项目,并可通过先按下鼠标左键、再释放鼠标左键,绘制任意斜率的直线,如下图所示。
实际运行结果:
2、根据数值微分直线生成算法思想, 完成如上图所示的任意斜率直线的绘制。
(1)类似以上步骤,在view.h头文件中添加基于数值微分DDA的直线绘制函数DrawLine_DDA();
(2)在CView.cpp文件中void CExp2View::DrawLine_DDA(CDC *pDC, CPoint p0, CPoint p1)函数中添加代码,实现数值微分DDA直线生成算法。
void Ctest1View::DrawLine_DDA(CDC* pDC, CPoint p0, CPoint p1)
{
CPoint t, p;
if (abs(p0.x - p1.x) == 0)//绘制垂直线
{
if (p0.y > p1.y) //交换顶点,使得起始点低于终点
{
t = p0; p0 = p1; p1 = t;
}
for (p = p0; p.y < p1.y; p.y++)
{
pDC->SetPixelV(p.x, p.y, RGB(255, 0, 0));
}
}
else
{
double k;
k = double(p1.y - p0.y) / double(p1.x - p0.x);
if ((0.0 <= k && k <= 1.0) || (k >= -1.0 && k < 0.0)) //绘制0<=k<=1 或-1<=k<0的直线
{
if (p0.x > p1.x) //交换端点
{
t = p0; p0 = p1; p1 = t;
}
double y = p0.y;
for (p = p0; p.x < p1.x; p.x++) //以x为主方向进行移动
{
//请在这里添加代码,描绘直线上的像素点
pDC->SetPixelV(p.x, p.y, RGB(255, 0, 0));
y = y + k;
if (y >= 0) {
p.y = int(y + 0.5);
}
else {
p.y = int(y - 0.5);
}
}
}
else if ((k > 1.0) || k < -1.0)//绘制k>1或k<-1的直线
{
if (p0.y > p1.y) //交换端点
{
t = p0; p0 = p1; p1 = t;
}
double x = p0.x;
for (p = p0; p.y < p1.y; p.y++) //以y为主方向进行移动
{
//请在这里添加代码,描绘直线上的像素点
pDC->SetPixelV(p.x, p.y, RGB(255, 0, 0));
x = x + 1 / k;
if (x >= 0) {
p.x = int(x + 0.5);
}
else {
p.x = int(x - 0.5);
}
}
}
}
}
(3)在CView.cpp文件中void CExp2View::OnLButtonUP(UINT nFlags, CPoint point)函数中调用DrawLine_DDA,即利用DDA生成直线算法绘制直线。
void Ctest1View::DrawLine_DDA(CDC* pDC, CPoint p0, CPoint p1)
{
CPoint t, p;
if (abs(p0.x - p1.x) == 0) // 绘制垂直线
{
if (p0.y > p1.y) // 交换顶点,使得起始点低于终点
{
t = p0; p0 = p1; p1 = t;
}
for (p = p0; p.y < p1.y; p.y++)
{
pDC->SetPixelV(p.x, p.y, RGB(255, 0, 0));
}
}
else
{
double k;
k = double(p1.y - p0.y) / double(p1.x - p0.x);
if ((0.0 <= k && k <= 1.0) || (k >= -1.0 && k < 0.0)) // 绘制0<=k<=1 或-1<=k<0的直线
{
if (p0.x > p1.x) // 交换端点
{
t = p0; p0 = p1; p1 = t;
}
double y = p0.y;
for (p = p0; p.x < p1.x; p.x++) // 以x为主方向进行移动
{
pDC->SetPixelV(p.x, round(y), RGB(255, 0, 0));
y = y + k;
}
}
else if ((k > 1.0) || k < -1.0) // 绘制k>1或k<-1的直线
{
if (p0.y > p1.y) // 交换端点
{
t = p0; p0 = p1; p1 = t;
}
double x = p0.x;
for (p = p0; p.y < p1.y; p.y++) // 以y为主方向进行移动
{
pDC->SetPixelV(round(x), p.y, RGB(255, 0, 0));
x = x + 1.0 / k;
}
}
}
}
3、根据圆的中点Bresenham生成算法思想和圆的对称性,完成圆的绘制。
(1) 创建一个新的MFC项目,如下图所示。
(2) 在view.h头文件中添加圆的某条直径的起点p0和终点p1,以及基于中点Bresenham的圆绘制函数void DrawCircle_MB(CDC *pDC, CPoint p0, CPoint p1);
protected:
CPoint p0; // 定义直径的起点
CPoint p1; // 定义直径的终点
void DrawCircle_MB(CDC *pDC, CPoint p0, CPoint p1);
(3) 在菜单“项目”中点击“类向导”,如下图所示。
(4) 在弹出的“MFC类向导”对话框中,在“类名”中选择“CView”类,在“消息”选项卡中选择“WM_LBUTTONDOWN”,然后点击“添加处理程序(A)…”,如下图所示。
(5) 类似地,在“MFC类向导”对话框中,在“类名”中选择“CView”类,在“消息”选项卡中选择“WM_LBUTTONUP”,然后点击“添加处理程序(A)…”。
(6) 在CView.cpp文件中void CExp3View::OnLButtonDown(UINT nFlags, CPoint point)函数添加代码,获得直径的起点坐标。
void CExp3View::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CRect rect; //定义矩形
GetClientRect(&rect); //获得客户区矩形的大小
p0.x=point.x; //将当前鼠标按下的点作为起点坐标
p0.y=point.y;
p0.x=p0.x-rect.Width()/2; //设备坐标系向自定义坐标系转换
p0.y=rect.Height()/2-p0.y;
CView::OnLButtonDown(nFlags, point);
}
(7) 在CView.cpp文件中void CExp3View::OnLButtonUP(UINT nFlags, CPoint point)函数添加代码,获得直径的终点坐标,并绘制圆。
void CExp3View::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
p1.x=point.x; //将当前释放鼠标的点作为终点
p1.y=point.y;
CRect rect; //定义矩形
GetClientRect(&rect); //获得客户区矩形的大小
CDC *pDC=GetDC(); //定义设备上下文指针
pDC->SetMapMode(MM_ANISOTROPIC); //自定义坐标系
pDC->SetWindowExt(rect.Width(),rect.Height()); //设置窗口比例
pDC->SetViewportExt(rect.Width(),-rect.Height()); //设置视区x轴水平向右,y轴垂直向上
pDC->SetViewportOrg(rect.Width()/2,rect.Height()/2); //设置客户区中心为坐标系原点
p1.x=p1.x-rect.Width()/2; //原始设备坐标系向自定义坐标系转换
p1.y=rect.Height()/2-p1.y;
DrawCircle_MB(pDC, p0, p1); //采用中点Bresenham直线生成算法绘制圆
ReleaseDC(pDC);
CView::OnLButtonUp(nFlags, point);
}
(8) 在CView.cpp文件中void CExp3View::DrawCircle_MB(CDC *pDC, CPoint p0, CPoint p1)函数中添加代码,实现圆的中点Bresenham生成算法。
void Ctest2View::DrawCircle_MB(CDC *pDC, CPoint p0, CPoint p1) //绘制圆的函数
{
CPoint center; //圆心
center.x = (p0.x + p1.x) / 2.0; center.y = (p0.y + p1.y) / 2.0; //计算圆心坐标
double r = sqrt(1.0*(p1.x - p0.x)*(p1.x - p0.x) + 1.0*(p1.y - p0.y)*(p1.y - p0.y)) / 2.0;//计算圆的半径
int x, y;
double d;
d = 1.25 - r;
x = 0;
y = (int)r;
while (x <= y)
{
// 绘制八分圆上的点
pDC->SetPixel(center.x + x, center.y + y, RGB(0, 0, 0));
pDC->SetPixel(center.x - x, center.y + y, RGB(0, 0, 0));
pDC->SetPixel(center.x + x, center.y - y, RGB(0, 0, 0));
pDC->SetPixel(center.x - x, center.y - y, RGB(0, 0, 0));
pDC->SetPixel(center.x + y, center.y + x, RGB(0, 0, 0));
pDC->SetPixel(center.x - y, center.y + x, RGB(0, 0, 0));
pDC->SetPixel(center.x + y, center.y - x, RGB(0, 0, 0));
pDC->SetPixel(center.x - y, center.y - x, RGB(0, 0, 0));
if (d < 0)
{
d += 2 * x + 3;
}
else
{
d += 2 * (x - y) + 5;
y--;
}
x++;
}
}
(9) 运行该项目,并可通过先按下鼠标左键、再释放鼠标左键,绘制任意大小的圆,如下图所示。
运行结果如图所示:
4、根据椭圆的中点Bresenham生成算法思想和椭圆的对称性,完成椭圆的绘制。
(1) 类似以上步骤,在view.h头文件中添加基于中点Bresenham的椭圆绘制函数void DrawEllipse_MB(CDC *pDC, CPoint p0, CPoint p1);以及绘画对称点函数 void EllipsePoint(int x, int y, CDC *pDC)
(2) 在CView.cpp文件中void CExp3View::EllipsePoint(int x, int y,CDC *pDC)函数中添加代码,根据点(x, y)描绘其他象限的对称点。
void Ctest2View::EllipsePoint(int x, int y,CDC *pDC) //四分法画椭圆子函数
{
CPoint center; //椭圆中心
center.x=(p0.x+p1.x)/2.0; center.y=(p0.y+p1.y)/2.0; //计算椭圆中心坐标
COLORREF clr=RGB(0,0,255); //定义椭圆颜色
pDC->SetPixelV(x+center.x,y+center.y,clr);
pDC->SetPixelV(-x+center.x,y+center.y,clr);
pDC->SetPixelV(x+center.x,-y+center.y,clr);
pDC->SetPixelV(-x+center.x,-y+center.y,clr);
}
(3) 在CView.cpp文件中void CExp3View::DrawEllipse_MB(CDC *pDC, CPoint p0, CPoint p1)函数中添加代码,实现椭圆的中点Bresenham生成算法。
void Ctest2View::DrawEllipse_MB(CDC *pDC, CPoint p0, CPoint p1) //绘制圆的函数
{
double a, b, d1, d2;
a = abs(p1.x - p0.x) / 2.0; //椭圆半轴
b = abs(p1.y - p0.y) / 2.0; //椭圆半轴
int x, y;
x = 0; y = b;
d1 = b * b + a * a * (-b + 0.25); //椭圆AC弧段的中点误差项初始值
EllipsePoint(x, y, pDC);
while (b * b * (x + 1) < a * a * (y - 0.5)) //椭圆AC弧段
{ //请在以下位置添加代码绘制椭圆AC弧段
if (d1 < 0) {
d1 += b * b * (2 * x + 3);
}
else
{
d1 += b * b * (x * 2 + 3) + a * a * (-2 * y + 2);
y--;
}
x++;
EllipsePoint(x, y, pDC);
}
d2 = b * b * (x + 0.5) * (x + 0.5) + a * a * (y - 1) * (y - 1) - a * a * b * b;//椭圆CB弧段的中点误差项初始值
while (y > 0) //椭圆CB弧段
{
if (d2 < 0)
{
d2 += b * b * (2 * x + 2) + a * a * (-2 * y + 3);
x++;
}
else
{
d2 += a * a * (-2 * y + 3);
}
y--;
EllipsePoint(x, y, pDC);
}
}
(4)在CView.cpp文件中void CExp3View::OnLButtonUP(UINT nFlags, CPoint point)函数中调用DrawEllipse_MB( ),即利用中点Bresenham生成算法绘制椭圆,效果如下图所示。
运行结果如图所示:
五、思考题
1、请问按下鼠标左键或释放鼠标左键时,所获得鼠标当前所指向的点坐标是以哪个坐标系为参考?
自己写
2、如何生成反走样(即抗锯齿)的直线? 请尝试编写代码。
自己写
3、假设两端点颜色分别为绿色和红色,如何生成颜色渐变的直线?请尝试编写代码。
自己写
4、绘制圆和绘制椭圆的算法区别是什么?
自己写
5、在第一象限的四分之一椭圆弧上,切线斜率等于-1的点坐标是多少?
自己写
6、在绘制第一象限的四分之一椭圆弧时,如何判断像素点从切线斜率|k|<1的上半部分转入切线斜率|k|>1的下半部分?
自己写
六、实验总结
自己写
暂无评论内容