有道云笔记详细地址:
文档:图片解码播放器小项目(详解).note
链接:http://note.youdao.com/noteshare?id=9f9a43ac5ec6828cf467940dfa10da51&sub=A8280EB4B5A146C9A1F6612031305071
文章目录
- 一、开始动手写代码
- 二、framebuffer驱动基本操作代码
- 三、图片显示原理和实践
- 四、图片显示的高级话题
- 五、任意分辨率大小图片显示
- 六、任意起点位置图片显示
- 七、BMP图片的显示
- 八、及时规整
- 九、jpg图片的显示原理分析
- 十、libjpeg介绍及开源库的使用方法
- 十一、libjpeg的移植实战
- 十二、使用libjpeg解码显示jpg图片
- 十三、解决解码显示中的问题
- 十四、解码显示png图片
- 十五、目录扫描,图片文件的管理模块
- 十六、添加触摸翻页功能(前提是已经学习过触摸屏驱动相关)
- 十七、总结与回顾
一、开始动手写代码
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;
}