【专题】frmaebuffer打印bmp图片

文章目录

  • 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:

  1. 帧缓存、帧缓冲技术;
  2. 是Linux为显示设备提供的一个接口,把显存抽象后的一种设备文件,通过该设备文件可以实现应用层向显存空间的数据写入,从而实现像素点的绘制。

2.2 如何操作framebuffer

通过内存映射技术,可以实现显存空间和用户空间的内存映射,通过向用户空间写入RGB颜色值,从而实现显存数据的写入。

像素点位数/位深度的种类:

  • RGB888 四个字节
  • RGB565 两个字节

framebuffer的操作流程:

  1. 打开显示设备文件/dev/fb0;
  2. 获取显示设备的相关参数(分辨率、像素点位数/位深度);
  3. 建立内存映射;
  4. 写入RGB颜色值;
  5. 解除内存映射;
  6. 关闭显示设备文件。
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命令码。

例如:

  1. FBIOGET_VSCREENINFO:这个宏就是我们现在需要的获取虚拟屏幕可变信息的命令码(分辨率、色深、像素格式等)。

  2. 还有这个:struct fb_var_screeninfo 可变屏幕信息结构体:

    c 复制代码
    struct 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 / transp RGB 分量位域信息,struct fb_bitfield 结构体 包含每个颜色通道:起始偏移位、占用位数。 👉RGB565:红占 5bit,绿占 6bit,蓝占 5bit
    nonstd 非标准像素格式标记,非 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_offsety_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 :对映射空间的读写权限

      1. PROT_READ

      2. 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()建立的内存映射,释放用户空间与物理显存之间的映射关系;取消后不能再通过该指针访问显存,关闭帧缓冲文件前必须调用。

    • 参数:

      1. addrmmap返回的映射起始虚拟地址(如前面的pmem
      2. 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图片像素数据这些非常规的存储方式:

  1. 每行的数据的长度必须是4的倍数;
  2. 像素点具体排列顺序通常为BGR ,而非RGB
  3. 像素数据在文件中通常是从图像的底部开始向上存储的,即第一行像素数据对应图像的最下方一行。

在下面的代码中,因为对于打印的图片我会对其进行处理,使其横向像素满足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);

好了,以上就是本期博客全部内容啦,希望能对屏幕前的你产生帮助,我们下期再见!

相关推荐
牢姐与蒯44 分钟前
Linux进程(三)之进程状态_僵尸孤儿
linux·运维·服务器·ubuntu·c
mengge.cloud1 小时前
云计算&服务器基础小白教程
android·linux·运维·服务器·网络·云计算
一路向北finish1 小时前
《Hadoop 高可用集群与 OpenStack Mitaka 控制节点部署全流程实操排坑指南》
大数据·linux·运维·服务器·hadoop·openstack
南棱笑笑生1 小时前
20260826解决在Ubuntu22.04系统下编译RK3572的Buildroot【linux-6.12.69】出现CMake异常的问题
linux·运维·服务器
lskblog2 小时前
Ubuntu 18.04 安装 LibreOffice(用于 Word 在线预览转换)
java·linux·python·ubuntu·word·php·laravel
敲上瘾2 小时前
Redis持久化存储机制:从RDB到AOF再到混合持久化的配置与实践
linux·数据库·redis·缓存
小张同学a.3 小时前
OpenStack 私有云实战 2—— Glance 镜像与 Nova 计算服务搭建
linux·运维·服务器·openstack
xiaoye-duck3 小时前
《Linux网络编程》TCP 网络编程(下):基于 TCP 多线程通用字典服务 + 远程命令执行实战
linux·服务器·tcp/ip
陈让然3 小时前
VS Code C/C++ 插件 cpptools 内存占用异常
linux·运维