问题
如何在平面上画直线?
本来我以为画直线会很容易,随便拿个直线公式,遍历X求Y画出来不就完了么,但事实并非如此。
由于像素都必须是位于整像素点上.所以要将来连续的直线离散化
算法1 DDA
- DDA算法主要是根据直线公式y = kx + b来推导出来的.
- 其关键之处在于如何设定单位步进,即一个方向的步进为单位步进,另一个方向的步进必然是小于1。
- 缺陷: 由于有浮点数运算与取整,该算法不利于硬件实现
// DDA 数字微分分析
if (x1 == x2&&y1 == y2) {
device_pixel(x1 + 0.5, y1 + 0.5, c);
return;
}
int dx = x2 - x1, dy = y2 - y1;
int step, k;
float xIncrement, yIncrement, x = x1, y = y1;
if (fabs(dx) > fabs(dy)) {
step = fabs(dx);
}
else {
step = fabs(dy);
}
xIncrement = (float)dx / (float)step;
yIncrement = (float)dy / (float)step;
device_pixel(x+0.5, y + 0.5, c);
for (k = 0; k < step; k++) {
x += xIncrement;
y += yIncrement;
device_pixel(x + 0.5, y + 0.5, c);
}
算法2 Bresenham算法
- Bresenham想法很简单,就是每X移动一个像素,则考虑Y应该是如何移动。
- 该算法把直线分为两种:一种是斜率<1的线,即近X轴线。另一种是斜率>1的线,即近Y轴线。
Bresenham算法的核心就是,当X加1后,如何决定Y的移动。
显然可见,近X轴直线的dy<dx。
所以一个直观的想法是,保存一个误差累计变量,每当X加1,误差变量便累计增加一个dy。
- 当累计误差小于等于dx时,Y不动,
- 当累计的误差大于dx时,Y加1,同时把累计误差减掉一个dx。
这样,算法将不停的将光栅线与实际线之间的误差减到最小。
// Bresenham 算法
int x=0, y=0, rem = 0;
if (x1 == x2&&y1 == y2) {
device_pixel(x1, y1,c);
}
else if (x1 == x2) {
int inc = (y1 <= y2) ? 1 : -1;
for (y = y1; y != y2; y += inc) device_pixel(x, y,c);
device_pixel( x2, y2, c);
}
else if (y1 == y2) {
int inc = (x1 <= x2) ? 1 : -1;
for (x = x1; x != x2; x += inc) device_pixel(x, y, c);
device_pixel(x2, y2, c);
}
else {
// 判断直线是近x轴还是近y轴
int dx = (x1 < x2) ? x2 - x1 : x1 - x2;
int dy = (y1 < y2) ? y2 - y1 : y1 - y2;
if (dx >= dy) {
// 靠近x轴
if (x2 < x1) x = x1, y = y1, x1 = x2, y1 = y2, x2 = x, y2 = y;
for (x = x1, y = y1; x <= x2; x++) {
device_pixel(x, y, c);
rem += dy;
if (rem >= dx) {
rem -= dx;
y += (y2 >= y1) ? 1 : -1;
device_pixel(x, y, c);
}
}
device_pixel(x2, y2, c);
}
else {
// 靠近y轴
if(y2<y1) x = x1, y = y1, x1 = x2, y1 = y2, x2 = x, y2 = y;
for (x = x1, y = y1; y <= y2; y++) {
device_pixel(x, y, c);
rem += dx;
if (rem >= dy) {
rem -= dy;
x+=(x2 >= x1) ? 1 : -1;
device_pixel(x, y, c);
}
}
device_pixel(x2, y2, c);
}
}