昨天晚上动手写了椭圆的光栅化实现,照着计算机图形学书上的伪代码编写了 C++ 的代码,结果运行结果完全出乎我的意料之外。
画出来的椭圆居然像这个样子:
这实在是不像椭圆了,呵呵。虽然说在网上看到有说 Bresenham 算法有一定的失真?但也不至于成这样啊。于是反复地 check 伪代码与我写的 C++ 代码,感觉算法上我的“翻译”应该是没有问题的。不会是这伪码有问题?所以是翻回前面反复地阅读和理解这个算法的原理,对照原理的公式和伪代码的表达,也没有问题啊,难道公式有问题?只是硬着头皮自己再推导一遍。还是没发现问题所在。头大了,一直折腾到 2 点多,实在是没找出问题在哪,想想第二天还得上班,没办法只好放下。心想第二天在代码中加入一些输出,把计算结果都输出来进行 check。
第二天中午休息的时候,在网上看到一个代码的实现,拷下来运行,虽然那份代码也有问题,但至少有一半的椭圆弧看起来是相当正常的。另一半没画正确,也是因为斜率为 -1 的判断有问题。再对照看了看我的代码,赫然发现,算法中用于存储决定下一点的选择策略的变量 d,在网上的代码用的是 int 型,而我自己则用的自定义的 INT16。难道是 INT16 太小导致的?于是我改成 INT32,一运行,正常了,虽然有些走样,但椭圆还是比较漂亮的。原来问题出在这里。INT16 的范围太小,而计算结果是 32 位的,截取成 16 位正负号就乱套了,唉,教训啊。OK 后的效果图如下:
记下来,给自己提个醒!
附上椭圆的生成代码,Bresenham算法:
void Draw2DLine::DrawEllipse(Point const& a_Center, UINT16 a_a, UINT16 a_b)
{
UINT16 x = 0, y = a_b;
UINT32 const taa = a_a*a_a;
UINT32 const tbb = a_b*a_b;
INT32 minYofDeltaX = static_cast<INT32>(tbb/sqrt(static_cast<double>(tbb + taa)));
INT32 p = tbb - taa*a_b; // 就是这个变量!
while( minYofDeltaX <= y)
{
DrawPoint(a_Center.x+x, a_Center.y+y);
DrawPoint(a_Center.x+x, a_Center.y-y);
DrawPoint(a_Center.x-x, a_Center.y+y);
DrawPoint(a_Center.x-x, a_Center.y-y);
if( p <= 0)
{
++x ;
}
else
{
++x;
--y;
}
p = tbb*(x+1)*(x+1) + taa*(y*y - y) - taa*tbb;
}
p = tbb*(x*x + x) + taa*(y*y - y) - taa*tbb;
while(y > 0)
{
DrawPoint(a_Center.x+x, a_Center.y+y);
DrawPoint(a_Center.x+x, a_Center.y-y);
DrawPoint(a_Center.x-x, a_Center.y+y);
DrawPoint(a_Center.x-x, a_Center.y-y);
if(p >= 0)
{
--y;
p = p - 2*taa*y - taa;
}
else
{
--y;
++x;
p = p - 2*taa*y - taa + 2*tbb*x + 2*tbb;
}
}
DrawPoint(a_Center.x+x, a_Center.y);
DrawPoint(a_Center.x-x, a_Center.y);
}