玩转嵌入式屏幕显示] (三)TFT-LCD 屏幕打孔+画线+画矩形+画圆 Bresenham 算法实现(基于打孔功能,该算法可移植到驱动程序之上的任何屏幕) - 画对角线
图中每个方格就是一个像素点,显然,每一个像素点只有显示颜色可以控制,不能控制显示像素点的一部分,所以红色的真实直线不可能表示出来。
在计算机中将真实的直线(红色)离散化,用图中黑色像素点近似显示,接下里问题转变为:
如何根据给出的直线确定要显示的像素点?
Bresenham提出了一种精确而有效的光栅线生成算法,该算法仅仅使用了增量整数计算,大大提高了画线效率,因此被广泛应用。
该算法的基本原理是:
计算 Δ x \Delta x Δx和 Δ y \Delta y Δy,取两者中的最大值设为 d i s t a n c e distance distance,然后依据 d i s t a n c e distance distance开始打点,每次循环都进行递增,对于另外一个坐标,则选择递增或者不递增。
算法实现如下:
/**
* @brief 带颜色画线函数(直线、斜线)
* @param x1,y1 起点坐标
* @param x2,y2 终点坐标
* @return none
*/
void LCD_Draw_ColorLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
uint16_t i = 0;
int16_t delta_x = 0, delta_y = 0;
int8_t incx = 0, incy = 0;
uint16_t distance = 0;
uint16_t t = 0;
uint16_t x = 0, y = 0;
uint16_t x_temp = 0, y_temp = 0;
if(y1 == y2)
{
/* 快速画水平线 */
LCD_Address_Set(x1, y1, x2, y2);
for(i = 0; i < x2 - x1; i++)
{
lcd_buf[2 * i] = color >> 8;
lcd_buf[2 * i + 1] = color;
}
LCD_WR_RS(1);
LCD_SPI_Send(lcd_buf, (x2 - x1) * 2);
return;
}
else
{
/* 画斜线(Bresenham算法) */
/* 计算两点之间在x和y方向的间距,得到画笔在x和y方向的步进值 */
delta_x = x2 - x1;
delta_y = y2 - y1;
if(delta_x > 0)
{
//斜线(从左到右)
incx = 1;
}
else if(delta_x == 0)
{
//垂直斜线(竖线)
incx = 0;
}
else
{
//斜线(从右到左)
incx = -1;
delta_x = -delta_x;
}
if(delta_y > 0)
{
//斜线(从左到右)
incy = 1;
}
else if(delta_y == 0)
{
//水平斜线(水平线)
incy = 0;
}
else
{
//斜线(从右到左)
incy = -1;
delta_y = -delta_y;
}
/* 计算画笔打点距离(取两个间距中的最大值) */
if(delta_x > delta_y)
{
distance = delta_x;
}
else
{
distance = delta_y;
}
/* 开始打点 */
x = x1;
y = y1;
//第一个点无效,所以t的次数加一
for(t = 0; t <= distance + 1;t++)
{
LCD_Draw_ColorPoint(x, y, color);
/* 判断离实际值最近的像素点 */
x_temp += delta_x;
if(x_temp > distance)
{
//x方向越界,减去距离值,为下一次检测做准备
x_temp -= distance;
//在x方向递增打点
x += incx;
}
y_temp += delta_y;
if(y_temp > distance)
{
//y方向越界,减去距离值,为下一次检测做准备
y_temp -= distance;
//在y方向递增打点
y += incy;
}
}
}
}
4. 画矩形算法
画矩形算法比较简单,直接放上算法的代码实现:
/**
* @breif 带颜色画矩形函数
* @param x1,y1 —— 矩形起始点
* @param x2,y2 —— 矩形终点
* @param color —— 颜色
* @retval none
*/
void LCD_Draw_ColorRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
LCD_Draw_ColorLine(x1,y1,x2,y1,color);
LCD_Draw_ColorLine(x1,y1,x1,y2,color);
LCD_Draw_ColorLine(x1,y2,x2,y2,color);
LCD_Draw_ColorLine(x2,y1,x2,y2,color);
}
5. 画圆算法
Bresenham画圆算法也称为中点画圆算法,与Bresenham 直线算法一样,其基本的方法是利用判别变量来判断选择最近的像素点,判别变量的数值仅仅用一些加、减和移位运算就可以计算出来。
该算法巧妙的利用了圆的八对称性,只计算出一个八分周上的点,其余的七个点利用对称性即可得出:
那么,如何计算出一个八分周(一段圆弧)上的像素点坐标呢?
算法实现代码如下:
/**
* @breif 带颜色画圆函数
* @param x1,x2 —— 圆心坐标
* @param r —— 半径
* @param color —— 颜色
* @retval none
*/
void LCD_Draw_ColorCircle(uint16_t x, uint16_t y, uint16_t r, uint16_t color)
{
/* Bresenham画圆算法 */
int16_t a = 0, b = r;
int16_t d = 3 - (r << 1); //算法决策参数
/* 如果圆在屏幕可见区域外,直接退出 */
if (x - r < 0 || x + r > LCD_Width || y - r < 0 || y + r > LCD_Height)
return;
/* 开始画圆 */
while(a <= b)
{
LCD_Draw_ColorPoint(x - b, y - a, color);
LCD_Draw_ColorPoint(x + b, y - a, color);
LCD_Draw_ColorPoint(x - a, y + b, color);
LCD_Draw_ColorPoint(x - b, y - a, color);
LCD_Draw_ColorPoint(x - a, y - b, color);
LCD_Draw_ColorPoint(x + b, y + a, color);
LCD_Draw_ColorPoint(x + a, y - b, color);
LCD_Draw_ColorPoint(x + a, y + b, color);
LCD_Draw_ColorPoint(x - b, y + a, color);
a++;
if(d < 0)
d += 4 * a + 6;
else
{
d += 10 + 4 * (a - b);
b--;
}
LCD_Draw_ColorPoint(x + a, y + b, color);
}
}
6.综合demo测试效果
int main(void)
{
HAL_Init();
SystemClock_Config();
LCD_Init();
LCD_Draw_ColorLine(0,120,240,120,RED); //画水平线
LCD_Draw_ColorLine(0,0,240,240,BLUE); //画斜线(从左到右,45°)
LCD_Draw_ColorLine(0,240,240,0,GREEN); //画斜线(从右到左,45°)
LCD_Draw_ColorLine(120,0,120,240,YELLOW); //画垂直线
LCD_Draw_ColorLine(180,0,60,240,RED); //画斜线(从左到右,120°)
LCD_Draw_ColorLine(60,0,180,240,RED); //画斜线(从右到左,60°)
LCD_Draw_ColorLine(0,60,240,180,RED); //画斜线(从左到右,180°)
LCD_Draw_ColorLine(0,180,240,60,RED); //画斜线(从左到右,30°)
LCD_Draw_ColorRect(60,60,180,180,PINK); //画矩形
LCD_Draw_ColorCircle(120,120,85, GBLUE); //画圆
while (1);
}
演示效果如下: