FrameBuffer 应用编程
什么是 Framebuffer 设备
Framebuffer 定义:Framebuffer 是帧缓冲,指一块内存,用于保存一帧图像
Linux 系统中的 Framebuffer:在 Linux 系统中,Framebuffer 是一种显示驱动接口,抽象并屏蔽了不同显示设备的硬件细节,对应用层提供了一块显示内存(显存)
Framebuffer 设备:在 Linux 中,显示设备被称为 FrameBuffer 设备,如 LCD 显示屏。这些设备对应的设备文件为 /dev/fbX,最多支持 32 个设备
应用程序与 Framebuffer:应用程序通过读写 /dev/fbX 设备文件来操作显示缓冲区,无需关心物理显存的细节,这些由 Framebuffer 设备驱动处理
示例操作:例如,使用 dd if=/dev/zero of=/dev/fb0 bs=1024 count=1125 命令可以将 LCD 清屏,填充为黑色
LCD 应用编程介绍
设备操作:应用程序通过对 LCD 设备节点 /dev/fb0 进行 I/O 操作,实现对 LCD 的显示控制,相当于读写 LCD 的显存
一般步骤
-
打开 /dev/fbX 设备文件
-
使用 ioctl() 函数获取显示设备参数(如分辨率、像素格式),计算显示缓冲区大小
-
通过 mmap 将显示缓冲区映射到用户空间
-
映射成功后,直接读写显示缓冲区进行绘图或图片显示
-
显示完成后,调用 munmap() 取消映射,调用 close() 关闭设备文件
使用 ioctl()获取屏幕参数信息
-
获取LCD参数信息:在打开LCD设备文件后,首先需要获取LCD的X轴分辨率、Y轴分辨率和像素格式等参数信息。这些信息用于计算LCD显示缓冲区的大小
-
使用ioctl函数获取屏幕参数:通过ioctl()函数获取屏幕参数信息。常用的请求包括
-
FBIOGET_VSCREENINFO:获取FrameBuffer设备的可变参数信息,使用struct fb_var_screeninfo结构体描述
- struct fb_var_screeninfo fb_var;
ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
- struct fb_var_screeninfo fb_var;
-
FBIOPUT_VSCREENINFO:设置FrameBuffer设备的可变参数信息,允许应用层修改和重新配置
- struct fb_var_screeninfo fb_var = {0};
/* 对 fb_var 进行数据填充 /
…
…
/ 设置可变参数信息 */
ioctl(fd, FBIOPUT_VSCREENINFO, &fb_var);
- struct fb_var_screeninfo fb_var = {0};
-
FBIOGET_FSCREENINFO:获取FrameBuffer设备的固定参数信息,应用程序不可修改,使用struct fb_fix_screeninfo结构体描述
- struct fb_fix_screeninfo fb_fix;
ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
- struct fb_fix_screeninfo fb_fix;
-
-
结构体和头文件
- 上述三个宏定义和两个数据结构体struct fb_var_screeninfo和struct fb_fix_screeninfo都定义在<linux/fb.h>头文件中,应用程序需要包含该头文件
-
struct fb_var_screeninfo 结构体(可变屏幕信息)
-
fb_var_screeninfo 结构体存储的是那些在运行时可能会改变的屏幕参数
-
-
计算显示缓冲区大小
- 通过xres(水平分辨率)、yres(垂直分辨率)和bits_per_pixel(像素深度)计算显示缓冲区的大小,公式为xres * yres * bits_per_pixel / 8
-
RGB像素格式
- 通过red、green、blue变量描述RGB颜色值中各颜色通道的位数和偏移量,确定LCD的RGB像素格式,如RGB888、RGB565等
-
struct fb_bitfield 结构体(固定屏幕信息)
-
fb_fix_screeninfo 结构体存储的是那些在设备初始化时确定且不会在运行时改变的屏幕参数
-
struct fb_bitfield {
__u32 offset; /* 偏移量 /
__u32 length; / 长度 /
__u32 msb_right; / != 0 : Most significant bit is right */
};
-
-
struct fb_fix_screeninfo 结构体
-
显存信息
- smem_start表示显存的起始地址,smem_len表示显存的长度,line_length表示屏幕一行的字节数,通常使用line_length * yres计算屏幕显示缓冲区的大小
-
编程获取 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 <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);
}
- 打开framebuffer设备:尝试以只写模式打开 /dev/fb0 设备文件,并将文件描述符存储在 fd 中
- 检查打开设备是否成功
- 如果打开失败(即 fd < 0),则输出错误信息并退出程序,返回 -1
- 获取屏幕信息
- 使用 ioctl 函数获取可变屏幕信息,并将结果存储在 fb_var 中
- 使用 ioctl 函数获取固定屏幕信息,并将结果存储在 fb_fix 中
- 打印屏幕信息
- 使用 printf 函数打印屏幕的分辨率、像素深度、每行的字节数以及像素格式(包括红、绿、蓝分量的偏移量和长度)
- 关闭设备文件,退出程序
- 测试
- 获取到屏幕参数
- 屏幕尺寸和分辨率:7寸 800x480 RGB 屏幕的分辨率为 800x480
- 像素深度:像素深度为 16bit,即每个像素点使用2个字节表示颜色
- 每行字节数:一行有 800 个像素点,每个像素点使用 16bit,合计 1600 个字节
- 像素格式:打印显示像素格式为 R<11 5> G<5 6> B<0 5>,表示 RGB 三种颜色分量的偏移量和长度
- 颜色通道:16bit 颜色值中,高 5 位表示 R,6 位表示 G,低 5 位表示 B,这对应 RGB565 格式
使用 mmap()将显示缓冲区映射到用户空间
- 存储映射 I/O 通过将显存直接映射到进程地址空间,提高了在处理大数据量(如高分辨率动态图像)时的 I/O 效率
LCD 应用编程练习之 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); \
})
// 宏定义,将ARGB8888(32位)颜色格式转换为RGB565(16位)颜色格式
/*定义一个临时变量 temp 并将传入的 color 参数赋值给它*/
/*temp & 0xF80000UL:筛选出原颜色值中的红色分量.>> 8:将筛选出的红色分量右移8位,转换到RGB565格式中的正确位置*/
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;//定位到起点
//如果 dir 为真(非零),则绘制水平线。
//如果 dir 为假(零),则绘制垂直线
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;
/* 对传入参数的校验 */
//确保变量 end_x 和 end_y 不会超出屏幕的边界
if (end_x >= width)
end_x = width - 1;
if (end_y >= height)
end_y = height - 1;
/* 填充颜色 */
temp = start_y * width; //定位到起点行首
//通过将行号 start_y 乘以每行的元素个数 width,计算出该行起始位置在一维数组中的索引
for ( ; start_y <= end_y; start_y++, temp+=width) {//temp 增加 width,以定位到下一行的行首
for (x = start_x; x <= end_x; x++)
screen_base[temp + x] = rgb565_color;//将当前元素(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;// 计算屏幕缓冲区的总大小(字节数)
//通过将每行的字节数乘以总行数,可以得到整个屏幕缓冲区的总字节大小
//fb_fix.line_length 表示每一行屏幕数据占用的字节数。
//这包括了每像素的字节数乘以屏幕宽度,以及可能的填充字节(即对于内存对齐可能额外增加的字节数)
width = fb_var.xres;// 获取屏幕的宽度(像素数)
height = fb_var.yres;
/* 将显示缓冲区映射到进程地址空间 */
screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
//mmap 是一个系统调用,用于将文件或设备映射到进程的地址空间
//void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
//NULL:表示由操作系统选择映射区域的起始地址
//screen_size:映射区域的大小,以字节为单位
//PROT_WRITE:表示映射区域的内存保护属性。PROT_WRITE 意味着该区域是可写的
//MAP_SHARED:表示映射的内存区域是共享的
//0:表示从文件的开始位置进行映射
//如果映射失败,则返回 MAP_FAILED
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); //取消映射
//screen_base 被赋值为 mmap 的返回值,因此它指向了帧缓冲设备的内存映射区域
close(fd); //关闭文件
exit(EXIT_SUCCESS); //退出进程
}
-
lcd_draw_point 函数:实现画点操作,参数 x 和 y 指定像素点的位置,参数 color 表示颜色
-
lcd_draw_line 函数:实现画线操作,参数 x 和 y 指定线的起始位置;参数 dir 表示方向(水平或垂直),参数 length 表示线的长度,参数 color 表示线条的颜色
-
lcd_draw_rectangle 函数:实现画矩形操作,参数 start_x 和 start_y 指定矩形左上角的位置,参数 end_x 和 end_y 指定矩形右下角的位置,参数 color 指定矩形边框的颜色
-
lcd_fill 函数:将一个矩形区域填充为指定颜色,参数 start_x 和 start_y 指定矩形左上角的位置,参数 end_x 和 end_y 指定矩形右下角的位置,参数 color 指定填充颜色
-
main 函数:执行以下操作
-
打开 framebuffer 设备文件
-
获取并打印屏幕的参数信息
-
计算屏幕缓冲区的大小
-
获取屏幕的宽度和高度
-
将显示缓冲区映射到进程的地址空间
-
调用自定义函数在屏幕上绘制方块、直线和矩形
-
取消映射并关闭文件
-
验证
- 显示的效果
应用编程练习之显示 BMP 图片
BMP 图像介绍
-
常用图片格式有四种:JPEG (JPG)、PNG、BMP 和 GIF(动态图片)
-
BMP 是 Windows 系统的标准图像文件格式,后缀名为 “.bmp”,无压缩、无失真,但文件大
-
BMP 文件的图像深度有 1bit、4bit、8bit、16bit、24bit 和 32bit
-
BMP 文件由四部分组成
-
BMP 文件头:包含文件格式、大小、位图数据的偏移量等信息
-
Windows 下为 bmp 文件头定义了如下结构体
-
typedef struct tagBITMAPFILEHEADER
{
UINT16 bfType;
DWORD bfSize;
UINT16 bfReserved1;
UINT16 bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER; -
bmp 文件头成员说明
-
-
bmp 文件头数据
-
bmp 文件头的大小固定为 14 个字节
-
bmp 文件头数据
-
0~01H:0x42、0x4D 对应的 ASCII 字符为 B、M,表示这是 Windows 支持的位图格式,必须是“BM”才是 Windows 位图文件
-
02~05H:对应文件大小,0x000BB848=768072 字节,与 image.bmp 文件大小相符
-
06~09H:保留字段
-
0A~0D:0x00000046=70,即从文件头部开始到位图数据需要偏移 70 个字节
-
-
-
位图信息头:包含位图信息头大小、图像尺寸、图像大小、位平面数、压缩方式及颜色索引信息
-
Windows 下为位图信息头结构体
-
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER; -
位图信息头成员说明
-
-
文件数据
-
位图信息头数据
-
0E~11H:0x00000038=56,这说明这个位图信息头的大小为 56 个字节
-
12~15H:0x00000320=800,图像宽度为 800 个像素,与文件属性一致
-
16~19H:0x000001E0=480,表示图像高度为 480 像素,与文件属性一致。正数表示倒向位图,图像数据从左下角到右上角排列,水平从左到右,垂直从下到上。正向位图数据从左上角到右下角排列,水平从左到右,垂直从上到下
-
1A~1BH:0x0001=1,这个值总为 1
-
1C~1DH:0x0010=16,表示每个像素占 16 个 bit
-
1E~21H:0x00000003=3,bit-fileds 方式
-
22~25H:0x000BB802=768002,图像的大小,注意图像的大小并不是 BMP 文件的大小,而是图像数据的大小
-
26~29H:0x00000EC2=3778,水平分辨率为 3778 像素/米
-
2A~2DH:0x00000EC2=3778,垂直分辨率为 3778 像素/米
-
2E~31H:0x00000000=0,本位图未使用调色板
-
32~35H:0x00000000=0
-
-
只有压缩方式选项被设置为 bit-fileds(0x3)时,位图信息头的大小才会等于 56 字节,否则,为 40 字节。
-
-
调色板:若使用索引表示图像,调色板为索引与颜色的映射表
-
位图数据:图像数据
-
对于 24 位位图,使用 3 个字节数据来表示一个像素点的颜色,对于 16
位位图,使用 2 个字节数据来表示一个像素点的颜色,同理,32 位位图则使用 4 个字节来描述 -
正向位图先存储图像的第一行数据,从左到右依次存放,接着存放第二行,依次这样;而倒向位图,
则先存储图像的最后一行(倒数第一行)数据,也是从左到右依次存放,接着倒数二行,依次这样- 正向位图和倒向位图
-
-
BMP 图像各数据段说明
-
-
BMP 文件头通常为 14 字节,位图信息头为 40 或 56 字节
-
真彩色图像(16位或24位色)不需要调色板,位图信息头后紧跟位图数据
- 某些 BMP 文件如16色、256色位图需要调色板
-
以16位色(RGB565)BMP图像为例,图像分辨率800x480,位深度16bit
-
16 位 BMP 示例图片
-
示例图片属性
-
image.bmp 文件的十六进制数据
-
-
RGB 和 Bit-Fields
-
当图像色彩超过256种时,需要使用16bpp或更高的位图(如24位、32位)
-
16bpp及以上位图不使用调色板,有两种编码格式:RGB 和 Bit-Fields(BF)
-
RGB 编码格式
-
24bpp-RGB:24位中低8位表示蓝色,中8位表示绿色,高8位表示红色
-
32bpp-RGB:低24位编码方式与24bpp相同,最高8位表示透明度Alpha
-
-
32bpp位图尺寸大,通常在图像处理中使用,半透明效果更适合用PNG格式
-
BF 编码格式
-
利用位域操作确定RGB三分量的信息容量
-
位图信息头中多出16字节,包含4个32bit位域掩码,分别是R、G、B、A分量的位域掩码
-
位域掩码指示R、G、B信息容量大小及位置偏移量
-
例如,16位色RGB565图像使用BF编码,R、G、B位域掩码分别为0xF800、0x07E0和0x001F
- R、G、B、A 四个分量的位域掩码
-
-
-
如何得到 16 位色 RGB565 格式 BMP 图像?
-
Photoshop 软件打开,打开之后点击菜单栏中的文件—>存储为
-
选择文件格式为 BMP 格式
-
BMP 高级模式
-
在 LCD 上显示 BMP 图像
#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 <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
/**** BMP文件头数据结构 ****/
typedef struct {
unsigned char type[2]; //文件类型
unsigned int size; //文件大小
unsigned short reserved1; //保留字段1
unsigned short reserved2; //保留字段2
unsigned int offset; //到位图数据的偏移量
} __attribute__ ((packed)) bmp_file_header;
/**** 位图信息头数据结构 ****/
typedef struct {
unsigned int size; //位图信息头大小
int width; //图像宽度
int height; //图像高度
unsigned short planes; //位面数
unsigned short bpp; //像素深度
unsigned int compression; //压缩方式
unsigned int image_size; //图像大小
int x_pels_per_meter; //像素/米
int y_pels_per_meter; //像素/米
unsigned int clr_used;
unsigned int clr_omportant;
} __attribute__ ((packed)) bmp_info_header;
/**** 静态全局变量 ****/
static int width; //LCD X分辨率
static int height; //LCD Y分辨率
static unsigned short *screen_base = NULL; //映射后的显存基地址
static unsigned long line_length; //LCD一行的长度(字节为单位)
/********************************************************************
* 函数名称: show_bmp_image
* 功能描述: 在LCD上显示指定的BMP图片
* 输入参数: 文件路径
* 返 回 值: 成功返回0, 失败返回-1
********************************************************************/
static int show_bmp_image(const char *path)
{
bmp_file_header file_h;
bmp_info_header info_h;
unsigned short *line_buf = NULL; //行缓冲区
unsigned long line_bytes; //BMP图像一行的字节的大小
unsigned int min_h, min_bytes;
int fd = -1;
int j;
/* 打开文件 */
if (0 > (fd = open(path, O_RDONLY))) {
perror("open error");
return -1;
}
/* 读取BMP文件头 */
if (sizeof(bmp_file_header) !=
read(fd, &file_h, sizeof(bmp_file_header))) {
perror("read error");
close(fd);
return -1;
}
if (0 != memcmp(file_h.type, "BM", 2)) { // 检查 BMP 文件类型
fprintf(stderr, "it's not a BMP file\n");
close(fd);
return -1;
}
/* 读取位图信息头 */
if (sizeof(bmp_info_header) !=
read(fd, &info_h, sizeof(bmp_info_header))) {
perror("read error");
close(fd);
return -1;
}
/* 打印信息 */
printf("文件大小: %d\n"
"位图数据的偏移量: %d\n"
"位图信息头大小: %d\n"
"图像分辨率: %d*%d\n"
"像素深度: %d\n", file_h.size, file_h.offset,
info_h.size, info_h.width, info_h.height,
info_h.bpp);
/* 将文件读写位置移动到图像数据开始处 */
if (-1 == lseek(fd, file_h.offset, SEEK_SET)) {
perror("lseek error");
close(fd);
return -1;
}
/* 申请一个buf、暂存bmp图像的一行数据 */
line_bytes = info_h.width * info_h.bpp / 8;
line_buf = malloc(line_bytes);
if (NULL == line_buf) {
fprintf(stderr, "malloc error\n");
close(fd);
return -1;
}
if (line_length > line_bytes)
min_bytes = line_bytes;
else
min_bytes = line_length;
/**** 读取图像数据显示到LCD ****/
/*******************************************
* 为了软件处理上方便,这个示例代码便不去做兼容性设计了
* 如果你想做兼容, 可能需要判断传入的BMP图像是565还是888
* 如何判断呢?文档里边说的很清楚了
* 我们默认传入的bmp图像是RGB565格式
*******************************************/
if (0 < info_h.height) {//倒向位图
if (info_h.height > height) {
min_h = height;
lseek(fd, (info_h.height - height) * line_bytes, SEEK_CUR);// 文件指针前移,跳过超出的部分
screen_base += width * (height - 1); //定位到屏幕左下角位置
}
else {
min_h = info_h.height;
screen_base += width * (info_h.height - 1); // 定位到屏幕对应位置
}
for (j = min_h; j > 0; screen_base -= width, j--) {// 从位图底部往顶部分行读取数据
read(fd, line_buf, line_bytes); //读取出图像数据
memcpy(screen_base, line_buf, min_bytes);//刷入LCD显存
}
}
else { //正向位图
int temp = 0 - info_h.height; //负数转成正数
if (temp > height)
min_h = height;
else
min_h = temp;
for (j = 0; j < min_h; j++, screen_base += width) {
read(fd, line_buf, line_bytes);
memcpy(screen_base, line_buf, min_bytes);
}
}
/* 关闭文件、函数返回 */
close(fd);
free(line_buf);
return 0;
}
int main(int argc, char *argv[])
{
struct fb_fix_screeninfo fb_fix;
struct fb_var_screeninfo fb_var;
unsigned int screen_size;
int fd;
/* 传参校验 */
if (2 != argc) {
fprintf(stderr, "usage: %s <bmp_file>\n", argv[0]);
exit(-1);
}
/* 打开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;
line_length = fb_fix.line_length;
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);
}
/* 显示BMP图片 */
memset(screen_base, 0xFF, screen_size);
//memset 函数用于将某一块内存的数据全部设置为指定的值
//void *memset(void *s, int c, size_t n);
//将映射到 screen_base 的显存区域填充为白色
show_bmp_image(argv[1]);
/* 退出 */
munmap(screen_base, screen_size); //取消映射
close(fd); //关闭文件
exit(EXIT_SUCCESS); //退出进程
}
-
show_bmp_image() 函数步骤
-
打开指定路径的 BMP 文件,得到对应的文件描述符 fd
-
调用 read()函数读取 BMP 文件头和位图信息头
-
获取到信息后使用 printf 将其打印出来
-
使用 lseek()函数将文件的读写位置移动到图像数据起始位置,即 bmp_file_header 结构体中的 offset 变量指定的地址偏移量
-
通过 info_h.height 判断 BMP 位图是正向还是倒向,并分别处理
-
处理倒向位图
-
处理高度大于屏幕高度的情况
-
设定最小高度 min_h 为屏幕高度
-
使用 lseek 跳过超出屏幕高度的部分
-
使用 lseek 跳过超出屏幕高度的部分
-
-
处理高度小于或等于屏幕高度的情况
-
设定最小高度 min_h 为位图高度
-
定位到屏幕对应位置
-
-
从位图底部往顶部分行读取数据并写入显存
- 从底部开始读取每行数据,并将数据复制到屏幕显存中
-
-
处理正向位图
-
将负高度转换为正高度
- 将 info_h.height 转换为正数
-
设定最小高度
-
从位图顶部往底部分行读取数据并写入显存
- 从顶部开始读取每行数据,并将数据复制到屏幕显存中
-
-
-
-
main()函数步骤
-
校验命令行参数
-
打开framebuffer设备并获取屏幕信息
-
映射显存到进程地址空间
-
调用 show_bmp_image 显示BMP图片
-
取消映射并关闭文件,退出进程
-
在开发板上测试
- 执行测试程序