文章目录
- framebuffer打印bmp格式图片专题
-
- 前言
- 一、bmp格式图片
-
- [1.1 概述](#1.1 概述)
- [1.2 位图文件头](#1.2 位图文件头)
- [1.2 位图信息头](#1.2 位图信息头)
- [1.3 位图数据](#1.3 位图数据)
- 二、framebuffer
-
- [2.1 什么是framebuffer:](#2.1 什么是framebuffer:)
- [2.2 如何操作framebuffer](#2.2 如何操作framebuffer)
-
- [3.2.1 打开显示设备文件](#3.2.1 打开显示设备文件)
- [3.2.2 获取显示设备的相关参数](#3.2.2 获取显示设备的相关参数)
- [3.2.3 建立内存映射](#3.2.3 建立内存映射)
- [3.2.4 写入RGB颜色值;](#3.2.4 写入RGB颜色值;)
- [3.2.5 解除内存映射与关闭显示设备文件](#3.2.5 解除内存映射与关闭显示设备文件)
- 三、frmaebuffer打印bmp图片

framebuffer打印bmp格式图片专题
前言
在上篇《文件操作学习3【目录IO、出错处理及framebuffer基础操作】》文章中我们了解到了,framebuffer的本质和基本使用方法,这篇文章我将分享如何通过framebuffer打印bmp格式的图片。
既然需要打印bmp图片,我们首先当然得了解bmp图片的存储格式。所以,这篇文章我们主要分为bmp图片、framebuffer和通过framebuffer实现打印三个部分组成。
一、bmp格式图片
1.1 概述
bmp是英文Bitmap(位图)的简写,它是Windows操作系统中的标准图像文件格式,随着windows的流行,bmp位图格式被广泛应用。
位图文件由四个部分组成:
• 位图文件头(bitmap-file header)
• 位图信息头(bitmap-information header)
• 调色板(color table);需要注意的是,16bit、24bit、32bit的位图一般没有调色板,因为从16bit开始就直接使用位图数据表示颜色了,所以后面的内容我们都以没有调色板的bmp图片为例来讲解。
• 位图数据(图像数据,Data Bits 或Data Body)阵列.

1.2 位图文件头
BMP位图文件头共14个字节,包含了文件类型标志、文件大小等信息,定义的结构体也比较,详细说明如下:
c
#pragma pack(1)//此处一定要强制让结构体按照指定字节数对齐
typedef struct info_bmp_file
{
unsigned short bfType;// 文件的类型,该值必需是0x4D42,也就是字符'BM'。
unsigned int bfSize;// 位图文件的大小,用字节为单位
unsigned short bfReserved1;// 保留,必须设置为0
unsigned short bfReserved2;// 保留,必须设置为0
unsigned int bfOffBits;// 位图数据距离文件开头偏移量,用字节为单位
}Info_ft;
#pragma pack()
1.2 位图信息头
BMP位图信息头共40个字节,包含了文件类型标志、文件大小等信息,定义的结构体也比较,详细说明如下:
c
typedef struct tagBITMAPINFOHEADER{
unsigned int biSize;// BITMAPINFOHEADER结构所需要的字数
unsigned int biWidth;// 图像宽度,单位为像素
unsigned int biHeight; // 图像高度,单位为像素,负数,则说明图像是正向的
unsigned short biPlanes;// 为目标设备说明位面数,其值将总是被设为1
unsigned short biBitCount;// 一个像素占用的bit位,值位1、4、8、16、24、32
unsigned int biCompression; // 压缩类型
unsigned int biSizeImage;// 位图数据的大小,以字节为单位
unsigned int biXPelsPerMeter; // 水平分辨率,单位 像素/米
unsigned int biYPelsPerMeter; // 垂直分辨率,单位 像素/米
unsigned int biClrUsed;
unsigned int biClrImportant;
}Info_bt;
读取bmp图片信息代码示例:
c
#include<stdio.h>
#pragma pack(1)
typedef struct info_bmp_file
{
unsigned short bfType;
unsigned int bfSize;
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned int bfOffBits;
}Info_ft;
#pragma pack()
typedef struct tagBITMAPINFOHEADER{
unsigned int biSize;
unsigned int biWidth;
unsigned int biHeight;
unsigned short biPlanes;
unsigned short biBitCount;
unsigned int biCompression;
unsigned int biSizeImage;
unsigned int biXPelsPerMeter;
unsigned int biYPelsPerMeter;
unsigned int biClrUsed;
unsigned int biClrImportant;
}Info_bt;
int main()
{
FILE *fp = fopen("3.bmp", "r");
if(NULL == fp)
{
printf("fopen error!\n");
return -1;
}
Info_ft info_file;
Info_bt info_bmp;
fread(&info_file, 14, 1, fp);
fread(&info_bmp, 40, 1, fp);
printf("%d %d %d %d %d\n", info_file.bfType,
info_file.bfSize, info_file.bfReserved1,
info_file.bfReserved2, info_file.bfOffBits);
printf("%d %d %d %d %d %d %d %d %d %d %d\n",
info_bmp.biSize, info_bmp.biWidth, info_bmp.biHeight,
info_bmp.biPlanes, info_bmp.biBitCount, info_bmp.biCompression,
info_bmp.biSizeImage, info_bmp.biXPelsPerMeter,
info_bmp.biYPelsPerMeter, info_bmp.biClrUsed,
info_bmp.biClrImportant);
printf("sizeof(Info_ft) = %ld\n", sizeof(Info_ft));
printf("sizeof(Info_bt) = %ld\n", sizeof(Info_bt));
fclose(fp);
return 0;
}
1.3 位图数据
BMP图像支持多种颜色深度,包括1位、4位、8位、16位、24位和32位等。不同颜色深度决定了每个像素所占用的字节数:
| 颜色深度(位/像素) | 每像素字节数 | 说明 |
|---|---|---|
| 1 | 0.125 | 单色图像,每个字节表示8个像素 |
| 4 | 0.5 | 16色图像,每个字节表示2个像素 |
| 8 | 1 | 256色图像,使用调色板 |
| 16 | 2 | 高彩色,RGB555或RGB565格式 |
| 24 | 3 | 真彩色,RGB三通道各占1字节 |
| 32 | 4 | 带Alpha通道的真彩色,RGBA四通道各占1字节 |
我们后文以24bit为例来进行讲解。
位图数据存放实际的argb值可以理解为每个像素点的颜色。由于Windows默认的扫描的最小单位是4字节,BMP图像顺应了这个要求,要求每行的数据的长度必须是4的倍数。
示例:
24位图像,宽度为10像素
- 每像素3字节 → 行字节数为 10 × 3 = 30字节
- 30字节不是4的整数倍 → 需要填充 2 字节→ 实际行字节数为 32字节
在24位BMP图像中,每个像素由3个字节组成,分别表示红色(R)、绿色(G)和蓝色(B)三个通道的强度值,范围为0~255。具体排列顺序通常为BGR ,而非RGB。
BMP图像的像素数据在文件中通常是从图像的底部开始向上存储的,即第一行像素数据对应图像的最下方一行。这种设计源于Windows GDI的坐标系,其中原点位于图像左上角,而扫描线是从下往上进行的。
对于bmp图片像素数据这些非常规的存储方式,在我们通过framebuffer帧缓存技术去在Linux真终端上打印图片时,就需要我们在代码中进行处理了。
二、framebuffer
2.1 什么是framebuffer:
- 帧缓存、帧缓冲技术;
- 是Linux为显示设备提供的一个接口,把显存抽象后的一种设备文件,通过该设备文件可以实现应用层向显存空间的数据写入,从而实现像素点的绘制。
2.2 如何操作framebuffer
通过内存映射技术,可以实现显存空间和用户空间的内存映射,通过向用户空间写入RGB颜色值,从而实现显存数据的写入。
像素点位数/位深度的种类:
- RGB888 四个字节
- RGB565 两个字节
framebuffer的操作流程:
- 打开显示设备文件/dev/fb0;
- 获取显示设备的相关参数(分辨率、像素点位数/位深度);
- 建立内存映射;
- 写入RGB颜色值;
- 解除内存映射;
- 关闭显示设备文件。
3.2.1 打开显示设备文件
通过我们的文件IO的函数接口open()打开framebuffer的设备文件,它在/dev/fb0这个目录下,
c
//1. 打开
fb = open("/dev/fb0", O_RDWR);
if(-1 == fb)
{
perror("open /dev/fb0 error");
return -1;
}
3.2.2 获取显示设备的相关参数
我们通过ioctl()这个系统调用的函数接口来获取设备的相关参数;
它的描述是这样的:
ioctl()系统调用用于操控特殊文件 底层对应的设备参数。参数fd必须是一个已经成功打开 的文件描述符。第二个参数是依赖具体设备的请求命令码(request) 。不同驱动,命令码含义完全不一样,没有全局统一标准。第三个参数是一个无类型的内存指针 。历史上C 语言还没有void*时,这里约定使用char *argp,所以手册后续讲解中将它命名为argp。也就是原型里的可变参数,你可以传任意类型指针。
在linux@ubuntu:/usr/include/linux$ 这个路径下的的fb.h文件是Linux 液晶屏帧缓冲驱动 专用头文件,里面定义了 Framebuffer 设备 (/dev/fb0) 所有可用的ioctl命令码。

例如:
-
FBIOGET_VSCREENINFO:这个宏就是我们现在需要的获取虚拟屏幕可变信息的命令码(分辨率、色深、像素格式等)。 -
还有这个:struct fb_var_screeninfo 可变屏幕信息结构体:
cstruct fb_var_screeninfo { __u32 xres; /* visible resolution */ __u32 yres; __u32 xres_virtual; /* virtual resolution */ __u32 yres_virtual; __u32 xoffset; /* offset from virtual to visible */ __u32 yoffset; /* resolution */ __u32 bits_per_pixel; /* guess what */ __u32 grayscale; /* 0 = color, 1 = grayscale, */ /* >1 = FOURCC */ struct fb_bitfield red; /* bitfield in fb mem if true color, */ struct fb_bitfield green; /* else only length is significant */ struct fb_bitfield blue; struct fb_bitfield transp; /* transparency */ __u32 nonstd; /* != 0 Non standard pixel format */ __u32 activate; /* see FB_ACTIVATE_* */ __u32 height; /* height of picture in mm */ __u32 width; /* width of picture in mm */ __u32 accel_flags; /* (OBSOLETE) see fb_info.flags */ };成员变量 含义说明 xres/yres可见屏幕分辨率(实际 LCD 显示宽高像素) 例:800×480,xres=800,yres=480 xres_virtual/yres_virtual虚拟显存分辨率 显存可以开辟一块比屏幕更大的画布,用于硬件滚屏、双缓冲。 例如虚拟高度设为 yres*2实现页面翻页xoffset/yoffset显示偏移量:从虚拟显存的 (xoffset,yoffset)位置开始截取画面输出到屏幕 修改yoffset配合大的yres_virtual,即可实现硬件滚动 (Panning)bits_per_pixel每个像素占用比特数,色深 常见值:16 (RGB565)、24 (RGB888)、32 (RGBA8888) grayscale灰度模式标志 0= 彩色;1= 灰度;大于 1 代表使用 FOURCC 压缩图像格式 red/green/blue/transpRGB 分量位域信息, struct fb_bitfield结构体 包含每个颜色通道:起始偏移位、占用位数。 👉RGB565:红占 5bit,绿占 6bit,蓝占 5bitnonstd非标准像素格式标记,非 0 代表不是标准 RGB 格式 activate参数生效时机。控制调用 FBIOPUT_VSCREENINFO后,新分辨率是立刻生效还是等到下一次 vsync 垂直同步height/width屏幕物理尺寸,单位:毫米 (mm),用于计算 DPI,不影响像素绘图 accel_flags硬件加速标志,已经废弃,不再使用
代码示例:
c
//2. 获取显示设备的相关参数
struct fb_var_screeninfo vinfo;
int ret = ioctl(fb, FBIOGET_VSCREENINFO, &vinfo);
if(-1 == ret)
{
perror("ioctl error");
return -1;
}
printf("vinfo.xres = %d, vinfo.yres = %d\n", vinfo.xres, vinfo.yres);
printf("vinfo.xres_virtual = %d, vinfo.yres_virtual = %d\n", vinfo.xres_virtual, vinfo.yres_virtual);
printf("vinfo.bits_per_pixel = %d\n", vinfo.bits_per_pixel);
3.2.3 建立内存映射


以这两张图为例:Linux帧缓冲通过mmap将位于内核空间的物理显存映射到用户进程虚拟地址空间,程序获得映射指针pmem后,便可利用公式*(pmem + y*w + x)直接计算像素偏移,通过指针赋值直接修改显存数据,无需调用read/write,显存更新后硬件自动刷新到800×600的显示屏上,实现屏幕绘图。显存可开辟一块大于实际屏幕xres × yres的虚拟画布xres_virtual × yres_virtual,屏幕仅截取画布的一部分画面进行显示,修改x_offset、y_offset显示偏移量,无需移动显存像素数据,就能快速切换可视区域,从而实现硬件滚屏、双缓冲翻页效果。
- mmap()
-
函数原型:
c#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset); -
功能 建立文件和用户空间的内存映射。
-
参数 addr :要映射的用户空间的首地址,一般情况下填NULL ,让内核自己寻找一块连续的内存空间,并将空间首地址返回;
length :要映射的用户空间大小;
prot :对映射空间的读写权限
-
PROT_READ
-
PROT_WRITE
flags :MAP_SHARED(共享映射区域)
fd :要映射的文件描述符;
offset:映射的偏移量
-
-
返回值 成功:返回映射到的用户空间首地址;
失败:返回MAP_FAILED((void *)-1)。
-
内存映射代码示例:
c
void *pmem = NULL;
//3. 建立内存映射
len = vinfo.xres_virtual*vinfo.yres_virtual*vinfo.bits_per_pixel/8;
pmem = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0);
if (MAP_FAILED == pmem)
{
perror("mmap error");
return -1;
}
3.2.4 写入RGB颜色值;
首先校验像素坐标是否超出可视屏幕范围,随后基于虚拟显存宽度计算目标像素的内存偏移,通过显存映射指针pmem直接向帧缓冲写入指定颜色值,完成单点绘制。
c
void show_point(int x, int y, unsigned int col)
{
//4. 写入RGB颜色值
if(x >= vinfo.xres || y >= vinfo.yres)
{
return;
}
*((unsigned int *)pmem + y * vinfo.xres_virtual + x) = col;
}
3.2.5 解除内存映射与关闭显示设备文件
- munmap()
-
函数原型:
c#include <sys/mman.h> int munmap(void *addr, size_t length); -
功能:
解除
mmap()建立的内存映射,释放用户空间与物理显存之间的映射关系;取消后不能再通过该指针访问显存,关闭帧缓冲文件前必须调用。 -
参数:
addr:mmap返回的映射起始虚拟地址(如前面的pmem)length:需要解除映射的内存字节长度,显存映射大小(finfo.smem_len)
-
返回值:
成功:返回
0失败:返回
-1,并设置errno错误号
-
代码示例:
c
void deinit_fb()
{
//5. 解除内存映射;
munmap(pmem, len);
//6. 关闭显示设备文件。
close(fb);
}
通过这些步骤我们就可以绘制一个点了,再借助这个点就可以封装其他绘制的函数接口.
三、frmaebuffer打印bmp图片
接下来,就是本片文章的重头戏啦:在framebuffer的已有的基础操作之上,对bmp格式的图片进行绘制。
这是我前面所提到的存储bmp图片的相关信息的结构体,在我们通过frmaebuffer打印bmp图片时需要用到里面的信息,因此我们先对其进行定义。
c
#pragma pack(1)
typedef struct info_bmp_file
{
unsigned short bfType;
unsigned int bfSize;
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned int bfOffBits;
}Info_ft;
#pragma pack()
typedef struct tagBITMAPINFOHEADER{
unsigned int biSize;
unsigned int biWidth;
unsigned int biHeight;
unsigned short biPlanes;
unsigned short biBitCount;
unsigned int biCompression;
unsigned int biSizeImage;
unsigned int biXPelsPerMeter;
unsigned int biYPelsPerMeter;
unsigned int biClrUsed;
unsigned int biClrImportant;
}Info_bt;
首先,我们需要打开bmp图片,读取文件头与信息头,并通过定义的结构体获取bmp图片的参数。
c
int fd = open(bmpfile, O_RDONLY);
if(-1 == fd)
{
perror("open file error");
return;
}
Info_ft fhead;
Info_bt ihead;
read(fd, &fhead, sizeof(fhead));
read(fd, &ihead, sizeof(ihead));
随后按图片宽高和位深计算像素总字节数并申请内存缓冲区,读取像素数据存入 buff,随后从图片最后一行向前逐行遍历每个像素,依次取出蓝、绿、红分量拼接成 24 位色彩值,调用 show_point 将像素绘制到 framebuffer 屏幕指定偏移 (x,y) 位置。
这里需要特别注意bmp图片像素数据这些非常规的存储方式:
- 每行的数据的长度必须是4的倍数;
- 像素点具体排列顺序通常为BGR ,而非RGB;
- 像素数据在文件中通常是从图像的底部开始向上存储的,即第一行像素数据对应图像的最下方一行。
在下面的代码中,因为对于打印的图片我会对其进行处理,使其横向像素满足4的倍数,这样就不用对代码进行更多的处理了,所以下面的代码仅适合横向像素为4的倍数的bmp图片,有兴趣的同学,可以想想怎么通过代码的修改实现所有的bmp图片打印。
c
size_t size = ihead.biHeight*ihead.biWidth*ihead.biBitCount/8;
unsigned char *buff = malloc(size);
if(NULL == buff)
{
perror("buff malloc error");
return;
}
read(fd, buff, size);
close(fd);
unsigned char *p = buff;
unsigned char r,g,b;
unsigned int col;
for(int i = ihead.biHeight-1; i >= 0; --i)
{
for(int j = 0; j < ihead.biWidth; ++j)
{
b = *p;++p;
g = *p;++p;
r = *p;++p;
col = (r << 16) | (g << 8) | (b << 0);
show_point(j+x, i+y, col);
}
}
最后,绘制完成后释放缓冲区并关闭文件。
c
free(buff);
好了,以上就是本期博客全部内容啦,希望能对屏幕前的你产生帮助,我们下期再见!