一、前言
1.1.开发环境:Visual Studio 2019
1.2.开发准备:
【Visual Studio 2019】创建 MFC 桌面程序 ( 安装 MFC 开发组件 | 创建 MFC 应用 | MFC 应用窗口编辑 | 为按钮添加点击事件 | 修改按钮文字 | 打开应用 )
1.3.题目要求:
采用你学过的任意一种算法(DDA/中点法/Bresenham)绘制任意直线及(椭)圆。具体要求如下:
1.将象素网格表现出来,建立网格坐标系(如下图示例所示,或任意其他形式皆可以,只要能展现出一个个独立的像素点形象就行);
2.用橡皮筋的形式输入参数(仿照windows画板程序用鼠标拖动画线,鼠标左键按下位置表示直线段起始点,左键弹起位置为终止点);
3.鼠标移动时,显示鼠标当前位置。
图1-1
二、创建项目
2.1.创建MFC项目
图2-1
2.2.项目命名、存储位置
图2-2
2.3.应用程序类型选择
图2-3
点击完成以后项目就创建成功了。
三、编写代码
3.1.设置UI界面
点击右下角资源视图,选择Menu下的IDR_MAINFRAME,进入页面。如果右侧没有资源视图选项卡,请看图3-2。
图3-1
图3-2
接下来就可以设计自己的菜单了,双击“在此输入”一栏,可以添加自己想要的菜单选项,我这里设置了两个菜单,一个是选择图形,一个是选择颜色,选择图形可以绘制直线,圆,颜色只设置了红色和绿色。
图3-3
3.2代码编辑
3.2.1绘制网格
题目需要用网格模拟像素,因此在绘制图形之前先绘制网格,在CMFCxxxxView.cpp文件下(xxxx指自定义的项目名称),找到OnDraw函数,添加以下代码,绘制边长为10的网格。
void CMFCApplication2View::OnDraw(CDC *pDC)
{
CMFCApplication2Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: 在此处为本机数据添加绘制代码
for (int i = 0; i < 1000; i++)
{
pDC->MoveTo(0, i * 10);
pDC->LineTo(6000, i * 10);
}
for (int i = 0; i < 1000; i++) {
pDC->MoveTo(i * 10, 0);
pDC->LineTo(i * 10, 6000);
}
}
3.2.2像素填充
题目需要模拟算法选点,像素填充,接下来是像素填充算法,首先在类视图下,右CMFCxxxxVie类,选择添加->添加函数,步骤如图。
图3-4
图3-5
点击确定后,在set_pixel函数中添加如下代码。由于绘制的网格是边长为10的,为了更好的模拟像素点,所以用Rectangle函数绘制放大十倍的矩形填充。
void CMFCApplication2View::set_pixel(int x,int y,COLORREF color)
{
// TODO: 在此处添加实现代码.
CClientDC dc(this);
CBrush newbrush;
newbrush.CreateSolidBrush(color);
CBrush* oldbrush = dc.SelectObject(&newbrush);
dc.Rectangle(x*10 , y*10 , x*10-10, y*10-10);
dc.SelectObject(oldbrush);
return;
}
3.2.3绘制直线
绘制直线采用中点画线法。用图3-4、图3-5的方法添加函数MidPointLine(int x0, int y0, int x1, int y1, COLORREF color),代码如下。
由于上文set_pixel函数绘制放大十倍的矩形填充,会导致绘制的图形坐标以十倍的数量偏移,导致鼠标点击位置与图像呈现位置有巨大偏移量,所以在绘制直线,将选取的点填充时,调用set_pixel函数的实参都要缩小十倍,因此除以十。
void CMFCApplication2View::MidPointLine(int x0, int y0, int x1, int y1, COLORREF color)
{
// TODO: 在此处添加实现代码.
int a, b, deta1, deta2, d, x, y;
//没有斜率的时候直接绘制
if (x0 == x1)//垂直x轴的直线
{
if (y0 < y1)
{
for (int i = y0; i <= y1; i++)
{
set_pixel(x0/10, i/10, color);
}
}
else
{
for (int i = y1; i <= y0; i++)
{
set_pixel(x0/10, i/10, color);
}
}
return;
}
//m为真说明斜率绝对值小于等于1
BOOL m = (fabs(y1 - y0) <= fabs(x1 - x0));
//用户从起点在右
if (x0 > x1)
{
d = x0; x0 = x1; x1 = d;
d = y0; y0 = y1; y1 = d;
}
a = y0 - y1;
b = x1 - x0;
x = x0;
y = y0;
//k绝对值小于等于1
if (m)
{
//也就是k小于等于1大于0
if (y0 < y1)
{
d = 2 * a + b;
deta1 = 2 * a;
deta2 = 2 * (a + b);
while (x < x1)
{
if (d < 0)
{
x++;
y++;
d += deta2;
}
else {
x++;
d += deta1;
}
set_pixel(x/10, y/10, color);
}
}
//也就是k大于等于-1小于0
else
{
d = 2 * a - b;
deta1 = 2 * a;
deta2 = 2 * (a - b);
while (x < x1)
{
if (d < 0)
{
x++;
d += deta1;
}
else
{
x++;
y--;
d += deta2;
}
set_pixel(x/10, y/10, color);
}
}
}
//k绝对值大于等于1
else
{
//k大于等于1到正无穷
if (y0 < y1)
{
d = a + 2 * b;
deta1 = 2 * b;
deta2 = 2 * (a + b);
while (y < y1)
{
if (d < 0)
{
y++;
d += deta1;
}
else
{
y++;
x++;
d += deta2;
}
set_pixel(x/10, y/10, color);
//pDC->SetPixel(x, y, color);
}
}
//k大于等于-1到负无穷
else
{
d = a - 2 * b;
deta1 = -2 * b;
deta2 = 2 * (a - b);
while (y > y1)
{
if (d < 0)
{
y--;
x++;
d += deta2;
}
else
{
y--;
d += deta1;
}
set_pixel(x/10, y/10, color);
}
}
}
}
3.2.4绘制圆
类视图->右击CMFCxxxView类->添加->添加变量
图3-6
同样的方法添加圆上的点m_br,标志位Flag
图3-7
图3-8
添加函数,计算半径
int CMFCApplication4View::ComputeRadius(CPoint cenp, CPoint ardp)
{
// TODO: 在此处添加实现代码.
int dx = cenp.x - ardp.x;
int dy = cenp.y - ardp.y;
return (int)sqrt(dx * dx + dy * dy);
}
添加函数,绘制圆
void CMFCApplication4View::Midpoint_DrawCircle(CPoint O, int r, COLORREF color)
{
// TODO: 在此处添加实现代码.
CDC* pDC = GetDC();
int d0, x = 0, y = r;
d0 = 1.25 - r;
while (x < y)
{
if (d0 >= 0)
{
d0 = d0 + 2 * (x - y) + 5;
x += 1;
y -= 1;
set_pixel((x + O.x) / 10, (y + O.y) / 10, color); //(x,y)
set_pixel((-x + O.x) / 10, (y + O.y) / 10, color); //(-x,y)
set_pixel((y + O.x) / 10, (x + O.y) / 10, color); //(y,x)
set_pixel((-y + O.x) / 10, (x + O.y) / 10, color); //(-y,x)
set_pixel((x + O.x) / 10, (-y + O.y) / 10, color); //(x,-y)
set_pixel((-x + O.x) / 10, (-y + O.y) / 10, color); //(-x,-y)
set_pixel((y + O.x) / 10, (-x + O.y) / 10, color); //(y,-y)
set_pixel((-y + O.x) / 10, (-x + O.y) / 10, color); //(-y,-x)
Sleep(50);
}
else
{
d0 = d0 + 2 * x + 3;
x += 1;
y = y;
set_pixel((x + O.x) / 10, (y + O.y) / 10, color); //(x,y)
set_pixel((-x + O.x) / 10, (y + O.y) / 10, color); //(-x,y)
set_pixel((y + O.x) / 10, (x + O.y) / 10, color); //(y,x)
set_pixel((-y + O.x) / 10, (x + O.y) / 10, color); //(-y,x)
set_pixel((x + O.x) / 10, (-y + O.y) / 10, color); //(x,-y)
set_pixel((-x + O.x) / 10, (-y + O.y) / 10, color); //(-x,-y)
set_pixel((y + O.x) / 10, (-x + O.y) / 10, color); //(y,-y)
set_pixel((-y + O.x) / 10, (-x + O.y) / 10, color); //(-y,-x)
Sleep(50);
}
}
}
3.2.5 添加单击鼠标响应事件
再添加CPoint类型成员变量,和COLORREF类型成员变量。
图3-9
图3-10
类视图->右击CMFCxxxView类->类向导,添加两个鼠标消息,分别是何时按下左键,何时释放左键,添加后点击确定。
图3-11
在刚才添加的鼠标消息后,编译器自动生成两个函数框架,在内部添加以下代码。
void CMFCApplication4View::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if (Flag == 1)
{
z_beginMousePoint = point;
}
else if (Flag == 2)
{
CDC* pDC = GetDC();
pDC->SelectStockObject(NULL_BRUSH);
m_b0 = point; //纪录第一次单击鼠标位置,定圆心
ReleaseDC(pDC); //释放设备环境
CView::OnLButtonDown(nFlags, point);
}
CView::OnLButtonDown(nFlags, point);
}
void CMFCApplication4View::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if (Flag == 1)
{
MidPointLine(z_beginMousePoint.x, z_beginMousePoint.y, point.x, point.y, color);
}
else if (Flag == 2)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CDC* pDC = GetDC();
pDC->SelectStockObject(NULL_BRUSH);
m_br = point; //纪录松开鼠标左键位置,定半径
int r = ComputeRadius(m_b0, m_br);
Midpoint_DrawCircle(m_b0, r, color);
ReleaseDC(pDC); //释放设备环境
}
CView::OnLButtonUp(nFlags, point);
}
将菜单中的每个选项ID与函数关联,首先在资源视图打开设计好的菜单,双击菜单项,编译器右下角会显示菜单项的对应的ID,在CMFCxxxxView类中右击选择类向导,搜索查询到的菜单项对应ID,然后添加处理程序,步骤如图所示,ID和函数一定要一一对应。
图3-12
图3-13
之后,在编译器自动生成的这四个处理程序函数框架中添加以下代码。
void CMFCApplication4View::CreateLine()
{
// TODO: 在此添加命令处理程序代码
Flag = 1;
}
void CMFCApplication4View::CreateCircle()
{
// TODO: 在此添加命令处理程序代码
Flag = 2;
}
void CMFCApplication4View::SetColor_Green()
{
// TODO: 在此添加命令处理程序代码
color = RGB(100, 200, 100);
}
void CMFCApplication4View::SetColor_Red()
{
// TODO: 在此添加命令处理程序代码
color = RGB(250, 0, 0);
}
3.2.6 实时显示鼠标坐标
void CMFCApplication4View::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CString str;
str.Format(_T("[%d,%d]"), point.x, point.y);
GetParent()->GetDescendantWindow(AFX_IDW_STATUS_BAR)->SetWindowTextW(str);
CView::OnMouseMove(nFlags, point);
}
接下来就可以运行了!效果如下。
计算机图形学初学者,如有错误,请指正。
参考博客:
计算机图形学之MFC实现数值微分画线、中点画线、Bresenham画线
C语言——中点画圆算法和Bresenham画圆算法(easyx图形库)
计算机图形学MFC实验--画线、画圆、画椭圆、区域填充算法(C++)
完整代码: