OpenGL学习进程(13)第十课:基本图形的底层实现及算法原理
本节介绍OpenGL中绘制直线、圆、椭圆,多边形的算法原理。
(1)绘制任意方向(任意斜率)的直线:
1)中点画线法:
中点画线法的算法原理不做介绍,但这里用到最基本的画0<=k<=1的中点画线法实现任意斜率k直线的绘制。
)当A点x坐标值大于B点坐标值时,即A点在B点的右侧时,交换A、B点的坐标。保证A点在B的左侧。
)考虑特殊情况,当直线AB的斜率不存在时,做近似处理,设置斜率为-(y0-y1)*,即近似无穷大。
)当斜率m满足0<=m<=1时,按书本上的中点画线算法进行处理。
)当m>1时和m<0时,以y为步进选择对象,计算x基于y的斜率,再重复中点画线的过程。
代码如下:
#include<GL/glut.h>
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"") void myInit(){
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluOrtho2D(0.0, , 0.0, );
}
void Drawpixel(int x, int y){
glPointSize(1.0);
glColor3f(1.0f, 0.0f, 0.0f);//设置颜色为红色
glVertex2i((int)(x), (int)(y));
}
void MidpointLine(int x0, int y0, int x1, int y1)
{
int a, b, d1, d2, d, x, y; float m;
if (x1<x0){
d = x0, x0 = x1, x1 = d;
d = y0, y0 = y1, y1 = d;
}
a = y0 - y1, b = x1 - x0;
if (b == )
m = - * a * ;
else
m = (float)a / (x0 - x1); x = x0, y = y0;
Drawpixel(x, y);
if (m >= && m <= ){
d = * a + b; d1 = * a, d2 = * (a + b);
while (x<x1){
if (d <= ){
x++, y++, d += d2;
}
else{
x++, d += d1;
}
Drawpixel(x, y);
}
}
else if (m <= && m >= -){
d = * a - b; d1 = * a - * b, d2 = * a;
while (x<x1){
if (d>){ x++, y--, d += d1; }
else{
x++, d += d2;
}
Drawpixel(x, y);
}
}
else if (m>){
d = a + * b; d1 = * (a + b), d2 = * b;
while (y<y1){
if (d>){
x++, y++, d += d1;
}
else{
y++, d += d2;
}
Drawpixel(x, y);
}
}
else{
d = a - * b; d1 = - * b, d2 = * (a - b);
while (y>y1){
if (d <= ){
x++, y--, d += d2;
}
else{
y--, d += d1;
}
Drawpixel(x, y);
}
}
}
void myDisplay(){
glClearColor(, , , );
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_POINTS);
MidpointLine(, , , );
MidpointLine(, , , );
glEnd();
glFlush();
}
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(, );
glutInitWindowPosition(, );
glutCreateWindow("中点画线法");
myInit();
glutDisplayFunc(myDisplay);
glutMainLoop();
return ;
}
2)Breseham算法:
在这里依然不介绍Breseham的算法原理,但依然以它为基础。Breseham算法与中点算法一样也面临着只能处理0<k<1的情况,要扩展到任意斜率需要进行下述操作:
)类似中点画线法,保证A(x0,y0)点在B(x1,y1)的左侧。
)斜率为无穷大时单独画线;
)其余情况分为4种情况:
一、斜率大于0小于1,按书本上的算法进行画线;
二、斜率大于1则交换横纵坐标;
三、斜率大于-1小于1的情况可先以y=y0作B点的对称点B',将AB’(此时斜率转化到情况二)画出之后,转化增量到相反的方向,画出直线AB;
四、当斜率小于-1时,先交换横纵坐标值转化为情况三,再利用情况三的算法转化为情况二进行画线。
总结:由于用的是转化法,无论斜率属于那种情况,计算增量的方式都转化到情况一上计算,因此我在这里将情况一抽象为一个函数,仅仅在画点时仅对不同情况做相应的坐标变换。
代码如下:
#include<GL/glut.h>
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"") void myInit(){
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluOrtho2D(0.0, , 0.0, );
}
void Drawpixel(int x, int y){
glPointSize(1.0);
glColor3f(1.0f, 0.0f, 0.0f);//设置颜色为红色
glVertex2i((int)(x), (int)(y));
}
void drawOrdinayLine(int x0, int y0, int x1, int y1, int flag){
int i;
int x, y, dx, dy, e;
dx = x1 - x0;
dy = y1 - y0;
e = -dx;
x = x0; y = y0;
for (i = ; i <= dx; i++){
switch (flag){
case : Drawpixel(x, y); break;
case : Drawpixel(x, * y0 - y); break;//增量为(y-y0)则,实际增量应取相反方向为y0-(y-y0)=2y0-y
case : Drawpixel(y, x); break;//这里也要交换
case : Drawpixel( * y0 - y, x); break;
}
x++;
e = e + * dy;
if (e >= ){
y++;
e = e - * dx;
}
}
}
void BresenhamLine(int x0, int y0, int x1, int y1){
int d;
int i;
if (x0>x1){
d = x0, x0 = x1, x1 = d;
d = y0, y0 = y1, y1 = d;
}
if (x0 == x1){
if (y0>y1){//保证y0<y1;
d = y0, y0 = y1, y1 = d;
}
for (i = y0; i< y1; i++){
Drawpixel(x0, i);
}
return;
}
float k = (1.0*(y1 - y0)) / (x1 - x0);
if ( <= k&&k <= ){ //直接画
drawOrdinayLine(x0, y0, x1, y1, );
}
else if (- <= k&&k<){//以y=y0作B点的对称点
drawOrdinayLine(x0, y0, x1, y1 + * (y0 - y1), );
}
else if (k>){//交换x,y的坐标值。
drawOrdinayLine(y0, x0, y1, x1, );
}
else if (k<-){
//交换x0和y0,x1和y1;
d = x0; x0 = y0; y0 = d;
d = x1; x1 = y1; y1 = d;
//交换两个点
d = x0, x0 = x1, x1 = d;
d = y0, y0 = y1, y1 = d;
drawOrdinayLine(x0, y0, x1, y1 + * (y0 - y1), );
}
}
void myDisplay(){
glClearColor(, , , );
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_POINTS);
int i;
BresenhamLine(, , , );
BresenhamLine(, , , );
BresenhamLine(, , , );
BresenhamLine(, , , );
glEnd();
glFlush();
}
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(, );
glutInitWindowPosition(, );
glutCreateWindow("中点画线法");
myInit();
glutDisplayFunc(myDisplay);
glutMainLoop();
return ;
}
(2)绘制圆和椭圆:
中点画线法利用直线方程,画圆和椭圆利用的是椭圆和圆的方程。
1)中点画圆法:
标准中点画圆算法只能实现八分之一的圆弧,这里利用椭圆的点的对称性,每画一个点就对称的画出其他七段圆弧上的点。最后每个点均加上一个平移量,即圆的中点坐标,实现任意指定圆心坐标。
2)中点画椭圆法:
)先画椭圆上顶点,然后以上顶点为基础运用椭圆的坐标方程式和中点画线法计算步进值。
)从上顶点开始,每两个像素点组成一组画一条直线,一个像素点可同时当开始点和结束点。
)利用椭圆的轴对称性对称的画出其它三条线。任意坐标的指定利用像素点的平移操作来进行。
代码如下:(圆和椭圆)
#include <GL/glut.h>
#include <math.h>
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"") void Init()
{
glClearColor(,,,);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluOrtho2D(-,,-,);
}
void CirclePoint(int x0,int y0,int x,int y){
//每画一个点对称的画出八个点,并按照中点坐标平移相同单位
glVertex2d(x + x0, y + y0);
glVertex2d(x + x0, -y + y0);
glVertex2d(-x + x0, -y + y0);
glVertex2d(-x + x0, y + y0);
glVertex2d(y + x0, x + y0);
glVertex2d(-y + x0, x + y0);
glVertex2d(y + x0, -x + y0);
glVertex2d(-y + x0, -x + y0);
}
void MidPointCircle(int x0,int y0,int r){//圆点和半径
//画右上方1/8的圆
int x, y;
float d;
x = ; y = r;
d = 1.25;
CirclePoint(x0, y0,x,y);
while (x <= y){
if (d < )
d += * x + ;
else{
d += * (x - y) + ;
y--;
}
x++;
CirclePoint(x0, y0, x, y);
}
} void Drawpixel(int x, int y){
glPointSize(1.0);
glColor3f(1.0f, 0.0f, 0.0f);//设置颜色为红色
glVertex2i((int)(x), (int)(y));
}
void drawLine(int x0,int y0,int x1,int y1){
int a, b, d1, d2, d, x, y; float m;
if (x1<x0){
d = x0, x0 = x1, x1 = d;
d = y0, y0 = y1, y1 = d;
}
a = y0 - y1, b = x1 - x0;
if (b == )
m = - * a * ;
else
m = (float)a / (x0 - x1); x = x0, y = y0;
Drawpixel(x, y);
if (m >= && m <= ){
d = * a + b; d1 = * a, d2 = * (a + b);
while (x<x1){
if (d <= ){
x++, y++, d += d2;
}
else{
x++, d += d1;
}
Drawpixel(x, y);
}
}
else if (m <= && m >= -){
d = * a - b; d1 = * a - * b, d2 = * a;
while (x<x1){
if (d>){ x++, y--, d += d1; }
else{
x++, d += d2;
}
Drawpixel(x, y);
}
}
else if (m>){
d = a + * b; d1 = * (a + b), d2 = * b;
while (y<y1){
if (d>){
x++, y++, d += d1;
}
else{
y++, d += d2;
}
Drawpixel(x, y);
}
}
else{
d = a - * b; d1 = - * b, d2 = * (a - b);
while (y>y1){
if (d <= ){
x++, y--, d += d2;
}
else{
y--, d += d1;
}
Drawpixel(x, y);
}
}
}
void drawOval(int x0,int y0,int a, int b){
int x, y;
int xx, yy;
double d1, d2;
x = ; y = b;
d1 = b*b + a*a*(-b + 0.25);
drawLine(x0 + x, y0 + y, x0+ x, y0 + y);
drawLine(x0 - x, y0-y, x0 - x, y0-y);
drawLine(x0 + x, y0-y, x0 + x, y0-y);
drawLine(x0 - x, y0+y, x0 - x, y0+y);
xx = x; yy = y;
while (b*b*(x + )<a*a*(y - 0.5))
{
if (d1<)
{
d1 += b*b*( * x + );
x++;
}
else
{
d1 += (b*b*( * x + ) + a*a*(- * y + ));
x++; y--;
}
drawLine(x0 + xx, y0+yy, x0 + x, y0+y);
drawLine(x0 - xx, y0-yy, x0 - x, y0-y);
drawLine(x0 + xx, y0-yy, x0 + x, y0-y);
drawLine(x0 - xx, y0+yy, x0 - x, y0+y);
xx = x; yy = y;
}
d2 = sqrt(b*(x + 0.5)) + sqrt(a*(y - )) - sqrt(a*b);
while (y>)
{
if (d2<)
{
d2 += b*b*( * x + ) + a*a*(- * y + );
x++; y--;
}
else
{
d2 += a*a*(- * y + );
y--;
}
drawLine(x0 + xx, y0+yy, x0 + x, y0+y);
drawLine(x0 - xx, y0-yy, x0 - x, y0-y);
drawLine(x0 + xx, y0-yy, x0 + x, y0-y);
drawLine(x0 - xx, y0+yy, x0 - x, y0+y);
xx = x; yy = y;
}
}
void display(){
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0,1.0,);
glBegin(GL_POINTS);
MidPointCircle(, , );
MidPointCircle(, -, );
MidPointCircle(, , );
MidPointCircle(-, , );
//drawOval(100,0,200,100);
//drawOval(-100, 0, 200, 100);
//drawOval(0, 100, 200, 100);
//drawOval(0, -100, 200, 100);
glEnd();
glFlush();
}
int main(int argc, char * argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
glutInitWindowPosition(, );
glutInitWindowSize(, );
glutCreateWindow("椭圆与圆的绘制");
Init();
glutDisplayFunc(display);
glutMainLoop();
return ;
}
(3)绘制多边形:
1)多边形的扫描转化:
待扫描多边形的坐标如下,为了能够在窗体上观察清楚,这里我放大40倍。
扫描线算法实现绘制填充多边形需要建立两张表:新边表(New Edge Table,NET)和 活动边表(Active Edge Table,AET);
新边表:记录多边形除水平边外的所有的边,记录在没条扫描线的表中,记录的格式为:
x_cross:当前扫描线与边的交点坐标;dx:从当前扫描线到下一条扫描线间x的增量((x2-x1)/(y2-y1));ymax:该边所交的最高扫描线。
建立的新边表如下:
格式:
建立的活性边表如下:
代码如下:
#include <GL/glut.h>
#include <stdio.h>
//待填充多边形的点的个数或边的个数
static int n;
//待填充多边形的顶点最大纵坐标值
static int max_y;
static int min_y;
struct POINT{
int x;//多边形顶点横坐标
int y;//多边形顶点纵坐标
};
typedef struct POINT Point;
struct EDGE{
struct POINT *point[];//存指向一条直线的两个点的指针
float k;//用来表示直线的斜率k=(x2-x1)/(y2-y1)
int flag;//用来标识一条边是否有效,当直线水平时此边无效,flag置为0。否则flag置为1。
};
typedef struct EDGE Edge;
//组成边表和活性边表的结点
struct SHEETNODE{
float x_cross;//扫描线与边交点的x坐标值
float dx;//dx从当前扫描线到下一条扫描线间x的增量((x2-x1)/(y2-y1));
int y_max;//与当前扫描线相交的边的最大纵坐标y
struct SHEETNODE *next;
};
typedef struct SHEETNODE SheetNode;
typedef struct SHEETNODE *Sheet;
Point *InitPoint(){
n = ;//六个点
Point *pNumGroup = malloc(n*sizeof(struct POINT));
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x =; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y =;
return pNumGroup;
}
void printPoints(Point *p){
int i;
for (i = ; i < n; i++){
printf("第%d个顶点:x=%2d,y=%2d\n", i + , p[i].x, p[i].y);
}
}
Edge *InitEdge(Point *p){
Edge *eNumGroup = malloc(n*sizeof(struct EDGE));
int i;
for (i = ; i < n; i++){
eNumGroup[i].point[] = &p[i];
if (i != n - )//当当前边不是最后一条边时
eNumGroup[i].point[] = &p[i + ];
else//当当前边是最后一条边时
eNumGroup[i].point[] = &p[];
}
//初始化EDGE中的k和flag
for (i = ; i < n; i++){
//如果两个点的横坐标相同,代表斜率不存在,设为flag=1,边有效k=1
if (eNumGroup[i].point[]->x == eNumGroup[i].point[]->x)
{
eNumGroup[i].flag = ; eNumGroup[i].k = ;
}
//如果两个点的纵坐标相同,代表斜率为1,属于无效边。flag=0;
else if (eNumGroup[i].point[]->y == eNumGroup[i].point[]->y)
eNumGroup[i].flag = ;
else{
eNumGroup[i].flag = ;
eNumGroup[i].k = 1.0* (eNumGroup[i].point[]->x - eNumGroup[i].point[]->x) / (eNumGroup[i].point[]->y - eNumGroup[i].point[]->y);
}
}
return eNumGroup;
}
int Min(int a, int b){
return a > b ? b : a;
}
int Max(int a, int b){
return a>b ? a : b;
}
void printEdge(Edge *edge){
int i;
for (i = ; i < n; i++){
if (edge[i].flag == )
printf("边%d有效,斜率为%2.1f\n", i + , edge[i].k);
}
}
//按x的递增顺序将结点插入边表
void InsertIntoSheet(SheetNode *tempNode, Sheet sheet){
SheetNode *ptr = sheet;
while (ptr->next != NULL){
if (tempNode->x_cross <= ptr->next->x_cross){
tempNode->next = ptr->next;
ptr->next = tempNode;
return;
}
else{
ptr = ptr->next;
}
}
ptr->next = tempNode;
}
Sheet InitNewEdgeSheet(Edge *e){
int i; int tempMax, tempMin;
max_y = e[].point[]->y;
min_y = e[].point[]->y;
for (i = ; i < n; i++){
tempMax = Max(e[i].point[]->y, e[i].point[]->y);
tempMin = Min(e[i].point[]->y, e[i].point[]->y);
if (max_y < tempMax)
max_y = tempMax;
if (min_y>tempMin){
min_y = tempMin;
}
}
//新边表
int j;
//标记边是否被插入新边表的标志数组
int *EdgeIntoSheetFlag = malloc(n*sizeof(int));
for (i = ; i < n; i++){
EdgeIntoSheetFlag[i] = ;
}
Sheet s = malloc(max_y*sizeof(struct SHEETNODE));
for (i = ; i <max_y; i++){
s[i].next = NULL;
}
for (i = ; i <max_y; i++){
//讨论y=i这条扫描线与每条边的相交情况
for (j = ; j < n; j++){
//如果这条边已经用过了,忽略这条边
if (EdgeIntoSheetFlag[j] == )
continue;
//如果扫描线与边相交==>y=i在当前边的最大y值和最小y值之间
if ((i <= e[j].point[]->y&&i >= e[j].point[]->y) || (i <= e[j].point[]->y) && (i >= e[j].point[]->y)){
EdgeIntoSheetFlag[j] = ;
SheetNode *tempNode = malloc(sizeof(struct SHEETNODE));
tempNode->dx = e[j].k;
tempNode->y_max = Max(e[j].point[]->y, e[j].point[]->y);
//取一条直线的任意端点,这里使用第一个点。交点的横坐标为x=k(i-y0)+y0
tempNode->x_cross = e[j].k*(i - e[j].point[]->y) + e[j].point[]->x;
tempNode->next = NULL;
//printf("插入 %.1f,%.1f,%d\n", tempNode->dx, tempNode->x_cross, tempNode->y_max);
InsertIntoSheet(tempNode, &s[i]);
}
}
}
return s;
}
void printSheet(Sheet sheet){
int i;
for (i = ; i <max_y; i++){
SheetNode *ptr = &sheet[i];
while (ptr->next != NULL){
printf("dx=%.1f x_cross=%.1f y_max=%d\n", ptr->next->dx, ptr->next->x_cross, ptr->next->y_max);
ptr = ptr->next;
}
}
}
Sheet InitDynamicEdgeSheet(Sheet sheet){
int i;
int j;
Sheet s_dynamic = malloc(max_y*sizeof(struct SHEETNODE));
for (i = ; i <max_y; i++){
s_dynamic[i].next = NULL;
}
SheetNode *tempNode;
SheetNode *ptrNode;
for (i = ; i <max_y; i++){
SheetNode *ptr = &sheet[i];
while (ptr->next != NULL){
tempNode = malloc(sizeof(struct SHEETNODE));
tempNode->next = NULL;
tempNode->dx = ptr->next->dx;
tempNode->x_cross = ptr->next->x_cross;
tempNode->y_max = ptr->next->y_max;
//printf("插入 %.1f,%.1f,%d\n", tempNode->dx, tempNode->x_cross, tempNode->y_max);
InsertIntoSheet(tempNode, &s_dynamic[i]);
if (tempNode->y_max>i){
for (j = i + ; j < tempNode->y_max; j++){
ptrNode = malloc(sizeof(struct SHEETNODE));
ptrNode->next = NULL;
ptrNode->dx = tempNode->dx;
ptrNode->y_max = tempNode->y_max;
//printf("%f\n",tempNode->dx);
ptrNode->x_cross = tempNode->x_cross + (j - i)*tempNode->dx;
//printf("插入 %.1f,%.1f,%d\n", ptrNode->dx, ptrNode->x_cross, ptrNode->y_max);
InsertIntoSheet(ptrNode, &s_dynamic[j]);
}
}
ptr = ptr->next;
}
}
return s_dynamic;
}
void displayLine(Point A, Point B){
glColor3f(1.0, 0.0, );
glBegin(GL_LINES);
//printf("A.x=%d A.y=%d",A.x,A.y);
//printf("B.x=%d B.y=%d", B.x, B.y);
//printf("\n");
glVertex2i(A.x, A.y);
glVertex2i(B.x, B.y);
glEnd();
}
void paint(Sheet sheet){
int i; int minNum; int j;
SheetNode *ptrNode = NULL;
SheetNode *A_Node = NULL;
SheetNode *B_Node = NULL;
for (i = ; i < max_y; i++){
ptrNode = &sheet[i];
while (ptrNode->next != NULL){
//结点总是成对出现
A_Node = ptrNode->next;
B_Node = ptrNode->next->next;
Point A, B;
A.y = i; B.y = i;
A.x = A_Node->x_cross;
B.x = B_Node->x_cross;
displayLine(A, B);
ptrNode = ptrNode->next->next;
}
}
}
Sheet ProcessBegin(){
//1.按顺序将多边形顶点输入数组
Point *p = NULL;
p = InitPoint();
printf("1.输出多边形的所有顶点坐标:\n"); printPoints(p);
//2.依据顶点初始化此多边形的各个边
Edge *e = NULL;
e = InitEdge(p);
printf("2.打印待扫描多边形所有的边\n"); printEdge(e);
//3.初始化新边表
Sheet newSheet = NULL;
newSheet = InitNewEdgeSheet(e);
printf("3.打印新边表:\n"); printSheet(newSheet);
//4.初始化活性边表
Sheet dynamicSheet = NULL;
dynamicSheet = InitDynamicEdgeSheet(newSheet);
printf("4.打印活性边表:\n"); printSheet(dynamicSheet);
//进行多边形扫描转化
return dynamicSheet;
}
void Init(){
glClearColor(, , , );
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//定义裁剪区域
gluOrtho2D(, , , );
} void display(void){
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1.0, , );
Sheet tempSheet = ProcessBegin();
paint(tempSheet);
glFlush();
}
int main(int argc, char *argv[]){
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(, );
glutInitWindowSize(, );
glutCreateWindow("扫描线画多边形");
glutDisplayFunc(display);
Init();
glutMainLoop();
return ;
}
控制台输出:
图形绘制:
由于这里图形太小所以多边形的扫描转化并不明显,因此我将每个坐标扩大40倍,通过修改代码中的initPoints()函数实现:
Point *InitPoint(){
n = ;//六个点
Point *pNumGroup = malloc(n*sizeof(struct POINT));
pNumGroup[].x = *; pNumGroup[].y = *;
pNumGroup[].x = *; pNumGroup[].y = *;
pNumGroup[].x = *; pNumGroup[].y = *;
pNumGroup[].x = *; pNumGroup[].y = *;
pNumGroup[].x = *; pNumGroup[].y = *;
pNumGroup[].x = *; pNumGroup[].y = *;
return pNumGroup;
}
效果如下:(由于活性边表太长无法显示全部)
2)多边形的区域填充:
在多边形的扫描转化的测试中已经涉及到多边形的区域填充,不过那里由活性边表绘制直线得到,这里讨论的是已经表示成点阵形式的填充图形,是像素的集合。
区域的表示可采用内点或者边界点表示两种表示形式。在内点表示中,区域内的所有像素着同一颜色;在边界点表示中,区域的边界点着同一颜色。区域填充是指先将区域内的一点赋予指定的颜色,然后将该颜色扩展到整个区域的过程。
区域分为四连通和八连通两种。这里以八连通区域为例介绍区域的递归填充算法和扫描线算法。
3.2.1区域填充的递归算法:
/*
---------------------------------八连通多边形区域填充递归算法---------------------------------------------------
1.初始化绘图区域的边界线的端点坐标(这里直线为例,不介绍曲线)
2.依据顶点初始化边界线
3.新建二维数组保存点阵的状态,数组值为1代表边界,0代表非边界
4.利用中点画线法计算各条边界线经过的像素,并将点阵中的对应坐标的值置为1,表示边界
上述过程意在完成图形的栅格化,并表示为点阵的形式,下面开始区域填充算法
5.根据点阵和递归填充算法将栅格化的图形画在显示窗体上
*/
//待填充多边形的点的个数或边的个数
#include <stdio.h>
#include <GL/glut.h> static int n;
static int y_max;
static int x_max;
struct POINT{
int x;//多边形顶点横坐标
int y;//多边形顶点纵坐标
};
typedef struct POINT Point;
static Point seed;//种子坐标值
Point *InitPoint(){
n = ;//六个点
Point *pNumGroup = malloc(n*sizeof(struct POINT));
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ;pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
seed.x = ;seed.y = ;
return pNumGroup;
}
void setEdge(int x, int y, int **NumGroup){
NumGroup[x][y] = ;
//printf("NumGroup[%d][%d]=1",x,y);
}
void MidpointLine(int x0, int y0, int x1, int y1, int **NumGroup)
{
int a, b, d1, d2, d, x, y; float m;
if (x1<x0){
d = x0, x0 = x1, x1 = d;
d = y0, y0 = y1, y1 = d;
}
a = y0 - y1, b = x1 - x0;
if (b == )
m = - * a * ;
else
m = (float)a / (x0 - x1); x = x0, y = y0;
setEdge(x, y, NumGroup);
if (m >= && m <= ){
d = * a + b; d1 = * a, d2 = * (a + b);
while (x<x1){
if (d <= ){
x++, y++, d += d2;
}
else{
x++, d += d1;
}
setEdge(x, y, NumGroup);
}
}
else if (m <= && m >= -){
d = * a - b; d1 = * a - * b, d2 = * a;
while (x<x1){
if (d>){ x++, y--, d += d1; }
else{
x++, d += d2;
}
setEdge(x, y, NumGroup);
}
}
else if (m>){
d = a + * b; d1 = * (a + b), d2 = * b;
while (y<y1){
if (d>){
x++, y++, d += d1;
}
else{
y++, d += d2;
}
setEdge(x, y, NumGroup);
}
}
else{
d = a - * b; d1 = - * b, d2 = * (a - b);
while (y>y1){
if (d <= ){
x++, y--, d += d2;
}
else{
y--, d += d1;
}
setEdge(x, y, NumGroup);
}
}
}
//在视窗上将此矩阵输出
void printEdgeMatrix(int **NumGroupMaxtrx, int x_num, int y_num){
int i;
int j;
for (i = ; i < x_num; i++){
for (j = ; j < y_num; j++){
//如果是边界点
if (NumGroupMaxtrx[i][j] == ){
glVertex2i(i, j);
}
} }
}
int **InitPointMatrixByPoint(Point *p){
int i;
y_max = p[].x;
x_max = p[].y;
for (i = ; i < n;i++){
if (p[i].x>x_max)
x_max = p[i].x;
if (p[i].y > y_max)
y_max =p[i].y;
}
y_max++; x_max++;
int **NumGroup_Matrix = malloc(x_max*sizeof(int *));
for (i = ; i < x_max;i++){
NumGroup_Matrix[i] = malloc(y_max*sizeof(int));
}
int j;
for (i = ; i < x_max;i++){
for (j = ; j < y_max;j++){
//取值有-1,0,1三种情况分别表示无效,内点和边界点
NumGroup_Matrix[i][j] = -;
}
}
//printf("%d,%d",p[5].x,p[5].y);
for (i = ; i < n; i++){
if (i != n - )
MidpointLine(p[i].x, p[i].y, p[i + ].x, p[i + ].y,NumGroup_Matrix);
else
MidpointLine(p[i].x, p[i].y, p[].x, p[].y,NumGroup_Matrix);
}
//这句话用来测试边界表示多边形边界是否正确
//printEdgeMatrix(NumGroup_Matrix,x_max,y_max);
return NumGroup_Matrix;
}
void fixPaint(int **numGroup,int x,int y){
if (x<||y<||x>=x_max||y>=y_max){
return;
}
//如果当前点是无效点,说明其在区域内部
if (numGroup[x][y] == -){
glVertex2i(x,y);
numGroup[x][y] = ;
fixPaint(numGroup, x, y + );
fixPaint(numGroup, x, y - );
fixPaint(numGroup, x+, y);
fixPaint(numGroup, x-, y);
fixPaint(numGroup, x + , y+);
fixPaint(numGroup, x + , y-);
fixPaint(numGroup, x - , y+);
fixPaint(numGroup, x - , y-);
}
else{
return;
}
}
void ProcessExcute(){
//1.初始化待填充区域的边界线段端点和种子坐标
Point *p=InitPoint();
//2.栅格化边界线段端点表示的待填充区域到像素阵列数组里
int **numGroupMatrix = InitPointMatrixByPoint(p);
//3.用递归法处理八连通区域的方法将栅格化的像素矩阵表示的区域填充
fixPaint(numGroupMatrix,seed.x,seed.y);
}
void display(){
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1.0, , );
glBegin(GL_POINTS);
ProcessExcute();
glEnd();
glFlush();;
}
//原画像素函数drawPixel改编而来 void Init(){
glClearColor(, , , );
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//定义裁剪区域
gluOrtho2D(, , , );
}
int main(int argc, char *argv[]){
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(, );
glutInitWindowSize(, );
glutCreateWindow("扫描线画多边形");
Init();
glutDisplayFunc(display);
glutMainLoop();
return ;
}
八连通区域处理区域填充时不可避免的要出现像素向原本不应该是区域内部的像素扩展,最终导致除边界像素外,所有的像素均被着色:
四连通区域不会出现这个问题:(将点的坐标均扩大10倍,将递归函数的八连通改为四连通):
Point *InitPoint(){
n = ;//六个点
Point *pNumGroup = malloc(n*sizeof(struct POINT));
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
seed.x = ; seed.y = ;
return pNumGroup;
}
void fixPaint(int **numGroup, int x, int y){
if (x< || y< || x>=x_max || y>=y_max){
return;
}
//如果当前点是无效点,说明其在区域内部
if (numGroup[x][y] == -){
glVertex2i(x, y);
numGroup[x][y] = ;
fixPaint(numGroup, x, y + );
fixPaint(numGroup, x, y - );
fixPaint(numGroup, x + , y);
fixPaint(numGroup, x - , y);
//fixPaint(numGroup, x + 1, y + 1);
//fixPaint(numGroup, x + 1, y - 1);
//fixPaint(numGroup, x - 1, y + 1);
//fixPaint(numGroup, x - 1, y - 1);
}
else{
return;
}
}
解决方法:
这就要求八连通区域所有的直线的斜率都必须是0或者不存在,类似下题:
修改上述初始化点的代码为:(其余不变),并放大6倍后显示:(最大只能放大6倍,6倍以上时程序会提醒fixPaint递归函数处中断,原因不明)
Point *InitPoint(){
n = ;//六个点
Point *pNumGroup = malloc(n*sizeof(struct POINT));
pNumGroup[].x =* ; pNumGroup[].y =* ;
pNumGroup[].x =* ; pNumGroup[].y =* ;
pNumGroup[].x =* ; pNumGroup[].y =* ;
pNumGroup[].x = *; pNumGroup[].y = * ;
pNumGroup[].x =* ; pNumGroup[].y = *;
pNumGroup[].x =* ; pNumGroup[].y = *;
pNumGroup[].x =* ; pNumGroup[].y = *;
pNumGroup[].x =* ; pNumGroup[].y = *;
seed.x = *; seed.y = *;
return pNumGroup;
}
3.2.2区域填充的扫描线算法:
扫描线区域填充算法和递归八连通区域填充算法一样无法解决斜率不为0或无穷大时的情况,因此测试时边界线的斜率必须k=0或者k无穷大
扫描线区域填充算法的原理:
)初始化,堆栈置空,将种子结点入栈;
)出栈,若栈空则结束;否则取栈顶元素,以栈顶元素的纵坐标y为当前扫描线;
)填充并确定种子所在区段。从种子出发沿当前扫描线向左右两个方向填充直到边界。分别标记区段左右两端的坐标为left和right;
)确定新的种子点。在区间[left,right]中检查与当前扫描线y上下相邻的两条扫描线上的像素。若存在非边界,未填充的像素,则把每一区间最右像素作为种子点压入堆栈。
)返回第二步,直至栈为空。
/*
---------------------------------区域填充扫描线算法---------------------------------------------------
1.初始化绘图区域的边界线的端点坐标(这里直线为例,不介绍曲线)
2.依据顶点初始化边界线
3.新建二维数组保存点阵的状态,数组值为1代表边界,0代表非边界
4.利用中点画线法计算各条边界线经过的像素,并将点阵中的对应坐标的值置为1,表示边界
上述过程意在完成图形的栅格化,并表示为点阵的形式,下面开始区域填充算法
5.根据点阵和扫描线算法将栅格化的图形画在显示窗体上
*/
#include <stdio.h>
#include <GL/glut.h> //待填充多边形的点的个数或边的个数
static int n;
static int y_max;
static int x_max;
struct POINT{
int x;//多边形顶点横坐标
int y;//多边形顶点纵坐标
};
typedef struct POINT Point;
static Point seed;//种子坐标值
Point *InitPoint(){
n = ;//六个点
Point *pNumGroup = malloc(n*sizeof(struct POINT));
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
pNumGroup[].x = ; pNumGroup[].y = ;
seed.x = ; seed.y = ;
return pNumGroup;
}
//原画像素函数drawPixel改编而来
void setEdge(int x, int y, int **NumGroup){
NumGroup[x][y] = ;
}
void MidpointLine(int x0, int y0, int x1, int y1, int **NumGroup)
{
int a, b, d1, d2, d, x, y; float m;
if (x1<x0){
d = x0, x0 = x1, x1 = d;
d = y0, y0 = y1, y1 = d;
}
a = y0 - y1, b = x1 - x0;
if (b == )
m = - * a * ;
else
m = (float)a / (x0 - x1); x = x0, y = y0;
setEdge(x, y, NumGroup);
if (m >= && m <= ){
d = * a + b; d1 = * a, d2 = * (a + b);
while (x<x1){
if (d <= ){
x++, y++, d += d2;
}
else{
x++, d += d1;
}
setEdge(x, y, NumGroup);
}
}
else if (m <= && m >= -){
d = * a - b; d1 = * a - * b, d2 = * a;
while (x<x1){
if (d>){ x++, y--, d += d1; }
else{
x++, d += d2;
}
setEdge(x, y, NumGroup);
}
}
else if (m>){
d = a + * b; d1 = * (a + b), d2 = * b;
while (y<y1){
if (d>){
x++, y++, d += d1;
}
else{
y++, d += d2;
}
setEdge(x, y, NumGroup);
}
}
else{
d = a - * b; d1 = - * b, d2 = * (a - b);
while (y>y1){
if (d <= ){
x++, y--, d += d2;
}
else{
y--, d += d1;
}
setEdge(x, y, NumGroup);
}
}
}
//在视窗上将此矩阵输出
void printEdgeMatrix(int **NumGroupMaxtrx, int x_num, int y_num){
int i;
int j;
for (i = ; i < x_num; i++){
for (j = ; j < y_num; j++){
//如果是边界点
if (NumGroupMaxtrx[i][j] == ){
glVertex2i(i, j);
}
} }
}
int **InitPointMatrixByPoint(Point *p){
int i;
y_max = p[].x;
x_max = p[].y;
for (i = ; i < n; i++){
if (p[i].x>x_max)
x_max = p[i].x;
if (p[i].y > y_max)
y_max = p[i].y;
}
y_max++; x_max++;
int **NumGroup_Matrix = malloc(x_max*sizeof(int *));
for (i = ; i < x_max; i++){
NumGroup_Matrix[i] = malloc(y_max*sizeof(int));
}
int j;
for (i = ; i < x_max; i++){
for (j = ; j < y_max; j++){
//取值有-1,0,1三种情况分别表示无效,内点和边界点
NumGroup_Matrix[i][j] = -;
}
}
//printf("%d,%d",p[5].x,p[5].y);
for (i = ; i < n; i++){
if (i != n - )
MidpointLine(p[i].x, p[i].y, p[i + ].x, p[i + ].y, NumGroup_Matrix);
else
MidpointLine(p[i].x, p[i].y, p[].x, p[].y, NumGroup_Matrix);
}
//这句话用来测试边界表示多边形边界是否正确
//printEdgeMatrix(NumGroup_Matrix,x_max,y_max);
return NumGroup_Matrix;
}
struct STACKNODE{
Point point;
struct STACKNODE *next;
};
typedef struct STACKNODE *PtrToNode;
typedef struct STACKNODE *Stack;
Stack createStack(){
Stack stack = malloc(sizeof(struct STACKNODE));
stack->next = NULL;
return stack;
}
void *Push(Stack stack,Point point){
PtrToNode tempNode = malloc(sizeof(struct STACKNODE));
tempNode->point.x = point.x;
tempNode->point.y = point.y;
tempNode->next = stack->next;
stack->next = tempNode;
}
Point *PopAndTop(Stack stack){
PtrToNode ptr = stack->next;
stack->next = stack->next->next;
return &ptr->point;
}
int IsNull(Stack s){
if (s->next == NULL)
return ;
else
return ;
}
void scanLineFixArea(int **numGroupMatrix){
Stack s = createStack();
Push(s,seed);
Point *tempPoint;
Point left, right;
int i;
while (!IsNull(s)){
tempPoint = PopAndTop(s);
glVertex2i(tempPoint->x, tempPoint->y); numGroupMatrix[tempPoint->x][tempPoint->y] = ;
left.y = tempPoint->y;
right.y = tempPoint->y;
left.x = tempPoint->x;
right.x = tempPoint->x;
while (numGroupMatrix[left.x][left.y] != ){
glVertex2i(left.x, left.y); numGroupMatrix[left.x][left.y]=;
left.x--;
}
while (numGroupMatrix[right.x][right.y] != ){
glVertex2i(right.x, right.y); numGroupMatrix[right.x][right.y] = ;
right.x++;
}
for (i = right.x; i >= left.x;i--){
if (numGroupMatrix[i][right.y+]==-){
right.y++;
right.x = i;
Push(s,right);
break;
}
}
right.y = tempPoint->y;
for (i = right.x; i >= left.x; i--){
if (numGroupMatrix[i][right.y - ] == -){
right.y--;
right.x = i;
Push(s, right);
break;
} }
}
}
void ProcessExcute(){
//1.初始化待填充区域的边界线段端点和种子坐标
Point *p = InitPoint();
//2.栅格化边界线段端点表示的待填充区域到像素阵列数组里
int **numGroupMatrix = InitPointMatrixByPoint(p);
//3.用扫描线算法进行区域填充:
scanLineFixArea(numGroupMatrix);
}
void display(){
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1.0, , );
glBegin(GL_POINTS);
ProcessExcute();
glEnd();
glFlush();;
}
void Init(){
glClearColor(, , , );
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//定义裁剪区域
gluOrtho2D(, , , );
}
int main(int argc, char *argv[]){
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(, );
glutInitWindowSize(, );
glutCreateWindow("扫描线区域填充算法");
Init();
glutDisplayFunc(display);
glutMainLoop();
return ;
}
将坐标值扩大20倍:
OpenGL学习进程(13)第十课:基本图形的底层实现及算法原理的更多相关文章
- WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[中篇]
原文:WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[中篇] 在[上篇]中,我们分别站在消息交换和编程的角度介绍了SOAP Fault和FaultException异常.在服务执行过 ...
- OpenGL学习进程(12)第九课:矩阵乘法实现3D变换
本节是OpenGL学习的第九个课时,下面将详细介绍OpenGL的多种3D变换和如何操作矩阵堆栈. (1)3D变换: OpenGL中绘制3D世界的空间变换包括:模型变换.视图变换.投影变换和视口 ...
- OpenGL学习进程(11)第八课:颜色绘制的详解
本节是OpenGL学习的第八个课时,下面将详细介绍OpenGL的颜色模式,颜色混合以及抗锯齿. (1)颜色模式: OpenGL支持两种颜色模式:一种是RGBA,一种是颜色索引模式. R ...
- OpenGL学习进程(10)第七课:四边形绘制与动画基础
本节是OpenGL学习的第七个课时,下面以四边形为例介绍绘制OpenGL动画的相关知识: (1)绘制几种不同的四边形: 1)四边形(GL_QUADS) OpenGL的GL_QUADS图 ...
- OpenGL学习进程(8)第六课:点、边和图形(三)绘制图形
本节是OpenGL学习的第六个课时,下面介绍OpenGL图形的相关知识: (1)多边形的概念: 多边形是由多条线段首尾相连而形成的闭合区域.OpenGL规定,一个多边形必须是一个“凸多边形”. ...
- OpenGL学习进程(7)第五课:点、边和图形(二)边
本节是OpenGL学习的第五个课时,下面介绍OpenGL边的相关知识: (1)边的概念: 数学上的直线没有宽度,但OpenGL的直线则是有宽度的.同时,OpenGL的直线必须是有限长度,而不是像数学概 ...
- OpenGL学习进程(6)第四课:点、边和图形(一)点
本节是OpenGL学习的第四个课时,下面介绍OpenGL点的相关知识: (1)点的概念: 数学上的点,只有位置,没有大小.但在计算机中,无论计算精度如何提高,始终不能表示一个无穷小的点 ...
- OpenGL学习进程(5)第三课:视口与裁剪区域
本节是OpenGL学习的第三个课时,下面介绍如何运用显示窗体的视口和裁剪区域: (1)知识点引入: 1)问题现象: 当在窗体中绘制图形后,拉伸窗体图形形状会发生变化: #include ...
- OpenGL学习进程(4)第二课:绘制图形
本节是OpenGL学习的第二个课时,下面介绍如何用点和线来绘制图形: (1)用点的坐标来绘制矩形: #include <GL/glut.h> void display(void) ...
随机推荐
- d-规则
[问题描述]对任意给定的m(m∈N+)和n(n∈N+),满足m<n,构造一初始集合:P={x|m≤x≤n,x∈N+} (m,n≤100).现定义一种d规则如下:若存在a∈P,且存在K∈N+ ,K ...
- HDU5509 : Pattern String
只要求出两个字符串的最小表示,然后就可以判断是否循环同构. 枚举最小表示的开头在哪个位置,然后求出Hash值,如果两个串的Hash值集合有交,那么说明循环同构. 因为串经过压缩,原串的长度很大,不能直 ...
- linux shell basic command
Learning basic Linux commands Command Description $ ls This command is used to check the contents of ...
- JAVA_javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
tomcat访问https请求返回: javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name at sun.se ...
- redis 的理解
1.Redis使用 C语言开发的.Redis 约定此版本号,为偶数的版本是稳定版(如:2.4版 2.6版),奇数版是非稳定版(如:2.5版 2.7版) 2.Redis 数据库中的所有的数据都存储在内存 ...
- JAVA基础整理-集合篇(一)
集合作为JAVA的基础知识,本来感觉自己理解的很清楚了,但是在最近的一次面试中还是答得不尽如人意!再次做一下整理,以便加深理解以及随时查阅. 首先,java.util包中三个重要的接口及特点:List ...
- 使用nodeJs安装Vue-cli
TIP:win10下安装,使用管理员身份进行,否则会有权限限制. 1,安装完成node,node有自带的npm,可以直接在cmd中,找到nodeJs安装的路径下,进行命令行全局安装vue-cli.(n ...
- 控制ASP.NET Web API 调用频率
很多的api,例如GitHub’s API 都有流量控制的做法.使用速率限制,以防止在很短的时间量客户端向你的api发出太多的请求.例如,我们可以限制匿名API客户端每小时最多60个请求,而我们可以让 ...
- HTML5- Canvas入门(一)
周老虎落网的时候,网易跟腾讯都推出了牛逼轰轰的HTML5页面来展示其关系网(网易http://news.163.com/special/data_zyk/ ,腾讯http://news.qq.com ...
- 《HiWind企业快速开发框架实战》(1)框架的工作原理
<HiWind企业快速开发框架实战>(1)框架的工作原理 1.HiWind架构 HiWind的基本架构如下: 持久层部分:同时为框架本身的业务服务,也为开发人员的自定义业务服务. 逻辑层: ...