STM32工作笔记0070---图片显示实验

技术交流QQ群【JAVA,C++,Python,.NET,BigData,AI】:170933152

先看这个BMP,可以用画图画一个BMP的图片,然后保存,保存的时候可以选择BMP的格式

保存以后可以打开看一看

去看一下,还可以给这个图片,画上一些颜色等.

然后,BMP图片可以看到有个色彩的深度设置,也就是说可以用1,4,8,16,24,32bit位,去描述一个点的颜色,当然

位越多的话,颜色越清晰,但是占用的空间也越大.

可以看到,刚才画的这个

文件的大小:

300多kb

然后把这个文件保存成jpg看看

可以看到大小一下子就变成了只有48kb,比原来小多了

然后再看看BMP的具体信息

然后再看一下JPG

/JPEG

把一张图片保存成JPG以后,他是有损压缩的,所以肯定会不如BMP图片显得清楚.,但是根本就看不出来,

因为jpg再压缩的过程中也保证了图片质量.

再看一下gif,是无损压缩.

这个gif是把多张图片切换显示做成的图片.

里面是定义了一些规则数据的,比如每张图片播放多长时间等.

接下来主要看实验代码:

打开图片显示实验,这个代码:

可以看到这部分就是操作图片的.c文件

然后再hardware文件夹中可以看到操作SPI FLASH的spi.c,操作sd卡的(后面补上)sdio_sdcard.c

操作lcd的lcd.c,这些不是这次的重点.

这里PICTURE这个文件夹中,

bmp.c用来解析bmp的

gif.c用来解析gif的

piclib.c是用来解析的入口文件

fjpgd.c用来解析jpg的

可以看到这里:

//智能画图
//FileName:要显示的图片文件  BMP/JPG/JPEG/GIF
//x,y,width,height:坐标及显示区域尺寸
//fast:使能jpeg/jpg小图片(图片尺寸小于等于液晶分辨率)快速解码,0,不使能;1,使能.
//图片在开始和结束的坐标点范围内显示
u8 ai_load_picfile(const u8 *filename,u16 x,u16 y,u16 width,u16 height,u8 fast)
{	
	u8	res;//返回值
	u8 temp;	
	if((x+width)>picinfo.lcdwidth)return PIC_WINDOW_ERR;		//x坐标超范围了.
	if((y+height)>picinfo.lcdheight)return PIC_WINDOW_ERR;		//y坐标超范围了.  
	//得到显示方框大小	  	 
	if(width==0||height==0)return PIC_WINDOW_ERR;	//窗口设定错误
	picinfo.S_Height=height;
	picinfo.S_Width=width;
	//显示区域无效
	if(picinfo.S_Height==0||picinfo.S_Width==0)
	{
		picinfo.S_Height=lcddev.height;
		picinfo.S_Width=lcddev.width;
		return FALSE;   
	}
	if(pic_phy.fillcolor==NULL)fast=0;//颜色填充函数未实现,不能快速显示
	//显示的开始坐标点
	picinfo.S_YOFF=y;
	picinfo.S_XOFF=x;
	//文件名传递		 
	temp=f_typetell((u8*)filename);	//得到文件的类型
	switch(temp)
	{											  
		case T_BMP:
			res=stdbmp_decode(filename); 				//解码bmp	  	  
			break;
		case T_JPG:
		case T_JPEG:
			res=jpg_decode(filename,fast);				//解码JPG/JPEG	  	  
			break;
		case T_GIF:
			res=gif_decode(filename,x,y,width,height);	//解码gif  	  
			break;
		default:
	 		res=PIC_FORMAT_ERR;  						//非图片格式!!!  
			break;
	}  											   
	return res;
}

这个函数,传入文件的路径,然后传入要在什么坐标开始输出这个图片,然后传入图片的宽度,传入图片的高度

然后他就会给显示图片; 

这里可以看到,

switch(temp)
	{											  
		case T_BMP:
			res=stdbmp_decode(filename); 				//解码bmp,这个是在bmp.c文件中	  	  
			break;
		case T_JPG:
		case T_JPEG:
			res=jpg_decode(filename,fast);				//解码JPG/JPEG,这个是在tjpgd.c文件中	  	  
			break;
		case T_GIF:
			res=gif_decode(filename,x,y,width,height);	//解码gif,这个是在gif.c文件中  	  
			break;
		default:
	 		res=PIC_FORMAT_ERR;  						//非图片格式!!!  
			break;
	}  				

具体的解码都在各自的.c文件中

这个程序的思路是这样的:

下面是三个图片文件的解码.c文件,然后,再piclib这个.c文件中,根据图片的类型,去分别调用对应的

图片解码文件,然后再piclib中还有一些共用的文件,可以被gif.c等,解码文件去调用,然后

在piclib.c中还有一个操作lcd的函数,用来在lcd屏幕上,显示图片文件.会调用到lcd.c来显示.

看看piclib.h文件

这是函数的声明部分

然后还有两个结构体,这两个结构体很重要,

然后:
第一个结构体,可以看到这里phy结尾的,这个就是物理层的意思.

可以看到这里面,通过把函数指针作为结构体的成员变量.

然后接下来有个变量,带有extern这个关键字,说明这个pic_phy是定义在外面的

这个pic_phy的类型是,_pic_phy

可以看到这个变量是定义在piclib.c文件中的

那么这个变量定义在这里有什么用呢

实际上.可以看到下面有个piclib_init函数

可以看到在这里初始化了.

可以看到这三个函数,

//
//画图初始化,在画图之前,必须先调用此函数
//指定画点/读点
void piclib_init(void)
{   
    //1.这个可以在LCD.C的文件中找到LCD_ReadPoint
	pic_phy.read_point=LCD_ReadPoint;  		//读点函数实现,仅BMP需要
    //2.这个可以在LCD.C的文件中找到draw_point
	pic_phy.draw_point=LCD_Fast_DrawPoint;	//画点函数实现
    //3.LCD_Fill 这个可以在LCD.C的文件中找到LCD_Fill 
	pic_phy.fill=LCD_Fill;					//填充函数实现,仅GIF需要


}

另外还有两个函数

//
//画图初始化,在画图之前,必须先调用此函数
//指定画点/读点
void piclib_init(void)
{
	pic_phy.read_point=LCD_ReadPoint;  		//读点函数实现,仅BMP需要
	pic_phy.draw_point=LCD_Fast_DrawPoint;	//画点函数实现
	pic_phy.fill=LCD_Fill;					//填充函数实现,仅GIF需要
	
    //1.这两个函数可以在piclib.c这个文件中找到 piclib_draw_hline
    //piclib_fill_color
    pic_phy.draw_hline=piclib_draw_hline;  	//画线函数实现,仅GIF需要
	pic_phy.fillcolor=piclib_fill_color;  	//颜色填充函数实现,仅TJPGD需要 

}

然后看看,可以看到上面有定义的,这两个函数

_pic_info picinfo;	 	//图片信息
_pic_phy pic_phy;		//图片显示物理接口	
//
//lcd.h没有提供划横线函数,需要自己实现
void piclib_draw_hline(u16 x0,u16 y0,u16 len,u16 color)
{
	if((len==0)||(x0>lcddev.width)||(y0>lcddev.height))return;
	LCD_Fill(x0,y0,x0+len-1,y0,color);	
}
//填充颜色
//x,y:起始坐标
//width,height:宽度和高度。
//*color:颜色数组
void piclib_fill_color(u16 x,u16 y,u16 width,u16 height,u16 *color)
{  
	LCD_Color_Fill(x,y,x+width-1,y+height-1,color);	
}

这两个函数,再LCD.C中没有所以这里自己再piclib.c中实现了.

可以看到,这里这样做的好处就是,如果你想要写自己的LCD显示程序,这里,你只需要把

对应的这5个函数,替换成自己的就可以了.

然后看picinfo这个结构体,可以看到他也是在piclib.h文件中定义的

这个主要是图像的信息.

这个是结构体,放了图像的信息

然后,这个结构体,再piclib_init中进行了初始化.

所以在解码图片之前,咱们需要调用这个函数初始化.

然后还要:

    picinfo.lcdwidth=lcddev.width;    //得到LCD的宽度像素
    picinfo.lcdheight=lcddev.height;//得到LCD的高度像素

在初始化的时候,还需要获取LCD 的宽度,和高度像素,实现对这个图片的初始化

然后还有三个函数

//快速ALPHA BLENDING算法.
//src:源颜色
//dst:目标颜色
//alpha:透明程度(0~32)
//返回值:混合后的颜色.
u16 piclib_alpha_blend(u16 src,u16 dst,u8 alpha)
{
	u32 src2;
	u32 dst2;	 
	//Convert to 32bit |-----GGGGGG-----RRRRR------BBBBB|
	src2=((src<<16)|src)&0x07E0F81F;
	dst2=((dst<<16)|dst)&0x07E0F81F;   
	//Perform blending R:G:B with alpha in range 0..32
	//Note that the reason that alpha may not exceed 32 is that there are only
	//5bits of space between each R:G:B value, any higher value will overflow
	//into the next component and deliver ugly result.
	dst2=((((dst2-src2)*alpha)>>5)+src2)&0x07E0F81F;
	return (dst2>>16)|dst2;  
}
//初始化智能画点
//内部调用
void ai_draw_init(void)
{
	float temp,temp1;	   
	temp=(float)picinfo.S_Width/picinfo.ImgWidth;
	temp1=(float)picinfo.S_Height/picinfo.ImgHeight;						 
	if(temp<temp1)temp1=temp;//取较小的那个	 
	if(temp1>1)temp1=1;	  
	//使图片处于所给区域的中间
	picinfo.S_XOFF+=(picinfo.S_Width-temp1*picinfo.ImgWidth)/2;
	picinfo.S_YOFF+=(picinfo.S_Height-temp1*picinfo.ImgHeight)/2;
	temp1*=8192;//扩大8192倍	 
	picinfo.Div_Fac=temp1;
	picinfo.staticx=0xffff;
	picinfo.staticy=0xffff;//放到一个不可能的值上面			 										    
}   
//判断这个像素是否可以显示
//(x,y) :像素原始坐标
//chg   :功能变量. 
//返回值:0,不需要显示.1,需要显示
u8 is_element_ok(u16 x,u16 y,u8 chg)
{				  
	if(x!=picinfo.staticx||y!=picinfo.staticy)
	{
		if(chg==1)
		{
			picinfo.staticx=x;
			picinfo.staticy=y;
		} 
		return 1;
	}else return 0;
}

这三个函数

这个就是上面说的piclib中定义的,共用的那三个函数

这个函数是一个透明处理的函数.

然后

在屏幕画点的时候的初始化函数

下面这个函数的作用是,如果一个图片,大于LCD屏幕的宽度了,那么

需要把这个图片缩小,这个时候就要判断,这个像素在屏幕上能不能显示.

然后前面那些工作做好以后,就可以去显示图片了

这个函数,就是在指定的位置,去显示一个图片.

可以看到这个函数,前面去做了一些分析,

然后做完前期工作以后,可以看到去调用了这个

f_typetell这个函数

这个函数是确定文件的类型.是bmp还是gif还是别的什么

然后,每个文件中都有对应的解码对应类型图片的代码

因为这里还用到了内存分配和释放,这里就用到了下面的这两个函数.

然后这里pic_get_tnum这个函数用来,获取某个目录中的所有的图片文件的个数.

这个网站,有文件处理的代码,可以免费用.

这里看一下这个函数

这个函数,点进去可以看到,里面有两个参数,一个是dir,这个是传入的文件目录,

然后第二个是FILEINFO 可以看到是OUT,这个是,返回的一个文件的信息.

也就是如果一个文件夹中有多个文件,那么每调用一次这个函数,就返回一个这个文件夹中的文件信息.

然后看看例子代码:

可以看到这里的这个例子,有个scan_files这个函数,

可以看到这个函数先去打开目录,然后再去调用f_readdir读取,这个目录中的文件

然后如果再碰到文件夹,就再去调用自己scan_files这个函数.

这样就返回了所有的文件.

再回去看咱们的pic_get_tnum这个函数.

可以看到咱们的这个函数,也是这样的,也就是说,

去调用f_readdir这个函数,去读取文件,然后,循环的去调用这个函数,然后不停的去获取文件,如果

文件是图片文件,那么就rval就++也就是返回图片的个数.

再去看main.c这个文件

可以看到,这里面

前面是一堆的初始化,然后主要看后面的内容

这里首先要打开这个目录,然后

去这个目录中获取所有的文件数

然后

然后这里,再去记录索引

也就是说,首先打开目录,然后去查找图片文件,然后就把

所有有效的图片文件的索引都记录下来.

接下来,就根据索引开始,然后打开每个文件,去显示了.

然后这里说一个重要的函数,

dir_sdi这个函数.

这个函数是在FATFS这个文件系统中的

然后这个函数的作用是改变索引.

改变当前目录的索引.

也就是,当获取了一个文件以后,调用一下这个函数,他就会把索引指向下一个文件.

然后可以看到去读取文件,然后再去调用ai_load_picfile

去进行显示这个图片.

然后其实也可以,去

去文件夹中,去挨个的去扫描文件然后再显示也行啊

但是我们这样记录下来文件的索引的好处是,下面上一张图片,下一张图片显示的时候

就方便了,可以根据索引拿出来直接显示,而不用再去挨个去文件夹中扫描了.

接下来把代码,下载到开发版

这里,可以看到图片显示的比较慢,

这个是跟,1.处理器性能有关

2.因为这里是从SD卡中读取的

那么如果想快速一点话

可以把图片放在SPI Flash中去,然后还可以放到SRAM中去,

这样速度就会快很多了,这样的话就需要去先把图片,用取模软件,得到图片的

2进制数据,然后再去显示这个图片就可以了.

还可以,先把图片提前进行解码,然后显示的时候就直接显示,也需要提前取模,取到图片的2进制数据.

然后再显示.

猜你喜欢

转载自blog.csdn.net/lidew521/article/details/108422899