openGL實現圖形學掃描線種子填充算法



title: "openGL實現圖形學掃描線種子填充算法"
date: 2018-06-11T19:41:30+08:00
tags: ["圖形學"]
categories: ["C++"]


先上效果圖

白色的起始種子點

代碼



#include <GL/glut.h>
#include <cmath>
#include <set>
#include <vector>
#include <unistd.h> //sleep函數
#include <iostream>
#include <algorithm> //find函數,查找vector中元素
#include <stack>


using namespace std;

//規格化為0.05的倍數
inline GLdouble normal(GLdouble x)
{
	return (round(x * 20) / 20);
}

typedef struct Point {
	GLdouble x, y;

	Point(GLdouble a = 0, GLdouble b = 0)
	{
		x = a, y = b;
	}

	// set會對插入的元素自動排序,需要重載運算符.定義排序規則
	//
	//
	// 重載運算符的要求
	// 1. 若A<B為真,則B<A為假
	// 2. 若A<B,B<C --> A<C
	// 3. A<A永遠為假
	// set中判斷元素是否相等
	// if(!(A<B||B<A)) --> A=B
	bool operator<(const Point &a) const
	{
		return ((x - a.x) < -0.01 || ((x - a.x) < 0.01 && (y - a.y) < -0.01));
	}

	bool operator==(const Point &a) const
	{
		return (abs(x - a.x) < 0.01 && abs(y - a.y) < 0.01);
	}
} point;

void drawGrid();

void initGraphBorder();

void drawGraphices();

void myDisplay();

void DDA(point A, point B);

void initGraphBorder();


point first_seed;
unsigned long m;
static int n = 40;
static GLfloat pointSize = 12.5;
set<point> graphBorder; // 圖形邊界數組<set>可以快速查找
//set雖然可以快速查找,但卻會打亂順序,不方便實現填充動畫
vector<point> graphFill;   // 要填充的數組
vector<point> graphVertex;  //存儲圖形定點數組,按順序
stack<point> seed;  //存儲種子

GLdouble p = float(2.0 / n); // 每個格子的大小

//畫網格坐標
void drawGrid()
{
	glColor3d(1, 1, 1); //網格用白色表示
	glLineWidth(1);
	glBegin(GL_LINES);

	for (int i = 0; i <= n; i++) {
		//畫豎線
		glVertex2d(-1 + 1.0 / n + i * 2.0 / n, -1);
		glVertex2d(-1 + 1.0 / n + i * 2.0 / n, 1);
		//畫橫線
		glVertex2d(-1, -1 + 1.0 / n + i * 2.0 / n);
		glVertex2d(1, -1 + 1.0 / n + i * 2.0 / n);
	}

	glEnd();
}


//DDA算法
void DDA(point A, point B)
{
	cout << "連接(" << A.x << ',' << A.y << ") 到 (" << B.x << ',' << B.y << ")\n";

	if (A.x > B.x) {
		swap(A, B);
	}

	A.x = normal(A.x);
	A.y = normal(A.y);
	B.x = normal(B.x);
	B.y = normal(B.y);
	double delta_x = B.x - A.x;
	double delta_y = B.y - A.y;
	double k = delta_y / delta_x;
	double x = A.x, y = A.y;

	if (k > -1 && k < 1) {
		//x是最大位移
		//cout << k << endl;
		while (true) {
			if ((x - B.x) > 0.01)break;

			graphBorder.insert(point(normal(x), normal(y)));
			x += p;
			y += (p * k);
		}
	}
	else if (k >= 1) {
		//Y是最大位移
		while (true) {
			if ((y - B.y) > 0.01) break;

			graphBorder.insert(point(normal(x), normal(y)));
			y += p;
			x += (p / k);
		}
	}
	else {
		while (true) {
			if ((B.y - y) > 0.01) break;

			graphBorder.insert(point(normal(x), normal(y)));
			y -= p;
			x -= (p / k);
		}
	}
}


//初始化圖形邊界數組
void initGraphBorder()
{
	//    graphVertex存儲圖形的頂點
	for (auto it = graphVertex.begin(); it != graphVertex.end(); it++) {
		if (it == graphVertex.end() - 1) {
			//最后一個點連接第一個點
			DDA(*it, *graphVertex.begin());
		}
		else {
			//連接it1和it2xr
			DDA(*it, *(it + 1));
		}
	}
}

/*********************************
 * 1. 初始化:堆棧置空,將種子seed(x,y)入棧
 * 2. 出棧: 若棧空則結束,否則將棧頂元素出棧,以y作為當前掃描線
 * 3. 填充並確定新種子點所在區域:從當前種子點出發,沿y掃描線向左右方向填充
 *    直到遇到邊界像素。標記當前區段的左右端點坐標為Xl, Xr.
 * 4. 確定新種子點:檢查[Xl-1,Xr]和[Xl+1,Xr]區域,若存在非邊界,未填充的
 *    像素,則把每一區間的最右像素作為種子點壓棧。返回第2步
**********************************/

// 沿掃描線的區域填充
// 從種子堆棧<stack>seed中取出種子點
// 把要填充的點加入<vector>graphFill
void floodFillSet()
{
	while (!seed.empty()) {
		point s = seed.top();   //當前種子點
		seed.pop();
		cout << "當前種子點  " << s.x << "  " << s.y << endl;
		//填充種子點及左右連續區域
		point t = s, xl, xr;

		//填充左側連續區域,並找到區域最左邊界
		while (true) {
			//到達邊界退出
			if (t.x < -1)break;
			graphFill.push_back(t);
			t.x -= p;
			//查找是否已填充
			auto it1 = find(graphFill.begin(), graphFill.end(), t);
			//查找是否是邊界
			auto it2 = graphBorder.find(t);

			if (it1 != graphFill.end() || it2 != graphBorder.end()) { //已填充或已達邊界
				xl = t;
				xl.x += p;
				break;
			}
		}

		t = s;

		//填充右側區域
		while (true) {
			if (t.x > 1)break;

			graphFill.push_back(t);
			t.x += p;
			//查找是否已填充
			auto it1 = find(graphFill.begin(), graphFill.end(), t);
			//查找是否是邊界
			auto it2 = graphBorder.find(t);

			if (it1 != graphFill.end() || it2 != graphBorder.end()) { //已填充或已達邊界
				xr = t;
				xr.x -= p;
				break;
			}
		}

		//在下一行找種子點
		for (int d : { 1, -1 }) {//先上再下
			bool status = false; //標記一段空白區域的開始
			t = xl;
			t.y += (p * d);   //移到下一行或上一行

			while ((t.x - xr.x) < 0.07) {
				auto it1 = find(graphFill.begin(), graphFill.end(), t);
				auto it2 = graphBorder.find(t);

				//找空白區域開始點
				//如果當前點是空白區域,且空白區域未開始 則s=true
				if (!status && it1 == graphFill.end() && it2 == graphBorder.end()) {
					status = true;
				}

				//區域開始且遇到邊界或填充區域
				if (status && (it1 != graphFill.end() || it2 != graphBorder.end())) { //到達邊界或已填充顏色,且左側有空白區域
					status = false;
					//添加左側一點為種子點
					seed.push(point(t.x - p, t.y));
				}

				// 到達最右區間xr,即便不是邊界,只要s=true也應標注最右種子點
				if (status && abs(t.x - xr.x) < 0.01) {   //注意浮點數比較大小
					status = false;
					seed.push(t);
				}

				t.x += p;
			}
		}
	}
}


//畫待填充的圖形邊界
void drawGraphices()
{
	glColor3f(0.5, 0.5, 0.2);
	glBegin(GL_POINTS);

	//C++11的新特性,可以遍歷vector map list or {1,2,3}
	for (auto it : graphBorder) {
		glVertex2d(it.x, it.y);
	}

	glEnd();
}

//畫填充像素
void drawFloodFill(unsigned long m)
{
	glColor3f(0.4, 0.8, 0.1);
	glBegin(GL_POINTS);

	for (unsigned long i = 0; i < m && i < graphFill.size(); i++) {
		glVertex2d(graphFill[i].x, graphFill[i].y);
		//myDisplay();
	}

	glEnd();
}

//繪圖
void myDisplay()
{
	glClearColor(0.1f, 0.3f, 0.33f, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	//畫圖形邊界
	drawGraphices();
	//畫m個填充像素
	drawFloodFill(m);
	glColor3d(1, 0, 0);
	glPointSize(pointSize);
	//加個白點,起始種子點
	glColor3d(1, 1, 1);
	glBegin(GL_POINTS);
	glVertex2d(first_seed.x, first_seed.y);
	glEnd();
	//畫網格
	drawGrid();
	glFlush();
}


int main(int argc, char *argv[])
{
	//存入要填充的圖形頂點 8
	//(-0.7,0.6) (0.7,0.6) (0.7,0) (0.3,0) (0.3,-0.5) (-0.3,-0.5)
	// (-0.3,0) (-0.7,0)
	cout << "請輸入頂點坐標,(-1,1) 輸入>1 結束 \n";
	double x, y;

	while (cin >> x) {
		if (x > 1)
			break;
		else
			cin >> y;

		graphVertex.emplace_back(point(x, y));
	}

	cout << "輸入初始種子點\n";
	cin >> x >> y;
	first_seed.x = x;
	first_seed.y = y;
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
	glutInitWindowSize(500, 500);
	glutInitWindowPosition(100, 100);
	//初始化邊界集
	initGraphBorder();
	//掃描線種子填充
	seed.push(first_seed);
	floodFillSet();
	glutCreateWindow("掃描線種子填充");
	glutDisplayFunc(&myDisplay);

	for (m = 0; m < graphFill.size(); m++) {
		usleep(10000);
		myDisplay();
	}

	glutMainLoop();
	return 0;
}

輸入樣例

-0.7 0.7
-0.35 0.7
-0.35 0
0.35 0
0.35 0.7
0.7 0.7
0.7 -0.7
-0.7 -0.7
2 
0 -0.2



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM