基于像素点的图形显示算法(Bresenham直线个人理解)

目录

目的

几何原理

像素点的确定

不同直线的规律

逻辑化简

方法1、按xymax讨论

方法2、按判断条件讨论

程序源码

方法一:

方法二:


目的

本实验基于STM32F103RC+TFTLCD屏,旨在于在LCD屏上,给定起始、终止坐标,便可以显示一条直线,然而众所周知,对于屏幕而言,我们只能操作各个像素点,因此,选择出最接近标准直线的像素点就成了本次实验的主要目的,最终得到的直线大概应该长这样

                                   

看起来很不规则,但是这里只是从(0,0)到(6,-10)的直线,因此放大来看很不规则,但是在显示屏上,肉眼很难区分出来


几何原理

像素点的确定

首先,由于屏幕由像素点组成,每个像素点又有自己的位置信息,因此我们不妨将屏幕显示近似为一个坐标系,并在坐标系中作出一条标准直线,起点为原点(如果以后起点改变,将所有坐标基于起点平移即可),找出最靠近直线的所有整数坐标(像素点),如原理图1所示         

原理图1

对于一条以原点为起点,终点为(5,2)作出的直线,斜率 k=0.4,我们描出所有最靠近直线的整数坐标,如原理图1中圈出部分,即有效点。

同时,我们标出纵轴上半个单位(0.5)的点,如图中绿点所示。不难发现,以横轴为参考轴,直线始终位于绿点和有效点之间。从数学的角度,0—5号点满足:

        (1)直线的横坐标按单位递增,纵坐标则按斜率递增

        (2)如果直线纵坐标低于绿点,则纵坐标相较上一个点不变,作为有效点(如图中第1、3号点);如果直线纵坐标高于绿点,则纵坐标相较上一个点加1,且下一次的绿点也上升一个单位

        (3)遍历横坐标,就可以获得所有点坐标

原理看似很简单,我们将原理图1中的所有情况,按步骤列出:(row / col分别对应有效点的横 / 纵坐标)

步骤判断数据处理
第一步   k   <0.5row++
第二步 k*2  >0.5row++  col++
第三步k*3-1<0.5row++
第四步k*4-1>0.5row++  col++
第五步k*5-2>0.5row++
。。。k*t - i<0.5row++
。。。k*t - i>0.5 row++  col++

据此,我们可以写出一个仅针对0~45°直线的程序,如下

//画粗线
//(x1,y1),(x2,y2):线条的起始坐标
//size:线条的粗细程度
//color:线条的颜色
void LCD_Draw_BLine(u16 x1,u16 y1,u16 x2,u16 y2,u8 size,u16 color)
{
	u16 t;
	u16 row=x1;
	u16 col=y1;
	u16 delta_x=x2-x1;
	u16 delta_y=y2-y1;
	u16 deviate;
	deviate=-delta_x;
	for(t=0;t<=delta_x;t++)
	{
		LCD_Draw_Fill_Circle(x1,y1,size,color);   //描点函数(row,col)
		deviate+=delta_y*2;
		if(deviate>0)
		{
			deviate-=delta_x*2;
			col++;
		}
		row++;
	}
}

不同直线的规律

要从上表中提取出程序逻辑,想必已经很简单了,但是我们先不着急写,因为我们这里讨论的直线仅在0~45°之间。真正的情况可以分为斜线、水平线、竖直线共12种,如左图所示。接下来我们讨论与上述0~45°线完全对立的 -135°~-90°的情况,对比寻找规律

                                                

如右图所示,经过对比,要想取到所有有效点而且不会遗漏,显而易见有以下几个特点

  1. 需要以变化量更大的轴作为参考轴,遍历参考轴得到所有对应的坐标
  2. 参考轴按单位1变化,非参考轴按变化率 k变化
  3. 若将变化量 △x 和 △y中较大者定义为 xymax,较小者定义为 xymin,则变化率 k=xymin/xymax
  4. 根据直线的指向,若终点横坐标为正,则横坐标以递增形式变化;若为负,则横坐标以递减形式变化;若为0,则横坐标不变化。y坐标同理
  5. 非参考轴坐标与中点进行比较,小于则保持不变,大于则非参考轴坐标变化,且中点坐标也变化

综合考虑以上特点,我们可以得出结论

  1. 比较x和y的变化量大小,并计算k
  2. 判断直线的指向,用方向变量inc作为递增量,根据不同的情况,inc=1或-1或0
  3. 用变量row、col分别存储有效点的横、纵坐标
  4. 变化率公式 k=xymin/xymax 和遍历判断公式 if (k*t - i >0.5) 联立可以化简遍历判断公式为                                                                                                  if (2*xymin*t - 2*xymax*i >xymax)                                                                                               其中,每次循环后 t 都要递增,当条件满足时 i 加1。
  5. 每次循环遍历中,参考轴坐标均发生变化,非参考轴坐标视判断公式而定。如果判断成立,则坐标发生变化,且下一次判断公式变化

综上所述,我们可以写一个简单的代码来表达它的逻辑

以变化量更大的作为参考轴,另一个则为非参考轴
根据直线的指向确定两个坐标轴的递增值inc=1或-1或0

遍历整条直线
{
    描点(row,col);
    err+=xymin*2;
    if(err>xymax)
    {
        非参考轴坐标+对应的inc;
        err-=xymax*2;
    }
    参考轴坐标+对应的inc;
}

经过上述逻辑处理,就可以一个点一个点地描绘出整条直线了

逻辑化简

上述逻辑看似很短,实际需要讨论很多种情况,如果分类不当会导致代码繁琐,化简方法有两个

方法1、按xymax讨论

对于遍历循环体内的逻辑,根据参考轴的不同(即△x和△y谁更大),我们可以将各种情况列写在如下表格中:

原代码逻辑情况分类

△x>△y       xymax=△x

△x<△y       xymax=△y

描点(row,col);

err+=2*xymin;

if(err>xymax)

       非参考轴坐标+=对应的inc

       err-=2*xymax

参考轴坐标+=对应的inc

errx

 

errx+=2*△x;

if(errx>xymax)

       errx-=2*xymax

 

描点(row,col);

errx+=2*△x;

if(errx>xymax)

       row+=incx;

       errx-=2*xymax

col+=incy;

erry

描点(row,col);

erry+=2*△y;

if(erry>xymax)

       col+=incy;

       erry-=2*xymax

row+=incx;

 

erry+=2*△y;

if(erry>xymax)

       erry-=2*xymax

 

表格中加粗部分代表实际会出现的情况,未加粗的两个部分是根据其左/右侧情况进行格式仿写的(相同颜色部分格式仿写),实际不参与原本的逻辑。但是我们可以观察△x>△y的条件下,格式仿写代码中的判断始终成立,因此在其条件下增加的代码必定执行,观察△x<△y同理。于是我们可以将两种情况合并为如下代码(仅循环体内部分)

描点(row,col);
errx+=2*△x;
erry+=2*△y;
if(errx>xymax)
    row+=incx;
    errx-=2*xymax;
if(erry>xymax)
    col+=incy;
    erry-=2*xymax;

方法2、按判断条件讨论

对于遍历循环体内的逻辑,根据是否满足判断条件(即err>xymax?),我们可以将各种情况列写在如下表格中:

原代码逻辑情况分类

err>xymax(符合条件)

err<=xymax(不符条件)

描点(row,col);

err+=2*xymin;

if(err>xymax)

       非参考轴坐标+=对应的inc

       err-=2*xymax

参考轴坐标+=对应的inc

△x>△y

xymax=△x

描点(row,col)

err+=2*xymin

err-=2*xymax

row+=incx

col +=incy

描点(row,col)

err+=2*xymin

------

row+=incx

------

△x<△y

xymax=△y

描点(row,col)

err+=2*xymin

err-=2*xymax

row+=incx

col +=incy

 

描点(row,col)

err+=2*xymin

------

------

col +=incy

 

不难看出,无论△x和△y谁大,只要符合判断条件,执行代码相同,都执行err-=2*xymax;而且,当且仅当 条件不成立且△x更大时,col不变化;同理,当且仅当 条件不成立且△y更大时,row不变化。

我们不妨将共同的代码提出至判断外统一执行,可以简化为如下

情况分类

err>xymax(符合条件)

err<=xymax(不符条件)都需要执行的代码

△x>△y

xymax=△x

 

 

 

err-=2*xymax

 

 

 

col -=incy

描点(row,col)

err+=2*xymin

row+=incx

col +=incy

△x<△y

xymax=△y

 

row-=incx

 

这样,逻辑就更加清晰明了了,且情况的讨论也更加简单,每种情况下的代码也只有一行,循环体内的代码简化如下

描点(row,col)
err+=2*xymin;
row+=incx;
col+=incy;
if(err>xymax)err-=2*xymax;
else
{
    if(xymax==delta_x)col-=incy;
	else row-=incx;
}

程序源码

方法一:

//画粗线方法1
//(x1,y1),(x2,y2):线条的起始坐标
//size:线条的粗细程度
//color:线条的颜色
void LCD_Draw_BLine(u16 x1,u16 y1,u16 x2,u16 y2,u8 size,u16 color)
{
	u16 t;
	int row=x1;
	int col=y1;	
	int err=0,xymax,xymin,delta_x,delta_y;  //定义偏量、xy中较大/较小值、x/y变化量
	int incx,incy;                          //x/y变化方向
	
	if(x2>x1){delta_x=x2-x1;incx=1;}	    //分别获取两个方向的位移(正值)和方向变量
	else if(x2==x1){delta_x=0;incx=0;}
	else {delta_x=x1-x2;incx=-1;}
	
	if(y2>=y1){delta_y=y2-y1;incy=1;}
	else if(y2==y1){delta_y=0;incy=0;}
	else {delta_y=y1-y2;incy=-1;}
	
	if(delta_x>delta_y){xymax=delta_x;xymin=delta_y;}  //区分最大轴(参考轴)位移、最小轴位移
	else {xymax=delta_y;xymin=delta_x;}
	
	for(t=0;t<=xymax;t++)
	{
		LCD_Draw_Fill_Circle(row,col,size,color);      //描点函数(row,col)
		err+=2*xymin;
		row+=incx;
		col+=incy;
		if(err>xymax)err-=2*xymax;
		else
		{
			if(xymax==delta_x)col-=incy;
			else row-=incx;
		}
	}
}

方法二:

//画粗线方法2
//(x1,y1),(x2,y2):线条的起始坐标
//size:线条的粗细程度
//color:线条的颜色
void LCD_Draw_BLine(u16 x1,u16 y1,u16 x2,u16 y2,u8 size,u16 color)
{
	u16 t; 
	int xerr=0,yerr=0,delta_x,delta_y,xymax; 
	int incx,incy,row,col; 
	delta_x=x2-x1; //计算坐标增量 
	delta_y=y2-y1; 
	row=x1; 
	col=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)xymax=delta_x;            //选取参考轴 
	else xymax=delta_y; 
	
	for(t=0;t<=xymax;t++)
	{
		LCD_Draw_Fill_Circle(row,col,size,color); //描点函数(row,col)
		xerr+=2*delta_x;
		yerr+=2*delta_y;
		if(xerr>xymax)
		{
			xerr-=2*xymax;
			row+=incx;
		}
		if(yerr>xymax)
		{
			yerr-=2*xymax;
			col+=incy;
		}
	}
}  

注:两个算法中均涉及到描点函数,实际上是一个画实心圆的函数,直线的粗细尺寸决定了圆的半径,相当于一个自定义粗细的圆头画笔。实心圆函数分析详见    基于像素点的图形显示算法(Bresenham圆形个人理解)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值