【计算机图形学】Boundary-Fill Algorithm


本博客基于课程"计算机图形学",教材使用为计算机图形学(第4版) [Computer Graphics with OpenGL, Fourth Edition],部分代码模板便来自于此教材,并且有所改动

实验思路

代码思路

  1. 变量介绍dcpt:点,用坐标表示,x和y成员分别表示x和y坐标;pointStruct:数据成员包含一个点和一个指向点变量的指针,即后面的栈是以链表的形式存储的,也便于实现递归;pHead指向链表的头指针(不可移动),p1相当于临时指针,也指向链表表头,但是可以移动,pTop为栈顶指针;构造函数:为头指针分配内存空间,并令p1指向和头指针同样内存位置,此时栈为空,pTop指向也是头指针所指,并且为了防止位置错误发生,此处三个指针的下一结点都需要指向NULL。析构函数:使用while循环,自栈顶依次释放每个点内存。pop:若成功删除栈顶点,返回true,否则返回falsepush:传入参数为一个点pt,先用p1申请新的内存空间,再将pt赋值给p1,再用栈顶指针指向p1同一内存空间;empty:用while循环清空栈,原理同析构函数;getTop:返回类型为一个点,返回栈顶变量的点成员;isEmpty:用while循环判断栈是否为空
  2. Boundaryfill()函数:传入参数为入栈的第一个点,栈不为空为while的执行条件。在执行while之前先把初始点入栈,并用dir二维数组表示为方向。进入while循环之后,用p1保存栈顶元素坐标,用来在后面for循环中实现点之间的方向搜索(类似于dfs),在for循环中,遍历四个点,并将符合条件的点入栈,退出for循环,继续while循环,出栈,然后填充颜色,再继续for循环。while循环完成退出后,颜色填充也就完成了

问题及解决方案

  1. MyStack.cpp中是CMyStack中各函数的实现。由于本人使用的开发环境是VSCode + GCC,而不是使用的 Visual Studio,所以不能联合编译,vscode不会生成makefile文件,所以MyStack.cpp中的代码不会被编译到main.cpp中,所以若读者也使用和本人相同的开发环境,需要手动把MyStack.cppMyStack.h中内容复制到main.cpp中。
  2. 代码中MyStack.cppMyStack.h配合main.cpp的用法,是c/c++工程中常用的使用方法。头文件(.h):写类的声明(包括类里面的成员和方法的声明)、函数原型、#define常数等,但一般来说不写出具体的实现。源文件(.cpp): 源文件主要写实现头文件中已经声明的那些函数的具体代码。需要注意的是,开头必须#include一下实现的头文件,以及要用到的头文件。例如:.h中的代码是一个宏定义在main.cpp中包含时,直接替换为#if !defined(AFX_MySTACK_H__3657DC2D_E518_4E5E_9A32_023B2A260ED7__INCLUDED_)#endif中的内容,#if !defined#pragma once两个宏的作用都是为了防止重定义。
  3. 为了保持良好的编程习惯,对应dir数组只访问而不修改,应直接定义为常量。
  4. 通过查阅,由于glReadPixels()函数性能较差,所以运行程序时会有卡顿,且占显存极高。在while循环中采用的时入栈一个点及其周围点后,便进行填充,而非将所有需要填充点入栈后再填充,是由于基于所给代码模板。若要使用类似于dfs递归的方式,需要额外设定记录数组,较为麻烦,不仅代码量增多,而且由于像素点较多,比较浪费内存,如果填充图形形状大小不固定,那还需要使用动态内存,便没有采用这个方式

实现代码

MyStack.h

// MyStack.h: interface for the MyStack class.
//
//

#ifndef AFX_MySTACK_H__3657DC2D_E518_4E5E_9A32_023B2A260ED7__INCLUDED_
#define AFX_MySTACK_H__3657DC2D_E518_4E5E_9A32_023B2A260ED7__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <GL/glut.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

struct dcpt {
	int x;
	int y;
};

struct pointStruct {
	dcpt point;
	pointStruct* pNext;
};

class CMyStack {
public:
	// Write your code here
	CMyStack();
	virtual ~CMyStack();
	bool pop();
	void push(dcpt pt);
	void empty();

	dcpt getTop();
	bool isEmpty();

private:
	// Write your code here
	pointStruct* pHead;
	pointStruct* p1;
	pointStruct* pTop;
};

#endif // !defined(AFX_MySTACK_H__3657DC2D_E518_4E5E_9A32_023B2A260ED7__INCLUDED_)

MyStack.cpp

// MyStack.cpp: implementation of the MyStack class.
//
//

#include "MyStack.h"

//
// Construction/Destruction
//

// Write your code here
CMyStack::CMyStack() {
	pHead = new pointStruct;
	p1 = pHead;
	pTop = pHead;
	pHead->pNext = NULL;
}
CMyStack::~CMyStack() {
	while (pTop != NULL) {
		p1 = pTop;
		pTop = pTop->pNext;
		delete p1;
	}
}
bool CMyStack::pop() {
	if (pTop == pHead)
		return false;
	p1 = pTop;
	pTop = pTop->pNext;
	delete p1;
	return true;
}
void CMyStack::push(dcpt pt) {
	p1 = new pointStruct;
	p1->point = pt;
	p1->pNext = pTop;
	pTop = p1;
}
void CMyStack::empty() {
	while (pTop !=pHead) {
		p1 = pTop;
		pTop = pTop->pNext;
		delete p1;
	}
}
dcpt CMyStack::getTop() {
	return pTop->point;
}
bool CMyStack::isEmpty() {
	if (pTop == pHead)
		return true;
	return false;
}

main.cpp

// ====== Computer Graphics Experiment #3 ======
// |          Boundary-Fill Algorithm          |
// =============================================
//
// Requirement:
//	Implement Boundary-Fill algorithm to fill polygon.

#include "MyStack.h"
#define WINDOW_HEIGHT 400

void Boundaryfill(int seedx, int seedy) {
	CMyStack stk1;
	long color = RGB(255, 0, 0); //填充颜色
	long boundary_color = RGB(255, 255, 255); //边界颜色
	unsigned char params[4]; //保存读取的一个像素点的颜色值
	dcpt p1;
	p1.x = seedx;
	p1.y = seedy;

	// Write your code here
	stk1.push(p1);
	const int dir[4][2] = { { 0, -1 }, { 1, 0 }, { 0, 1 }, { -1, 0 } };
	while (!stk1.isEmpty()) {
		p1 = stk1.getTop();
		stk1.pop();

		glColor3f(1, 0, 0);
		glBegin(GL_POINTS);
		glVertex2i(p1.x, p1.y);
		glEnd();

		for (int i = 0; i < 4; i++) {
			int tx = p1.x + dir[i][0];
			int ty = p1.y + dir[i][1];
			glReadPixels(tx, ty, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, params);
			long c1 = RGB(params[0], params[1], params[2]);
			if (c1 == color || c1 == boundary_color)
				continue;
			dcpt tmp;
			tmp.x = tx;
			tmp.y = ty;
			stk1.push(tmp);
		}
	}
}


void MyPolygonFill(int n, dcpt* vertices)
// n --- Number of vertices
// vertices --- vertex coordinates
{
	int max = vertices[0].x;
	int mix = vertices[0].x;
	int may = vertices[0].y;
	int miy = vertices[0].y;
	glColor3f(1.0, 1.0, 1.0);
	glBegin(GL_LINE_LOOP);
	for (int i = 0; i < n; i++) {
		glVertex2i(vertices[i].x, vertices[i].y);
		if (vertices[i].x > max)
			max = vertices[i].x;
		if (vertices[i].x < mix)
			mix = vertices[i].x;
		if (vertices[i].y > may)
			may = vertices[i].y;
		if (vertices[i].y < miy)
			miy = vertices[i].y;
	}
	glEnd();
	int sx = (max + mix) / 2;
	int sy = (may + miy) / 2;
	Boundaryfill(sx, sy);
}

// Initialization function
void init(void) {
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glPixelStorei(GL_PACK_ALIGNMENT, 1);
}

// Display callback function
void display(void) {
	static dcpt v[4];
	v[0].x = 260, v[0].y = 150;
	v[1].x = 281, v[1].y = 200;
	v[2].x = 340, v[2].y = 230;
	v[3].x = 370, v[3].y = 150;

	glClear(GL_COLOR_BUFFER_BIT);

	MyPolygonFill(4, v);

	glFlush();
}

// Reshape callback function
void reshape(int w, int h) {
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0, w, 0, h);
}

// Keyboard callback function
void keyboard(unsigned char key, int x, int y) {
	switch (key) {
	case 27:
		exit(0);
	}
}

// Main program entrance
int main(int argc, char* argv[]) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB);
	glutInitWindowSize(650, 400);
	glutInitWindowPosition(50, 100);
	glutCreateWindow("Polygon Fill");
	init();
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutMainLoop();
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值