1.framebuffer概念
UI技术:User interface
framebuffer:帧缓冲、帧缓存技术
Linux内核专门为图形化显示提供的一套应用程序接口。
2.基本操作
(1)操作步骤
1. 打开显示设备(/dev/fb0)
2. 获取显示设备相关参数(分辨率,像素格式)—》ioctl
3. 建立显存空间和用户空间的内存映射
4. 向映射的用户空间写入RGB颜色值
5. 解除映射关系
6. 关闭显示设备
(2)函数接口
int init_fb(char *devname)
{
//1. 打开显示设备(/dev/fb0)
fb = open(devname, O_RDWR);
if (-1 == fb)
{
perror("open fb error");
return -1;
}
//2. 获取显示设备相关参数(分辨率,像素格式)
int ret = ioctl(fb, FBIOGET_VSCREENINFO, &vinfo);
if (ret < 0)
{
perror("ioctl error");
return -1;
}
printf("xres = %d, yres = %d\\n", vinfo.xres, vinfo.yres);
printf("xres_virtual = %d, yres_virtual = %d\\n", vinfo.xres_virtual, vinfo.yres_virtual);
printf("bits_per_pixel = %d\\n", vinfo.bits_per_pixel);
//3. 建立显存空间和用户空间的内存映射
size_t 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 (pmem == MAP_FAILED)
{
perror("mmap error");
return -1;
}
return 0;
}
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
功能:建立内存映射
参数:
addr :映射的用户空间首地址
NULL:让操作系统自己分配用户空间
length:要映射的空间大小
prot: 操作权限
PROT_READ Pages may be read.
PROT_WRITE Pages may be written
flag : MAP_SHARED
fd:显示设备文件描述符
offset:偏移量
0:从显存开头映射
返回值:
成功:映射的用户空间首地址
失败:MAP_FAILED((void *)-1)
int uninit_fb()
{
//5. 解除映射关系
//6. 关闭显示设备
size_t len = vinfo.xres_virtual * vinfo.yres_virtual * vinfo.bits_per_pixel/8;
munmap(pmem, len);
close(fb);
}
3.基本算法实现
(1)画一个点
void draw_point(int x, int y, unsigned int col)
{
if (x >= vinfo.xres || y >= vinfo.yres)
{
return ;
}
if (vinfo.bits_per_pixel == RGB_FMT_888)
{
unsigned int *p = pmem;
*(p+vinfo.xres_virtual*y+x) = col;
}
else if (vinfo.bits_per_pixel == RGB_FMT_565)
{
unsigned short *p = pmem;
*(p+vinfo.xres_virtual*y+x) = col;
}
}
算法思想:
边界检查:首先检查坐标 (x, y) 是否在屏幕有效范围内(x < vinfo.xres 且 y < vinfo.yres),若越界则直接返回。
像素格式处理:
若为 32位RGB888格式(RGB_FMT_888):
将显存指针 pmem 转换为 unsigned int* 类型。
计算目标像素的线性地址:pmem + vinfo.xres_virtual * y + x。
将颜色值 col 直接写入该地址。
若为 16位RGB565格式(RGB_FMT_565):
将显存指针 pmem 转换为 unsigned short* 类型。
计算目标像素的线性地址:pmem + vinfo.xres_virtual * y + x。
将颜色值 col(需确保是16位)写入该地址。
(2)画一条横线
void draw_x_line(int x, int y, int end, unsigned int col)
{
for(int i = 0; i < end; ++i)
{
draw_point((x + i), y, col);
}
}
算法思想:
水平线绘制:从起点 (x, y) 开始,向右连续绘制 end 个像素。
循环控制:
初始化计数器 i = 0。
循环条件 i < end,每次迭代调用 draw_point(x + i, y, col) 绘制一个像素。
循环结束后,形成一条从 (x, y) 到 (x + end – 1, y) 的水平线段。
(3)画一条竖线
void draw_y_line(int x, int y, int end, unsigned int col)
{
for(int i = 0; i < end; ++i)
{
draw_point(x, (y + i), col);
}
}
算法思想:
垂直线绘制:从起点 (x, y) 开始,向下连续绘制 end 个像素。
循环控制:
初始化计数器 i = 0。
循环条件 i < end,每次迭代调用 draw_point(x, y + i, col) 绘制一个像素。
循环结束后,形成一条从 (x, y) 到 (x, y + end – 1) 的垂直线段。
(4)画一个矩形
void draw_rect(int x, int y, int weight, int high, unsigned int col)
{
draw_x_line(x, y, weight, col);
draw_x_line(x, (y + high), weight, col);
draw_y_line(x, y, high, col);
draw_y_line((x + weight), y, high, col);
}
算法思想:
矩形边框绘制:通过组合四条边完成矩形绘制:
上边:调用 draw_x_line(x, y, weight, col)。
下边:调用 draw_x_line(x, y + high, weight, col)。
左边:调用 draw_y_line(x, y, high, col)。
右边:调用 draw_y_line(x + weight, y, high, col)。
注意:
此函数仅绘制矩形边框,不填充内部。
(5)画一个圆
void draw_Circle(int x, int y, int r, unsigned int col)
{
int x0 = 0;
int y0 = 0;
int si = 0;
for (si = 0; si <= 360; si++)
{
x0 = r * sin(PI * 2 / 360 * si) + x;
y0 = r * cos(PI * 2 / 360 * si) + y;
draw_point(x0, y0, col);
}
}
算法思想:
参数化圆绘制:
遍历角度 si 从 0° 到 360°,步长为 1°。
对每个角度 si:
计算圆上点的坐标:
x0 = r * sin(θ) + x(θ = PI * 2 / 360 * si 弧度)。
y0 = r * cos(θ) + y。
调用 draw_point(x0, y0, col) 绘制单个像素。
效果:
通过离散点近似形成一个圆形,精度取决于角度步长。
(6)画一个图
int get_bmp_head_info(const char *bmpname, Bmp_file_head_t *pheadinfo, Bmp_info_t *pbmpinfo)
{
FILE *fp = fopen(bmpname, "r");
if (NULL == fp)
{
perror("fopen error");
return -1;
}
fread(pheadinfo, sizeof(Bmp_file_head_t), 1, fp);
fread(pbmpinfo, sizeof(Bmp_info_t), 1, fp);
fclose(fp);
return 0;
}
void draw_bmp(int x, int y, char *bmpname)
{
Bmp_file_head_t headinfo;
Bmp_info_t bmpinfo;
get_bmp_head_info(bmpname, &headinfo, &bmpinfo);
int fd = open(bmpname, O_RDONLY);
if (-1 == fd)
{
perror("open bmp error");
return ;
}
lseek(fd, 54, SEEK_SET);
unsigned char *buff = malloc(bmpinfo.biHeight*bmpinfo.biWidth*bmpinfo.biBitCount/8);
read(fd, buff, bmpinfo.biHeight*bmpinfo.biWidth*bmpinfo.biBitCount/8);
close(fd);
unsigned char *p = buff;
unsigned char r, g, b;
for (int j = bmpinfo.biHeight-1; j >= 0; j–)
{
for (int i = 0; i < bmpinfo.biWidth; i++)
{
b = *p;++p;
g = *p;++p;
r = *p;++p;
if (vinfo.bits_per_pixel == RGB_FMT_888)
{
unsigned int col = (r << 16) | (g << 8) | (b << 0);
draw_point(i+x, j+y, col);
}
else if (vinfo.bits_per_pixel == RGB_FMT_565)
{
unsigned short col = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
draw_point(i+x, j+y, col);
}
}
}
free(buff);
}
get_bmp_head_info(const char *bmpname, Bmp_file_head_t *pheadinfo, Bmp_info_t *pbmpinfo)
算法思想:
文件操作:
以只读模式打开BMP文件,失败时返回错误。
头信息读取:
使用 fread 读取BMP文件头(Bmp_file_head_t)到 pheadinfo。
继续读取BMP信息头(Bmp_info_t)到 pbmpinfo。
资源释放:关闭文件句柄,返回成功状态。
draw_bmp(int x, int y, char *bmpname)
算法思想:
头信息解析:调用 get_bmp_head_info 获取BMP的宽、高、位深等信息。
像素数据加载:
打开文件并跳过54字节的文件头和信息头。
根据BMP信息头中的图像尺寸和位深(如 biWidth × biHeight × biBitCount/8),分配缓冲区 buff 并读取像素数据。
像素绘制:
BMP像素顺序:BMP文件存储顺序为从下到上,需反向遍历行(j 从 biHeight-1 到 0)。
颜色转换:
对于 RGB888格式屏幕:将BGR像素数据(BMP格式)转换为RGB888,合并为 unsigned int 后调用 draw_point。
对于 RGB565格式屏幕:将RGB分量压缩为5-6-5位,合并为 unsigned short。
绘制每个像素到屏幕坐标 (x + i, y + j)。
(7)在图中加入字(使用字模)
void draw_word(int x, int y, unsigned char *pword, int w, int h, unsigned int col)
{
for (int j = 0; j < h; j++)
{
for (int i = 0; i < w; i++)
{
unsigned char tmp = pword[i+j*w];
for (int k = 0; k < 8; k++)
{
if (tmp & 0x80)
{
draw_point(x+i*8+k, y+j, col);
}
else
{
}
tmp = tmp << 1;
}
}
}
}
算法思想:
字模数据解析:
字模数据 pword 为 w × h 的字节数组,每个字节表示8个水平像素点(1位1像素)。
逐行逐像素绘制:
外层循环遍历行(j 从 0 到 h-1)。
内层循环遍历列(i 从 0 到 w-1):
获取当前字节 tmp = pword[i + j * w]。
对字节的8个位(从高位到低位):
若某位为 1,调用 draw_point(x + i*8 + k, y + j, col) 绘制像素。
若为 0,跳过(透明或背景)。
效果:
将1位字模数据按指定颜色展开为8倍宽度的字符图形。
评论前必须登录!
注册