屏幕是有若干像素点组成的,任何图形画到屏幕上都要先转为像素点,也就是光栅化,下面模拟直线的光栅化。
像素的最小单位为1,且为整数,所以在直线上只能取在X轴或Y轴上步进为1的最接近的整数点,至于是X轴还是Y轴取决于跨度大的轴,假设直线起点为(x1,y1),终点为(x2,y2) ,如果|x2-x1|>=|y2-y1|,即斜率位于-1和1之间,则X轴每次步进一个像素,否则Y轴。
下面的代码模拟几种光栅化直线的算法,为了方便假设x2>x1>0,y2>y1>0,且斜率位于0到1之间,win32编程中窗口的XY轴正方向分别向右向下,与平常的坐标系不太一样,这里以Windows窗口坐标为准。画直线的时候端点也有可能出现浮点数,所以输入点都使用float类型。在测试下面代码之前首先创建一个Windows桌面应用程序。
1、通过公式直接计算
下面的函数DrawLine_1在X方向步进1,Y值通过直线的斜截公式算出
void DrawLine_1(HDC hdc, float x1, float y1, float x2, float y2)
{
//转为斜截式,y=k*x+b,分求出k和b
float dx = x2 - x1;
float dy = y2 - y1;
float k = dy / dx;
float b = y1 - k * x1;
int num = dx;//获取点的个数
int x = round(x1);
int y = round(x * k + b);
for (int i = 0; i <= num; i++)
{
::SetPixel(hdc, x, y, RGB(255, 0, 0));
x += 1;
y = round(x * k + b);
}
}
x值只要在第一次取x1最接近的整数,然后使用整数加1即可,y值每次都要用round函数取整数,取整数还可以用y=x * k + b+0.5的形式,这种方式每次都要使用浮点数的乘法和加法,效率太低。因为x每次加1,y轴增加的值是固定的k,所以只要在前一个准确y值上加k就可以算出当前准确的y值,然后取整,即DDA算法。
2、DDA算法(Digital Differential Analyzer数值微分法)
DrawLine_2函数中fy为当前x对应的准确的y值,x每次加1,fy的值每次增加斜率k,最终y的值由fy取整
void DrawLine_2(HDC hdc, float x1, float y1, float x2, float y2)
{
float dx = x2 - x1;
float dy = y2 - y1;
int stepx = 1;
float stepy = dy / dx;
int num = round(dx);
int x = round(x1);
float fy = (x - x1) * dy / dx + y1;//(x-x1)/dx=(y-y1)/dy
int y = round(fy);
for (int i = 0; i <= num; i++)
{
::SetPixel(hdc, x, y, RGB(255, 0, 0));
x += stepx;
fy += stepy;
y = round(fy);
}
}
下面展示以三种方式画出的直线对比,黑线为系统函数LineTo画出,红线为上面两种方法画出
MoveToEx(hdc, 50, 50, nullptr);
LineTo(hdc, 150, 100);
DrawLine_1(hdc, 50, 55, 150, 105);
DrawLine_2(hdc, 50, 60, 150, 110);
DrawDDC函数针对任意输入值使用DDC算法画出直线,为了看得清楚还是以斜率在和不在-1~1之间分为两个分支,斜率不在-1到1内一般只要将xy互换即可, 代码如下
void DrawDDC(HDC hdc, float x1, f