直线在格化中进行扫描转换时,输入为A,B两点坐标,输出需为这条直线最贴合的中间点的坐标。
数值微分法(DDA):
因为直线的一阶导数是连续的,且 △ X和 △ Y是成比例的,所以我们可以在直线上无限加上无限小的 ε X和 ε Y来确认点。
当精度无限高时,可以绘制出毫无误差的直线,但设备的精度是有限的,没有必要那么精确。
为了保证栅格化直线的连续,我们选择 ε =1/max(|△x|,|△y|),即取跨度最大的X或Y的值来作为首先前进方向。
此时分为两种情况:
1.直线的斜率|k|<1:
此时|△y|>|△x|,所以我们选择y方向来作为ε的取值。
{
- y1 = y ±1
- x1 = x±1/k
}
2.直线的斜率|k|>1:
此时|△x|>|△y|,所以我们选择x方向来作为ε的取值。
{
- x1 = x±1
- y1 = y±k
}
代码实现则为:
private void DrawLine_Rasterization(Vector2 A, Vector2 B)
{
Graphics g = this.CreateGraphics();
Pen pen = new Pen(Color.Red, 1);
Pen linepen = new Pen(Color.Yellow);
var k = (B.Y - A.Y) / (B.X - A.X);
if (Math.Abs(k)>=1)
{
var x0 = A.X;
var y0 = A.Y;
while (y0 <B.Y)
{
var y1 = y0 + 1f;
var x1 = x0 + (1 / k);
var roundx = Convert.ToInt32(x1);
Point pos = MoveToCoordinate(new Vector2(roundx, y1));
g.DrawRectangle(pen, pos.X - coord, pos.Y - coord, coord, coord);
g.DrawLine(linepen, MoveToCoordinate(new Vector2(x0, y0)), pos);
x0 = x1;
y0 = y1;
}
}
else
{
var x0 = A.X;
var y0 = A.Y;
while (y0 < B.Y)
{
var y1 = y0 + k;
var x1 = x0 + 1f;
var roundy = Convert.ToInt32(y1);
Point pos = MoveToCoordinate(new Vector2(x1, roundy));
g.DrawRectangle(pen, pos.X - coord, pos.Y - coord, coord, coord);
g.DrawLine(linepen, MoveToCoordinate(new Vector2(x0, y0)), pos);
x0 = x1;
y0 = y1;
}
}
}
此算法优点:直观,容易实现
缺点:有浮点数和浮点运算,性能损耗大
Bresenham中点算法
给与的任何直线,我们都可以用 y=kx+b的方式来表示。
即,在平面坐标中,对于任何一点(x,y),可以用F(x,y)来判断点与直线的位置关系。
F(x,y)>0则点在直线上方
F(x,y)=0则在直线上
F(x,y)<0则在直线下方
首先以k<1的时候来推算,会比较方便
中点Bresenham算法的基本原理:
和数值微分法DDA相似,都会使用k与1的大小关系来判断向x方向还是向y方向扫描。
为了讨论方便,首先假设0<k<1。
如图,在向X方向扫描后,可以检测到F(x+1)位于pu和pd之间,Bersenham引入了pu和pd的中点M(x + 1,F(x) + 0.5),来作为判断该像素绘制在pd点或是pu点的依据。
即,若|pu-Q|>|pd-Q| (M点在直线上方)则取pu,反之(M点在直线下方)则取pd。
M点的坐标为(x+1,y+0.5),将此坐标带入F(x,y)则可以取出点M与直线的位置关系:
d = y+0.5-k(x+1) - b
即,当d<0时,M在直线下方,我们取上方的点pu,则y1=y+1
当d>0时,M在直线上方,我们取下方的点pd,则y1=y
d=0时,M在直线中,取哪个都行。
误差项递推
由于像素点和数据点之间会有误差,所以我们必须使用上一次得出的d来规避误差
为了讨论方便,首先假设0<=k<=1。
此时的误差项递推也需要分为两种情况
1.上一次的d<0时:
我们在上一次扫描中,因为d<0,进行了y1 = y + 1的操作,则这一次的判断中,M点的坐标为(x+2,y+1.5)
此时将公式代入可得出:
d = y+1.5 - k(x+2) - b
和上面得出的上一轮M的坐标对比可得到d的增量为:
△ d = 1 - k
2.上一次的d>0时:
和1同理,可得出M点的坐标为(x+2,y+0.5)
此时将公式代入:
d = y+0.5 - k(x+2) - b
可得出d的增量为:
△d = - k
d的初值
为了讨论方便,首先假设0<=k<=1。
d的初值为从初始点扫描起的第一个M点,即(x+1,y+0.5)
公式代入得:
d = 0.5-k
从这里就可以对运算中的浮点数计算进行优化,这就是比DDA灵活的地方:
我们只运用到了d的符号,并没有运用过d的实际大小,所以我们把d放大任意整数倍,仍然不会影响到我们计算的结果。
对于初值 d=0.5-k,我们把d放大2倍
对于所有的k,已知k=△y/△x,x与y均为整数
我们可以放大△x倍
则d的初值:
d =△x -2△y
d<0时的增量:
△d = 2△x-2△y
d>0时的增量:
△d = -2△y
可以看到,我们忽略了k<0和k>1的情况,这个情况我们只需要把坐标系旋转到满足0<k<1即可
代码:
var k = ((float)B.Y - (float)A.Y) / ((float)B.X - (float)A.X);
var dx = Math.Abs(B.X - A.X);var dy = Math.Abs(B.Y - A.Y);
int startd = 0;int lessd = 0;int mored = 0;
bool swichxy = false;
int yoffset = 1;
if (dy>dx)
{
var temp = A.Y;A.Y = A.X;A.X = temp;
temp = B.Y;B.Y = B.X;B.X = temp;
dx = Math.Abs(B.X - A.X); dy = Math.Abs(B.Y - A.Y);
swichxy = true;
}
//交换AB,确保B的X比A大
if (A.X > B.X)
{
var temp = A;
A = B;
B = temp;
}
if (B.Y <= A.Y)
{
yoffset = -1;
}
var x0 = A.X; var y0 = A.Y;
startd = dx - 2 * dy;
lessd = 2 * dx - 2 * dy;
mored = -2 * dy;
while (x0 < B.X)
{
var x1 = x0 + 1;
var y1 = y0 + (startd < 0 ? yoffset : 0);
startd = startd + (startd < 0 ? lessd : mored);
Point pos = MoveToCoordinate(new Vector2(x1, y1));
if (swichxy)
{
pos = MoveToCoordinate(new Vector2(y1, x1));
}
g.DrawRectangle(pen, pos.X - coord, pos.Y - coord, coord, coord);
//g.DrawLine(linepen, MoveToCoordinate(new Vector2(x0, y0)), pos);
x0 = x1;
y0 = y1;
}
Bresenham算法
在bresenham中点算法中,我们引入了中点,候选点,候选点中点,只是为了引入误差项这个概念.
我们可以直接一点,直接引入更简洁的误差项:
通过d与0.5的比较,可以达到和中点Bresenham中D相同的答案
此时
d0=0
Δd=k
x1=x+1
d>0.5时y1=y+1 此时d=d-1
d<0.5时y1=y
d=0.5时,任取
这里可以看到算法中仍有浮点运算
我们令e=d-0.5,然后乘2Δx,可以消除掉这些浮点
即
e0=-Δx
Δe=k →2Δy
e>0时y1=y+1此时e = e-2Δx
e<0时y1=y
e=0时,任取
如图
代码:
var k = ((float)B.Y - (float)A.Y) / ((float)B.X - (float)A.X);
var dx = Math.Abs(B.X - A.X); var dy = Math.Abs(B.Y - A.Y);
int counter = 0;int de = 0;int e = 0;
bool swichxy = false;
int yoffset = 1;
if (dy > dx)
{
var temp = A.Y; A.Y = A.X; A.X = temp;
temp = B.Y; B.Y = B.X; B.X = temp;
dx = Math.Abs(B.X - A.X); dy = Math.Abs(B.Y - A.Y);
swichxy = true;
}
//交换AB,确保B的X比A大
if (A.X > B.X)
{
var temp = A;
A = B;
B = temp;
}
if (B.Y <= A.Y)
{
yoffset = -1;
}
var x0 = A.X; var y0 = A.Y;
counter = -2 * dx;
de = dx - 2 * dy;
e =-1*dx;
while (x0 < B.X)
{
Point pos = MoveToCoordinate(new Vector2(x0, y0));
if (swichxy)
{
pos = MoveToCoordinate(new Vector2(y0, x0));
}
g.DrawRectangle(pen, pos.X, pos.Y - coord , coord, coord);
//g.DrawLine(linepen, MoveToCoordinate(new Vector2(x0, y0)), pos);
var x1 = x0 + 1;
var y1 = y0;
e = e + 2 * dy;
//这里选择e=0时y=y
if (e > 0)
{
y1 = y0 + yoffset;
e = e - 2 * dx;
}
x0 = x1;
y0 = y1;
}