FrameBuffer 应用编程
- 19.1 什么是 FrameBuffer
- 19.2 LCD 的基础知识
- 19.3 LCD 应用编程介绍
- 使用 ioctl()获取屏幕参数信息
- 使用 mmap()将显示缓冲区映射到用户空间
- 19.4 LCD 应用编程练习之 LCD 基本操作
- 19.5 LCD 应用编程练习之显示 BMP 图片
- 在 LCD 上显示 BMP 图像
- 在开发板上测试
本章学习 Linux 下的 Framebuffer 应用编程,通过对本章内容的学习,大家将会了解到 Framebuffer 设备
究竟是什么?以及如何编写应用程序来操控 FrameBuffer 设备。
本章将会讨论如下主题。
⚫ 什么是 Framebuffer 设备?
⚫ LCD 显示的基本原理;
⚫ 使用存储映射 I/O 方式编写 LCD 应用程序。
⚫ 在 LCD 上打点、画线;
⚫ BMP 图片格式详解;
⚫ 在 LCD 上显示图片;
19.1 什么是 FrameBuffer
Frame 是帧的意思,buffer 是缓冲的意思,所以 Framebuffer 就是帧缓冲,这意味着 Framebuffer 就是一块内存,里面保存着一帧图像。帧缓冲(framebuffer)是 Linux 系统中的一种显示驱动接口,它将显示设备(譬如 LCD)进行抽象、屏蔽了不同显示设备硬件的实现,对应用层抽象为一块显示内存(显存),它允许上层应用程序直接对显示缓冲区进行读写操作,而用户不必关心物理显存的位置等具体细节,这些都由Framebuffer 设备驱动来完成。
所以在 Linux 系统中,显示设备被称为 FrameBuffer 设备(帧缓冲设备),所以 LCD 显示屏自然而言就是 FrameBuffer 设备。FrameBuffer 设备对应的设备文件为/dev/fbX(X 为数字,0、1、2、3 等),Linux下可支持多个 FrameBuffer 设备,最多可达 32 个,分别为/dev/fb0 到/dev/fb31,开发板出厂系统中,/dev/fb0设备节点便是 LCD 屏。
应用程序读写/dev/fbX 就相当于读写显示设备的显示缓冲区(显存),譬如 LCD 的分辨率是 800480,每一个像素点的颜色用 24 位(譬如 RGB888)来表示,那么这个显示缓冲区的大小就是 800 x 480 x 24 / 8 = 1152000 个字节。譬如执行下面这条命令将 LCD 清屏,也就是将其填充为黑色(假设 LCD 对应的设备节点是/dev/fb0,分辨率为 800480,RGB888 格式):
dd if=/dev/zero of=/dev/fb0 bs=1024 count=1125
这条命令的作用就是将 1125x1024 个字节数据全部写入到 LCD 显存中,并且这些数据都是 0x0。
19.2 LCD 的基础知识
关于 LCD 相关的基础知识,本书不再介绍,开发板配套提供的驱动教程中已经有过详细的介绍,除此之外,网络上也能找到相关内容。
19.3 LCD 应用编程介绍
本小节介绍如何对 FrameBuffer 设备(譬如 LCD)进行应用编程,通过上面的介绍,相信大家应该已经知道如何操作 LCD 显示设备了,应用程序通过对 LCD 设备节点/dev/fb0(假设 LCD 对应的设备节点是/dev/fb0)进行 I/O 操作即可实现对 LCD 的显示控制,实质就相当于读写了 LCD 的显存,而显存是 LCD 的显示缓冲区,LCD 硬件会从显存中读取数据显示到 LCD 液晶面板上。
在应用程序中,操作/dev/fbX 的一般步骤如下:
①、首先打开/dev/fbX 设备文件。
②、使用 ioctl()函数获取到当前显示设备的参数信息,譬如屏幕的分辨率大小、像素格式,根据屏幕参数计算显示缓冲区的大小。
③、通过存储映射 I/O 方式将屏幕的显示缓冲区映射到用户空间(mmap)。
④、映射成功后就可以直接读写屏幕的显示缓冲区,进行绘图或图片显示等操作了。
⑤、完成显示后,调用 munmap()取消映射、并调用 close()关闭设备文件。
从上面介绍的操作步骤来看,LCD 的应用编程还是非常简单的,这些知识点都是在前面的入门篇中给大家介绍过。
使用 ioctl()获取屏幕参数信息
当打开 LCD 设备文件之后,需要先获取到 LCD 屏幕的参数信息,譬如 LCD 的 X 轴分辨率、Y 轴分辨率以及像素格式等信息,通过这些参数计算出 LCD 显示缓冲区的大小。
通 过 ioctl() 函 数 来 获 取 屏 幕 参 数 信息, 对 于 Framebuffer 设备来说, 常 用 的 request 包 括FBIOGET_VSCREENINFO、FBIOPUT_VSCREENINFO、FBIOGET_FSCREENINFO。
⚫ FBIOGET_VSCREENINFO:表示获取 FrameBuffer 设备的可变参数信息,可变参数信息使用 struct fb_var_screeninfo 结 构 体 来 描 述 , 所 以 此 时 ioctl() 需 要 有 第 三 个 参 数 , 它 是 一 个 struct fb_var_screeninfo *指针,指向 struct fb_var_screeninfo 类型对象,调用 ioctl()会将 LCD 屏的可变参数信息保存在 struct fb_var_screeninfo 类型对象中,如下所示:
struct fb_var_screeninfo fb_var;
ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
⚫ FBIOPUT_VSCREENINFO:表示设置 FrameBuffer 设备的可变参数信息,既然是可变参数,那说明应用层可对其进行修改、重新配置,当然前提条件是底层驱动支持这些参数的动态调整,譬如在我们的 Windows 系统中,用户可以修改屏幕的显示分辨率,这就是一种动态调整。同样此时 ioctl()需要有第三个参数,也是一个 struct fb_var_screeninfo *指针,指向 struct fb_var_screeninfo 类型对象,表示用 struct fb_var_screeninfo 对象中填充的数据设置 LCD,如下所示:
struct fb_var_screeninfo fb_var = {0};
/* 对 fb_var 进行数据填充 */
......
......
/* 设置可变参数信息 */
ioctl(fd, FBIOPUT_VSCREENINFO, &fb_var);
上面所提到的三个宏定义 FBIOGET_VSCREENINFO 、 FBIOPUT_VSCREENINFO 、FBIOGET_FSCREENINFO 以及 2 个数据结构 struct fb_var_screeninfo 和 struct fb_fix_screeninfo 都定义在<linux/fb.h>头文件中,所以在我们的应用程序中需要包含该头文件。
#define FBIOGET_VSCREENINFO 0x4600
#define FBIOPUT_VSCREENINFO 0x4601
#define FBIOGET_FSCREENINFO 0x4602
struct fb_var_screeninfo 结构体
struct fb_var_screeninfo 结构体内容如下所示:
示例代码 19.3.1 struct fb_var_screeninfo 结构体
struct fb_var_screeninfo {
__u32 xres; /* 可视区域,一行有多少个像素点,X 分辨率 */
__u32 yres; /* 可视区域,一列有多少个像素点,Y 分辨率 */
__u32 xres_virtual; /* 虚拟区域,一行有多少个像素点 */
__u32 yres_virtual; /* 虚拟区域,一列有多少个像素点 */
__u32 xoffset; /* 虚拟到可见屏幕之间的行偏移 */
__u32 yoffset; /* 虚拟到可见屏幕之间的列偏移 */
__u32 bits_per_pixel; /* 每个像素点使用多少个 bit 来描述,也就是像素深度 bpp */
__u32 grayscale; /* =0 表示彩色, =1 表示灰度, >1 表示 FOURCC 颜色 */
/* 用于描述 R、G、B 三种颜色分量分别用多少位来表示以及它们各自的偏移量 */
struct fb_bitfield red; /* Red 颜色分量色域偏移 */
struct fb_bitfield green; /* Green 颜色分量色域偏移 */
struct fb_bitfield blue; /* Blue 颜色分量色域偏移 */
struct fb_bitfield transp; /* 透明度分量色域偏移 */
__u32 nonstd; /* nonstd 等于 0,表示标准像素格式;不等于 0 则表示非标准像素格式 */
__u32 activate;
__u32 height; /* 用来描述 LCD 屏显示图像的高度(以毫米为单位) */
__u32 width; /* 用来描述 LCD 屏显示图像的宽度(以毫米为单位) */
__u32 accel_flags;
/* 以下这些变量表示时序参数 */
__u32 pixclock; /* pixel clock in ps (pico seconds) */
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* angle we rotate counter clockwise */
__u32 colorspace; /* colorspace for FOURCC-based modes */
__u32 reserved[4]; /* Reserved for future compatibility */
};
通过 xres、yres 获取到屏幕的水平分辨率和垂直分辨率,bits_per_pixel 表示像素深度 bpp,即每一个像素点使用多少个 bit 位来描述它的颜色,通过 xres * yres * bits_per_pixel / 8 计算可得到整个显示缓存区的大小。
red、green、blue 描述了 RGB 颜色值中 R、G、B 三种颜色通道分别使用多少 bit 来表示以及它们各自的偏移量,通过 red、green、blue 变量可知道 LCD 的 RGB 像素格式,譬如是 RGB888 还是 RGB565,亦或者是 BGR888、BGR565 等。struct fb_bitfield 结构体如下所示:
示例代码 19.3.2 struct fb_bitfield 结构体
struct fb_bitfield {
__u32 offset; /* 偏移量 */
__u32 length; /* 长度 */
__u32 msb_right; /* != 0 : Most significant bit is right */
};
struct fb_fix_screeninfo 结构体
struct fb_fix_screeninfo 结构体内容如下所示:
示例代码 19.3.3 struct fb_fix_screeninfo 结构体
struct fb_fix_screeninfo {
char id[16]; /* 字符串形式的标识符 */
unsigned long smem_start; /* 显存的起始地址(物理地址) */
__u32 smem_len; /* 显存的长度 */
__u32 type;
__u32 type_aux;
__u32 visual;
__u16 xpanstep;
__u16 ypanstep;
__u16 ywrapstep;
__u32 line_length; /* 一行的字节数 */
unsigned long mmio_start; /* Start of Memory Mapped I/O(physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */
__u32 accel; /* Indicate to driver which specific chip/card we have */
__u16 capabilities;
__u16 reserved[2];
};
smem_start 表示显存的起始地址,这是一个物理地址,当然在应用层无法直接使用;smem_len 表示显存的长度,这个长度并一定等于 LCD 实际的显存大小。line_length 表示屏幕的一行像素点有多少个字节,通常可以使用 line_length * yres 来得到屏幕显示缓冲区的大小。
通过上面介绍,接下来我们编写一个示例代码,获取 LCD 屏幕的参数信息,示例代码如下所示:
示例代码 19.3.4 获取屏幕的参数信息
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
int main(int argc, char *argv[])
{
struct fb_fix_screeninfo fb_fix;
struct fb_var_screeninfo fb_var;
int fd;
/* 打开 framebuffer 设备 */
if (0 > (fd = open("/dev/fb0", O_WRONLY))) {
perror("open error");
exit(-1);
}
/* 获取参数信息 */
ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
printf("分辨率: %d*%d\n"
"像素深度 bpp: %d\n"
"一行的字节数: %d\n"
"像素格式: R<%d %d> G<%d %d> B<%d %d>\n",
fb_var.xres, fb_var.yres, fb_var.bits_per_pixel,
fb_fix.line_length,
fb_var.red.offset, fb_var.red.length,
fb_var.green.offset, fb_var.green.length,
fb_var.blue.offset, fb_var.blue.length);
/* 关闭设备文件退出程序 */
close(fd);
exit(0);
}
首先打开 LCD 设备文件,开发板出厂系统,LCD 对应的设备文件为/dev/fb0;打开设备文件之后得到文件描述符 fd,接着使用 ioctl()函数获取 LCD 的可变参数信息和固定参数信息,并将这些信息打印出来。
在测试之前,需将 LCD 屏通过软排线连接到开发板(掉电情况下连接),连接好之后启动开发板。
使用交叉编译工具编译上述示例代码,将编译得到的可执行文件拷贝到开发板 Linux 系统的用户家目录下,并直接运行它,如下所示:
笔者使用的是 7 寸 800480 RGB 屏,与上图打印显示的分辨率 800480 是相符的;像素深度为 16,也就意味着一个像素点的颜色值将使用 16bit(也就是 2 个字节)来表示;一行的字节数为 1600,一行共有 800个像素点,每个像素点使用 16bit 来描述,一共就是 800*16/8=1600 个字节数据,这也是没问题的。
打印出像素格式为 R<11 5> G<5 6> B<0 5>,分别表示 R、G、B 三种颜色分量对应的偏移量和长度,第一个数字表示偏移量,第二个参数为长度,从打印的结果可知,16bit 颜色值中高 5 位表示 R 颜色通道、中间 6 位表示 G 颜色通道、低 5 位表示 B 颜色通道,所以这是一个 RGB565 格式的显示设备。
Tips:正点原子的 RGB LCD 屏幕,包括 4.3 寸 800480、4.3 寸 480272、7 寸 800480、7 寸 1024600 以 及 10.1 寸 1280*800 硬件上均支持 RGB888,但 ALPHA/Mini I.MX6U 开发板出厂系统中,LCD 驱动程序将其实现为一个 RGB565 格式的显示设备,用户可修改设备树使其支持 RGB888,或者通过 ioctl 修改。
前面我们提到可以通过 ioctl()去设置 LCD 的可变参数,使用 FBIOPUT_VSCREENINFO 宏,但不太建议大家去改这些参数,如果 FrameBuffer 驱动程序支持不够完善,改完之后可能会出现一些问题!这里就不再演示了。
使用 mmap()将显示缓冲区映射到用户空间
在入门篇 13.5 小节中给大家介绍了存储映射 I/O 这种高级 I/O 方式,它的一个非常经典的使用场景便是用在 Framebuffer 应用编程中。通过 mmap()将显示器的显示缓冲区(显存)映射到进程的地址空间中,这样应用程序便可直接对显示缓冲区进行读写操作。
为什么这里需要使用存储映射 I/O 这种方式呢?其实使用普通的 I/O 方式(譬如直接 read、write)也是可以的,只是,当数据量比较大时,普通 I/O 方式效率较低。假设某一显示器的分辨率为 1920 * 1080,像素格式为 ARGB8888,针对该显示器,刷一帧图像的数据量为 1920 x 1080 x 32 / 8 = 8294400 个字节(约等于 8MB),这还只是一帧的图像数据,而对于显示器来说,显示的图像往往是动态改变的,意味着图像数据会被不断更新。
在这种情况下,数据量是比较庞大的,使用普通 I/O 方式必然导致效率低下,所以才会采用存储映射I/O 方式。
19.4 LCD 应用编程练习之 LCD 基本操作
本小节编写应用程序,在 LCD 上实现画点(俗称打点)、画线、画矩形等基本 LCD 操作,示例代码如下所示:
示例代码 19.4.1 LCD 画点、画线、画矩形操作
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#define argb8888_to_rgb565(color) ({ \
unsigned int temp = (color); \
((temp & 0xF80000UL) >> 8) | \
((temp & 0xFC00UL) >> 5) | \
((temp & 0xF8UL) >> 3); \
})
static int width; //LCD X 分辨率
static int height; //LCD Y 分辨率
static unsigned short *screen_base = NULL; //映射后的显存基地址
/********************************************************************
* 函数名称: lcd_draw_point
* 功能描述: 打点
* 输入参数: x, y, color
* 返 回 值: 无
********************************************************************/
static void lcd_draw_point(unsigned int x, unsigned int y, unsigned int color) {
unsigned short rgb565_color = argb8888_to_rgb565(color);//得到 RGB565 颜色值
/* 对传入参数的校验 */
if (x >= width)
x = width - 1;
if (y >= height)
y = height - 1;
/* 填充颜色 */
screen_base[y * width + x] = rgb565_color; }
/********************************************************************
* 函数名称: lcd_draw_line
* 功能描述: 画线(水平或垂直线)
* 输入参数: x, y, dir, length, color
* 返 回 值: 无
********************************************************************/
static void lcd_draw_line(unsigned int x, unsigned int y, int dir,
unsigned int length, unsigned int color) {
unsigned short rgb565_color = argb8888_to_rgb565(color);//得到 RGB565 颜色值
unsigned int end;
unsigned long temp;
/* 对传入参数的校验 */
if (x >= width)
x = width - 1;
if (y >= height)
y = height - 1;
/* 填充颜色 */
temp = y * width + x;//定位到起点
if (dir) { //水平线
end = x + length - 1;
if (end >= width)
end = width - 1;
for ( ; x <= end; x++, temp++)
screen_base[temp] = rgb565_color;
}
else { //垂直线
end = y + length - 1;
if (end >= height)
end = height - 1;
for ( ; y <= end; y++, temp += width)
screen_base[temp] = rgb565_color;
} }
/********************************************************************
* 函数名称: lcd_draw_rectangle
* 功能描述: 画矩形
* 输入参数: start_x, end_x, start_y, end_y, color
* 返 回 值: 无
********************************************************************/
static void lcd_draw_rectangle(unsigned int start_x, unsigned int end_x,
unsigned int start_y, unsigned int end_y,
unsigned int color) {
int x_len = end_x - start_x + 1;
int y_len = end_y - start_y - 1;
lcd_draw_line(start_x, start_y, 1, x_len, color);//上边
lcd_draw_line(start_x, end_y, 1, x_len, color); //下边
lcd_draw_line(start_x, start_y + 1, 0, y_len, color);//左边
lcd_draw_line(end_x, start_y + 1, 0, y_len, color);//右边
}
/********************************************************************
* 函数名称: lcd_fill
* 功能描述: 将一个矩形区域填充为参数 color 所指定的颜色
* 输入参数: start_x, end_x, start_y, end_y, color
* 返 回 值: 无
********************************************************************/
static void lcd_fill(unsigned int start_x, unsigned int end_x,
unsigned int start_y, unsigned int end_y,
unsigned int color) {
unsigned short rgb565_color = argb8888_to_rgb565(color);//得到 RGB565 颜色值
unsigned long temp;
unsigned int x;
/* 对传入参数的校验 */
if (end_x >= width)
end_x = width - 1;
if (end_y >= height)
end_y = height - 1;
/* 填充颜色 */
temp = start_y * width; //定位到起点行首
for ( ; start_y <= end_y; start_y++, temp+=width) {
for (x = start_x; x <= end_x; x++)
screen_base[temp + x] = rgb565_color;
} }
int main(int argc, char *argv[])
{
struct fb_fix_screeninfo fb_fix;
struct fb_var_screeninfo fb_var;
unsigned int screen_size;
int fd;
/* 打开 framebuffer 设备 */
if (0 > (fd = open("/dev/fb0", O_RDWR))) {
perror("open error");
exit(EXIT_FAILURE);
}
/* 获取参数信息 */
ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
screen_size = fb_fix.line_length * fb_var.yres;
width = fb_var.xres;
height = fb_var.yres;
/* 将显示缓冲区映射到进程地址空间 */
screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
if (MAP_FAILED == (void *)screen_base) {
perror("mmap error");
close(fd);
exit(EXIT_FAILURE);
}
/* 画正方形方块 */
int w = height * 0.25;//方块的宽度为 1/4 屏幕高度
lcd_fill(0, width-1, 0, height-1, 0x0); //清屏(屏幕显示黑色)
lcd_fill(0, w, 0, w, 0xFF0000); //红色方块
lcd_fill(width-w, width-1, 0, w, 0xFF00); //绿色方块
lcd_fill(0, w, height-w, height-1, 0xFF); //蓝色方块
lcd_fill(width-w, width-1, height-w, height-1, 0xFFFF00);//黄色方块
/* 画线: 十字交叉线 */
lcd_draw_line(0, height * 0.5, 1, width, 0xFFFFFF);//白色线
lcd_draw_line(width * 0.5, 0, 0, height, 0xFFFFFF);//白色线
/* 画矩形 */
unsigned int s_x, s_y, e_x, e_y;
s_x = 0.25 * width;
s_y = w;
e_x = width - s_x;
e_y = height - s_y;
for ( ; (s_x <= e_x) && (s_y <= e_y);
s_x+=5, s_y+=5, e_x-=5, e_y-=5)
lcd_draw_rectangle(s_x, e_x, s_y, e_y, 0xFFFFFF);
/* 退出 */
munmap(screen_base, screen_size); //取消映射
close(fd); //关闭文件
exit(EXIT_SUCCESS); //退出进程
}
在示例代码中定义了一个宏 argb8888_to_rgb565,用于实现将 unsigned int 类型的颜色(也就是ARGB8888 颜色)转换为 RGB565 颜色
程序中自定义了 4 个函数:
lcd_draw_point:用于实现画点、打点操作,参数 x 和 y 指定像素点的位置,参数 color 表示颜色。
lcd_draw_line:用于实现画线操作,参数 x 和 y 指定线的起始位置;参数 dir 表示方向,水平方向(dir!=0)还是垂直方向(dir=0),不支持斜线画法,画斜线需要一些算法去操作,这不是本章内容需要去关注的知识点;参数 length 表示线的长度,以像素为单位;参数 color 表示线条的颜色。
lcd_draw_rectangle:用于实现画矩形操作,参数 start_x 和 start_y 指定矩形左上角的位置;参数 end_x和 end_y 指定矩形右下角的位置;参数 color 指定矩形 4 个边的线条颜色。
lcd_fill:将一个指定的矩形区域填充为参数 color 指定的颜色,参数 start_x 和 start_y 指定矩形左上角的位置;参数 end_x 和 end_y 指定矩形右下角的位置;参数 color 指定矩形区域填充的颜色。
具体代码的实现各位读者自己去看,非常简单,来看下 main()中做了哪些事情:
⚫ 首先调用 open()打开 LCD 设备文件得到文件描述符 fd; ⚫ 接着使用 ioctl 函数获取 LCD 的可变参数信息和固定参数信息,通过得到的信息计算 LCD 显存大小、得到 LCD 屏幕的分辨率,从图 19.3.1 可知,ALPHA/Mini I.MX6U 开发板出厂系统将 LCD 实现为一个 RGB565 显示设备,所以程序中自定义的 4 个函数在操作 LCD 像素点时、都是以 RGB565的格式写入颜色值。
⚫ 接着使用 mmap 建立映射;
⚫ 映射成功之后就可以在应用层直接操作 LCD 显存了,调用自定义的函数在 LCD 上画线、画矩形、画方块;
⚫ 操作完成之后,调用 munmap 取消映射,调用 close 关闭 LCD 设备文件,退出程序。编译应用程序:
将编译得到的可执行文件拷贝到开发板 Linux 系统的用户家目录下,执行应用程序(在测试之前,先将出厂系统对应的 Qt GUI 应用程序退出):
此时 LCD 屏上将会显示程序中绘制的方块、矩形、以及线条:
忽略手机拍摄的问题,实际效果各位读者运行程序便知。
19.5 LCD 应用编程练习之显示 BMP 图片
我们常用的图片格式有很多,一般最常用的有三种:JPEG(或 JPG)、PNG、BMP 和 GIF。其中 JPEG(或 JPG)、PNG 以及 BMP 都是静态图片,而 GIF 则可以实现动态图片。在本小节实验中,我们选择使用BMP 图片格式。
BMP(全称 Bitmap)是 Window 操作系统中的标准图像文件格式,文件后缀名为“.bmp”,使用非常广。它采用位映射存储格式,除了图像深度可选以外,图像数据没有进行任何压缩,因此,BMP 图像文件所占用的空间很大,但是没有失真、并且解析 BMP 图像简单。
BMP 文件的图像深度可选 lbit、4bit、8bit、16bit、24bit 以及 32bit,典型的 BMP 图像文件由四部分组
成:
①、BMP 文件头(BMP file header),它包含 BMP 文件的格式、大小、位图数据的偏移量