【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格式图片的编码特点:

  1. 每个像素点占3个字节存放的BGR数据  B蓝色  G绿色  R红色。(注意顺序,不是R-G-B)
  2. 图片的宽度占用的字节数如果不能被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);

这样做有以下几个好处:

  • 函数的灵活性大大地提高了。
  • 逻辑更加清晰, 不需要全局变量了,因此不用猜参数"从哪里来,到哪里去" 。
  • 后续维护、修改更方便。不用担心参数"哪里用到了,哪里没用,能不能用(是全局变量还是局部变量)"的问题。
posted @ 2023-08-30 17:12  FBshark  阅读(242)  评论(0编辑  收藏  举报