基于MFC框架的OpenGL绘图:填充

基于MFC框架的OpenGL绘图:填充,第1张

文章目录
  • 效果图
  • 一、准备工作
    • 1. 新增菜单栏选项卡
    • 2. 切换Display()函数的绘图模式
  • 二、填充思路
    • 1. 二维数组保存填充位置
    • 2. 绘制画面
    • 3. 种子填充
  • 总结


效果图

对小学生来说还是太幼稚了,但对于大学生刚刚好的画风。


一、准备工作 1. 新增菜单栏选项卡


2. 切换Display()函数的绘图模式

加入全局变量type,用它来控制绘图模式

int type;

在刚给菜单栏建的响应消息里更改它

// 画轮廓线模式
void CMy22uCGv1View::OnFillDrawLine()
{
	type = 2;
	InitGridPoints();
}

// 填充模式
void CMy22uCGv1View::OnFillSeed()
{
	type = 3;
}

// 清空画面
void CMy22uCGv1View::OnFillClear()
{
	type = 0;
	InitGridPoints();
	Invalidate(false);
}

在Display函数里用switch(type)的方式切换模式

void CMy22uCGv1View::Display()
{
	// 清空屏幕
	glClearColor(0.2f, 0.2f, 0.2f,1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	// 画网格
	DrawGrids();

	// 选择模式
	switch (type) {
	case 1:
		DrawLines();
		break;
	case 2:
		// 画轮廓线模式
		DrawPoints();
		break;
	case 3:
		// 填充模式
		SeedFill();
		break;
	default:
		break;
	}
}
二、填充思路 1. 二维数组保存填充位置

给它来个初始化

// 在按键响应消息中调用
void CMy22uCGv1View::InitGridPoints()
{
	// 这是俩全局变量
	WIDTH = 0;
	HEIGHT = 0;
	
	CRect rc;
	GetClientRect(rc);
	float width = rc.right - rc.left;
	float height = rc.bottom - rc.top;
	float wNum = (int)(width / height * 10) / 10.f;

	for (float i = -wNum; i < wNum + 0.0001f; i += GRIDGAP)
	{
		WIDTH++;
	}
	for (float i = -1; i < 1.0001f; i += GRIDGAP)
	{
		HEIGHT++;
	}
	WIDTH--;
	HEIGHT--;
	for (int i = 0; i < WIDTH; i++) {
		for (int j = 0; j < HEIGHT; j++) {
			// gridPoints数组用0,1,2表示空白、轮廓、填充部分
			gridPoints[i][j] = 0;
		}
	}
}
2. 绘制画面

又到了坐标转换时间……

void CMy22uCGv1View::DrawPoints()
{
	// 画轮廓线模式,保存绘制终点endPoint位置
	if (type == 2 && isDrawing) {
		// 使用上一篇文章里写的ChangePos函数将屏幕坐标转换为视口坐标,然后再把它转成二维数组的xy
		int x = (int)(ChangePos(endPoint.x, 1) / GRIDGAP + WIDTH * 0.5f);
		int y = (int)(ChangePos(endPoint.y, 2) / GRIDGAP + HEIGHT * 0.5f);
		y = HEIGHT - 1 - y;

		if (gridPoints[x][y] == 0) {
			gridPoints[x][y] = 1;
		}
	}
	
	// 绘制画面
	for (int i = 0; i < WIDTH; i++) {
		for (int j = 0; j < HEIGHT; j++) {
			if (gridPoints[i][j] == 1 || gridPoints[i][j] == 2) {
				// 轮廓颜色是白色,填充颜色是浅蓝色
				if (gridPoints[i][j] == 1)
					glColor3f(0.95f, 0.95f, 0.95f);
				else
					glColor3f(0.6f, 0.8f, 1.0f);
					
				// 将二维数组的xy转成视口坐标后,绘制
				DrawSquare(i * GRIDGAP - GRIDGAP * WIDTH * 0.5f, (HEIGHT - 1 - j) * GRIDGAP - GRIDGAP * HEIGHT * 0.5f);
			}
		}
	}
}

画正方形函数长这样

void CMy22uCGv1View::DrawSquare(float x,float y)
// 直接用正方形覆盖网格
{
	glBegin(GL_POLYGON);
	glVertex2f(x, y);
	glVertex2f(x + GRIDGAP, y);
	glVertex2f(x + GRIDGAP, y + GRIDGAP);
	glVertex2f(x, y + GRIDGAP);
	glEnd();
}

现在的效果就是可以随便画画了,但还不能填充

3. 种子填充

朝着4个或8个方向不停扩散的广度优先搜索,或许该用队列+结构体实现,但最后并没有改成那样……总之能跑就行

void CMy22uCGv1View::SeedFill()
{
	// 鼠标抬起时,isClick会置为true
	if (isClick) {
		isClick = false;
		// 将鼠标坐标转换为视口坐标后,转成二维数组的xy
		int x = (int)(ChangePos(endPoint.x, 1) / GRIDGAP + WIDTH * 0.5f);
		int y = (int)(ChangePos(endPoint.y, 2) / GRIDGAP + HEIGHT * 0.5f);
		y = HEIGHT - 1 - y;
		
		// canFillPoints是个和gridPoints同样大的二维数组,用来判断当前位置是否遍历过
		memset(canFillPoints, true, sizeof(canFillPoints));
		bool canDraw = true;

		// queue是一款我的队列
		int head = 0, tail = 0;
		queue[tail][0] = x;
		queue[tail][1] = y;
		tail++;
		canFillPoints[x][y] = false;

		while (head < tail) {
			int nx = queue[head][0];
			int ny = queue[head][1];

			// 要求轮廓线必须是一个闭合图形,否则填充失败
			if (nx < 0 || nx >= WIDTH || ny < 0 || ny >= HEIGHT || gridPoints[nx][ny] != 0) {
				CString strDlg;
				strDlg.Format(L"填充范围不在笔刷范围内");
				MessageBox(strDlg);
				canDraw = false;
				break;
			}

			// 四连通
			// 空白区域 并且 没有被遍历过
			if (gridPoints[nx - 1][ny] == 0 && canFillPoints[nx - 1][ny]) {
				// 将该位置标记为已遍历,并入队
				canFillPoints[nx - 1][ny] = false;
				queue[tail][0] = nx - 1;
				queue[tail][1] = ny;
				tail++;
			}
			if (gridPoints[nx + 1][ny] == 0 && canFillPoints[nx + 1][ny]) {
				canFillPoints[nx + 1][ny] = false;
				queue[tail][0] = nx + 1;
				queue[tail][1] = ny;
				tail++;
			}
			if (gridPoints[nx][ny - 1] == 0 && canFillPoints[nx][ny - 1]) {
				canFillPoints[nx][ny - 1] = false;
				queue[tail][0] = nx;
				queue[tail][1] = ny - 1;
				tail++;
			}
			if (gridPoints[nx][ny + 1] == 0 && canFillPoints[nx][ny + 1]) {
				canFillPoints[nx][ny + 1] = false;
				queue[tail][0] = nx;
				queue[tail][1] = ny + 1;
				tail++;
			}
			head++;
		}
		
		// 当前区域可填充,把它实际填上
		if (canDraw) {
			head = 0;
			while (head < tail) {
				int nx = queue[head][0];
				int ny = queue[head][1];
				gridPoints[nx][ny] = 2;
				head++;
			}
		}
	}
	
	// 无论是否更改gridPoints二维数组都要调用该函数绘图
	DrawPoints();
}

现在就实现填充效果了


总结

我总结了如下三点…

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/792818.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-05
下一篇 2022-05-05

发表评论

登录后才能评论

评论列表(0条)

保存