【玩转嵌入式屏幕显示】(三)TFT-LCD屏幕打点 + 画线 + 画矩形 + 画圆Bresenham算法实现(基于打点函数,算法可移植到任何屏幕的驱动程序之上)

0. 引言

TFT-LCD屏幕的画直线、画斜线、画矩形、画圆等算法都是基于打点函数的,所以此程序可以移植到任何屏幕的基本驱动程序之上。

1. 打点函数 —— 底层函数(移植需修改)

打点函数其实就是屏幕显存(液晶控制器显存)中某一个点的颜色值。

  • 针对SPI驱动的TFT-LCD屏幕:
/**
 * @brief	带颜色画点函数
 * @param   x,y	—— 画点坐标
 * @return  none
 */
void LCD_Draw_ColorPoint(uint16_t x, uint16_t y,uint16_t color)
{
    LCD_Address_Set(x, y, x, y);
    LCD_Write_2Byte(color);
}

2. Bresenham算法简介

Bresenham算法是计算机图形学领域使用最广泛的直线扫描转换方法,是计算机图形学中为了“显示器由像素构成”的这个特性而设计出来的算法,使得在求直线各点的过程中全部以整数来运算,因而大幅度了提升计算速度。

3. Bresenham直线算法

快速画水平线

水平线的像素点连续,所以非常好画,直接循环打点就行,算法实现如下:

    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;
    }

画斜线


图中每个方格就是一个像素点,显然,每一个像素点只有显示颜色可以控制,不能控制显示像素点的一部分,所以红色的真实直线不可能表示出来。

在计算机中将真实的直线(红色)离散化,用图中黑色像素点近似显示,接下里问题转变为:

如何根据给出的直线确定要显示的像素点?

Bresenham提出了一种精确而有效的光栅线生成算法,该算法仅仅使用了增量整数计算,大大提高了画线效率,因此被广泛应用。

该算法的基本原理是:

计算 Δ x \Delta x Δ y \Delta y ,取两者中的最大值设为 d i s t a n c e distance ,然后依据 d i s t a n c e 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);

}

演示效果如下:

发布了156 篇原创文章 · 获赞 489 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/Mculover666/article/details/95502286