18. FrameBuffer 应用编程

1. 什么是 FrameBuffer

FrameBuffer 就是帧缓冲,也就是一块内存,里面保存着一帧图像。是 Linux 系统中的一种显示驱动接口,它将显示设备进行抽象,屏蔽了不同显示设备硬件的实现,对应用层抽象为一块显示内存,允许上层应用程序直接对显示缓冲区进行读写操作,用户不必关系物理显存的位置等具体细节,这些由 FrameBuffer 设备驱动完成。
对应的设备文件为 /dev/fdX,Linux 最多有 32 个。
譬如 LCD 的分辨率是 800*480,每一个像素点的颜色用 24 位(譬如 RGB888)来表示,那么这个显示缓冲区的大小就是 800 x 480 x 24 / 8 = 1152000 个字节。

2. LCD 应用编程介绍

  1. 首先打开 /dev/fbX 设备文件
  2. 使用 ioctl() 获取当前显示设备的参数信息
  3. 通过存储映射 IO 将显示缓冲区映射到用户空间
  4. 映射成功后就可以直接读取屏幕的显示缓冲区
  5. 完成显示后,取消映射并关闭设备文件

2.1 使用 ioctl() 获取屏幕参数信息

对 于 Framebuffer 设 备 来 说 , 常 用 的 request 包 括 FBIOGET_VSCREENINFO、FBIOPUT_VSCREENINFO、FBIOGET_FSCREENINFO。

  • FBIOGET_VSCREENINFO: 获取设备的可变参数信息,保存在 struct fb_var_screeninfo 结构体中
    struct fb_var_screeninfo fb_var;
    ioctl(fd,FBIOGET_VSCREENINFO,&fb_var);
    
  • FBIOPUT_VSCREENINFO: 设置可变参数信息
    struct fb_var_screeninfo fb_var = {0};
    // 对 fb_var 进行数据填充
    ioctl(fd,FBIOPUT_VSCREENINFO,&fb_var);
    
  • FBIOGET_FSCREENINFO: 获取设备的固定参数信息,意味着应用程序不可修改
    struct fb_fix_screeninfo fb_fix;
    ioctl(fd,FBIOGET_FSCREENINFO,&fb_fix);
    

上述这些宏都定义在<linux/fb.h>
在这里插入图片描述

#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=open("/dev/fb0",O_WDONLY);
	// 获取参数信息
	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);
	return 0;
}

2.2 为什么要使用 mmap() 将显示缓冲区映射到用户空间

使用普通文件 IO 的方式也可以,但是,当数据量比较大时,普通 IO 方式效率较低,而且显示器的图像往往是动态变化的,而 mmap() 可以将显存映射到进程的地址空间中,这样应用程序便可直接对显示缓冲区进行读写操作。

3. 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); \
			})
static int width;		// LCD X 分辨率
static int height;		// LCD Y 分辨率
static unsigned short *screen_base = NULL;	// 映射后的显存基地址

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;
}

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;
	}
}

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);//右边
}

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=open("/dev/fb0",O_RDWR);
	ioctl(fd, FBIOGET_CSCREENINFO, &fb_fix);
	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");
		close(fd);
		return -1;
	}
	// 画正方形方块
	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,0fFF00);					// 绿色方块
	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);
	return 0;
}

4. 显示 BMP 图像

4.1 BMP 图像介绍

文件类型是 bmp,它采用位映射存储格式,除了图像深度可选外,图像数据没有进行任何压缩,所以 bmp 文件所占用的空间很大,但是没有失真,而且解析 bmp 图像简单。
bmp 图像深度可选 1bit、4bit、16、24 以及 32,典型的 bmp 图像文件由四部分构成:

  • bmp 文件头:包含 bmp 文件的格式、大小、位图数据的偏移量等信息
  • 位图信息头:包含位图信息头大小、图像的尺寸、图像大小、位平面数、压缩方式以及颜色索引等信息
  • 调色板:这部分是可选的,如果使用索引来表示图像,调色板就是索引与其对应颜色的映射表
  • 位图数据:也就是图像数据
    一般常见的图像都是以 16 位(R、G、B 三种颜色分别使用 5bit、6bit、5bit 来表示)、24 位(R、G、B 三种颜色都使用 8bit 来表示)色图像为主,我们称这样的图像为真彩色图像,真彩色图像是不需要调色板的,即位图信息头后面紧跟的就是位图数据了。
    对某些 BMP 位图文件说并非如此, 譬如 16 色位图、256 色位图,它们需要使用到调色板,具体调色板如何使用,我们不关心,我们将会以 16 位色(RGB565)BMP 图像为例
    在 windows 下查看 bmp 文件属性,然后使用十六进制查看该文件,文件头的内容如下:
    在这里插入图片描述
    在这里插入图片描述

4.1.1 bmp 头文件

在这里插入图片描述
00~01H:0x42、0x4D 对应的 ASCII 字符分别为为 B、M,表示这是 Windows 所支持的位图格式,该字段必须是“BM”才是 Windows 位图文件。
02~05H:对应于文件大小,0x000BB848=768072 字节,与 image.bmp 文件大小是相符的。
06~09H:保留字段。
0A~0D:0x00000046=70,即从文件头部开始到位图数据需要偏移 70 个字节。
bmp 文件头的大小固定为 14 个字节

4.1.2 位图信息头

在这里插入图片描述
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 字节。56 个字节相比于 40 个字节,多出了 16 个字节,那么多出的 16 个字节数据描述了什么信息呢? 稍后再给大家介绍。

4.1.3 位图数据

位图数据就是图像的数据,对于 24 位位图,使用 3 字节数据来表示一个像素点的颜色。当图像中引用的色彩超过 256 种时,就需要 16bpp 或更高 bpp 的位图(24 位、32 位)。调色板不适合 bpp 较大的位图,因此 16bpp 及以上的位图都不使用调色板,不使用调色板的位图图像有两种编码格式:RGB 和 Bit-Fields(下称 BF)
RGB 编码格式是一种均分的思想,使 Red、Green、Blue 三种颜色信息容量一样大,譬如 24bpp-RGB,它通常只有这一种编码格式,在 24bits 中,低 8 位表示 Blue 分量;中 8 为表示 Green 分量;高 8 位表示 Red分量
BF 编码格式与 RGB 不同,它利用位域操作,人为地确定 RGB 三分量所包含的信息容量。位图信息头介绍中提及到,当压缩方式选项置为 BF 时, 位图信息头大小比平时多出 16 字节, 这 16 个字节实际上是 4 个 32bit 的位域掩码,按照先后顺序,它们分别是 R、G、B、A 四个分量的位域掩码。位域掩码的作用是指出 R、G、B 三种颜色信息容量的大小,分别使用多少个 bit 数据来表示,以及三种颜色分量的位置偏移量。譬如对于 16 位色的 RGB565 图像,它的 R、G 和 B 分量的位域掩码分别是 0xF800、0x07E0 和 0x001F,也就是 R 通道使用 2 个字节中的高 5 位表示,G 通道使用 2 个字节中的中间 6 位表示。而 B 通道则使用 2 个字节中的最低 5 位表示, 如下图所示:
在这里插入图片描述
在 windows 下得到的通常是 24 位,我们需要使用 ps 来得到 RGB565 格式的位图。使用 ps 打开后,直接另存为 bmp 格式,然后选择 16 位,选择 R5 G6 B5 格式。

4.2 在 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>

// 文件头结构体
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;
static int height;
static unsigned short *screen_base = NULL;
static unsigned long line_length;	// LCD 一行的长度

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)) 
	{
		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 
	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[])
{
	if(argc!=2)
	{
		fprintf(stderr,"usage:%s <bmp_file>\n",argv[0]);
		return -1;
	}
	struct fb_fix_screeninfo fb_fix;
	struct fb_var_screeninfo fb_var;
	unsigned int screen_size;

	// 打开 framebuffer 设备
	int fd=open("/dev/fb0",O_RDWR);

	// 获取参数信息
	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");
		close(fd);
		return -1;
	}
	// 显示 bmp 图片
	memset(screen_base,0xFF,screen_size);
	show_bmp_image(argv[1]);
	
	munmap(screen_base,screen_size);
	close(fd);
	return 0;
}
  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Framebuffer应用是指在Linux系统中使用Framebuffer驱动程序来控制LCD显示设备。Framebuffer是一块内存,保存着一帧图像的每一个像素颜色值。通过设置LCD控制器的时序、信号极性以及分配Framebuffer的大小和位置,可以实现在LCD上显示图像。在应用层,可以使用ioctl函数读取屏幕的参数信息,然后通过mmap映射Framebuffer,在Framebuffer中写入数据来实现图像的显示。Framebuffer应用的好处是可以直接对显存缓冲区进行读写操作,不必关心硬件的物理地址,简化了读写速度和方便性。\[2\]\[3\] #### 引用[.reference_title] - *1* [Framebuffer的配置及应用——先转载留着,以后一定要弄懂](https://blog.csdn.net/weixin_33890499/article/details/85844617)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [【嵌入式Linux】嵌入式Linux应用开发基础知识之Framebuffer应用编程和字符汉字显示](https://blog.csdn.net/weixin_43444989/article/details/122918794)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [19.Frambuffer应用编程](https://blog.csdn.net/qq_42174306/article/details/125589673)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值