目录
目的
本实验基于STM32F103RC+TFTLCD屏,旨在于在LCD屏上显示一个实心圆形,然而众所周知,对于屏幕而言,我们只能操作各个像素点,因此,选择出最接近标准圆形的像素点就成了本次实验的主要目的,最终得到的圆形大概应该长这样
看起来很不规则,但是这里只是半径为5个像素点的圆形,因此放大来看并不规则,但是在显示屏上,肉眼很难区分出来
几何原理
首先,由于屏幕由像素点组成,每个像素点又有自己的位置信息,因此我们不妨将屏幕显示近似为一个坐标系,并在坐标系中作出一个标准圆形,圆心为原点(如果以后圆心改变,将所有坐标基于圆心平移即可),找出最接近圆边界的所有整数坐标(像素点),如原理图黑色部分,有了边界坐标,圆内的坐标就简单了
根据对称原理,我们可以将圆形分为八分,于是只需要求得其中任意一份中满足条件的所有点,就能根据它们推算出剩下的所有点,我们以下述原理图中蓝色部分为例
接下来进行一些几何学的分析来求取具体每个点的坐标,首先需要知道这里的坐标并不能单纯根据圆的方程求解各个坐标的准确值,然后靠四舍五入近似,因为这里的每个点的确定是来源于在圆边境内外两侧更接近圆的点,简单的四舍五入会遗失大量有效点
由于我们暂时只考虑边界,因此要先确定边界从哪开始(45°线与圆交点)到哪结束(x轴)。不难得到起点(0.707*r,r),终点(r,0)。
我们以x轴为参考轴,每单位x坐标对应得到一个最接近圆的y坐标即可。因此对于任意一个横坐标 i,我们可以从0到 r 遍历 y 坐标,直到对应坐标点( i,ym)在圆外为止。而对于其中ym值,可以用几何公式 x²+y²>r² 判断得到第一个在圆外的点,如上图中粉色部分
这里说明一下,我们寻找ym是为了判断是否已经到达圆的边界,此时( i,ym-1)和( i,ym)刚好分别位于圆的内外两侧,接下来我们只要根据点到圆的距离公式 d=|x²+y²-r²| 判断其中哪个更加接近圆,通过遍历x轴便可以获取到每个横坐标对应的边界点的纵坐标
我们通过上述方法可以得到蓝色区域内最接近圆的所有点,通过对称原理就能轻而易举的得到所有点了,如上图中绿色部分。有了整个圆上的坐标,填充出整个圆形就易如反掌了
代码优化
整个圆形显示算法的原理已经完成,利用上述原理得到一个实心圆已经可以实现了。
但是经过实验发现几个小问题需注意,分别如下
参考区域选取
在原理图的蓝色部分,我们选取1/8圆作为参考区域得到点坐标,然后利用对称原理获取整个圆形的坐标,原理的确没错,但是参考区域的选择会对结果产生一定的影响
如下图所示,按照x轴为参考轴,以此获取纵坐标,得到所有有效点(下图中绿色点)。如果照之前的方法选择0~45°区域,可以发现,有部分有效点被忽略掉了(下图中红色点)。这是由于我们遍历x轴时,每个横坐标只能对应得到一个纵坐标,因此,我们需要选择以下调整:
选择一、遍历一次横坐标得到ym值,再遍历一次纵坐标得到xm值,收集两次遍历得到的所有点(可行但效率低)
选择二、选择下图中蓝色区域为参考区域,仍然遍历横坐标得到ym值,然后进行1/8对称
为什么选择二不会遗漏有效点呢?
其实从图中就可以看出来,圆在0~45°中的表达式不是y(x)的函数,即每个x有且仅有一个y与之对应;而在45°~90°中的表达式不是x(y)的函数,但却符合y(x)函数关系,因此可以保证每个x有且仅有一个y与之对应
因此,我们在编写代码时,只需要横坐标遍历 0~0.707r 的范围即可
计算量
在原理图的粉色部分,我们为了判断圆的边界用到了一个复杂的数学表达式来计算点( i,ym-1)和( i,ym)到圆的距离,我们现在列出两个算式 d1=| i²+(ym-1)²-r² | 和 d2=| i²+ym²-r² |。且已知绝对值符号内d1为负,d2为正。(因为分别位于圆的内外两侧)
我们需要他们中的较小者作为有效点坐标,因此我们可以判断他们差值的符号 d2-d1=2i²+2ym²-2ym+1-2r² ,如果为负就选取( i,ym),反之同理。由于一般横纵坐标都是远大于1的,因此上式顺理成章简化为 D2-D1= i²+ym²-ym-r² 。同理判断符号即可
填充方式
根据原理图,我们每次循环遍历,就可以获得一个坐标,通过1/8对称就可以获得一组坐标(8个坐标)
如果仅按照横/纵向填充,执行速度很快(因为没有重复填充),但是有可能会出现缺漏情况(区域选择合适则不会出现),如左图。
因此,我们在获取到一组坐标(8个坐标)后,可通过矩形范围填充的方式执行,便可以完全填充整个圆形了,牺牲了速度,但是保证了准确性。如右图
但是,如果按上述参考区域选取的方法选择45°~90°,则建议采用横/纵向填充,几乎没有重复,执行速度最快
程序源码
//画实心圆
//x,y 圆心坐标
//r 半径
//color 填充颜色
void LCD_Draw_Fill_Circle(u16 x,u16 y,u16 r,u16 color)
{
int i,j;
u16 ym;
int xymax=r/1.414; //45°线与圆的交点坐标(取整)
for(i=0;i<=xymax;i++) //遍历x轴,45°~90°区域
{
j=0;
while(i*i+j*j-r*r<=0)j++; //找到圆的边界
ym=(i*i+j*j-r*r-j<0)?j:(j-1); //边界内外侧更接近圆的坐标
LCD_Fill(x-i,y-ym,x+i,y-ym,color); //按行填充
LCD_Fill(x-i,y+ym,x+i,y+ym,color);
LCD_Fill(x-ym,y-i,x+ym,y-i,color);
LCD_Fill(x-ym,y+i,x+ym,y+i,color);
}
}