昨天晚上動手寫了橢圓的光柵化實現,照著計算機圖形學書上的偽代碼編寫了 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);
}