【Linux 应用开发】Linux 电子相册项目的一些技能点和问题解决
一、input输入类——触摸屏
①、(input类设备)识别出输入设备的文件
如鼠标、触摸屏等都为输入设备,其对应的设备文件在/dev/input/目录下。
其通常都有多个文件:
如何识别哪个是触摸屏的文件呢?使用 od -x 命令:
sudo od -x /dev/input/event3
执行完上述命令,再按触摸屏,如果串口输出有反应:
则可判断当下文件即为触摸屏的文件。
另外,也可以查看 /proc/bus/input/devices 文件,通过看其 Name 属性,进行判断:
二、显示设备类
2.1 (fb设备) 显示bmp的流程图
linuxfb设备显示bmp的思路很简单,就是把bmp每一像素的数据读出来,然后写进linuxfb文件的每一像素中去。
但是编程过程中,主要有以下的三个矛盾:
- bmp 图片位深度和 LCD的位深度各自有多种,两者不一致 (例如bmp格式为RGB888, 而屏幕的格式为RGB565,或者ARGB8888)
- bmp 图片有正序、倒序两种类型(原点分别在左上角、左下角),大多数bmp原点为倒序(左下角),而 LCD 的原点往往在左上角。
- Windows 系统会给宽度像素不能按4对齐的,添加垃圾字节。(以下有详细介绍)
同时,为了增大程序的灵活性,还应该能够设置图片显示的位置(即指定bmp图片在LCD上显示的左上角原点位置)
因此程序流程图可以设计如下:(按照边读取,边显示的思路)
上面的流程图是按照“边读取,边显示”思路写的。当然,也可以一次性读完,然后整体对图片的缓存进行处理,再进行显示:
程序如下:
/*
* @func:图片数据处理函数,解决bmp图片格式与lcd冲突、原点不统一的矛盾。
* @param: bufIn bmp输入缓存; bufOut 用于显示的缓存
*/
int picProcess(char* bufIn,int* bufOut,int width,int height)
{
if(bufIn==NULL)
{
printf("bufIn is NULL\n");
return -1;
}
for(int i=0;i<height*width;i++) //将24位bmp图片数据变成800*480*4个字节可在lcd中显示的数据
{
bufOut[i] = 0x00<<24|bufIn[3*i+0]|bufIn[3*i+1]<<8|bufIn[3*i+2]<<16;
}
for(int i=0;i<width;i++) //将图片关于x轴镜像
{
for(int j=0;j<height/2;j++)
{
bufOut[j*width+i] ^= bufOut[width*(height-1-j)+i];
bufOut[width*(height-1-j)+i] ^= bufOut[j*width+i];
bufOut[j*width+i] ^= bufOut[width*(height-1-j)+i];
}
}
return 1;
}
2.2(fb设备)Linux bmp宽度字节数不能被4整除显示错位问题
主要参考文章:《gec6818_液晶屏显示bmp图片》
辅助参考文章:《关于bmp图片倾斜的解决方法》
24位bmp格式图片的编码特点:
- 每个像素点占3个字节存放的BGR数据 B蓝色 G绿色 R红色。(注意顺序,不是R-G-B)
- 图片的宽度占用的字节数如果不能被4整除,window系统会给每一行尾端填充垃圾数凑够4字节整除。
如示例图中一张750*450的bmp图片(正常显示为右图),每一行的数据应该是750x3 = 2250字节,但2250不能被4整除,所以系统会自动在每一行后面补齐2个字节数据,达到2252字节,这样图片的长就被拉长了,就会出现倾斜现象(左图)。
那么如何解决这个问题呢,原博这样解决:
//判断宽度是否能被4整除
if(width%4==0){
//被4整除,一下子读完
read(bmpfd,bmpbuf,width*height*3);
}else{
//不被4整除,一行一行读
//每行读到最后(width),设置当前往后偏移(宽%4的余数)
for(i=0;i<height;i++){
read(bmpfd,bmpbuf+i*width*3,width*3);
lseek(bmpfd,width%4,SEEK_CUR); //跳过垃圾数据
}
}
是通过 lseek(bmpfd,width%4,SEEK_CUR); 跳过垃圾数据实现的。
原博的程序不太严谨,仅仅考虑了 width(图片宽度),而没算进去 byte_depth(图片颜色深度),因此我小小修改如下:
//检查图片宽度字节数是否能被4整除
div4_mod = (pic_w * byte_depth)%4; //算进去 byte_depth(图片颜色深度)
printf("div4_mod = %d\n", div4_mod);
if(div4_mod==0){
//被4整除,一下子读完
read(bmpfd,bmpbuf,width*height*byte_depth);
}else{
for(i=0;i<height;i++){
read(bmpfd,bmpbuf+i*width*byte_depth,width*byte_depth);//一行一行地读,bmpbuf为整张bmp的buf
lseek(bmpfd,div4_mod,SEEK_CUR); //跳过垃圾数据
}
}
2.3 扩展:_attribute__((packed))详解
定义bmp 结构体时,用到了这个
/**** BMP文件头数据结构 ****/
typedef struct {
unsigned char type[2]; //文件类型
unsigned int size; //文件大小
unsigned short reserved1; //保留字段1
unsigned short reserved2; //保留字段2
unsigned int offset; //到位图数据的偏移量
} __attribute__ ((packed)) bmp_file_header;
GNU C的一大特色(却不被初学者所知)就是__attribute__机制。__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。
__attribute__语法格式为:
__attribute__ ((attribute-list))
其位置约束为:放于声明的尾部, “;”之前。
packed是类型属性(Type Attribute)的一个参数,使用packed可以减小对象占用的空间,其告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。这个功能是跟操作系统没关系,跟编译器有关,gcc编译器不是紧凑模式的,我在windows下,用vc的编译器也不是紧凑的,用tc的编译器就是紧凑的。
举例来说:
在GCC下:struct my{ char ch; int a;} sizeof(int)=4;sizeof(my)=8;(非紧凑模式)
在GCC下:struct my{ char ch; int a;}__attrubte__ ((packed)) sizeof(int)=4;sizeof(my)=5(紧凑模式)
另外,__attribute__ 也可以指定4字节对齐,如下所示:
struct student
{
char name[7];
uint32_t id;
char subject[5];
} __attribute__ ((aligned(4)));
2.4 bmp_show.c 的代码重构
以前自己的代码充满了全局变量,如下所示:
//LCD 屏幕参数
static int lcd_w = 0;
static int lcd_h = 0;
int fd = 0;
int lcd_linelen = 0; //一行数据的字节数
static unsigned short *lcd_buf = NULL; //mmap()之后得到的新空间
static int byte_depth = 0;
static int totallen = 0; //lcd 缓存总共的字节数
static int lcd_byte_depth = 0;
//图片参数
int pic_w = 0;
int pic_h = 0;
int fpic = -1;
//bmp_file_header bmp_fhead;
//bmp_info_header bmp_info;
而现在,为了代码逻辑更清晰,做了以下几件事:
第一,将以前关于lcd的参数的全局变量,打包定义成了一个结构体。这样代码也更清爽的同时,逻辑性更强一点。
typedef struct
{
unsigned int lcd_w;
unsigned int lcd_h;
unsigned int lcd_linelen;//一行数据的字节数;//在lcd_init和bmp_show中用到
unsigned int totallen; //lcd 缓存总共的字节数
int lcd_byte_depth;
unsigned short *lcd_buf;////mmap()之后得到的新空间,//在lcd_init和bmp_show中用到
}Lcd_info;
//LCD 屏幕参数
static Lcd_info lcd_info;
第二,将以前关于图片的参数的全局变量,定义为函数的形参,通过函数的参数传递实现沟通。
//修改前:
//int lcd_getbmpinfo(char *path);
//void bmp_show(int off);
//修改后:
int lcd_getbmpinfo(char *path, int *fpic, int *pic_w, int *pic_h, int *byte_depth);//利用指针传递返回参数,如图片的文件描述符、宽、高、字节深度
void bmp_show(int fpic, int off, int start_x, int start_y, int pic_w, int pic_h, int byte_depth);
这样做有以下几个好处:
- 函数的灵活性大大地提高了。
- 逻辑更加清晰, 不需要全局变量了,因此不用猜参数"从哪里来,到哪里去" 。
- 后续维护、修改更方便。不用担心参数"哪里用到了,哪里没用,能不能用(是全局变量还是局部变量)"的问题。