bmp格式的位图文件的编码格式简单,解析也较容易。在linux framebuffer 的基础上,可以很快的编写一些代码来显示图片。本文将基于此实现在arm开发板上对bmp位图的显示。 bmp格式中的结构组成分为四部分: 位图头:保存位图文件的总体信息。 位图信息:保存位图图像的详细信息。 调色板:保存所用颜色的定义。 位图数据:保存一个又一个像素的实际图像。 我们要做的就是通过读取到的位图头和位图信息,获取图片的信息,并根据此来决定对位图数据的处理。详细的bmp信息可以在网络上查到。这里定义了两个结构体来存储这些信息,如下: /位图头/ struct bmp_head { char map_id[2]; //标识符,识别位图类型,一般为‘B’‘M’ unsigned int file_size; //用字节表示整个文件的大小 int reserved; //保留,设置为0 unsigned int offset; //从文件开始到位图数据开始之间的数据(bitmap data)之间的 //偏移量 }; /*bmp位图信息*/ struct bmp_info { unsigned int cur_size; //当前结构体的大小,通常是40或56 int width; //位图的宽度,以像素为单位 int hight; //位图的高度,以像素为单位 short reserved; //这个字的值永远是1 short bpp; //每像素占用的位数,即bpp unsigned int compression;//压缩方式 unsigned int map_size; //用字节数表示的位图数据的大小。该数必须是4的倍数 int x_ppm; //用像素/米表示的水平分辨率 int y_ppm; //用像素/米表示的垂直分辨率 unsigned int palette; //调色板规范 unsigned int bitmapdata;//该域的大小取决于压缩方法,它包含所有的位图数据字节, //这些数据实际就是彩色调色板的索引号 /*cur_size = 40不包含以下信息,=56时则包含之*/ unsigned int R; unsigned int G; unsigned int B; unsigned int A; }; 其中struct bmp_head为14个字节大小,struct bmp_info通常为40字节,不使用最后的4个字段,这最后四个字段用于压缩方式为Bit_Files(compression=3)。下面给出了在linux下的获取这个两个结构体信息的函数(我不太熟悉linux下的C,所以代码显得很臃肿,好在基本上能用): /*获取文件头*/ int get_bmp_head(int fp,struct bmp_head *bmp_h) { char tmp[36]; int ret; if(fp < 0 || bmp_h == NULL) return -1; ret = lseek(fp,0,SEEK_SET);//调整文件读取指针为距文件开头偏移为0 if(ret < -1){ return -1; } ret = read(fp,tmp,14);//读取文件头,这里如果直接读到bmp_h中,在我的编译器上无 //法正确填入各字段,所以用tmp来转存后从中读取 if(ret < 0){ return -1; } bmp_h->map_id[0] = tmp[0]; bmp_h->map_id[1] = tmp[1]; bmp_h->file_size = (tmp[2]&0xff) | (tmp[3]&0xff)<<8 | (tmp[4]&0xff)<<16 | (tmp[5]&0xff)<<24; bmp_h->reserved = 0;//位图的定义中规定为0 bmp_h->offset = (tmp[10]&0xff)|(tmp[11]&0xff)<<8|(tmp[12]&0xff)<<16|(tmp[13]&0xff)<<24; return 0; } /*获取位图信息*/ int get_bmp_info(int fp,struct bmp_info *bmp_tmp) { unsigned char tmp[20]={0}; int ret,s;//s标记须获取的数据段是40/56 if(fp < 0 || bmp_tmp == NULL) return -1; /*****************获取数据块大小*****************/ ret = lseek(fp,14,SEEK_SET); if(ret < -1){ return -2; } ret = read(fp,tmp,4); if(ret < 0){ return -3; } s = tmp[0]; /**************************************/ ret = lseek(fp,14,SEEK_SET); if(ret < -1){ return -4; } ret = read(fp,bmp_tmp,s);//将位图信息填入结构字段中 if(ret < 0){ return -5; } ret = lseek(fp,34,SEEK_SET);//从这个偏移处的数据出现了问题重新读取 if(ret == -1){ return -6; } ret = read(fp,tmp,tmp[0]>40?36:20); if(ret < 0){ return -7; } /*填写各个字段(很糟糕一段,很臃肿 :( )*/ bmp_tmp->map_size = (tmp[0]&0xff)|(tmp[1]&0xff)<<8|(tmp[2]&0xff)<<16|(tmp[3]&0xff)<<24; bmp_tmp->y_ppm = (tmp[4]&0xff)|(tmp[5]&0xff)<<8|(tmp[6]&0xff)<<16|(tmp[7]&0xff)<<24; bmp_tmp->x_ppm = (tmp[8]&0xff)|(tmp[9]&0xff)<<8|(tmp[10]&0xff)<<16|(tmp[11]&0xff)<<24; bmp_tmp->palette = (tmp[12]&0xff)|(tmp[13]&0xff)<<8|(tmp[14]&0xff)<<16|(tmp[15]&0xff)<<24; bmp_tmp->bitmapdata = (tmp[16]&0xff)|(tmp[17]&0xff)<<8|(tmp[18]&0xff)<<16|(tmp[19]&0xff)<<24; if( s == 56){ bmp_tmp->R = (tmp[20]&0xff)|(tmp[21]&0xff)<<8|(tmp[22]&0xff)<<16|(tmp[23]&0xff)<<24; bmp_tmp->B = (tmp[28]&0xff)|(tmp[29]&0xff)<<8|(tmp[30]&0xff)<<16|(tmp[31]&0xff)<<24; bmp_tmp->A = (tmp[32]&0xff)|(tmp[33]&0xff)<<8|(tmp[34]&0xff)<<16|(tmp[35]&0xff)<<24; } return 0; } 下面是对数据段的读取,数据的压缩方式有多种,我在网上找到的图片都是24bit的(即bpp=24),所以只编写了这种格式的解码。24bit的各颜色分量为(R:8 G:8 B:8),存储的格式为3个字节代表一个像素点。这里我只是将数据段读到内存缓冲区中,如下: 这里先定义了一个结构体: struct bmp_list { struct bmp_head *head; struct bmp_info *info; }; /*读取数据段,并返回这个函数指针*/ char *read_bmp(int fp,struct bmp_list *map_list) { int ret,map_size; char *data; /*只能对24bpp做处理*/ if(map_list->info->bpp != 24 || map_list->info->compression !=0 ){ return NULL; } /*****************************************************************/ map_size = map_list->info->width * map_list->info->hight * map_list->info->bpp/8;//本因该直接使用map_size的但返回的值总是为0 data = (char *)malloc(map_size); if(data == NULL){ printf("failed to get mem\n"); return NULL; } for(ret=0;ret < map_size ;ret++) data[ret]=0; /****************************************************************/ ret = lseek(fp,map_list->head->offset,SEEK_SET); //数据段开始地址 if(ret == -1){ return NULL; } ret = read(fp,data,map_size); if(ret == -1){ printf("read:wrong\n"); return NULL; } return data; } 位图相关的部分差不多就这些了,总的说来bmp格式的解析很容易,即便我的代码看着那么麻烦。 Framebuffer 这个地方不讲Framebuffer架构和驱动方面的,我对此亦是一知半解。 我是ARM11平台上做的程序,板子用的是飞凌的ok6410,需要配置内核支持24bpp。 先看几个要用到的结构体: /*用于文件操作*/ struct file_user_fb { int fp; //打开的设备文件描述符 char *fbp; //映射后的内存指针,通过它直接对lcd屏写数据 }; struct fb_info { struct fb_var_screeninfo vinfo; //可变的屏信息 struct fb_fix_screeninfo finfo; //固定的屏信息 }; 结构体中的两个字段的具体内容可以在<linux/fb.h>中找到,这里就不粘贴复制了。 下面是framebuffer相关函数。 /*打开fbx,并获得相关屏信息*/ /* *dev_file:设备名 如:/dev/fb0 */ struct file_user_fb *lcd_key_init(char dev_file[],struct fb_info *ihd) { long scr_size; struct file_user_fb *key_bf; key_bf = (struct file_user_fb *)malloc(sizeof(struct file_user_fb)); if(key_bf == NULL){ perror("Failed to get mem\n"); return NULL; } /*打开fb*/ key_bf->fp = open(dev_file,O_RDWR); if(key_bf->fp < 0){ perror("Fail open file\n"); return NULL; } /*获取屏信息*/ if (ioctl(key_bf->fp,FBIOGET_FSCREENINFO,&ihd->finfo)){ printf("Error reading fixed information\n"); close(key_bf->fp); free(key_bf); return NULL; } if (ioctl(key_bf->fp,FBIOGET_VSCREENINFO,&ihd->vinfo)){ printf("Error reading variable information\n"); close(key_bf->fp); free(key_bf); return NULL; } /*映射到用户空间*/ scr_size = ihd->vinfo.xres * ihd->vinfo.yres * 4; key_bf->fbp =(char *) mmap (NULL, scr_size, PROT_READ | PROT_WRITE, MAP_SHARED, key_bf->fp,0); if ((int)key_bf->fbp == -1) { printf ("Error: failed to map framebuffer device to memory.\n"); close(key_bf->fp); free(key_bf); return NULL; } //memset();//可以初始内存 return key_bf; } /*退出的清理函数*/ void lcd_key_exit(struct file_user_fb *key_bf,struct fb_info *ihd) { long scr_size; if(key_bf == NULL) return; scr_size = ihd->vinfo.xres * ihd->vinfo.yres * 4; munmap (key_bf->fbp, scr_size); close(key_bf->fp); free(key_bf); } 下面要讲最后两个函数,其实只能算一个,两个显示图片的方向相反。lcd上的显示函数: void show_bmp_24_down( char *fbp,char *map_data,struct fb_info *ihd, struct bmp_info *bi) { int x_t=0,y_t=0; //lcd屏缓冲区控制 int w_t=0,z_t=0; //读取图片的起始位置 for(y_t=0, w_t=0;(y_t < ihd->vinfo.yres) && (w_t < bi->hight)&&(w_t>=0);y_t++, w_t++) { for(x_t=0, z_t=0;x_t<ihd->vinfo.xres*4 && (z_t<bi->width*3)&&(z_t>=0);x_t+=4, z_t+=3) { *(fbp + x_t + y_t*ihd->vinfo.xres*4 + 0) = *(map_data + w_t*bi->width*3 + z_t + 0); *(fbp + x_t + y_t*ihd->vinfo.xres*4 + 1) = *(map_data + w_t*bi->width*3 + z_t + 1); *(fbp + x_t + y_t*ihd->vinfo.xres*4 + 2) = *(map_data + w_t*bi->width*3 + z_t + 2); *(fbp + x_t + y_t*ihd->vinfo.xres*4 + 3) = 0; } } } void show_bmp_24_up( char *fbp,char *map_data,struct fb_info *ihd, struct bmp_info *bi) { int x_t=0,y_t=0; //lcd屏缓冲区控制 int w_t=0,z_t=0; //读取图片的起始位置 for(y_t=0, w_t=bi->hight-200;(y_t < ihd->vinfo.yres) && (w_t < bi->hight)&&(w_t>=0);y_t++, w_t--) { for(x_t=0*4, z_t=0*3;x_t<ihd->vinfo.xres*4 && (z_t<bi->width*3)&&(z_t>=0);x_t+=4, z_t+=3) { *(fbp + x_t + y_t*ihd->vinfo.xres*4 + 0) = *(map_data + w_t*bi->width*3 + z_t + 0); *(fbp + x_t + y_t*ihd->vinfo.xres*4 + 1) = *(map_data + w_t*bi->width*3 + z_t + 1); *(fbp + x_t + y_t*ihd->vinfo.xres*4 + 2) = *(map_data + w_t*bi->width*3 + z_t + 2); *(fbp + x_t + y_t*ihd->vinfo.xres*4 + 3) = 0; } } } 这里涉及到两个内存区数据的交换,主要是将位图缓冲区的数据放入fb中。通过x_t,y_t独立控制fb每个像素点数据的获取,x_t在此须为4(24bpp)的倍数,缓冲区行线上4个字节对应一个像素点,像素点偏移1,内存偏移4个字节。第二for循环中“=”左边即是一个像素点对应的内存区的4个字节,第3个字节未用。两个for循环遍历了整个内存区域,使位图能全屏显示(当图片分别大于屏的长宽时,往往只能显示图片的一部分)。读取的数据会从左上角按“Z”填充fb缓冲区。 “=”右边的就是需要显示的图片数据。通过对w_t决定需要位图高度的起始位置,z_t则为宽度。需要注意的是,当设定起始宽度时要为3(24bpp)的倍数. |
接下来是测试代码:
可能用到的头文件:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <asm/ioctl.h>
///////////////////////////////
#define WIN_NUM (4)
#define FB_NAME0 "/dev/fb0"
struct fb_info scr_info[WIN_NUM];
struct file_user_fb *lcd_fb0;
struct bmp_list map_list;
int main(int argc,char *argv[])
{
int bmp_fp;
int ret,chose=1;
char *map_data;
if(argc != 2){
printf("usege: bmp xxx");
exit(1);
}
/*关联到fb0*/
lcd_fb0 = lcd_key_init(FB_NAME0,&scr_info[0]);
if(lcd_fb0 == NULL){
printf("open fb0 fail!\n");
exit(1);
}
/*打开位图文件*/
bmp_fp = open(argv[1],O_RDWR);
if(bmp_fp < 0){
lcd_key_exit(lcd_fb0,&scr_info[0]);
printf("open map wrong\n");
exit(2);
}
map_list.head = (struct bmp_head *)malloc(sizeof(struct bmp_head));
if(map_list.head == NULL){
printf("mem for map head wrong\n");
exit(3);
}
map_list.info = (struct bmp_info *)malloc(sizeof(struct bmp_info));
if(map_list.info == NULL){
printf("mem for map info wrong\n");
exit(4);
}
/*读取位图信息*/
ret = get_bmp_head(bmp_fp,map_list.head);
ret = get_bmp_info(bmp_fp,map_list.info);
printf("****************************|\n"
"* Map information:\n "
"* Style:%c%c\n"
"* Size:%d Width:%d Hight:%d Bpp:%d Compression:%d\n"
"* MADE BY Yaong\n"
"****************************|\n",map_list.head->map_id[0],map_list.head->map_id[1], \
map_list.head->file_size,map_list.info->width,map_list.info-
>hight,map_list.info->bpp,map_list.info->compression);
/*读取位图信息*/
map_data = read_bmp(bmp_fp,&map_list);
while(chose != 0){
scanf("%d",&chose);
switch(chose){
case 1:/*向上显示*/
show_bmp_24_up(lcd_fb0>fbp,map_data,&scr_info[0],map_list.info);
break;
case 2:/*向下显示*/
show_bmp_24_down(lcd_fb0>fbp,map_data,&scr_info[0],map_list.info);
break;
default:
printf("no commamd!");
break;
}
putchar('\n');
}
if(map_data != NULL)
free(map_data);
exit_bmp(bmp_fp,map_list.head,map_list.info);
lcd_key_exit(lcd_fb0,&scr_info[0]);
return 0;
}
/*清理函数*/
void exit_bmp(int fb,struct bmp_head *bh,struct bmp_info *bi)
{
if(bh != NULL)
free(bh);
if(bi != NULL)
free(bi);
if(fb >= 0)
close(fb);
printf("Exit!\n");
}
(完)
可能用到的头文件:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <asm/ioctl.h>
///////////////////////////////
#define WIN_NUM (4)
#define FB_NAME0 "/dev/fb0"
struct fb_info scr_info[WIN_NUM];
struct file_user_fb *lcd_fb0;
struct bmp_list map_list;
int main(int argc,char *argv[])
{
int bmp_fp;
int ret,chose=1;
char *map_data;
if(argc != 2){
printf("usege: bmp xxx");
exit(1);
}
/*关联到fb0*/
lcd_fb0 = lcd_key_init(FB_NAME0,&scr_info[0]);
if(lcd_fb0 == NULL){
printf("open fb0 fail!\n");
exit(1);
}
/*打开位图文件*/
bmp_fp = open(argv[1],O_RDWR);
if(bmp_fp < 0){
lcd_key_exit(lcd_fb0,&scr_info[0]);
printf("open map wrong\n");
exit(2);
}
map_list.head = (struct bmp_head *)malloc(sizeof(struct bmp_head));
if(map_list.head == NULL){
printf("mem for map head wrong\n");
exit(3);
}
map_list.info = (struct bmp_info *)malloc(sizeof(struct bmp_info));
if(map_list.info == NULL){
printf("mem for map info wrong\n");
exit(4);
}
/*读取位图信息*/
ret = get_bmp_head(bmp_fp,map_list.head);
ret = get_bmp_info(bmp_fp,map_list.info);
printf("****************************|\n"
"* Map information:\n "
"* Style:%c%c\n"
"* Size:%d Width:%d Hight:%d Bpp:%d Compression:%d\n"
"* MADE BY Yaong\n"
"****************************|\n",map_list.head->map_id[0],map_list.head->map_id[1], \
map_list.head->file_size,map_list.info->width,map_list.info-
>hight,map_list.info->bpp,map_list.info->compression);
/*读取位图信息*/
map_data = read_bmp(bmp_fp,&map_list);
while(chose != 0){
scanf("%d",&chose);
switch(chose){
case 1:/*向上显示*/
show_bmp_24_up(lcd_fb0>fbp,map_data,&scr_info[0],map_list.info);
break;
case 2:/*向下显示*/
show_bmp_24_down(lcd_fb0>fbp,map_data,&scr_info[0],map_list.info);
break;
default:
printf("no commamd!");
break;
}
putchar('\n');
}
if(map_data != NULL)
free(map_data);
exit_bmp(bmp_fp,map_list.head,map_list.info);
lcd_key_exit(lcd_fb0,&scr_info[0]);
return 0;
}
/*清理函数*/
void exit_bmp(int fb,struct bmp_head *bh,struct bmp_info *bi)
{
if(bh != NULL)
free(bh);
if(bi != NULL)
free(bi);
if(fb >= 0)
close(fb);
printf("Exit!\n");
}
(完)