Chinaunix首页 | 论坛 | 博客
  • 博客访问: 50881
  • 博文数量: 9
  • 博客积分: 285
  • 博客等级: 民兵
  • 技术积分: 40
  • 用 户 组: 普通用户
  • 注册时间: 2011-04-07 19:50
文章分类
文章存档

2014年(2)

2013年(4)

2012年(1)

2011年(2)

分类:

2011-08-14 16:01:12

  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; //固定的屏信息
};
结构体中的两个字段的具体内容可以在中找到,这里就不粘贴复制了。
下面是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_tvinfo.xres*4 && (z_twidth*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_tvinfo.xres*4 && (z_twidth*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
#include
#include
#include
#include
#include
#include

///////////////////////////////

#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");
}
(完)


阅读(2546) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:S3C6410的SPI总线读写

给主人留下些什么吧!~~