DDA 算法
DDA 算法解决这样一个问题:给定屏幕上的两个像素点,画出以这两点为端点的线段。
首先定义最左上角的像素点坐标为 (0, 0),y 轴方向向下,x 轴方向向右,于是坐标 (y, x) 表示屏幕上第 y 行,第 x 列的像素点。令输入的两个点为 A (y1, x1),B (y2, x2)。
考虑一种特殊的情况:A 在 B 的左下角,并且线段的斜率 0 < k < 1:
- 确定需要绘制的像素数量。由于斜率 k 小于 1,所以在 x ∈ [ x 1 , x 2 ] x\in[x1, x2] x∈[x1,x2] 的闭区间上,对每一个横坐标 x,找到合适的高度 y,绘制像素 (y, x),所以一共需要绘制 x2-x1+1 个像素点。我们通过变量 x 遍历这个区间来绘制线段。
- 对每一个横坐标 x,如何确定要绘制的 y?一般的做法是通过直线方程计算,但是运算量较大。假设从 A 开始,绘制的第一个像素是 A (y1, x1),下一个 x = x1 + 1,容易得到此时的 y = y1 + k,这样在循环中通过步进的方式,用加法代替了乘法。
- 这时的 y 还是浮点数, ⌊ y + 0.5 ⌋ \lfloor y+0.5\rfloor ⌊y+0.5⌋ 得到的整数即为最接近 y 的整数,这样就确定了每一次循环中要绘制的 (y, x)。
为什么斜率 0 < k < 1 的时候要对每一个 x ∈ [ x 1 , x 2 ] x\in[x1, x2] x∈[x1,x2] 绘制,而不是 y ∈ [ y 1 , y 2 ] y\in[y1, y2] y∈[y1,y2] 呢?因为如果用后面的方法,点会变得很稀疏,所以如果是斜率 k > 1 的情况,采用 y ∈ [ y 1 , y 2 ] y\in[y1, y2] y∈[y1,y2] 的区间,并用变量 y 遍历区间,来确定每一个 y 对应的 x 就可以了。
完整代码如下,drawPixel
前两个参数代表要绘制的像素坐标,第三个参数是像素的颜色。
C++ 代码
void drawLine(const int y1, const int x1, const int y2, const int x2, const Color& c) {
if (y1 == y2 && x1 == x2) {
drawPixel(y1, x1, c);
} else if (y1 == y2) {
int inc = x2 > x1 ? 1 : -1;
for (int x = x1; x != x2; x += inc)
drawPixel(y1, x, c);
} else if (x1 == x2) {
int inc = y2 > y1 ? 1 : -1;
for (int y = y1; y != y2; y += inc)
drawPixel(y, x1, c);
} else {
// 基于DDA算法
double y = y1, x = x1, dy = y2 - y1, dx = x2 - x1;
if (abs(dx) > abs(dy)) {
// x主导方向
int steps = abs(dx) + 1;
double incY = dy / abs(dx);
double incX = x2 > x1 ? 1 : -1;
for (int i = 0; i < steps; ++i, y += incY, x += incX)
drawPixel(y + 0.5, x, c);
} else {
// y主导方向
int steps = abs(dy) + 1;
double incY = y2 > y1 ? 1 : -1;
double incX = dx / abs(dy);
for (int i = 0; i < steps; ++i, y += incY, x += incX)
drawPixel(y, x + 0.5, c);
}
}
}