framebuffer显示bmp格式文件

本文详细介绍了如何在基于ARM11的飞凌OK6410开发板上使用LinuxFramebuffer技术实现BMP位图的显示。包括位图文件的解析、结构体定义、数据读取以及在Framebuffer上的显示流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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");
}
(完)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值