【计算机图形学入门】MFC项目模拟像素点多边形扫描线填充法填充多边形(附源码)

本文详细介绍了如何在VisualStudio2019环境中使用MFC创建一个桌面程序,实现自定义网格中的多边形绘制与填充功能,包括创建项目、设置UI、编写代码(涉及Edge类和扫描线填充算法)以及设置填充颜色。
摘要由CSDN通过智能技术生成

一、前言

1.1.开发环境:Visual Studio 2019

1.2.开发准备:

【Visual Studio 2019】创建 MFC 桌面程序 ( 安装 MFC 开发组件 | 创建 MFC 应用 | MFC 应用窗口编辑 | 为按钮添加点击事件 | 修改按钮文字 | 打开应用 )

1.3.题目要求:

在自定义的网格像素环境下,指定多边形,指定填充颜色,实现类似下图的填充效果。

图1-1

二、创建项目

2.1.创建MFC项目

图2-1

2.2.项目命名、存储位置

图2-2

2.3.应用程序类型选择

图2-3

点击完成以后项目就创建成功了。

三、编写代码

3.1.设置UI界面

点击右下角资源视图,选择Menu下的IDR_MAINFRAME,进入页面。如果右侧没有资源视图选项卡,设置方式参考我的另一篇博客(绘制直线和圆)的图3-2。

设置好菜单,注意右下角我标注的地方,这里显示菜单项的ID,每个不同的ID对应着该菜单项的响应函数。

图3-1

接下来在打开类视图,右击View类,选择类向导,在命令中找到刚才添加的菜单项对应的ID,双击为它们添加函数框架。

图3-2

3.2.绘制多边形

用Vector容器存储多边形顶点信息,在鼠标点击左键时,先判断多边形是否绘制完成,这里设置一个bool类型的成员变量判断,若绘制完成多边形则退出函数,若没有绘制完成,则不退出,然后判断是否为第一个顶点,如果是,则直接存入Vector容器,不链接顶点,但如果不是,则连接顶点,将点信息存入容器,绘制完成时点击鼠标右键,首先判断多边形是否绘制完成,若绘制完成多边形则退出函数,否则判断顶点数是否少于3个,若大于等于3个,则将第一个点和最后一个相连。

在view类中使用类导向添加点击左键处理程序和点击右键处理程序,在消息中选择鼠标消息,双击左右键处理程序,添加后确定。

图3-3

在点击左键处理程序中添加以下代码

void CMFCApplication12View::OnLButtonDown(UINT nFlags, CPoint point)
{

	if (IsCompleted == TRUE)
	{
		return;
	}
	else
	{
		// 如果是第一个点,存入points数组中
		if (points.empty())
		{
			points.push_back(point);
		}
		// 若不是第一个点,则连接当前点和上一个点
		else
		{
			CDC* pDC = GetDC();

			// 获取points数组的最后一个point(就是上一个点),并将当前绘图位置移动到该点
			//MidLine(pDC, points[points.size() - 1].x, points[points.size() - 1].y, point.x, point.y);
			pDC->MoveTo(points[points.size() - 1]);
			// 连接上一个点和当前点
			pDC->LineTo(point);
			points.push_back(point); // 将当前点加入数组
			ReleaseDC(pDC);
		}
	}
	CView::OnLButtonDown(nFlags, point);
}

在点击右键函数中添加以下代码

void CMFCApplication12View::OnRButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	//m_iPolyDotNumbers = 0;
	if (IsCompleted == TRUE)
	{
		return;
	}

	else
	{
		// 如果前面少于3个点,则无法连接成为多边形,弹出错误信息
		if (points.size() < 3) {
			CString message = _T("顶点数量少于3,无法绘制多边形");
			CString caption = _T("多边形绘制错误");
			MessageBox(message, caption, MB_ICONERROR);
		}

		// 若points已存储有2个以上的顶点,则连接上一个点和第一个点
		else
		{
			CDC* pDC = GetDC();
			// 连接第一个点和当前点
			pDC->MoveTo(points[points.size() - 1]);
			pDC->LineTo(points[0]);
			ReleaseDC(pDC);

			// 成功消息
			CString message = _T("多边形绘制完毕");
			CString caption = _T("多边形绘制完毕");
			MessageBox(message, caption, MB_OK);

			IsCompleted = TRUE;
		}
	}
	CView::OnRButtonDown(nFlags, point);
}

3.3多边形扫描法

添加Edge类,存储边信息。以下是.h文件,.cpp文件不需要修改。

#pragma once
class Edge
{
public:
    float x_lower; // 扫描线与边的交点x值(初始值为线段下端点x值)
    float dx; // 斜率倒数
    float y_upper; // 线段上端点y值
    // 构造函数
    Edge() : x_lower(0.0), dx(0.0), y_upper(0.0) {}
    Edge(float x_val, float dx_val, float y_val);
};

获取边表。首先定义Edge类型的Vector容器,然后定义map容器,key为多边形边的最低点y值,value为Vector<Edge>,其中包括x_lower,扫描线与边的交点x值(初始值为线段下端点x值),float dx斜率倒数,float y_upper线段上端点y值。遍历每个顶点,建立边表信息,代码如下。

void CMFCApplication12View::getET()
{
	// TODO: 在此处添加实现代码.
	for (int i = 1; i <= points.size(); i++)
	{
		float dx;
		float x_lower, y_lower, y_upper;

		// points中最后一个点和第一个点的连线
		if (i == points.size())
		{
			if (points[i - 1].x == points[0].x)
			{
				continue; // 边平行于x轴,不处理
			}
			else if (points[i - 1].y == points[0].y)
			{
				dx = 0.0;//平行y轴的点
			}
			else
			{
				dx = (points[i - 1].x - points[0].x) * 1.0 / (points[i - 1].y - points[0].y);//斜率的倒数
			}

			if (points[0].y <= points[i - 1].y)//确定最后一条边的最高点等
			{
				y_lower = points[0].y;
				y_upper = points[i - 1].y;
				x_lower = points[0].x;
			}
			else
			{
				y_lower = points[i - 1].y;
				y_upper = points[0].y;
				x_lower = points[i - 1].x;
			}
			// 插入到Edge边信息中
			Edge e(x_lower, dx, y_upper);
			// 插入到ET表中
			ET[y_lower].push_back(e);//map会对边信息根据y_lower由低到高排序
		}

		else//不是最后一条边
		{
			if (points[i - 1].x == points[i].x)
			{
				continue; // 边平行于x轴,不处理
			}
			else if (points[i - 1].y == points[i].y)//绘制图形中有平行y轴的线
			{
				dx = 0;
			}
			else
			{
				dx = (points[i - 1].x - points[i].x) * 1.0 / (points[i - 1].y - points[i].y);//b/a
			}

			// 线段上端点的x,y值
			if (points[i - 1].y <= points[i].y)//确定边的最高点等
			{
				y_lower = points[i - 1].y;
				y_upper = points[i].y;
				x_lower = points[i - 1].x;
			}
			else
			{
				y_lower = points[i].y;
				y_upper = points[i - 1].y;
				x_lower = points[i].x;
			}

			// 计算多边形的最高点y值
			if (y_upper >= YMAX)
			{
				YMAX = y_upper;
			}

			// 插入到Edge边信息中
			Edge e(x_lower, dx, y_upper);
			// 插入到ET表中
			ET[y_lower].push_back(e);//map会对边信息根据y_lower由低到高排序
		}
	}
}

建立边表后,从多边形的最高点到最低点,扫描线扫描,更新AET表,判断有哪些新的边加入,若扫描线大于等于边的最低点y值,则需要加入新边;有哪些边要删除,若扫描线大于等于边的最高点的y值,则需要删除该边。在扫描过程中将扫描线与边的交点记录下来,并对每次扫描线与边的交点进行从小到大排序,以保证填充正确,填充完毕后清除记录的交点,进行下一次扫描。代码如下。

void CMFCApplication12View::PolygonFill(COLORREF color)
{
	// TODO: 在此处添加实现代码.
	// 获取ET表
	getET();

	// 从ymin到ymax进行扫描,将y初始化为ymin
	float y = ET.begin()->first;

	auto it_ET = ET.begin(); // AET初始化为ET表中ymin映射的边
	AET = (it_ET++)->second;

	while (y < YMAX)
	{
		// 移除已经超出扫描线的边
		std::vector<Edge> newAET;
		for (auto it = AET.begin(); it != AET.end(); ++it) {
			if (it->y_upper > y) {
				newAET.push_back(*it);
			}
		}
		AET = newAET;

		//检查是否有新的边加入
		if (it_ET != ET.end() && y >= it_ET->first) {
			AET.insert(AET.begin(), it_ET->second.begin(), it_ET->second.end());
			it_ET++;
		}

		// 获取活动边与当前扫描线的所有交点
		auto it = AET.begin();
		while (it != AET.end())
		{
			float x1 = it->x_lower;
			it->x_lower += it->dx;
			it++;

			if (it != AET.end())
			{
				float x2 = it->x_lower;
				it->x_lower += it->dx;
				it++;

				// 扫描线遇到两条线相交的顶点时,不记录交点
				if (x1 != x2)
				{
					scanPoints.push_back(CPoint(x1, y));
					scanPoints.push_back(CPoint(x2, y));
				}
			}
		}

		// 对扫描到的所有交点进行升序排列
		std::sort(scanPoints.begin(), scanPoints.end(), compareCPoint);

		// 获取画板
		CDC* pDC = GetDC();
		// 成对绘制,i每次+2
		for (int i = 0; i < scanPoints.size(); i += 2)
		{
			set_pixel(scanPoints[i].x / 10, scanPoints[i].y / 10, scanPoints[i + 1].x / 10, scanPoints[i + 1].y / 10, color);
			Sleep(50);
		}
		ReleaseDC(pDC);

		// 当前扫描线已绘制完毕,清空交点,继续扫描
		scanPoints.clear();
		y += 1.0;
	}
}
bool CMFCApplication12View::compareCPoint(const CPoint& a, const CPoint& b)
{
	return a.x < b.x;
}

3.4设置任意颜色

在MFC项目中有系统自带的自定义颜色窗口,添加一个成员变量color1。在3.1中,我们为菜单项“设置颜色”添加了处理函数的框架,在这个框架中添加代码,代码如下。

void CMFCApplication12View::SetfillColor()
{
	// TODO: 在此添加命令处理程序代码
	// TODO: 在此处添加实现代码.
	CColorDialog dlg(RGB(0, 0, 0), CC_RGBINIT | CC_FULLOPEN);

	if (dlg.DoModal() == IDOK)
	{
		// 获取用户选择的颜色  
		color1 = dlg.GetColor();
	}
}

在3.1中,我们为菜单项“填充”添加了处理函数的框架,在这个框架中添加代码,代码如下。

void CMFCApplication12View::fill_object()
{
	// TODO: 在此添加命令处理程序代码
	PolygonFill(color1);
}

四、结果展示

五、总结与源码

参考博客:计算机图形学实验——利用MFC对话框实现多边形绘制与填充(扫描线填充算法)


源码链接:

http://链接:https://pan.baidu.com/s/1om791RVqTYM0a0cKglrd4Q

提取码:f05p

计算机图形学初学者,如有错误请指正。
 

  • 34
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值