- 效果图
- 一、准备工作
- 1. 新增菜单栏选项卡
- 2. 切换Display()函数的绘图模式
- 二、填充思路
- 1. 二维数组保存填充位置
- 2. 绘制画面
- 3. 种子填充
- 总结
效果图
对小学生来说还是太幼稚了,但对于大学生刚刚好的画风。
一、准备工作 1. 新增菜单栏选项卡
加入全局变量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();
}
现在的效果就是可以随便画画了,但还不能填充
朝着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();
}
现在就实现填充效果了
总结
我总结了如下三点…
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)