绘制一条颜色渐变的直线,直线上每一个点的颜色都来自端点颜色的线性插值。线性插值公式为
P
=
(
1
?
t
)
P
s
t
a
r
t
+
t
P
e
n
d
P
是直线上任意一个点,
P
s
t
a
r
t
是直线的起点,
P
e
n
d
是直线的终点
对应直线上任意一点的颜色有
c
=
(
1
?
t
)
c
s
t
a
r
t
+
t
c
e
n
d
P=(1-t)P_{start}+tP_{end}\\ P是直线上任意一个点,P_{start}是直线的起点,P_{end}是直线的终点\\对应直线上任意一点的颜色有\\c=(1-t)c_{start}+tc_{end}
P=(1?t)Pstart?+tPend?P是直线上任意一个点,Pstart?是直线的起点,Pend?是直线的终点对应直线上任意一点的颜色有c=(1?t)cstart?+tcend?
根据之前学的直线光栅化算法可知,绘制直线的时候是有主位移方向的,如果主位移方向是x,有
x
=
(
1
?
t
)
x
s
t
a
r
t
+
t
x
e
n
d
x=(1-t)x_{start}+tx_{end}
x=(1?t)xstart?+txend?如果主位移方向是y有
y
=
(
1
?
t
)
y
s
t
a
r
t
+
t
y
e
n
d
y=(1-t)y_{start}+ty_{end}
y=(1?t)ystart?+tyend?。带入到上面的公式有
c
=
x
e
n
d
?
x
x
e
n
d
?
x
s
t
a
r
t
?
c
s
t
a
r
t
+
x
?
x
s
t
a
r
t
x
e
n
d
?
x
s
t
a
r
t
?
c
e
n
d
或者
c
=
y
e
n
d
?
y
y
e
n
d
?
y
s
t
a
r
t
?
c
s
t
a
r
t
+
y
?
y
s
t
a
r
t
y
e
n
d
?
y
s
t
a
r
t
?
c
e
n
d
c=\frac{x_{end}-x}{x_{end}-x_{start}}\cdot c_{start}+\frac{x-x_{start}}{x_{end}-x_{start}}\cdot c_{end}或者c=\frac{y_{end}-y}{y_{end}-y_{start}}\cdot c_{start}+\frac{y-y_{start}}{y_{end}-y_{start}}\cdot c_{end}
c=xend??xstart?xend??x??cstart?+xend??xstart?x?xstart???cend?或者c=yend??ystart?yend??y??cstart?+yend??ystart?y?ystart???cend?
综合来看就是下面的代码
CRGB CLine::LinearInterP(double t, double tStart, double tEnd, CRGB cStart, CRGB cEnd)
{
CRGB color;
color = (tEnd - t) * cStart / (tEnd - tStart) + (t - tStart) * cEnd / (tEnd - tStart);
return color;
}
在画直线的时候调用这个算法就可以画出颜色渐变的直线,这里以Bresenham算法为例。
#define COLOR(x) RGB(x.m_red*255,x.m_green*255,x.m_blue*255)
void CLine::DrawLine(CDC* pDC)
{
int dx = abs(m_p1.x - m_p0.x);//m_p0,m_p1(CPoint2)
int dy = abs(m_p1.y - m_p0.y);
double k = (double)(dy) / (double)(dx); //斜率
BOOL wayChange = FALSE;//主方向是否发生改变,默认是x方向
int e, mainway, subway;
e = -dx;
mainway = dx;
subway = dy;
int addx, addy;
addx = (m_p1.x > m_p0.x) ? 1 : ((m_p1.x < m_p0.x) ? -1 : 0);
addy = (m_p1.y > m_p0.y) ? 1 : ((m_p1.y < m_p0.y) ? -1 : 0);
if (dy > dx) {//主方向是y
mainway = dy;
subway = dx;
wayChange = TRUE;
}
CPoint2 p = m_p0;
for (int i = 0; i <= mainway; i++) {
pDC->SetPixel(p.x,p.y, COLOR(p.c));
if (wayChange) {
p.c = LinearInterP(p.y,m_p0.y,m_p1.y,m_p0.c,m_p1.c);
p.y += addy;
}
else {
p.c = LinearInterP(p.x, m_p0.x, m_p1.x, m_p0.c, m_p1.c);
p.x += addx;
}
e += 2 * subway;
if (e >= 0) {
if (wayChange)
p.x += addx;
else
p.y += addy;
e -= 2 * mainway;
}
}
}
直线扫描转换算法在处理非水平,非垂直和非45度直线会出现如图所示的锯齿或台阶转边界,这样的现象称为走样,走样只能减轻但不能避免。
Wu反走样算法是以Bresenham算法为基础对距离进行加权的一种算法。
如果采用Bresenham算法,那么显示的是点D,对于Wu反走样算法,理想直线上的点C的上下两个可能近似点都要显示,但是亮度不同。一个像素点距离理想直线距离越远,该像素点的颜色就越接近背景色,亮度就越大;一个像素点距离理想直线越近该像素点的颜色就越接近理想直线,亮度就越小。
例如点C和点D的距离为e,那么点D的亮度就是e,点E的亮度就是1-e。
算法中e的递推式就是Bresenham算法中距离误差d的递推式。
主位移方向每递增一个单位有
e
i
+
1
=
e
i
+
k
当
e
i
+
1
≥
1.0
时
e
i
+
1
要减
1
e_{i+1}=e_i+k当e_{i+1}\geq1.0时e_{i+1}要减1
ei+1?=ei?+k当ei+1?≥1.0时ei+1?要减1。
void CALine::DrawLine(CDC* pDC)
{
int dx = abs(m_p1.x - m_p0.x);//m_p0,m_p1(CPoint)
int dy = abs(m_p1.y - m_p0.y);
double k = (double)(dy) / (double)(dx); //斜率
BOOL wayChange = FALSE;//主方向是否发生改变,默认是x方向
int mainway, subway;
double e = 0.0;
mainway = dx;
subway = dy;
int addx, addy;
addx = (m_p1.x > m_p0.x) ? 1 : ((m_p1.x < m_p0.x) ? -1 : 0);
addy = (m_p1.y > m_p0.y) ? 1 : ((m_p1.y < m_p0.y) ? -1 : 0);
if (dy > dx) {//主方向是y
mainway = dy;
subway = dx;
wayChange = TRUE;
}
CPoint2 p = m_p0;
for (int i = 0; i <= mainway; i++) {
CRGB c0(e, e, e);
CRGB c1(1 - e, 1 - e, 1 - e);
if (wayChange) {
pDC->SetPixelV(p.x + addx, p.y, COLOR(c1));
pDC->SetPixelV(p.x, p.y, COLOR(c0));
}
else {
pDC->SetPixelV(p.x, p.y + addy, COLOR(c1));
pDC->SetPixelV(p.x, p.y, COLOR(c0));
}
if (wayChange) {
p.y += addy;
}
else {
p.x += addx;
}
e += (double)dy / dx;
if (e >= 0.0) {
if (wayChange)
p.x += addx;
else
p.y += addy;
e -= 1.0;
}
}
}
彩色的那条线是Bresenham算法画的直线,黑色的那条线是Wu反走样算法画的直线,将图片稍微放大就可以看到区别(一个比较粗糙,一个比较光滑)。