Bresenham直线算法
首先针对斜率[0,1]的直线。
原理:保证从左往右,从下往上画。每次往右走一个单位准备取像素点;取像素点之前,判断是否需往上走一个单位再取。这里用变量e来做判断。
e初始是-dx,e更新的时候+2dy,然后再判断,要上移就-2dx。这是最终的e,它的演变过程如下。
初始
利用中点判断,把x(针对像素,必为整数)代入直线方程,得到的y的小数部分若>=0.5,像素点上移。
当然不必真的用直线方程计算。每当x+1,有y+k。y同样针对像素,必为整数。引入d来代替方程结果y,用d与0.5比较。
每次x++,d+=k,d就走到这条直线的下一判定处,大于0.5,y++,d-1。
(d代表实际y的小数部分,那么每次y++,有d-1,确保d的值可以直接和0.5比,而不是以后要和1.5,2.5,3.5…)
小升级
把和0.5比较的d,下移0.5,换成e来表示。d一开始是0,那么e一开始是-0.5,e的所有计算变化,与d相同,只是变成与0对比
大升级
刚才,e的相关方程有: e=-0.5, e=e+k,e=e-1
所有方程,两边乘以2Δx。
2eΔx=-Δx 2eΔx=2eΔx+2kΔx=2eΔx+2Δy 2eΔx=2eΔx-2Δx
把所有2eΔx换成e
e=-Δx e=e+2Δy e=e-2Δx
斜率[0,1]的直线,OVER
第一步,要保证x0不会在x1的右边。
斜率无穷大(竖线,x0=x1),从下往上画。
其他的斜率情况,都往斜率[0,1]转换。
1.斜率[-1,0),即y1低于y0。那么,一镜像,就变成了斜率[0,1]的模样,终点换成镜像点。实际取点则取(x, 2 * y0 - y),换回来。
2.斜率大于1,同时交换起点终点各自的x和y,如(1,3)to(2,5)变成(3,1)to(5,2),交换后就能直接用斜率[0,1]的逻辑处理像素取点前的判定,实际取点则取(y,x),换回来。如计算取(4,2),实际取(2,4)。
3.斜率小于-1,交换xy,此时是右下到左上,再交换两点位置,变成斜率[-1,0)的情况。对号入座,替换符号就行。
Bresenham直线_代码
#pragma region Line
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 = 0; i <= dx; i++) {
switch (flag) {
case 0:
glVertex2i(x, y); break;
case 1:
glVertex2i(x, 2 * y0 - y); break;//增量为(y-y0)则,实际增量应取相反方向为y0-(y-y0)=2y0-y
case 2:
glVertex2i(y, x); break;//这里也要交换
case 3:
glVertex2i(2 * y0 - y, x); break;
}
x++;
e = e + 2 * dy;
if (e >= 0) {
y++;
e = e - 2 * dx;
}
}
}
void Swap(int *a, int *b) {
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
void BresenhamLine(int x0, int y0, int x1, int y1) {
int i;
if (x0 > x1) {//总是从左端开始
Swap(&x0, &x1);
Swap(&y0, &y1);
}
if (x0 == x1) {
if (y0 > y1) {//从y0开始“往上”画,所以不允许y1更低;
Swap(&y0, &y1);
}
for (i = y0; i < y1; i++) {
glVertex2i(x0, i);
}
return;
}
float k = (1.0*(y1 - y0)) / (x1 - x0);//根据斜率调整取点
if (0 <= k && k <= 1) { //直接画
drawOrdinayLine(x0, y0, x1, y1, 0);
} else if (-1 <= k && k < 0) {//以y=y0作B点的对称点
drawOrdinayLine(x0, y0, x1, y1 + 2 * (y0 - y1), 1);
} else if (k > 1) {//交换x,y的坐标值。
Swap(&x0, &y0);//可以直接交换传入函数的参数,这为了直观。
Swap(&x1, &y1);
drawOrdinayLine(x0, y0, x1, y1, 2);
} else if (k < -1) {
//交换xy
Swap(&x0, &y0);
Swap(&x1, &y1);
//交换两个点
Swap(&x0, &x1);
Swap(&y0, &y1);
drawOrdinayLine(x0, y0, x1, y1 + 2 * (y0 - y1), 3);
}
}
#pragma endregion
直线算法部分OVER
另外,不觉得这个算法比起中点画线法有哪里提升,不过增量计算是更简单了。
基本二维变换
这就写一下缩放、平移、旋转。
所有的基本二维变换,用矩阵的乘法就可以完成,只需知道该给变换矩阵赋什么样的值。
初始点的向量矩阵,与变换矩阵相乘,得到变换后的结果。
书上的公式用的是列向量。若用行向量的话只需要把变换矩阵转置,然后交换左右,得到的结果也是行向量。一定要注意左右。
这里都是针对(0,0)原点来运算的。而要针对任意点变换,书上介绍的方法是:平移到原点,变换,平移回去。
若要针对不同方向缩放,而非沿着xy轴缩放,同理:旋转一个角度(根据需求方向),针对xy轴缩放,旋转回去。
先执行的,为左。
连续变换也很简单,如平移/旋转,变换矩阵是可加的,把平移量或者旋转角度相加即可,连续缩放则将缩放因子相乘。
这里用一个五角星验证。
Point a, b, c, d, e;
a.x = 60 - 20 * sin(36 * 3.14 / 180); a.y = 60 - 20 * cos(36 * 3.14 / 180);
b.x = 60; b.y = 80;
c.x = 60 + 20 * sin(36 * 3.14 / 180); c.y = 60 - 20 * cos(36 * 3.14 / 180);
d.x = 60 - 20 * cos(18 * 3.14 / 180); d.y = 60 + 20 * sin(18 * 3.14 / 180);
e.x = 60 + 20 * cos(18 * 3.14 / 180); e.y = 60 + 20 * sin(18 * 3.14 / 180);
全部代码
#include <GL/glut.h>
#include <stdio.h>
#include <math.h>
#include <iostream>
using namespace std;
float deltax, deltay;
float intervalx, intervaly;
float _angle;
struct Point {
int x;
int y;
};
/*采用行向量矩阵,把P'=M*P的变换矩阵M和列向量P转置,CT=(AB)T=BT*AT,得到的结果也是行向量*/
void MultiplyMatrixAB(int a[3][3], float b[3][3], float c[3][3], int row) {
int i, j, k, sum;
for (i = 0; i < row; i++) {
for (k = 0; k < 3; k++) {
sum = 0;
for (j = 0; j < 3; j++)
sum += a[i][j] * b[j][k];
c[i][k] = sum;
}
}
}
void Scaling2D(Point shape[], int n, Point Rshape[]) {
/*这里是左边P乘以右边S,左边是仅有一行,相当于是把计算设计时的P'=S*P的P转置后放在左边。
右边S也是相当于转置过的矩阵,在这无法体现。旋转变化时,rotate[0][1]就取正sin,rotate[1][0]=-sin
用列向量的公式是第一行第二列取-sin,第二行第一列取sin。只要正确转置就行。*/
float scale[3][3] = { 1,0,0,0,1,0,0,0,1 };
scale[0][0] = deltax;
scale[1][1] = deltay;
int pnt[1][3] = { 0,0,1 };
int i;
float result[1][3] = { 0 };
for (i = 0; i < n; i++) {//有n个点要变换,循环n次
pnt[0][0] = shape[i].x;//shape存储了变形前的坐标,每个点依次变换
pnt[0][1] = shape[i].y;
MultiplyMatrixAB(pnt, scale, result, 1);//数组的值存在堆中,数组传递给函数的变量是一个指针,被调函数内通过指针直接改变存储的值
Rshape[i].x = result[0][0];//Rshape存储了变形后的坐标
Rshape[i].y = result[0][1];
}
}
void Translation2D(Point shape[], int n, int deltax, int deltay, Point Rshape[]) {
float trans[3][3] = { 1,0,0,0,1,0,0,0,1 };
trans[2][0] = deltax;//同样,相比于笔记的公式,转个置。
trans[2][1] = deltay;
int pnt[1][3] = { 0,0,1 };
int i;
float result[1][3] = { 0 };
for (i = 0; i < n; i++) {
pnt[0][0] = shape[i].x;
pnt[0][1] = shape[i].y;
MultiplyMatrixAB(pnt, trans, result, 1);
Rshape[i].x = result[0][0];
Rshape[i].y = result[0][1];
}
}
void Rotation2D(Point shape[], int n, float angle, Point Rshape[]) {
angle *= 3.14 / 180;
float rotate[3][3] = { 0,0,0,0,0,0,0,0,1 };
rotate[0][0] = rotate[1][1] = cos(angle);
rotate[0][1] = sin(angle);
rotate[1][0] = -rotate[0][1];//取负肯定比再sin一次快。
int pnt[1][3] = { 0,0,1 };
int i;
float result[1][3] = { 0 };
for (i = 0; i < n; i++) {
pnt[0][0] = shape[i].x;
pnt[0][1] = shape[i].y;
MultiplyMatrixAB(pnt, rotate, result, 1);
Rshape[i].x = result[0][0];
Rshape[i].y = result[0][1];
}
}
/*Bresenham画线法*/
#pragma region Line
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 = 0; i <= dx; i++) {
switch (flag) {
case 0:
glVertex2i(x, y); break;
case 1:
glVertex2i(x, 2 * y0 - y); break;//增量为(y-y0)则,实际增量应取相反方向为y0-(y-y0)=2y0-y
case 2:
glVertex2i(y, x); break;//这里也要交换
case 3:
glVertex2i(2 * y0 - y, x); break;
}
x++;
e = e + 2 * dy;
if (e >= 0) {
y++;
e = e - 2 * dx;
}
}
}
void Swap(int *a, int *b) {
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
void BresenhamLine(int x0, int y0, int x1, int y1) {
int i;
if (x0 > x1) {//总是从左端开始
Swap(&x0, &x1);
Swap(&y0, &y1);
}
if (x0 == x1) {
if (y0 > y1) {//从y0开始“往上”画,所以不允许y1更低;
Swap(&y0, &y1);
}
for (i = y0; i < y1; i++) {
glVertex2i(x0, i);
}
return;
}
float k = (1.0*(y1 - y0)) / (x1 - x0);//根据斜率调整取点
if (0 <= k && k <= 1) { //直接画
drawOrdinayLine(x0, y0, x1, y1, 0);
} else if (-1 <= k && k < 0) {//以y=y0作B点的对称点
drawOrdinayLine(x0, y0, x1, y1 + 2 * (y0 - y1), 1);
} else if (k > 1) {//交换x,y的坐标值。
Swap(&x0, &y0);//可以直接交换传入函数的参数,这为了直观。
Swap(&x1, &y1);
drawOrdinayLine(x0, y0, x1, y1, 2);
} else if (k < -1) {
//交换xy
Swap(&x0, &y0);
Swap(&x1, &y1);
//交换两个点
Swap(&x0, &x1);
Swap(&y0, &y1);
drawOrdinayLine(x0, y0, x1, y1 + 2 * (y0 - y1), 3);
}
}
#pragma endregion
void Transform2DSegment() {
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0.0, 1.0, 1.0);
glBegin(GL_POINTS);
//绘制带小箭头的坐标系
BresenhamLine(10, 200, 400, 200);
BresenhamLine(395, 195, 400, 200);
BresenhamLine(400, 200, 395, 205);
BresenhamLine(200, 400, 200, 10);
BresenhamLine(200, 400, 205, 395);
BresenhamLine(200, 400, 195, 395);
//试验
Point a, b, c, d, e;
a.x = 60 - 20 * sin(36 * 3.14 / 180); a.y = 60 - 20 * cos(36 * 3.14 / 180);
b.x = 60; b.y = 80;
c.x = 60 + 20 * sin(36 * 3.14 / 180); c.y = 60 - 20 * cos(36 * 3.14 / 180);
d.x = 60 - 20 * cos(18 * 3.14 / 180); d.y = 60 + 20 * sin(18 * 3.14 / 180);
e.x = 60 + 20 * cos(18 * 3.14 / 180); e.y = 60 + 20 * sin(18 * 3.14 / 180);
Point shape[5] = { a,b,c,d,e };
Point Rshape[5] = { 0 };
Scaling2D(shape, 5, Rshape);
Translation2D(Rshape, 5, intervalx, intervaly, Rshape);
Rotation2D(Rshape, 5, _angle, Rshape);
//到第一象限显示
for (int i = 0; i < 5; i++) {
shape[i].x = 200 + shape[i].x;
Rshape[i].x = 200 + Rshape[i].x;
shape[i].y = 200 + shape[i].y;
Rshape[i].y = 200 + Rshape[i].y;
}
//输出结果
for (int i = 0; i < 4; i++) {
BresenhamLine(shape[i].x, shape[i].y, shape[i + 1].x, shape[i + 1].y);
BresenhamLine(Rshape[i].x, Rshape[i].y, Rshape[i + 1].x, Rshape[i + 1].y);
}
BresenhamLine(shape[0].x, shape[0].y, shape[4].x, shape[4].y);
BresenhamLine(Rshape[0].x, Rshape[0].y, Rshape[4].x, Rshape[4].y);
glEnd();
glFlush();
}
void main(int argc, char **argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
cout << "输入X轴与Y轴的缩放因子" << endl;
cin >> deltax >> deltay;
cout << "输入X轴与Y轴的坐标平移量" << endl;
cin >> intervalx >> intervalx;
cout << "输入旋转角度-360~360" << endl;
cin >> _angle;
glutInitWindowPosition(50, 100);
glutInitWindowSize(500, 400);
glutCreateWindow("变,都可以变");
glClearColor(0.5, 0.5, 0.5, 0);
glMatrixMode(GL_PROJECTION);
gluOrtho2D(0.0, 500.0, 0.0, 500.0);
glutDisplayFunc(Transform2DSegment);
glutMainLoop();
}