1.基于S5PV210的图片解码播放器(详解)

有道云笔记详细地址:
文档:图片解码播放器小项目(详解).note
链接:http://note.youdao.com/noteshare?id=9f9a43ac5ec6828cf467940dfa10da51&sub=A8280EB4B5A146C9A1F6612031305071

文章目录

一、开始动手写代码

1、Makefile介绍

(1)这是一个通用的项目管理的Makefile体系,自己写的(有子文件夹组织的)项目可以直接套用这套Makefile体系。

(2)包含三类:顶层Makefile、Makefile.build(完全不需要改动)、子文件夹下面的Makefile。
子文件夹下的Makefile一般是类似下面的内容。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pwsgMELC-1570028293237)(F95308E307894BA1A9ECE5BC07CFA0A1)]

(3)可以参考:http://www.cnblogs.com/lidabo/p/4521123.html。

2、使用SI建立工程

打开SI,在E:\Linux\winshare\x210kernel\tupian_project中新建一个SI_Proj文件夹,用于建立SI工程,然后新建一个输出hello word的main.c文件,并在ubuntu中make整个工程,并make cp到根文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28cgC87l-1570028293240)(D3212F76475F446D8A238276FDAB198D)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3oy9ZgzM-1570028293241)(61AD59BE2EEA47A89184B37C33D2317A)]

启动开发板,进入driver_test目录下,可以查看到我们的工程目录,进入执行生成的imageplayer文件,可以输出hello word,到此新建工程结束。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tuvzJgKR-1570028293244)(321FA7F935AC44FBAEB009AF46656C7A)]

新建一个脚本文件run.sh,用于管理各种可执行文件,比如上面我们生成的./imageplayer文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bOKzRF4J-1570028293246)(107A0A80C7444F0EACCFD669B6224802)]

二、framebuffer驱动基本操作代码

E:\Linux\winshare\x210kernel\tupian_project

显示图片,需要framebuffer驱动。

①在SI工程中新建fb.c文件(保存在display文件夹下)

并在display文件夹里新建Makefile并添加内容

obj-y += fb.o(这就是子makefile,作用就是让fb.c能被编译进去)

②这里需要用到framebuffer显示图片

故将我们在驱动学习中framebuffer哪一阶段的应用程序拷贝到fb.c中
(即E:\Linux\4.LinuxDriver\7.frambuffer\7.1.fb1\appfb.c),并修改部分代码

(即将之前的所有宏定义和函数声明放在fb.h中,并在主Makefile中添加命令,使其将子目录能编译进去)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eGAxAg1q-1570028293250)(C0D3963AFF084915B8931301C41F7343)]

man.c如下:

#include <stdio.h>
#include <fb.h>
#include <unistd.h>

int main(void)
{
	int ret=-1;
	
	printf("image player demo........star \n");
	
	//第一步,打开设备
	ret = fb_open();
	if(ret < 0)
	    {
			perror("fb_open error.\n");
			return -1;
		}
	fb_draw_badk(WIDTH, HEIGHT, YELLOW);//调用背景填充函数
	
}

从新make编译,并拷贝到根文件系统中,重启开发板,执行脚本,显示预期一致。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-86ARibZo-1570028293255)(9AEAFC67E9E34E4BB28D68B3BA5FD8A5)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RXbxT1fd-1570028293256)(2108333A2096471A97479662733A7263)]

三、图片显示原理和实践

1、图片显示原理

  • (1)概念1:像素
    我们开发板的屏幕像素为1024*600,填充部分颜色,即是想要实现的图案
  • (2)概念2:点阵(像素点构成的阵列)
  • (3)分辨率
    物理分辨率:物理屏幕像素点的真实个数;
    显示分辨率:可以小于等于物理分辨率;(通过抽样)
  • (4)清晰度(与分辨率和像素间距有关,主观概念)
    像素间距相同时(物理尺寸固定了,则像素间距就固定了),分辨率越大越清晰;分辨率相同时,像素间距越小越清晰。
  • (5)bpp(RGB565、RGB888)
    像素深度,每个像素用多少bit数据表示。
    一般每个像素点使用32bit(4个字节),但一般是24位色(高八位一般没用或者用着其他标识,用00或者ff填充以达到内存对齐);
    RGB888表示红用8位,绿8位,蓝8位。
    (6)颜色序(RGB、BGR)

2、图片点阵数据获取

(1)Image2LCD软件提取

  • 输出数据类型:C语言数组;
  • 一般不要图像头数据,只需要纯数据;
  • 一般水平扫描;
  • 一般选24位真彩色(即RGB888);
  • 选1024*600后,点右边按钮更新;
  • 输出图像调整中,可以调整RGB。
  • 最后点击保存为xxx.h文件(保存在include目录下)
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YGYaR6s8-1570028293257)(4E611CF9449B4344ADB09C2D7307643A)]

在fb.c中添加如下代码,且在主函数中调动该函数,编译程序,拷贝到跟文件系统
//测试显示1024600分辨率的图片
void fb_draw_picture(void)
{
unsigned char
pdata=gImage_1024;//指针指向图像数组
unsigned int i,j,cnt;
unsigned int*p=pfb;//等于显示缓存区的首地址

for(i=0;i<HEIGHT;i++)
{
for(j=0;j<WIDTH;j++)
{
         cnt=i*WIDTH+j;//取出当前像素点编号
         cnt*=3;//一个像素点是3个字节,当前像素点的数据在数组中的下标
         //当前像素点对应的图像数据的RGB应该分别是pData[cnt+0]、pData[cnt+1]、pData[cnt+2]
         //当前像素点的数据
         *p=((pdata[cnt+0]<<0)|(pdata[cnt+1]<<8)|(pdata[cnt+2]<<16));//可以在这里修改,达到正确的显示(当RB相反时)
     p++;
}
}

}

重启开发板,执行.、run.sh脚本,看到屏幕如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NNbLZtZH-1570028293258)(63BFF505A0AF4F519147EEF410B2D335)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10zlyp5i-1570028293259)(CD381E03B4114362AE2046736739C10D)]

与我们图片周边是粉色颜色不符合,原因肯定是RGB弄反了,这里,我们可以在上面的fb_draw_picture函数中进行修改,将RB调换,从新编译,执行脚本,显示和我们的原图就一样了。
*p=((pdata[cnt+2]<<0)|(pdata[cnt+1]<<8)|(pdata[cnt+0]<<16));//可以在这里修改,达到正确的显示(当RB相反时)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3l0QCy6i-1570028293260)(05F0F24F487648B7BB91E05F2335095B)]

(2)通过图片/视频文件直接代码方式提取

四、图片显示的高级话题

1、RGB顺序调整

(1)RGB顺序有三个地方都有影响

  • 第一个是fb驱动中的排布;
  • 第二个是应用程序中的排布;
  • 第三个是图像数据本身排布(Image2LCD中调整RGB顺序);

(2)如果三个点中RGB顺序是一致的就会显示正常。如果三个设置不一致就可能会导致显示结果中R和B相反了;

(3)实际写程序时,一般不去分析这东西,而是根据实际显示效果去调。如果反了就去调正应用程序中的RGB顺序就行了,这样最简单。

2、显示函数的其他写法

void fb_draw_picture2(void)
{
unsigned char* pdata=gImage_1024;//指针指向图像数组
unsigned int x,y,cnt;

for(y=0;y<HEIGHT;y++)
{
for(x=0;x<WIDTH;x++)
{
    cnt=y*WIDTH+x;//取出当前像素点编号          
    //当前像素点对应的图像数据的RGB应该分别是pData[cnt+0]、pData[cnt+1]、pData[cnt+2]
    //当前像素点的数据
    *(pfb+cnt)=((pdata[cnt*3+2]<<0)|(pdata[cnt*3+1]<<8)|(pdata[cnt*3+0]<<16));这里的像素矩阵和cnt有线性关系,所以可以这样写
    }
    }

}

五、任意分辨率大小图片显示

(1)图片比屏幕分辨率大

这种情况下多出来的部分肯定是没法显示的。处理方法是直接在显示函数中把多余不能被显示的部分给丢掉。

(2)图片大小比屏幕大小要小

这种情况下图片只是填充屏幕中一部分,剩余部分仍然保持原来的底色。
在获取图片数据时,大小和图片实际大小在这里是一致的。假如不一致呢?

  • ①从新使用Image2LCD软件制作分辨率为320*480的图片
  • ②重新编写测试图片显示函数后,编译拷贝…重启开发板,对比两种写法
//测试显示分辨率比屏幕小的图片
//pdata://指针指向图像数组
void fb_draw_picture3(unsigned char* pdata)
{
    //unsigned char* pdata=gImage_320480;
    unsigned int x,y,cnt;
    unsigned int a=0;

//图片的分辨率是320*480
    for(y=0;y<480;y++)
    {
		for(x=0;x<320;x++)
		{
                  cnt=y*WIDTH+x;/*cnt始终都是framebuff像素点的编号*/
      		     *(pfb+cnt)=((pdata[a+0]<<16)|(pdata[a+1]<<8)|(pdata[a+2]<<0));
		     a+=3;/*由于空缺一部分,像素点编号与像素点矩阵不存在倍数关系了,此时应该三个三个地传送进来*/                       
		}
	}
}
//测试显示分辨率比屏幕小的图片 另一种写法,对比看出
//pdata:指针指向图像数组
void fb_draw_picture4(unsigned char* pdata)
{
   // unsigned char* pdata=gImage_320480;//指针指向图像数组
    unsigned int x,y,cnt1;
    unsigned int cnt2=0;

 //图片的分辨率是320*480
    for(y=0;y<480;y++)
    {
	for(x=0;x<320;x++)
	{
             cnt1=y*WIDTH+x;//取出当前屏幕像素点编号   
             cnt2=y*320+x;	//取出当前图片像素点编号  
             //当前像素点对应的图像数据的RGB应该分别是pData[cnt+0]、pData[cnt+1]、pData[cnt+2]
             //当前像素点的数据
   	 *(pfb+cnt1)=((pdata[cnt2*3+2]<<0)|(pdata[cnt2*3+1]<<8)|(pdata[cnt2*3+0]<<16));这里的像素矩阵和cnt有线性关系,所以可以这样写

	}
    }
}

另一种写法,对比两种函数的写法,看出区别,烧录效果都一样,这里,不再赘述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VBD33q4p-1570028293261)(108ACE9C4BBF4C62A50F3FAFA3E634CB)]

六、任意起点位置图片显示

1、小图片任意起点(但整个图片显示没有超出屏幕范围内)

算法1:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s1btYqX1-1570028293263)(B819CC65CF0C41769D83150895A1202B)]

//测试在屏幕指定坐标显示图片的位置 
//x0:图片想要显示的x坐标  y0:图片想要显示的y坐标
void fb_draw_picture5(unsigned int x0,unsigned int y0,unsigned char* pdata)
{
    //unsigned char* pdata=gImage_320480;//指针指向图像数组
    unsigned int x,y,cnt1;
    unsigned int cnt2=0;

 //图片的分辨率是320*480
    for(y=y0;y<480+y0;y++)
    {
	for(x=x0;x<320+x0;x++)
	{
             cnt1=y*WIDTH+x;//取出当前屏幕像素点编号   
             cnt2=(y-y0)*320+(x-x0);	//取出当前图片像素点编号  

   	 *(pfb+cnt1)=((pdata[cnt2*3+2]<<0)|(pdata[cnt2*3+1]<<8)|(pdata[cnt2*3+0]<<16));这里的像素矩阵和cnt有线性关系,所以可以这样写
	/*左值考虑当前像素点在fb中的偏移量*/
      /*右值考虑当前像素点在图像数据数组的下标(这里是三个为一个元素的数组的下标,因此上面会*3)*/

	}
    }
}

算法2:(因为每循环一次,+3)

//测试在屏幕指定坐标显示图片的位置,和fb_draw_picture5函数一样的功能,只是实现算法不一样,更加简单
//x0:图片想要显示的x坐标  y0:图片想要显示的y坐标
void fb_draw_picture6(unsigned int x0,unsigned int y0,unsigned char* pdata)
{
    //unsigned char* pdata=gImage_320480;
    unsigned int x,y,cnt;
    unsigned int a=0;

//图片的分辨率是320*480
    for(y=y0;y<480+y0;y++)
    {
	for(x=x0;x<320+x0;x++)
		{
                  cnt=y*WIDTH+x;/*cnt始终都是framebuff像素点的编号*/
      		     *(pfb+cnt)=((pdata[a+0]<<16)|(pdata[a+1]<<8)|(pdata[a+2]<<0));
		     a+=3;/*由于空缺一部分,像素点编号与像素点矩阵不存在倍数关系了,此时应该三个三个地传送进来*/
                         
		}
	}
}

在主函数中,调用上面两种不同算法实现的任意起点位置图片显示函数

int main()
{
int ret=-1;
	printf("image player demo........star \n");
	//第一步,打开设备
	ret = fb_open();
	if(ret < 0)
	    {
			perror("fb_open error.\n");
			return -1;
		}
  
fb_draw_badk(WIDTH, HEIGHT, RED);//调用背景填充函数
fb_draw_picture6((1024-320)/2,(600-480)/2,gImage_320480);//居中显示该图片
fb_close();
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jmpwWQkS-1570028293264)(E33B3E0C2EBC41DDAAFAB3FBCB3BF3D2)]

2、起点导致图片超出屏幕外

(1)现象
在主函数中调用任意起点位置图片显示函数:
fb_draw_picture5(900,100);//图片大小超出屏幕范围测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xSoDa7N4-1570028293265)(979CE78FD41744F2B7D0CE7C59E6EB29)]

  • 左右超出去,会在相反方向补全:这是内部for循环可能超过1024的界定(但没有超出fb的大小)造成的。
  • 上下超出去,则会消失:因为双缓冲进到了另一帧。如果没有双缓冲,则会内存溢出。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mX0sz4KF-1570028293265)(042BB86735124F09B09E687683944174)]

(2)修改代码,使得超出部分不再显示。
算法1:

//x和y两个方向超出屏幕外的部分都不显示
void fb_draw_picture7(unsigned int x0,unsigned int y0, unsigned char* pdata)
{
    //unsigned char* pdata=gImage_320480;
    unsigned int x,y,cnt;
    unsigned int a=0;

//图片的分辨率是320*480
    for(y=y0;y<480+y0;y++)
    {
    		if(y>=HEIGHT)//y方向超出
	    {
		 a+=3;
		 break;//最后一行已经显示了,剩下的不用考虑了
	     }

	for(x=x0;x<320+x0;x++)
		{

			if(x>=WIDTH)//x方向超出了
			 {
			      a+=3;
			    continue;//仅结束本次循环
			  }		  
			
                  cnt=y*WIDTH+x;/*cnt始终都是framebuff像素点的编号*/
      		     *(pfb+cnt)=((pdata[a+0]<<16)|(pdata[a+1]<<8)|(pdata[a+2]<<0));
		     a+=3;/*由于空缺一部分,像素点编号与像素点矩阵不存在倍数关系了,此时应该三个三个地传送进来*/
                         
		}
	}
}

算法2:

//x和y两个方向超出屏幕外的部分都不显示 和fb_draw_picture7一样的功能 实现算法不一样
void fb_draw_picture8(unsigned int x0,unsigned int y0, unsigned char* pdata)
{
   // unsigned char* pdata=gImage_320480;//指针指向图像数组
    unsigned int x,y,cnt1;
    unsigned int cnt2=0;

 //图片的分辨率是320*480
    for(y=y0;y<480+y0;y++)
    {
	    		if(y>=HEIGHT)//y方向超出
		    {
			 break;//最后一行已经显示了,剩下的不用考虑了
		     }
	for(x=x0;x<320+x0;x++)
	{
			if(x>=WIDTH)//x方向超出了
			 {
			    continue;//仅结束本次循环
			  }	
             cnt1=y*WIDTH+x;//取出当前屏幕像素点编号   
             cnt2=(y-y0)*320+(x-x0);	//取出当前图片像素点编号  

   	 *(pfb+cnt1)=((pdata[cnt2*3+2]<<0)|(pdata[cnt2*3+1]<<8)|(pdata[cnt2*3+0]<<16));这里的像素矩阵和cnt有线性关系,所以可以这样写
	/*左值考虑当前像素点在fb中的偏移量*/
      /*右值考虑当前像素点在图像数据数组的下标(这里是三个为一个元素的数组的下标,因此上面会*3)*/

	}
    }
}

在主函数中调用该函数:

//fb_draw_picture7(900,100);
	fb_draw_picture8(900,100,gImage_320480);//图片大小超出屏幕范围测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tn3LV1BO-1570028293266)(682FEBF34B51492B922EC770ED1D7FC1)]

七、BMP图片的显示

1、图片文件的本质

(1)图片文件是二进制文件。
文件分两种,即二进制文件、文本文件;

(2)不同格式的图片文件的差别。
图片被压缩与否的区别。

(3)BMP图片的基本特征
未被压缩的元素位图格式(bit map)。

2、BMP图片详解

(1)如何识别BMP文件?
每种图片格式都有定义好的一种识别方法,BMP图片特征是以0x424D开头;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0cOqRkx3-1570028293267)(0078B254880F41FA8EA756D6AEF94BA2)]

(2)BMP文件组成
头信息+有效信息

3、BMP文件头信息、图片有效数据区

以下对BMP文件的测试都是参考这里:https://blog.csdn.net/oqqHuTu12345678/article/details/78862613

(1)文件大小

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RYjtiQur-1570028293268)(F124EA63B0BC497AB887E31665689473)]

(2)有效数据开始的位置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kojTUOTh-1570028293292)(4030384155844CF2AB82D92A32C1EC27)]

(3)信息头的大小:40个字节

(4)分辨率

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QIiMZfWn-1570028293293)(88E96E617AF2439C82B9D8C3B68FCE86)]

(5)24位真彩色

4、写代码解析BMP图片

  • 第一步:打开BMP图片(并将BMP文件放入工程中)
  • 第二步:判断图片格式是否真是BMP
  • 第三步:解析头信息,得到该BMP图片的详细信息
  • 第四步:根据第三步得到的信息,去合适位置提取真正的有效图片信息
  • 第五步:将得到的有效数据丢到fb中去显示
  • 这样实际比较繁琐!使用结构体比较好!

①.新建Fb_bmp.c文件,写入程序如下:

//path是bmp图片的pathname
//该函数解析path图片,并将图片数据丢到bmp_buf中
//返回值错误时返回-1,正确返回0
int bmp_analyze(unsigned char *path)
{

	int fd=-1;
	unsigned char buf[54]={0};
	ssize_t ret=-1;

	
	//第一步:打开BMP图片
	fd=open(path,O_RDONLY);
	if(fd<0)
		{
			fprintf(stderr,"open %s error.\n",path);
			return -1;	 	
		}

	// 第二步: 读取文件头信息
	ret=read(fd,buf,54);//这里为什么是54,因为有效数据开始的位置地址为0x36=54
	if(ret!=54)
		{
			fprintf(stderr,"read file header error.\n");
			return -1;	 	
		}
	
	// 解析头
	// 第三步: 判断是不是BMP图片
	if(buf[0]!='B'||buf[1]!='M')//BMP头肯定为BM
		{
			fprintf(stderr,"file %s is not a bmp picture.\n",path);
			return -1;
		}
 
	printf("file %s is a bmp picture....ok\n",path);
    
    // 第四步:
	printf("width is %d\n",*((unsigned int*)(buf+0x12)));//这里为什么是buf+0x12,因为0x12是位图的宽度
	printf("hith   is %d\n",*((unsigned int*)(buf+0x16)));//0x16是位图的高度
	close(fd);
	return 0;
}

②. 函数分析:这里为什么是54???

ret=read(fd,buf,54);//这里为什么是54,因为有效数据开始的位置地址为0x36=54

因为有效数据开始的位置地址为0x36=54
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4gWJOTD0-1570028293294)(41C67C8DFD754CA89F9D1BEC321CF639)]

为什么这里使用了buf+0x12??

printf("width is %d\n",
*((unsigned int*)(buf+0x12)));//这里为什么是buf+0x12,
因为0x12是位图的宽度printf("hith   is %d\n",*((unsigned int*)(buf+0x16)));//0x16是位图的高度

原因如下,这是在上面的博客地址中BMP头信息的地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cdIb2y69-1570028293295)(EB2DCC1F79B4439C9E2EDD771061DC7B)]

③.主函数中调用该函数

bmp_analyze("cute_pic.bmp");//必须保证在工程中已经存在该BMP文件

且修改dispaly子文件夹下的Makefile,包含Fb_bmp.c,不然编译肯定报错

obj-y += Fb_bmp.o

④.编译拷贝到跟文件系统,然后运行run.shu脚本,打印BMP信息正确。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pWkAAKht-1570028293296)(BC2C0AC96CA94264A9E629DA27D9D3C0)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SK6ebTeN-1570028293297)(ABC4C30F2F03460BBE73F7C8FC8D346E)]

5、用结构体方式解析BMP信息头

①.在fb_bmp.h中添加一下结构体,此结构体是百度BMP信息头结构体得来

typedef struct
{
   unsigned  char    bfType[2];//文件类?
   unsigned long    bfSize; //位图大小 
   unsigned short   bfReserved1; //位0 
   unsigned short   bfReserved2; //位0 
   unsigned long    bfOffBits;//到数据偏移量
}__attribute__((packed))BitMapFileHeader;//使编译器不优化,其大小为14字节 
 
//信息头结构体
typedef struct 
{ 
    unsigned long biSize;// BitMapFileHeader 字节数
    long biWidth;//位图宽度 
    long biHeight;//位图高度,正位正向,反之为倒图 
    unsigned short biPlanes;//为目标设备说明位面数,其值将总是被设为1
    unsigned short biBitCount;//说明比特数/象素,为1、4、8、16、24、或32。 
    unsigned long biCompression;//图象数据压缩的类型没有压缩的类型:BI_RGB 
    unsigned long biSizeImage;//说明图象的大小,以字节为单位 
    long biXPelsPerMeter;//说明水平分辨率 
    long biYPelsPerMeter;//说明垂直分辨率 
    unsigned long biClrUsed;//说明位图实际使用的彩色表中的颜色索引数
    unsigned long biClrImportant;//对图象显示有重要影响的索引数,0都重要。 
} __attribute__((packed)) BitMapInfoHeader;

②.将判断一个图片文件是不是一个合法的bmp文件从新封装为一个函数,然后在主函数中先调用该函数,判断是否为一个合法的bmp文件

// 判断一个图片文件是不是一个合法的bmp文件
// 返回值: 如果是则返回0,不是则返回-1
int is_bmp(const char *path)
{

int fd=-1;
unsigned char buf[2]={0};
ssize_t ret=-1;

//第一步:打开BMP图片
fd=open(path,O_RDONLY);
if(fd<0)
	{
		fprintf(stderr,"open %s error.\n",path);
		return -1;	 	
	}

// 第二步: 读取文件头信息
ret=read(fd,buf,2);//只读取文件头2个字节
if(ret!=2)
	{
		fprintf(stderr,"read file header error.\n");
		ret = -1;
		goto close;
	}

// 解析头
// 第三步: 判断是不是BMP图片
if(buf[0]!='B'||buf[1]!='M')//BMP头肯定为BM
	{
		fprintf(stderr,"file %s is not a bmp picture.\n",path);
		ret = -1;
		goto close;
	}
else
	{
		ret=0;
	}

printf("file %s is a bmp picture....ok\n",path);

close:
close(fd);
return ret;
}

③.调用BMP头信息结构体,实现解析BMP图片数据,并将数据丢到FB中显示

//path是bmp图片的pathname
//该函数解析path图片,并将图片数据丢到bmp_buf中
//返回值错误时返回-1,正确返回0
int bmp_analyze(unsigned char *path)
{

	int fd=-1;
	BitMapFileHeader fheader;
	BitMapInfoHeader info;
	ssize_t ret=-1;
	int i=0;

	
	//第一步:打开BMP图片
	fd=open(path,O_RDONLY);
	if(fd<0)
		{
			fprintf(stderr,"open %s error.\n",path);
			return -1;	 	
		}

	// 第二步: 读取文件头信息
	ret=read(fd,&fheader,sizeof(fheader));
	printf("bfsize =%d.\n",fheader.bfSize);//位图大小
	printf("boffsize =%d.\n",fheader.bfOffBits);//有效信息偏移量

#if 	0
	for( i=0;i<sizeof(fheader);i++)//测试打印出的字节数据是否一致
	{
         printf("%x ",*((unsigned char *)&fheader+i));
		 
	}
       printf("\n");
#endif
	 ret= read(fd,&info,sizeof(info));/*会沿着继续读下去*/
 	printf("picture resolution : %d * %d.\n",info.biHeight,info.biWidth);//读取位图宽度和高度
	 printf("picture bpp : %d \n",info.biBitCount);//像素比特数

      // 第三步: 读取图片有效信息
	// 先把文件指针移动到有效信息的偏移量处
	//然后读出图片大小=info.biHeight*info.biWidth*info.biBitCount/8个字节,这个公式在驱动frambuff有说明
	lseek(fd,fheader.bfOffBits,SEEK_SET);//从文件开始的位置开始	 
	unsigned long len=info.biHeight*info.biWidth*info.biBitCount /8;  
	printf("len put to buff  :%ld.\n",len);	

	read(fd,BMP_BUF_SIZE,len);	 	 	
	//把内容丢到fb中去显示	      
	fb_draw(BMP_BUF_SIZE);//用于测试使用结构体来解析图片数据时,图片会180°显示后还原显示函数
	/*数据和使用image2lcd获取的不一样        
	*这里会旋转180度颠倒       
	*即第一个像素放到最后一个像素点像素,依次类推了	 */

	close(fd);
	return 0;

}

====> 细节:上面的这段代码用于测试打印出的像素数据是否一致

#if  1
	for( i=0;i<sizeof(fheader);i++)//测试打印出的字节数据是否一致
	{
         printf("%x ",*((unsigned char *)&fheader+i));
		 
	}
       printf("\n");
#endif

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iTFQuaXg-1570028293298)(DF2F4E57A4DF40A593A45C8E4183301E)]

④.重新修改图片显示函数

注意:

  • 使用结构体来解析图片数据时,和使用image2lcd不一样。
  • 这里会旋转180度颠倒,即第一个像素放到最后一个像素点像素,依次类推了。
  • 因此在fb_draw()函数中,需要将最后一个像素点数据放在第一个像素处显示,以此类推。
//用于测试使用结构体来解析图片数据时,图片会180°显示后还原显示函数
void fb_draw(unsigned char* pdata)
{
    //unsigned char* pdata=gImage_320480;
    unsigned int x,y,cnt;
    unsigned int a=0;

/*数据和使用image2lcd获取的不一样 这里会旋转180度颠倒
即第一个像素放到最后一个像素点像素,依次类推了*/
a= HEIGHT*WIDTH*3-3;//让a一开始就指向了最后一个像素的第一个字节 
for(y=0;y<HEIGHT;y++)
    {
for(x=0;x<WIDTH;x++)
{
                  cnt=y*WIDTH+x;/*cnt始终都是framebuff像素点的编号*/
      		     *(pfb+cnt)=((pdata[a+0]<<16)|(pdata[a+1]<<8)|(pdata[a+2]<<0));
		     a-=3;/*由于空缺一部分,像素点编号与像素点矩阵不存在倍数关系了,此时应该三个三个地传送进来*/
                         
}
}
}

主函数调用:

is_bmp("cute_pic.bmp");
	bmp_analyze("cute_pic.bmp");

smen_len = xres_virtual * yres_virtual * bpp / 8

若调用之前没有修改的显示图片函数,则会180°倒置 如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EpPJinvf-1570028293301)(1E94ED09D0EB467E824F9370DDCEE951)]

调用上面修改之后的fb_draw函数,则会正常显示如下:”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g5dpCreK-1570028293301)(FCDB3B7AE5354B6CAED5EB7C109086FD)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7RqjGE1N-1570028293302)(D2A7680579BC45439B8F0D4123910267)]

八、及时规整

1、再次强调规范问题

  • (1)函数、变量起名字要合法合理;小写函数;
  • (2)要写注释;第一步,第二步……可以先写注释再写代码;
  • (3)函数长短要合适;
  • (4)多文件组织,每个东西丢到合理的位置

2、为什么要规整项目?

  • (1)完全自由写项目时不可能一步到位,只能先重内容和功能,后补条理和规范;
  • (2)规整的过程也是一个梳理逻辑和分析架构的过程。

3、对本项目进行规整

(1)去掉测试显示时头文件形式提供的图片显示相关的东西

  • 即去除fb_draw_picture_n()测试函数,因为我们的最终目的是解析bmp格式图片(已经实现了,因此要删除之前的测试函数)

4、一些重构代码的技巧

  • (1)用#if 0 #endif来屏幕不需要的代码,不要用/* */;
  • (2)暂时不要的代码先不要删除,而是屏幕掉

5、添加DEBUG宏以控制调试信息输出

debug宏添加好后,要使能输出可以有2种方式:

第一种:在debug宏定义之前定义DEBUG宏。

#define	DEBUG 	//打开调试信息输出的开关
// debug宏的定义
#ifdef DEBUG
#define debug(...)                                                      \
        {                                                               \
            fprintf(stderr, "[debug][%s:%s:%d] ",                     \
                    __FILE__, __FUNCTION__, __LINE__);                  \
            fprintf(stderr, __VA_ARGS__);                               \
        }
#else
#define debug(...)  
#endif

第二种:在编译参数中添加-DDEBUG编译选项。(在makefile中添加)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L3HQS8vw-1570028293303)(AB4DB63A7A10497DBD3A7ECDD8AB4127)]

6、图片信息用结构体来封装传递

//封装图片各种信息

//封装图片各种信息
typedef struct pic_info
{
    char* pathname;//路径和文件名字
    unsigned int width;//位图宽度
    unsigned int height;//位图高度
    unsigned int bpp;//位图bpp
     char *pData;//指向图片有效数据存储的buff
}pic_info;

从而需要修改相关代码。

①.主要是Fb_bmp.c中的bmp_analyze函数

//path是bmp图片的pathname
//该函数解析path图片,并将图片数据丢到bmp_buf中
//返回值错误时返回-1,正确返回0
int bmp_analyze(struct pic_info *pPic)
{

int fd=-1;
BitMapFileHeader fheader;
BitMapInfoHeader info;
ssize_t ret=-1;
unsigned long len;
//int i=0;


//第一步:打开BMP图片
fd=open(pPic->pathname,O_RDONLY);
if(fd<0)
{
fprintf(stderr,"open %s error.\n",pPic->pathname);
return -1;
}

// 第二步: 读取文件头信息
ret=read(fd,&fheader,sizeof(fheader));
debug("bfsize =%ld.\n",fheader.bfSize);//位图大小
debug("boffsize =%ld.\n",fheader.bfOffBits);//有效信息偏移量

#if  0
for( i=0;i<sizeof(fheader);i++)//测试打印出的字节数据是否一致
{
         printf("%x ",*((unsigned char *)&fheader+i));
		 
}
       printf("\n");
#endif
ret= read(fd,&info,sizeof(info));/*会沿着继续读下去*/
 	 debug("picture resolution : %ld * %ld.\n",info.biHeight,info.biWidth);//读取位图宽度和高度
debug("picture bpp : %hu \n",info.biBitCount);//像素比特数

pPic->width = info.biWidth;//填充图片的各个数据
pPic->height =info.biHeight;
pPic->bpp = info.biBitCount;// 利用输出型参数

debug("image resolution: %d * %d, bpp=%d.\n", pPic->width, pPic->height, pPic->bpp);

      // 第三步: 读取图片有效信息
// 先把文件指针移动到有效信息的偏移量处
//然后读出图片大小=info.biHeight*info.biWidth*info.biBitCount/8个字节,这个公式在驱动frambuff有说明
	lseek(fd,fheader.bfOffBits,SEEK_SET);//从文件开始的位置开始	 
	 len=info.biHeight * info.biWidth * info.biBitCount /8;  
debug("len put to buff  :%ld.\n",len);

	read(fd,bmp_buf,len);	 
pPic->pData = bmp_buf;

	//把内容丢到fb中去显示	      
fb_draw(pPic);//用于测试使用结构体来解析图片数据时,图片会180°显示后还原显示函数


close(fd);
return 0;
}

②.fb.c中的fb_draw函数

//用于测试使用结构体来解析图片数据时,图片会180°显示后还原显示函数
void fb_draw(struct pic_info *pPic)
{
    char* pData=pPic->pData;	//指针指向图像数组
   //const char *pData = (const char *)pPic->pData;	
    unsigned int x,y,cnt;
    unsigned int a=0;

debug("image resolution: %d * %d, bpp=%d.\n", pPic->width, pPic->height, pPic->bpp);

if((pPic->bpp !=24))
{
fprintf(stderr,"bpp %s is not support.\n",pPic->bpp);
return -1;
}

/*数据和使用image2lcd获取的不一样 这里会旋转180度颠倒
即第一个像素放到最后一个像素点像素,依次类推了*/
a = pPic->height * pPic->width * 3 - 3;//让a一开始就指向了最后一个像素的第一个字节 
for (y=0; y<(pPic->height); y++)
{
for (x=0; x<(pPic->width); x++)
		{ 
                  cnt = WIDTH * y + x;/*cnt始终都是framebuff像素点的编号,注意这里的WIDTH是屏幕的高度*/
      		     *(pfb+cnt)=((pData[a+0]<<16)|(pData[a+1]<<8)|(pData[a+2]<<0));
a-=3;/*由于空缺一部分,像素点编号与像素点矩阵不存在倍数关系了,此时应该三个三个地传送进来*/
                         
}
}
}
                  		}	}}

主函数调用:开发板能正常显示图片,且终端打印信息正确。

struct pic_info pictrue;//要显示的图片
	// 测试bmp图片显示,ok	
	pictrue.pathname = "cute_pic.bmp";// 指向要显示的图片
	bmp_analyze(&pictrue);

在这个过程中遇到的问题:一定要多用DEBUG宏以控制调试信息输出,例如我遇到了刚开始段错误以及图片不正常显示等问题,都是跟踪debug宏输出调试信息解决:截图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q7suSW5M-1570028295463)(A55015739417449E9DA6413A53AF013D)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z0Y7tgBb-1570028293304)(EB2BDF4E94F447579A7E58BE8AD4BC3E)]

九、jpg图片的显示原理分析

1、认识jpg图片

(1)属于二进制文件。
(2)有其固定的识别特征,后面我们判断是否为jpg图片,需要识别特征http://www.cnblogs.com/Wendy_Yu/archive/2011/12/27/2303118.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kWfIx3oM-1570028293305)(028CAD3110E342D495E2A018F13EC352)]

(3)是经过压缩的图片格式。

2、jpg图片如何显示

(1)jpg图片中的二进制数并不对应像素数据。

(2)LCD显示器的接口仍然是framebuffer。

(3)要显示jpg图片必须先解码jpg得到相应的位图数据。

3、如何解码jpg图片

(1)图片编码和解码对应着压缩和解压缩过程

(2)编码和解码其实就是一些数学运算(压缩度、算法复杂度、时间、清晰度)

(3)软件编解码和硬件编解码需要频繁进行编解码的话,一般使用硬件编解码。

(4)不同的图片格式其实就是编解码的算法不同,结果是图片特征不同

(5)编程实战:使用开源编解码库

十、libjpeg介绍及开源库的使用方法

1、libjpeg介绍

(1)基于linux的开源软件;

(2)C语言编写(gcc、使用Makefile管理);

(3)提供JPEG图片的编解码算法实现;

2、libjpeg版本及下载资源

(1)经典版本v6b:https://sourceforge.net/projects/libjpeg/files/libjpeg/6b/

(2)最新版本v9b:http://www.ijg.org/

3、开源库的使用方法

(1)移植(源码下载、解压、配置、修改Makefile、编译或交叉编译)

  • 移植的目的是由源码得到三个东西:动态库.so,静态库.a,头文件.h。
    (2)部署(部署动态库so、部署静态库.a和头文件.h)
  • 动态库是程序在运行时才需要的,编译程序时不需要。
  • 静态库是静态连接时才需要,动态链接时不需要。
  • 头文件.h是在编译程序时使用的,运行时不需要的。

总结:静态库和头文件,是在编译链接过程中需要的,因此要把静态库.a文件和头文件.h文件放到ubuntu的文件系统中。
动态库是在运行时需要的,所以动态库so文件要放到开发板的文件系统中(放的过程就叫部署)。

(3)注意三个编译链接选项:-I -l(小L) -L
举例如-I:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1yTyLgOS-1570028293306)(2B410CAF37F6411894CBC0E744109972)]

  • -I 是编译选项(准确的是说是makefile预处理选项CFLAGS或者CPPFLAGS中指定),用来指定预处理时查找头文件的范围的。
  • -l(小写L)是链接选项(LDFLAGS中指定),用来指定链接额外的库的名字(譬如我们用到了数学函数,就用-lm,链接器就会去链接libm.so;那么我们使用了libjpeg,对应的库名字就叫libjpeg.so,就需要用-ljpeg选项去链接)。
  • -L是链接选项(LDFLAGS中指定),用来指定链接器到哪个路径下面去找动态链接库。

总结:-l(小写L)是告诉链接器要链接的动态库的名字,而-L是告诉链接器要链接的动态库的路径。

十一、libjpeg的移植实战

1、移植

(1)源码下载、解压至/root/decodeporting/目录下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-42S0TZJA-1570028293307)(B5B1205C32FB487F9F3E038F90100C38)]

(2)编译前的配置(分析configure文件的usage、借鉴前辈的设置)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Kkgv1sf-1570028293307)(377FB57A528F4FCE91DD7AB6C597EBAF)]

得到应该在/root/decodeporting/jpeg-6b目录下执行下面命令:
./configure --prefix=/opt/libcode --exec-prefix=/opt/libcode --enable-shared --enable-static-build=i386 -host=arm

说明:/opt/libcode:用于存放移植后得到的3个文件,所以我们需要先建立这个目录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PRPsXThY-1570028293308)(AAB032DBC7124B72B33A4F0BDEC6D82F)]

指定编译结果放置的目录(如果没有则需要先建立相应的目录)
配置完成后生成了makefile文件,通过查看知道需要在/opt/libcode中创建include、bin、lib目录。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ssiiMTdD-1570028293309)(0FA0FF963EA94C1DAF84E2AF8FFB634D)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-00fVttTK-1570028293310)(8E4BBCC6A90B4D50BF14DD9FD7825A5F)]

(3)Makefile检查,主要查看交叉编译设置是否正确

CC=gcc 改为 CC=arm-linux-gcc //编译器,如果不改,只能在ubuntu使用,不能在开发板使用。
AR=ar rc 改为 AR=arm-linux-ar rc
AR2=ranlib 改为 AR2=arm-linux-ranlib

(4)编译:执行make命令。
(5)安装:执行make install-lib;
安装就是将编译生成的库文件、头文件、可执行文件分别装载到–prefix --exec-prefix所指定的那些目录中去。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2SyFEl6H-1570028293311)(4950154F750A4101A3134EF8E7337367)]

2、部署

前面只是完成移植,相关文件都在ubuntu上。我们要把动态链接库(.so文件)放到开发板的根文件系统中(/root/porting_x210/rootfs/rootfs),可以考虑三者之一:

  • 第一个:/root/porting_x210/rootfs/rootfs/lib

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iILIW5AC-1570028293312)(B2A88702CAF84D8FB75C5CA24B63EBE9)]

  • 第二个:/root/porting_x210/rootfs/rootfs/usr/lib
  • 第三个:任意指定目录
    (前两个在程序运行时,可以自动找到。后面任意目录时,需要在程序中指定)
    我们选择第二个,然后将生成的动态.so文件拷贝到/usr/lib目录下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2n67efTl-1570028293313)(9E9B683BCA8A4CFF99DAC980FE0F1964)]

十二、使用libjpeg解码显示jpg图片

1、如何使用一个新的库

(1)思路一:网络上找别人使用过后写的文档、博客等作为参考。
(2)思路二:看库源码自带的文档(说明文档和示例代码)。我们从思路二出发。

2、libjpeg说明文档和示例代码

(1)说明文档:README和libjpeg.doc
(2)示例代码:example.c

3、结合说明文档来实践

example.c测试程序提供了两个接口:

  • 写jpeg文件
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mR27RL8z-1570028293314)(90878E2F277C49A5835043BC095FF187)]

  • 读jpeg文件
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-weJUH05t-1570028293314)(F546B1326276447E9F88F109D10587FC)]

另外,还定义了几个外部变量和一个外部函数:

extern JSAMPLE * image_buffer; 
extern int image_height; 
extern int image_width;  
void put_scanline_someplace(char *buf, int count);

这个函数就是在读jpeg文件时,扫描一行的数据通过这个函数传出

4、解读example.c和移植

  • 解读example.c
  • 根据example.c的步骤移植添加到我们之前的工程中
    建立fb_jpeg.c文件,并根据example.c读jpeg文件源码开始,移植到我们的.c文件,如下:
/*
 * 本文件用来解码jpg图片,并调用fb.c中的显示接口来显示到lcd上
 */
#include <stdio.h>
 
#include <config.h>			// for debug
#include <fb_jpeg.h>	

#include <jpeglib.h>
#include <jerror.h>
#include <string.h>



struct my_error_mgr 
{  
	struct jpeg_error_mgr pub;	/* "public" fields */  
//	jmp_buf setjmp_buffer;	/* for return to caller */
};
typedef struct my_error_mgr * my_error_ptr;


// 自己定义的错误处理函数
METHODDEF(void)my_error_exit (j_common_ptr cinfo)
{  
	//my_error_ptr myerr = (my_error_ptr) cinfo->err;  
	//(*cinfo->err->output_message) (cinfo);    
	fprintf(stderr, "my_error_exit\n");
	//longjmp(myerr->setjmp_buffer, 1);
}

/*
 * 函数功能: 解码jpg图片,并将解码出来的数据存储
 * 函数参数: pPIC,记录源jpg图片,解码出来的图片宽高、图片数据缓冲区等信息
 * 返回值  : 成功解码则返回0,失败则返回-1
 */
 
 int jpg_analyze(struct pic_info *pPic)
{
	struct jpeg_decompress_struct cinfo;		// cinfo贯穿整个解码过程的信息记录和传递的数据结构
	struct my_error_mgr jerr;					// 错误处理的	
	//JSAMPARRAY buffer = NULL;					// 指向解码行数据的指针
	char * buffer = NULL;
	FILE * infile;								// 指向fopen打开源jpg图片文件的指针
	int row_stride;								// 解码出来的一行图片信息的字节数

	if ((infile = fopen(pPic->pathname, "rb")) == NULL) 
	{    
		fprintf(stderr, "can't open %s\n", pPic->pathname);    
		return -1;  
	}

	// 第1步: 错误处理函数部分的绑定
	cinfo.err = jpeg_std_error(&jerr.pub);	
	jerr.pub.error_exit = my_error_exit;
	// 给解码器做必要的内存分配和数据结构的初始化
	jpeg_create_decompress(&cinfo);

	// 第2步: 将fopen打开的源jpg图片和解码器相关联
	jpeg_stdio_src(&cinfo, infile);

	// 第3步: 读jpg文件头
	jpeg_read_header(&cinfo, TRUE);

 //打印出图片的宽度高度信息等	
 	debug("image resolution: %d * %d, bpp/8=%d.\n", 
		cinfo.output_width, cinfo.output_height, cinfo.output_components);
	

	//  解码完了,做各种清理工作
	jpeg_destroy_decompress(&cinfo);
	fclose(infile);


	return 0;
}

主函数调用该函数:

pictrue.pathname = "cute_pic.jpg";// 指向要显示的图片
	jpg_analyze(&pictrue);

5、代码问题

(1)测试代码,先试图读取jpg图片头信息。
(2)问题排除

  • 编译时问题:主要就是头文件包含,除了在代码中包含头文件外,还要注意指明头文件的路径。
  • 因为尽管包含了头文件,但只会在当前的目录和path指定的路径中寻找。我们应该在总Makefile中指定。

注意-I、-l、-L三个编译链接选项的使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sopy99CJ-1570028293315)(DEA568A9B0354A7197E0266DB241057B)]

重新make&make cp,然后启动开发板,运行。run.sh脚本,能正常打印信息,但当我们把lib目录下的动态链接文件删除后,从新运行如下,显示错误。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pd0iigYt-1570028293316)(33FBA27822014DEFB0DA8FF56E14AA5C)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xM7ubpjT-1570028293317)(E61CEBABD49E427C961318ED635FB27A)]

6、部署动态库以使程序运行起来

(1)一般放到开发板根文件系统/lib或者/usr/lib下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUfcVOF5-1570028293318)(5E99CC674B9C4D05A5C5172E0E75E3A3)]

  • 这样不需要给系统指定库路径,就能自动找到。
  • 强调一下是开发板根文件系统下的路径,千万不要弄成了ubuntu的根文件系统下的目录。

(2)放到自定义的第三方的目录
(在开发板根文件中新建/opt/mylib/目录,用于存放动态库)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hhtPeh5p-1570028293318)(BD30339BB3674C40A22F9026B2D74B4E)]

将该自定义第三方目录导出到环境变量LD_LIBRARY_PATH下即可。

export LD_LIBRARY_PATH=/opt/mylib:$LD_LIBRARY_PATH

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDhapWrH-1570028293319)(9352EB30DE4E4E999510DE73E8BE569A)]

  • 可以使用echo $LD_LIBRARY_PATH查看当前的路径环境变量包含哪些路径。
  • 可以把上述操作写进到run.sh脚本文件中,省得每次都要这样操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cgfuI58T-1570028293320)(11C16DA371884FD3B36B71D21360CB28)]

7、测试读取头信息

在fb_jpeg.c文件中的jpg_analyze函数中继续添加源码如下(注意,都是根据example.c的步骤添加修改的):

// 第1步: 错误处理函数部分的绑定
	cinfo.err = jpeg_std_error(&jerr.pub);	
	jerr.pub.error_exit = my_error_exit;
	// 给解码器做必要的内存分配和数据结构的初始化
	jpeg_create_decompress(&cinfo);

	// 第2步: 将fopen打开的源jpg图片和解码器相关联
	jpeg_stdio_src(&cinfo, infile);

	// 第3步: 读jpg文件头
	jpeg_read_header(&cinfo, TRUE);

	// 第4步: 启动解码器
	jpeg_start_decompress(&cinfo);
	
 	debug("image resolution: %d * %d, bpp/8=%d.\n", 
		cinfo.output_width, cinfo.output_height, cinfo.output_components);
	
	// 解码出来的数据一行的字节数
	row_stride = cinfo.output_width * cinfo.output_components;
	buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
	if (NULL == buffer)
	{
		fprintf(stderr, "cinfo.mem->alloc_sarray error.\n");
		return -1;
	}
	// 第5步: 逐行解码,并将解码出的数据丢到事先准备好的缓冲区去
	while (cinfo.output_scanline < cinfo.output_height) 
	{	 
		// 解码一行信息,并且丢到buffer中
		jpeg_read_scanlines(&cinfo, buffer, 1);
		 
		// 将buffer中这一行数据移走到别的地方去暂存或者使用,总之是要腾出buffer空间
		// 来给循环的下一次解码一行来使用
		memcpy(pPic->pData + (cinfo.output_scanline-1) * row_stride, buffer, row_stride);
	}

	// 第6步: 解码完了,做各种清理工作
	jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);
	fclose(infile);


	//显示该图片,先填充pPic的数据
	pPic->width = cinfo.output_width;
	pPic->height = cinfo.output_height;
	pPic->bpp = cinfo.output_components * 8;
	fb_draw(pPic);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lxf9N4iO-1570028293321)(4C252DD4039F465C8E472329B321EC79)]

主函数调用如下:

对pictrue.pData 缓存区必须先指定填充大小,

// ②测试jpg图片显示
	pictrue.pathname = "cute_pic.jpg";// 指向要显示的图片
	pictrue.pData = bmp_buf;
	jpg_analyze(&pictrue);

从新make&&make cp.,运行脚本,开发板显示图片有问题。

十三、解决解码显示中的问题

1、问题分析及解决记录

(1)根据LCD错误的显示状态,分析有可能是显示函数fb_draw中的图片宽高数据有误,于是在fb_draw函数中添加debug打印出宽和高来。结果发现是对的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UpJaS1Tm-1570028293322)(C18EA21AA593465AAA3BBA366367BBE2)]

(2)显示函数中的图片宽高和fb宽高都是对的,结果显示时还是只有一溜(其余位置黑屏),可能的一个原因就是:显示数据本身不对,很多都是0。

  • 如何验证?只要把显示数据打印出来看一看就知道了。
    在fb.c中的fb_draw显示函数中添加如下打印信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-auLfphsS-1570028293323)(B6CCE5C0E9F14E0ABB14633E95409E8B)]

  • 结果发现打印出的待显示数据是很多0,说明显示函数的待显示数据就是错的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kaw9ATL2-1570028293324)(F395199C15B6419EBDFBF35F4AA0016B)]

(3)这些待显示数据为什么会错?

  • 第一种可能性就是libjpeg解码出来的数据就是错的;
  • 第二种可能性是解码一行出来暂存到buffer的时候,或者memcpy从暂存的buffer拿出来给pData指向的空间的时候给搞错了。
  • 相对来说第二种很好验证而第一种不好验证。只需要在jpeg_read_scanlines函数后面直接打印显示解码出来的一行数据,就可以知道是不是第二种情况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vkp7C8L4-1570028293326)(DAFADBC0E41F4719A566AEC825768CF9)]

  • 结果打印出来好多0,说明是第一种情况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XM7DlKT-1570028293326)(29E9D6CAA3DC45389EDCCE3E8E87A5D7)]

(4)截至目前已经锁定问题,就是jpeg_read_scanlines解码出来的数据本身就不对。
(5)可能的问题

  • 有可能是libjpeg本身就有问题;
  • 有可能我们对libjpeg的部署不对导致他工作不对;
  • 有可能我们写的代码不对,也就是说我们没用正确的方法来使用libjpeg。

(6)没有思路怎么办

  • 方法一:去网上找一些别人写的libjpeg解码显示图片的示例代码,多看几个,对着和我们的关键部位对比,寻找思路。
  • 方法二:如果在网上找不到相关资料,这时候就只有硬着头皮去看源码了。譬如去libjpeg的源码中查看:jpeg_read_scanlines、cinfo.mem->alloc_sarray等。
  • 怎么解决的????
    在jpg_analyze函数中修改如下源码,即可解决(即下面标红的,就是修改后的)
int jpg_analyze(struct pic_info *pPic)
{
    .......
//JSAMPARRAY buffer = NULL; // 指向解码行数据的指针 这是原生的
char * buffer = NULL;
............................
    
// 第4步: 启动解码器
jpeg_start_decompress(&cinfo);

 	debug("image resolution: %d * %d, bpp/8=%d.\n", 
cinfo.output_width, cinfo.output_height, cinfo.output_components);

// 解码出来的数据一行的字节数
row_stride = cinfo.output_width * cinfo.output_components;

/*按照提供的example.c,这里这样使用就是有问题,所以解码出来的数据全是0,故我们选择下面另一种方法,自己申请空间*/
//buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
buffer = (char *)malloc(row_stride);
if (NULL == buffer)
{
fprintf(stderr, "cinfo.mem->alloc_sarray error.\n");
return -1;
}
// 第5步: 逐行解码,并将解码出的数据丢到事先准备好的缓冲区去
	while (cinfo.output_scanline < cinfo.output_height) 
	{	 
// 解码一行信息,并且丢到buffer中
//jpeg_read_scanlines(&cinfo, buffer, 1);
jpeg_read_scanlines(&cinfo, &buffer, 1);

.............
}

编译拷贝,重启开发板运行,就能显示图片了(但图片是翻过来,还有RGB不对) 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gP5rv6a6-1570028293327)(8FD52B9118C849FEB98E343DBCCE9827)]

(7)解决了buffer申请导致的问题之后,我们再来解决2个遗留的问题

  • 一个就是RGB顺序问题,另一个是图像转了180度的问题。

void fb_draw2(const struct pic_info *pPic)
{
const char *pData = (const char *)pPic->pData;  // 指针指向图像数组
unsigned int cnt = 0, a = 0;
unsigned int x, y;

if ((pPic->bpp != 32) && (pPic->bpp != 24))
{
fprintf(stderr, "BPP %d is not support.\n", pPic->bpp);
return;
}

a = 0;
for (y=0; y<pPic->height; y++)
{
for (x=0; x<pPic->width; x++)
		{ 
//cnt表示当前像素点的编号
cnt = WIDTH * y + x;
// 当前像素点对应的图像数据的RGB就应该分别是:
			// pData[cnt+0]  pData[cnt+1]  pData[cnt+2]  
// 当前像素点的数据
*(pfb + cnt) = ((pData[a+2]<<0) | (pData[a+1]<<8)| (pData[a+0]<<16)); 
			//*p = ((pData[cnt+0]<<16) | (pData[cnt+1]<<8)| (pData[cnt+2]<<0)); 
a += 3;
}
}
}

(8)添加了fb_draw2函数并且调用后,2个遗留问题彻底解决。至此,jpg图片显示完美实现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KdXBagMd-1570028293336)(677469A2D0244133A3AAC8DDF98E846E)]

2、结束jpg图片部分

(1)加上jpg图片格式识别:判断开头和结尾的特征字节。

  • 在fb.jipeg.c中添加is_jpg函数,用于判断一个图片文件是不是jpg图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oiTagJUQ-1570028293338)(1897C78CE11B49588308F94D0CBED565)]

// 函数功能: 判断一个图片文件是不是jpg图片
// 函数参数: path是图片文件的pathname
// 返回值:   如果是jpg则返回0,不是则返回1,错误返回-1
int is_jpg(const char *path)
{
	FILE *file = NULL;
	char buf[2] = {0};
	// 打开文件
	file = fopen(path, "rb");
	if (NULL == file)
	{
		fprintf(stderr, "fopen %s error.\n", path);
		fclose(file);
		return -1;
	}
	// 读出前2个字节
	fread(buf, 1, 2, file);
	debug("read: 0x%x%x\n", buf[0], buf[1]);
	// 判断是不是0xffd8
	if (!((buf[0] == 0xff) && (buf[1] == 0xd8)))
	{
		fclose(file);
		return 1;		// 不是jpg图片
	}
	// 是0xffd8开头,就继续
	// 文件指针移动到倒数2个字符的位置
	fseek(file, -2, SEEK_END);
	// 读出2个字节
	fread(buf, 1, 2, file);
	debug("read: 0x%x%x\n", buf[0], buf[1]);
	// 判断是不是0x
	if (!((buf[0] == 0xd9) && (buf[1] == 0xa)))
	{
		fclose(file);
		return 1;		// 不是jpg图片
	}

	fclose(file);	
	return 0;
}
  • 测试该函数是否正确。

主函数调用:

// ②测试jpg图片显示
	ret=is_jpg("cute_pic.jpg");
	printf("ret = %d.\n",ret);	
	pictrue.pathname = "cute_pic.jpg";// 指向要显示的图片
	pictrue.pData = bmp_buf;	
	jpg_analyze(&pictrue);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hT8VQJZe-1570028293339)(4A9DEE52195D4D79B3D9CF90B9011F36)]

(2)对外封装好用的jpg图片显示函数

// 封装的一个对外使用的jpg显示函数
// 本函数对外只需要一个jpg图片的pathname即可,那些复杂的数据结构都是jpg显示模块内部处理的
// 正确显示图片返回0,显示过程中出错则返回-1
int display_jpg(const char *pathname)
{
	int ret = -1;
	struct pic_info picture;
	
	// 第一步: 检测给的图片是不是jpg图片
	ret = is_jpg(pathname);
	if (ret != 0)
	{
		return -1;
		printf("ret = %d.\n",ret);
	}
	printf("ret = %d.\n",ret);	
	// 第二步: 解析该jpg图片
	picture.pathname = pathname;
	picture.pData = rgb_buf;
	jpg_analyze(&picture);

	// 第三步: 显示该jpg图片
	fb_draw2(&picture);
}

(3)对外封装好用的bmp图片显示函数

// 封装的一个对外使用的bmp显示函数
// 本函数对外只需要一个bmp图片的pathname即可,那些复杂的数据结构都是bmp显示模块内部处理的
// 正确显示图片返回0,显示过程中出错则返回-1
int display_bmp(const char *pathname)
{
	int ret = -1;
	struct pic_info picture;
	
	// 第一步: 检测给的图片是不是jpg图片
	ret = is_bmp(pathname);
	if (ret != 0)
	{
		return -1;
		printf("ret = %d.\n",ret);
	}
	printf("ret = %d.\n",ret);	
	// 第二步: 显示该jpg图片
	picture.pathname = pathname;
	picture.pData = rgb_buf;
	bmp_analyze(&picture);

}

测试:

主函数调用:编译拷贝,重启运行开发板,开发板图片正常显示,先显示jpg图片,3s后显示bmp图片,完美解决所以小问题。

// ③测试新的显示接口函数jpg图片显示和bmp图片显示
 display_jpg("cute_pic2.jpg");
	sleep(3);
	display_bmp("cute_pic.bmp");

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r9PaEWNd-1570028293339)(6A684775A885476FBB5016BC74A25F34)]

十四、解码显示png图片

1、思路分析

(1)png更像是jpg而不像是bmp;
(2)png和jpg都是压缩格式的图片,都是二进制文件,不同之处是压缩和解压缩的算法不同。
(3)通过libjpeg来编解码jpg图片,那么同样有一个libpng用来编解码png图片。
(4)工作思路和顺序
找到并移植并部署libpng,然后查readme和其他文档示例代码等来使用libpng提供的API来对png图片进行解码,并将解码出来的数据丢到framebuffer中去显示。

2、libpng移植

(1)下载源码包:

(2)解压、配置、修改Makefile、编译、部署。注意实际路径。

①解压:

root@wwj:~/decodeporting# cp /mnt/hgfs/Linux/winshare/x210kernel/tupian_project/libpng-1.6.6.tar.gz  ./

root@wwj:~/decodeporting# tar -zxvf libpng-1.6.6.tar.gz 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kMNv4Ks3-1570028293340)(DE86545FFEF74281A0AF742027A6B5E8)]

②配置:进入libpng-1.6.6目录下,运行一下命令

./configure --host=arm-linux --enable-shared --enable-static --prefix=/opt/libcode

(3)配置出错,报错信息:configure: error: zlib not installed

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ieZ15hGA-1570028293341)(F555948F39AE4DE3BEB966D7A8B43460)]

  • 分析问题是因为libpng依赖于zlib库,所以要先移植zlib库才可以。

3、zlib移植

①下载zlib库,并拷贝到上面的/root/decodeporting/目录下,解压…这里不再赘述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tHI4Y2ZU-1570028293342)(5F69EC1AE95E418CBB7A18A5F23DF9E1)]

②导出CC以确定配置时为arm-linux-:

root@wwj:~/decodeporting/zlib-1.2.8# export CC=arm-linux-gcc

③配置zlib库,得到makefile:

root@wwj:~/decodeporting/zlib-1.2.8# ./configure  -shared --prefix=/opt/libcode

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ym3IoAFZ-1570028293343)(D762437977594388BE1A40866B580906)]

④make && make install:执行后/out/libcode目录下的lib和include目录下就有zlib的静态库动态库和头文件了,然后回到上面的libpng的配置步骤

4、再次回到==》(2、libpng移植)配置环节

①配置:进入libpng-1.6.6目录下,配置libpng,还是报错,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9NCHbQz5-1570028293349)(A5F829A8813A40E58DABE79307F097CB)]

原因是因为没有导出相关环境变量,所以libpng在配置的时候找不到刚才移植的zlib库的库文件和头文件。

②解决方案就是使用epport临时性的导出,在终端中依次输入以下命令:

export LDFLAGS="-L/opt/libcode/lib"
export CFLAGS="-I/opt/libcode/include"
export CPPFLAGS="-I/opt/libcode/include"

③导出后再次配置就过了,然后编译和安装

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vEjg5ap0-1570028293350)(175E21CB39F9461188F34D576F6FB4A0)]

④ make && make install

然后在/opt/libcode/lib目录下,可以看到动态库文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ffGuKGWX-1570028293350)(99155F8BD8AE458DAFD98A667B54E254)]

5、参考源码包自带的资料开始编程

(1)readme

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-29r8cYhs-1570028293351)(C1819047A3694F849462D862B5537D6F)]

(2)libpng-manual.txt

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IX8ZQYtd-1570028293352)(150DFB1780DD47D8BAFB9E2D9B7B6878)]

(3)example.c 和 pngtest.c

  • 仔细阅读example.c源码,推敲各个函数功能,然后着手编写代码

6、开始编程

①先is_png函数使用判断一个图片文件是不是png图片

新建fb_png.c,并将之前的fb_jpeg.c中的函数原型cp过来修改,并在子文件夹下makefile包含头文件等,这里不再赘述

  • ①测试我们写的int is_png函数(函数内容是直接将example.c 中的check_if_png函数cp过来的)
int is_png(const char *path)
{
FILE *fp = NULL;
	char buf[PNG_BYTES_TO_CHECK];  

/* Open the prospective PNG file. */
	if ((*fp = fopen(file_name, "rb")) == NULL)     
		return -1;  
     /* Read in some of the signature bytes */   
	if (fread(buf, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK)    
		return -1;  
	/* Compare the first PNG_BYTES_TO_CHECK bytes of the signature.      Return nonzero (true) if they match */  
return(!png_sig_cmp(buf, (png_size_t)0, PNG_BYTES_TO_CHECK));
}

然后同样步骤将之前jpeg显示函数cp过来修改,我们这里只是先测试is_png函数能否工作。

int display_png(const char *pathname)
{
int ret = -1;
struct pic_info picture;

// 第一步: 检测给的图片是不是jpg图片
ret = is_png(pathname);
if (ret != 0)
{
return -1;
printf("ret = %d.\n",ret);
}
printf("ret = %d.\n",ret);

/*
// 第二步: 解析该jpg图片
picture.pathname = pathname;
picture.pData = rgb_buf;
jpg_analyze(&picture);

// 第三步: 显示该jpg图片
fb_draw2(&picture);
*/
}

主函数调用:

//④测试png图片显示
	display_png("cute_pic4.png");//测试是否为png文件

make编译报错如下:没有找到这个,原因是没有包含相关头文件 怎么解决?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x9k8W7fl-1570028293353)(367344F78ED748C08E3B9F067829EB30)]

我们可以在ubuntu中使用grep命令查找这个变量头文件在那?

grep "PNG_BYTES_TO_CHECK" * -nR

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SCUgs6wV-1570028293354)(4333D403D98D48F39ACDC12A03395C2B)]

我们也在开始处直接宏定义即可

继续make,报错如下,包含了头文件的,为什么还是找不到这个函数,原因是主Makefile 没有链接进来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-soHKLIVm-1570028293354)(2F887788925D4B0CB4F302C28DF961F7)]

从新make&&make cp…重启开发板,运行脚本,显示找不到lib相关动态库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BOtjiVOT-1570028293355)(79774617725B4D5AA2CFB9897C76AF0C)]

解决方法:我们将之前生成的动态库拷贝到我们的跟文件系统的目录下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Br8MkVY1-1570028293356)(F010731D32354AEF8947303C8BC946C6)]

然后从新make &&make cp ,运行后并没有打印 is_png函数相关信息,经过debug排查问题,发现错误(方法就是使用debug定位错误在最后的png_sig_cmp函数这,然后使用SI建立libpng-1.6.6的工程,找到这个函数的原型,分析它的功能,发现正常匹配的话,返回值应该是0,但是我们这里使用!取反了,故判断不是png),修改,从新执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9ZWQPHz-1570028293357)(FD1C79F424904EABA29A61157858E5E2)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zFW82L9K-1570028293358)(16BB1996F2454DFFA52FB94F596DE013)]

② int png_analyze解码图片函数的编写(上)

这个函数参考example.c中的解码函数read_png函数了(因为过于复杂),我们选择直接在网上参考别人的。

int png_analyze(struct pic_info *pPic)
{
FILE *fp = NULL;
	png_structp png_ptr;   
png_infop info_ptr;
int color_type;
png_bytep* row_pointers;
unsigned long len = 0;
int pos = 0;
int i = 0, j = 0;

	if ((fp = fopen(pPic->pathname, "rb")) == NULL) 
	{	 
		fprintf(stderr, "can't open %s\n", pPic->pathname);    
return -1;
}

// 第1步: 相关数据结构实例化
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if (png_ptr == 0)
{
fclose(fp);
return -1;
}

info_ptr = png_create_info_struct(png_ptr);
  	if (info_ptr == 0)
  	{
   		png_destroy_read_struct(&png_ptr, 0, 0);
   		fclose(fp);
   		return -1;
  	}

// 第2步: 设置错误处理函数
if (setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
fclose(fp);
return -1;
}

// 第3步: 将要解码的png图片的文件指针和png解码器绑定起来
png_init_io(png_ptr, fp);

// 第4步: 读取png图片信息
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_ALPHA, 0);

// 第5步: 相关图片信息打印出来看一看
color_type = info_ptr->color_type;
debug("color_type = %d\n", color_type);

pPic->width = info_ptr->width;
pPic->height = info_ptr->height;
pPic->bpp = info_ptr->pixel_depth;
len = info_ptr->width * info_ptr->height * info_ptr->pixel_depth / 8;
debug("width = %u, height = %u, bpp = %u\n", pPic->width, pPic->height, pPic->bpp);
//打印png图片相关信息


return 0;

}

make 报错如下:没找到这个文件,说明我们配置libpng的时候这几个文件没有生成,需要我们手动添加到动态库。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rYJYcEAu-1570028293359)(D5CB69D31E744F34BEE454DD05A4944D)]

依次执行以下命令即可:

cp pngstruct.h  /opt/libcode/include/
cp pnginfo.h  /opt/libcode/include/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Aw5FpM0u-1570028293359)(900D1EEE7A4B4A8694CE7BA93D12AE48)]

然后make && make cp,…从新启动开发板,运行脚本,显示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nqXVTaoZ-1570028293360)(D2FF42F0690D49EB892F76A3EAA5F801)]

③ int png_analyze解码图片函数的编写(下)

在上一步中,已经实现将相关图片信息打印出来了,接下来就是图片的显示相关了。
继续对函数进行添加:

int png_analyze(struct pic_info *pPic)
{
// 第5步: 相关图片信息打印出来看一看
.......
// 第6步: 读取真正的图像信息
row_pointers = png_get_rows(png_ptr,info_ptr);

// 只处理RGB24位真彩色图片,其他格式的图片不管
// 第7步: 图像数据移动到我们自己的buf中
if(color_type == PNG_COLOR_TYPE_RGB)//PNG_COLOR_TYPE_RGB=2,和我们之前打印的信息要吻合
  	{
   		//memcpy(pPic->pData, row_pointers, len);/*直接这样不行,因为数据是乱的*/
for(i=0; i<pPic->height; i++)
{
for(j=0; j<3*pPic->width; j+=3)
{
pPic->pData[pos++] = row_pointers[i][j+0]; //red
pPic->pData[pos++] = row_pointers[i][j+1]; //green
pPic->pData[pos++] = row_pointers[i][j+2]; //blue
}
}
  	}
// 第8步: 收尾处理
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
// close file
fclose(fp);

return 0;
}

主函数调用

//④测试png图片显示
	display_png("cute_pic4.png");

然后make && make cp,…从新启动开发板,运行脚本,显示如下,且开发板正常显示PNG图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RAGV2YKS-1570028293361)(DCB95ACEBFFC47B58399AF514EC13A85)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nKvrT7Dq-1570028293362)(C6561FDA37464B3DBDACCA7827858480)]

到这里,对png图片的解码显示就搞定了。

十五、目录扫描,图片文件的管理模块

1、图片文件的管理

(1)在物理磁盘存储层次上,用一个文件夹来管理;

(2)在程序中,用数据结构来管理。

  • 用数组管理
  • 用链表管理

(4)编程实战(细节见代码,下面是关键点)

  • a、新建一个文件夹image_manage,记得要在makefile中添加新建的文件夹路径,以及在新建文件夹中新建makefile来管理文件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YEwdwJbR-1570028293363)(87268EC3BE6C44B9B855B6A0B5B53FE5)]

  • b、文件夹的打开操作、读取操作
    opendir的使用====打开文件夹

readdir的使用====读取文件夹的文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AY0KryjT-1570028293364)(98CEAA27434D458686A02A24C4F5487E)]

2、图片信息的自动检索

linux读取文件夹内容,我们直接百度,选择这篇文章参考,修改为自己的
https://blog.csdn.net/weixin_40535588/article/details/89668934

①使用readdir函数来读取文件夹下内容

新建image_manage.h

typedef enum image_type
{
	IMAGE_TYPE_BMP,
	IMAGE_TYPE_JPG,
	IMAGE_TYPE_PNG,
	IMAGE_TPPE_UNKNOWN,
}image_type_e;

// 结构体用来封装一个图片的信息
typedef struct image_info
{
	char pathname[PATHNAME_LEN];	// 图片文件的pathname
	image_type_e type;				// 图片文件的格式
}image_info_t;

新建image_manage.c, 使用readdir函数来读取文件夹内容
(readdir函数有问题,有时可以,有时不行,仔细阅读man手册,发现readdir函数中真正起作用的是lstat函数)

// images数组本来是空的,然后程序初始化时会去一个事先约定好的目录(image目录)下去
// 递归检索所有的文件及子文件夹,并且将所有的图片格式收集并且填充记录到images数组中
// 经过检索后,image数组中就记录了所有的图片,然后显示图片逻辑部分再去这个图片库中
// 拿出相应的图片来显示即可
// path是要去检索的文件夹的目录的路径
int scan_image(const char *path)
{
	// 在本函数中递归检索path文件夹,将其中所有图片填充到iamges数组中去
	DIR *dir;
	struct dirent *ptr;
	char base[1000];

	if ((dir = opendir(path)) == NULL)
	{
		perror("Open dir error...");
		exit(1);
	}

	// readdir函数每调用一次就会返回opendir打开的basepath目录下的一个文件,直到
	// basepath目录下所有文件都被读完之后,就会返回NULL
	while ((ptr = readdir(dir)) != NULL)
	{
		if(strcmp(ptr->d_name, ".")==0 || strcmp(ptr->d_name, "..")==0)    ///current dir OR parrent dir
			continue;

		debug("d_name = %s.\n", ptr->d_name);
		debug("d_type = %d, DT_REG = %d, DT_DIR = %d, DT_UNKNOWN = %d.\n", 
			ptr->d_type, DT_REG, DT_DIR, DT_UNKNOWN);
		switch (ptr->d_type)
		{
			case DT_REG:			// 普通文件
				printf("d_name:%s/%s\n", path, ptr->d_name);
				break;
			case DT_DIR:			// 文件夹
				memset(base,'\0',sizeof(base));
				strcpy(base,path);
				strcat(base,"/");
				strcat(base,ptr->d_name);
				scan_image(base);
				break;
			case DT_UNKNOWN:		// 不识别的文件格式
				printf("unknown file type.\n");
				break;
			default:
				break;
		}
	}
}

主函数调用该函数:

scan_image("./image");//readdir读取文件夹下相关内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mLcsAZVL-1570028293364)(E53B4EA5A57245498B1D152C0033B06C)]

②使用lstat函数来实现读取文件夹下内容

/*
使用lstat函数来实现读取文件夹下的内容,因为使用readdir函数有问题
扫描目录,索引图片,并完成图片数据的初始化
参数:目录的路径
*/
int scan_image2(const char *path)
{
	// 在本函数中递归检索path文件夹,将其中所有图片填充到iamges数组中去
	DIR *dir;
	struct dirent *ptr;
	char base[1000];
	struct stat sta;

	if ((dir = opendir(path)) == NULL)
	{
		perror("Open dir error...");
		exit(1);
	}

	// readdir函数每调用一次就会返回opendir打开的basepath目录下的一个文件,直到
	// basepath目录下所有文件都被读完之后,就会返回NULL
	while ((ptr = readdir(dir)) != NULL)
	{
		if(strcmp(ptr->d_name, ".")==0 || strcmp(ptr->d_name, "..")==0)    ///current dir OR parrent dir
			continue;

		// 用lstat来读取文件属性并判断文件类型
		memset(base,'\0',sizeof(base));
		strcpy(base,path);
		strcat(base,"/");
		strcat(base,ptr->d_name);
		lstat(base, &sta);

		if (S_ISREG(sta.st_mode))
		{
			//printf("regular file.\n");
			//printf("d_name:%s/%s\n", path, ptr->d_name);
			// 如果是普通文件,就要在这里进行处理:
			// 处理思路就是 先判定是否属于已知的某种图片格式,如果是则放到images数组中
			// 如果都不属于则不理他
			if (!is_bmp(base))
			{
				strcpy(images[image_index].pathname, base);
				images[image_index].type = IMAGE_TYPE_BMP;
			}
			if (!is_jpg(base))
			{
				strcpy(images[image_index].pathname, base);
				images[image_index].type = IMAGE_TYPE_JPG;
			}
			if (!is_png(base))
			{
				strcpy(images[image_index].pathname, base);
				images[image_index].type = IMAGE_TYPE_PNG;
			}		
			image_index++;
			
		}
		if (S_ISDIR(sta.st_mode))
		{
			//printf("directory.\n");
			//printf("d_name:%s/%s\n", path, ptr->d_name);
			scan_image2(base);
		}
	}
}
//打印出文件夹下图片的路径和文件名
void print_images(void)
{
	int i;

	printf("iamge_index = %d.\n", image_index);
	for (i=0; i<image_index; i++)
	{
		printf("images[i].pathname = %s,		type = %d.\n", images[i].pathname, images[i].type);
	}
}

测试print_images函数,主函数调用:

scan_image2("./image");
	print_images();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mTbZ7a2n-1570028293366)(4A769E0560F54CCD83D97AD7CED27F5E)]

//循环显示各个图片函数,并打印出文件路径和文件名

void show_images(void)
{
	int i;

	for (i=0; i<image_index; i++)
	{
		switch (images[i].type)
		{
			case IMAGE_TYPE_BMP:
				display_bmp(images[i].pathname);		break;
			case IMAGE_TYPE_JPG:
				display_jpg(images[i].pathname);		break;
			case IMAGE_TYPE_PNG:
				display_png(images[i].pathname);		break;
			default:
				break;
		}
		sleep(2);
	}
}

测试show_images函数,主函数调用://循环显示各个图片函数,并打印出文件路径和文件名

scan_image2("./image");
	//print_images();
	while(1)
		{
			show_images();
	}

编译拷贝,运行开发板,能看到循环显示我们imag目录下的图片,且终端移植打印出各个图片的类型及其路径等相关信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V9CU2FbQ-1570028293366)(7094E6834F6641808B5A5759E6CB6017)]

十六、添加触摸翻页功能(前提是已经学习过触摸屏驱动相关)

完成触摸屏驱动后,点击左右屏幕实现切换图片。

1、读取触摸坐标数据

参考如下触摸屏驱动移植实战中的app_input目录下的程序,并修改为我们的

在image_manage.c中新建一下函数,
开发板上的触摸屏设备文件是/dev/input/event2(我这里是event2)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rFj7AdC6-1570028293367)(06488D27A01C4AC0838088DEFECA8619)]

//定义系统中的触摸屏设备的设备名
#define DEVICE_TOUCHSCREEN			"/dev/input/event2"
/*
*检测触摸屏,根据结果实现图片上下切换
*参数:无传参
*/
int ts_updown(void)
{	
	// 第一步: 触摸屏的触摸操作检测
	int fd = -1, ret = -1;
	struct input_event ev;
	
	fd = open(DEVICE_TOUCHSCREEN, O_RDONLY);// 打开设备文件
	if (fd < 0)
	{
		perror("open");
		return -1;
	}	
	while (1)
	{
		// 读取一个event事件包
		memset(&ev, 0, sizeof(struct input_event));
		ret = read(fd, &ev, sizeof(struct input_event));
		if (ret != sizeof(struct input_event))
		{
			perror("read");
			close(fd);
			return -1;
		}		
		// 解析event包,才知道发生了什么样的输入事件
		printf("-------------------------\n");
		printf("type: %hd\n", ev.type);
		printf("code: %hd\n", ev.code);
		printf("value: %d\n", ev.value);
		printf("\n");
	}	
	// 关闭设备
	close(fd);

	// 第二步: 根据触摸坐标来翻页
 
return 0;
}

主函数调用:

//⑥测试触摸翻页显示图片功能
	scan_image2("./image");
	 ts_updown();

make &&make cp, 开发板运行该程序,触摸屏幕,终端显示坐标信息如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qj8K5A6s-1570028293368)(89E56667FE684193A3DD7DFE324C32F0)]

code 0表示x坐标,value为x的值;code 1表示y坐标,value为y的值。

2、使用触摸坐标判断并执行翻页操作

修改ts_updown函数,使其能实现触摸翻页,红色部分为修改的源码

//定义触摸翻页区域的宽度
#define   TOUCH_WIDTH			200
/*
*检测触摸屏,根据结果实现图片上下切换
*参数:无传参
*/
int ts_updown(void)
{
// 第一步: 触摸屏的触摸操作检测
int fd = -1, ret = -1;
struct input_event ev;
int i = 0;     // 用来记录当前显示的是第几个图片

fd = open(DEVICE_TOUCHSCREEN, O_RDONLY);// 打开设备文件
if (fd < 0)
{
perror("open");
return -1;
}
while (1)
{
// 读取一个event事件包
memset(&ev, 0, sizeof(struct input_event));
ret = read(fd, &ev, sizeof(struct input_event));
if (ret != sizeof(struct input_event))
{
perror("read");
close(fd);
return -1;
}

// 第二步: 根据触摸坐标来翻页
if ((ev.type == EV_ABS) && (ev.code == ABS_X))
{
// 确定这个是x坐标 如果在这个0~200的宽度范围
if ((ev.value >= 0) && (ev.value < TOUCH_WIDTH))
{
// 上翻页
if (i-- <= 1)
{
i = image_index;
debug("i=%d.\n", i);
}

} //如果在这个1024-200 ~1024的宽度范围
else if ((ev.value > (WIDTH - TOUCH_WIDTH)) && (ev.value <= WIDTH))
{
// 下翻页
if (i++ >= image_index)
{
i = 1;
debug("i=%d.\n", i);
}
}
else
{
// 不翻页
}
show_image(i - 1);//将i用来记录当前显示的是第几个图片,然后传给这个函数,用于显示图片

}

/*
// 解析event包,才知道发生了什么样的输入事件
printf("-------------------------\n");
printf("type: %hd\n", ev.type);
printf("code: %hd\n", ev.code);
printf("value: %d\n", ev.value);
printf("\n");
*/
}
// 关闭设备
close(fd);


return 0;

}

主函数调用该函数

//⑥测试触摸翻页显示图片功能
scan_image2("./image");
 ts_updown();

make &&make cp, 开发板运行该程序,触摸屏幕,终端显示坐标信息如下:

(1)执行./run.sh后会阻塞,如果点击触摸屏,会在终端中显示测试的内容。
(2)在不同区域点一下,有不同的效果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hGATF2JG-1570028293369)(C0C0B0C9E68547D79AE8D4A06B25A431)]

十七、总结与回顾

1、项目总结

(1)项目描述:软硬件平台等

硬件平台使用使用s5pv210开发板,软件以linux平台开发。
支持解码显示bmp,png,JPEG图片,并通过点击触摸屏左右两端实现上下切换图片的效果

(2)重点和难点

第三方库libpng、 libgpeg库的移植
图片文件的管理和检索,刚开始使用read函数,出现问题,后来使用lstat函数来实现读取文件夹下内容,实现目录扫描

(3)主要技术:

  • linux Framebuffer驱动移植
  • linux input输入子系统移植
  • linux i2c子系统
  • 触摸屏驱动移植
  • libjpeg库移植
  • libpng库移植

1.1.驱动模块(驱动模块主要的任务就是进行Framebuffer地址映射)

  • linux Framebuffer驱动
    参考前面我们学习的framebufer驱动,移植framebufer驱动。
  • 触摸屏(gslX680)驱动
    参考驱动篇触摸屏驱动,移植gslX680驱动。

1.2.目录扫描,图片管理模块

  • 目录扫描功能采用递归方法,扫描目录的所有文件,经过排除,最后只对普通文件做进一步处理。图片管理采用数组来组织。

1.3.图片切换模块

  • 完成触摸屏驱动后,点击左右屏幕实现切换图片。

1.4.图片显示模块

  • BMP显示

通过判断头两个字符是否为”BM”,判断是否为BMP图片,BMP本身没有对数据进行压缩,所以可直接读取数据进行操作

  • JPEG显示

判断是否为JPEG图片, 通过判断特点位是否为JPEG专用字符,进而确认为JPEG图片后,解码jpg图片(使用libjpeg库提供JPEG图片的编解码算法实现),并将解码出来相应的位图数据进行存储。显示该图片

  • 显示PNG图片

通过调用libpng库函数png_sig_cmp,判断是否为PNG图片。显示PNG图片,运用libpng库,完成PNG图片的显示

TOUCH_WIDTH 200



/*
*检测触摸屏,根据结果实现图片上下切换
*参数:无传参
*/
int ts_updown(void)
{
// 第一步: 触摸屏的触摸操作检测
int fd = -1, ret = -1;
struct input_event ev;
int i = 0; // 用来记录当前显示的是第几个图片

fd = open(DEVICE_TOUCHSCREEN, O_RDONLY);// 打开设备文件
if (fd < 0)
{
perror(“open”);
return -1;
}
while (1)
{
// 读取一个event事件包
memset(&ev, 0, sizeof(struct input_event));
ret = read(fd, &ev, sizeof(struct input_event));
if (ret != sizeof(struct input_event))
{
perror(“read”);
close(fd);
return -1;
}

// 第二步: 根据触摸坐标来翻页
if ((ev.type == EV_ABS) && (ev.code == ABS_X))
{
// 确定这个是x坐标 如果在这个0~200的宽度范围
if ((ev.value >= 0) && (ev.value < TOUCH_WIDTH))
{
// 上翻页
if (i-- <= 1)
{
i = image_index;
debug(“i=%d.\n”, i);
}

} //如果在这个1024-200 ~1024的宽度范围
else if ((ev.value > (WIDTH - TOUCH_WIDTH)) && (ev.value <= WIDTH))
{
// 下翻页
if (i++ >= image_index)
{
i = 1;
debug(“i=%d.\n”, i);
}
}
else
{
// 不翻页
}
show_image(i - 1);//将i用来记录当前显示的是第几个图片,然后传给这个函数,用于显示图片

}

/*
// 解析event包,才知道发生了什么样的输入事件
printf("-------------------------\n");
printf(“type: %hd\n”, ev.type);
printf(“code: %hd\n”, ev.code);
printf(“value: %d\n”, ev.value);
printf("\n");
*/
}
// 关闭设备
close(fd);

return 0;

}



## 主函数调用该函数
	//⑥测试触摸翻页显示图片功能
	scan_image2("./image");
	 ts_updown();

make &&make cp, 开发板运行该程序,触摸屏幕,终端显示坐标信息如下:

(1)执行./run.sh后会阻塞,如果点击触摸屏,会在终端中显示测试的内容。
(2)在不同区域点一下,有不同的效果。

[外链图片转存中...(img-hGATF2JG-1570028293369)]

 
# 十七、总结与回顾
## 1、项目总结
### (1)项目描述:软硬件平台等
硬件平台使用使用s5pv210开发板,软件以linux平台开发。
支持解码显示bmp,png,JPEG图片,并通过点击触摸屏左右两端实现上下切换图片的效果

### (2)重点和难点
第三方库libpng、 libgpeg库的移植
图片文件的管理和检索,刚开始使用read函数,出现问题,后来使用lstat函数来实现读取文件夹下内容,实现目录扫描

### (3)主要技术:
- linux Framebuffer驱动移植
- linux input输入子系统移植
- linux i2c子系统
- 触摸屏驱动移植
- libjpeg库移植
- libpng库移植

#### 1.1.驱动模块(驱动模块主要的任务就是进行Framebuffer地址映射)
- linux Framebuffer驱动
参考前面我们学习的framebufer驱动,移植framebufer驱动。
- 触摸屏(gslX680)驱动
参考驱动篇触摸屏驱动,移植gslX680驱动。

#### 1.2.目录扫描,图片管理模块
- 目录扫描功能采用递归方法,扫描目录的所有文件,经过排除,最后只对普通文件做进一步处理。图片管理采用数组来组织。

#### 1.3.图片切换模块
- 完成触摸屏驱动后,点击左右屏幕实现切换图片。

#### 1.4.图片显示模块
- BMP显示

通过判断头两个字符是否为”BM”,判断是否为BMP图片,BMP本身没有对数据进行压缩,所以可直接读取数据进行操作
- JPEG显示

判断是否为JPEG图片, 通过判断特点位是否为JPEG专用字符,进而确认为JPEG图片后,解码jpg图片(使用libjpeg库提供JPEG图片的编解码算法实现),并将解码出来相应的位图数据进行存储。显示该图片
- 显示PNG图片

通过调用libpng库函数png_sig_cmp,判断是否为PNG图片。显示PNG图片,运用libpng库,完成PNG图片的显示

















参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:精致技术 设计师:CSDN官方博客 返回首页

打赏作者

MrT_WANG

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值