Linux Framebuffer 图形库笔记
一、项目概述
这是一个基于Linux Framebuffer的轻量级图形库,提供基本的图形绘制功能,支持点、线、矩形、圆、BMP图片显示和中文字符显示。
二、代码
1. framebuffer.h
cpp
#ifndef __FRAMEBUFFER_H__
#define __FRAMEBUFFER_H__
#include "utf.h"
#define RGB888_FMT 32 // RGB888格式(32位色)
#define RGB565_FMT 16 // RGB565格式(16位色)
// 函数声明
extern int init_fb(char *devname); // 初始化framebuffer
extern void draw_point(int x, int y, unsigned int col); // 绘制像素点
extern void uninit_fb(int fd); // 清理framebuffer资源
extern void draw_clear(unsigned int col); // 清屏
// 基本图形绘制函数
extern void draw_h_line(int x, int y, int len, unsigned int col); // 水平线
extern void draw_s_line(int x, int y, int len, unsigned int col); // 垂直线
extern void draw_rectangle(int x, int y, int w, int h, unsigned int col); // 矩形
extern void draw_x_line(int x1, int y1, int x2, int y2, unsigned int col); // 任意角度直线
extern void draw_circle(int x0, int y0, int r, unsigned int col); // 圆形
extern void draw_bmp(int x, int y, char *picname, int w, int h); // BMP图片显示
// 文字显示函数
extern void draw_word(int x, int y, unsigned char *word, int w, int h, unsigned int col); // 英文字符
extern int draw_utf8(UTF8_INFO *info, int x, int y, char* zi, unsigned int col, unsigned int col1); // 单个中文字符
extern int draw_utf8_str(UTF8_INFO *info, int arg_x, int arg_y, char* zi, unsigned int col, unsigned int col1); // 中文字符串
#endif
2. framebuffer.c
cpp
#include "framebuffer.h"
#include <fcntl.h>
#include <linux/fb.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
void *pmem; // 显存映射指针
struct fb_var_screeninfo vinf; // 屏幕信息结构体
/**
* 初始化framebuffer
* @param devname 设备路径(如"/dev/fb0")
* @return 文件描述符,失败返回-1
*/
int init_fb(char *devname)
{
// 1. 打开显示设备
int fd = open(devname, O_RDWR);
if (-1 == fd)
{
perror("fail open fb");
return -1;
}
// 2. 获取显示设备参数(分辨率、位深度)
int ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinf);
if (-1 == ret)
{
perror("fail ioctl");
return -1;
}
printf("xres = %d, yres = %d\n", vinf.xres, vinf.yres);
printf("xres_virtual = %d, yres_virtual = %d\n", vinf.xres_virtual,
vinf.yres_virtual);
printf("bits_per_pixel : %d\n", vinf.bits_per_pixel);
// 计算显存大小
size_t len = vinf.xres_virtual * vinf.yres_virtual * vinf.bits_per_pixel / 8;
// 3. 建立显存和用户空间的映射关系
pmem = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if ((void *)-1 == pmem)
{
perror("fail mmap");
return -1;
}
return fd; // 返回文件描述符
}
/**
* 绘制单个像素点
* @param x 横坐标
* @param y 纵坐标
* @param col 颜色值
*/
void draw_point(int x, int y, unsigned int col)
{
// 边界检查
if (x >= vinf.xres || y >= vinf.yres)
{
return;
}
// 根据位深度选择不同的写入方式
if (vinf.bits_per_pixel == RGB888_FMT) // 32位色
{
unsigned int *p = pmem;
*(p + y * vinf.xres_virtual + x) = col;
}
else if (vinf.bits_per_pixel == RGB565_FMT) // 16位色
{
unsigned short *p = pmem;
*(p + y * vinf.xres_virtual + x) = col;
}
return;
}
/**
* 清理framebuffer资源
* @param fd 文件描述符
*/
void uninit_fb(int fd)
{
size_t len = vinf.xres_virtual * vinf.yres_virtual * vinf.bits_per_pixel / 8;
munmap(pmem, len); // 解除内存映射
close(fd); // 关闭文件
}
/**
* 清屏函数
* @param col 填充颜色
*/
void draw_clear(unsigned int col)
{
for (int j = 0; j < vinf.yres_virtual; j++)
{
for (int i = 0; i < vinf.xres_virtual; i++)
{
draw_point(i, j, col);
}
}
}
/**
* 绘制水平线
* @param x 起始点x坐标
* @param y 起始点y坐标
* @param len 线长
* @param col 颜色
*/
void draw_h_line(int x, int y, int len, unsigned int col)
{
for (int i = x; i < x + len; i++)
{
draw_point(i, y, col);
}
}
/**
* 绘制垂直线
* @param x 起始点x坐标
* @param y 起始点y坐标
* @param len 线长
* @param col 颜色
*/
void draw_s_line(int x, int y, int len, unsigned int col)
{
for (int i = y; i < y + len; i++)
{
draw_point(x, i, col);
}
}
/**
* 绘制矩形(空心)
* @param x 左上角x坐标
* @param y 左上角y坐标
* @param w 宽度
* @param h 高度
* @param col 颜色
*/
void draw_rectangle(int x, int y, int w, int h, unsigned int col)
{
draw_h_line(x, y, w, col); // 上边
draw_s_line(x, y, h, col); // 左边
draw_s_line(x + w, y, h, col); // 右边
draw_h_line(x, y + h, w, col); // 下边
}
/**
* 绘制任意角度直线(使用Bresenham算法思想)
* @param x1 起点x坐标
* @param y1 起点y坐标
* @param x2 终点x坐标
* @param y2 终点y坐标
* @param col 颜色
*/
void draw_x_line(int x1, int y1, int x2, int y2, unsigned int col)
{
int x = 0;
int y = 0;
// 处理垂直线特殊情况
if (x1 == x2)
{
if (y2 > y1)
{
draw_s_line(x1, y1, y2 - y1, col);
}
else
{
draw_s_line(x2, y2, y1 - y2, col);
}
return;
}
// 计算斜率和截距
double k = (double)(y2 - y1) / (double)(x2 - x1);
double b = y1 - k * x1;
// 从x值较小的点开始绘制
int start_x = (x1 > x2 ? x2 : x1);
int end_x = (x1 > x2 ? x1 : x2);
for (int x = start_x; x <= end_x; x++)
{
y = x * k + b;
draw_point(x, y, col);
}
return;
}
/**
* 绘制圆形
* @param x0 圆心x坐标
* @param y0 圆心y坐标
* @param r 半径
* @param col 颜色
*/
void draw_circle(int x0, int y0, int r, unsigned int col)
{
int x = 0;
int y = 0;
// 通过三角函数计算圆上的点
for (double si = 0; si <= 360; si += 0.01)
{
x = r * cos(2 * 3.14159 / 360 * si) + x0;
y = r * sin(2 * 3.14159 / 360 * si) + y0;
// 绘制5个点使圆形更粗更清晰
draw_point(x, y, col);
draw_point(x - 1, y, col);
draw_point(x + 1, y, col);
draw_point(x, y - 1, col);
draw_point(x, y + 1, col);
}
}
/**
* 显示BMP图片(24位位图)
* @param x 左上角x坐标
* @param y 左上角y坐标
* @param picname 图片文件名
* @param w 图片宽度
* @param h 图片高度
*/
void draw_bmp(int x, int y, char *picname, int w, int h)
{
int fd = open(picname, O_RDONLY);
if (-1 == fd)
{
perror("fail open bmp");
return;
}
lseek(fd, 54, SEEK_SET); // 跳过BMP文件头(54字节)
unsigned char r, g, b;
unsigned char *buff = malloc(w * h * 3); // 分配图片数据缓冲区
read(fd, buff, w * h * 3); // 读取图片数据
unsigned char *p = buff;
// BMP文件存储顺序是从下到上,需要反向处理
for (int j = h - 1; j >= 0; j--)
{
for (int i = 0; i < w; i++)
{
b = *p; p++; // 蓝色分量
g = *p; p++; // 绿色分量
r = *p; p++; // 红色分量
if (vinf.bits_per_pixel == RGB888_FMT)
{
// RGB888格式:RRRRRRRR GGGGGGGG BBBBBBBB
unsigned int col = (r << 16) | (g << 8) | (b << 0);
draw_point(i + x, j + y, col);
}
else if (vinf.bits_per_pixel == RGB565_FMT)
{
// RGB565格式:RRRRR GGGGGG BBBBB
unsigned short col = ((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3) << 0);
draw_point(i + x, j + y, col);
}
}
}
free(buff); // 释放缓冲区
close(fd); // 关闭文件
}
/**
* 显示英文字符(8xN点阵)
* @param x 起始x坐标
* @param y 起始y坐标
* @param word 字模数据指针
* @param w 字符宽度(字节数)
* @param h 字符高度(像素数)
* @param col 字符颜色
*/
void draw_word(int x, int y, unsigned char *word, int w, int h,
unsigned int col)
{
for (int j = 0; j < h; j++)
{
for (int i = 0; i < w; i++)
{
unsigned char tmp = word[i + j * w]; // 获取字模数据
for (int k = 0; k < 8; k++) // 处理每个字节的8个位
{
if (tmp & 0x80) // 最高位为1表示需要绘制
{
draw_point(i * 8 + k + x, j + y, col);
}
// 否则绘制背景色(这里注释掉了,保持透明)
tmp = tmp << 1; // 左移处理下一位
}
}
}
}
/**
* 显示单个中文字符
* @param info 字库信息结构体
* @param x 起始x坐标
* @param y 起始y坐标
* @param zi UTF-8编码的中文字符
* @param col 字体颜色
* @param col1 背景颜色(未使用)
* @return 处理的字节数
*/
int draw_utf8(UTF8_INFO *info, int x, int y, char *zi, unsigned int col,
unsigned int col1)
{
unsigned long out = 0;
// 将UTF-8编码转换为Unicode
int ret = enc_utf8_to_unicode_one((unsigned char *)zi, &out);
// 获取对应字模数据
unsigned char *data = get_utf_data(info, out);
unsigned char temp = 0;
unsigned int i, j, k;
unsigned int num = 0;
// 绘制字模
for (i = 0; i < info->height; i++) // 遍历每一行
{
for (j = 0; j < info->width / 8; j++) // 每行有多少字节
{
temp = data[num++]; // 获取字模数据
for (k = 0; k < 8; k++) // 处理每个字节的8位
{
if (0x80 & temp) // 最高位为1表示需要绘制
{
draw_point(x + k + j * 8, y + i, col);
}
// 否则绘制背景色(这里注释掉了)
}
3. utf.h
cpp
#ifndef UTF
#define UTF
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
// 类型别名定义
#define u8 unsigned char
#define u16 unsigned short
#define u32 unsigned int
// 字库文件路径宏定义
#define ZIKUK_FILE_SMALL "./ziku" // 小字库
#define ZIKUK_FILE_BIG "./ziku2_w32_h32" // 大字库(32x32)
/*** 字模文件缓存结构体 ***/
typedef struct
{
char path[256]; // 字模库文件路径
unsigned width; // 字模宽度(像素)
unsigned height; // 字模高度(像素)
unsigned zimo_size; // 每个字字模字节数
unsigned char* g_ziku_data; // 字模库文件缓存区
} UTF8_INFO;
// 函数声明
extern void init_utf8(UTF8_INFO *info); // 初始化字库
extern unsigned char *get_utf_data(UTF8_INFO *info, int out); // 获取字模数据
extern void uninit_utf8(UTF8_INFO *info); // 清理字库资源
extern int enc_utf8_to_unicode_one(const unsigned char* pInput, unsigned long *Unic); // UTF-8转Unicode
extern int enc_unicode_to_utf8_one(unsigned long unic, unsigned char *pOutput, int outSize); // Unicode转UTF-8
extern int enc_get_utf8_size(const unsigned char pInput); // 获取UTF-8编码字节数
#ifdef __cplusplus
}
#endif
#endif // UTF
4. utf.c
cpp
#include "utf.h"
/**
* 获取UTF-8编码字节数
* @param pInput UTF-8编码的第一个字节
* @return 该字符UTF-8编码的字节数,失败返回-1
*/
int enc_get_utf8_size(const unsigned char pInput)
{
unsigned char c = pInput;
// UTF-8编码规则:
// 0xxxxxxx -> 1字节
// 110xxxxx -> 2字节
// 1110xxxx -> 3字节
// 11110xxx -> 4字节
// 111110xx -> 5字节
// 1111110x -> 6字节
if(c < 0x80) return 1; // ASCII字符
if(c>=0x80 && c<0xC0) return -1; // 非法(10xxxxxx)
if(c>=0xC0 && c<0xE0) return 2; // 2字节UTF-8
if(c>=0xE0 && c<0xF0) return 3; // 3字节UTF-8
if(c>=0xF0 && c<0xF8) return 4; // 4字节UTF-8
if(c>=0xF8 && c<0xFC) return 5; // 5字节UTF-8
if(c>=0xFC) return 6; // 6字节UTF-8
return -1;
}
/**
* UTF-8转Unicode(单字符)
* @param pInput UTF-8编码的字符
* @param Unic 输出的Unicode值
* @return 处理的字节数,失败返回0
*/
int enc_utf8_to_unicode_one(const unsigned char* pInput, unsigned long *Unic)
{
assert(pInput != NULL && Unic != NULL);
char b1, b2, b3, b4, b5, b6;
*Unic = 0x0; // 初始化为0
int utfbytes = enc_get_utf8_size(*pInput);
unsigned char *pOutput = (unsigned char *) Unic;
switch (utfbytes)
{
case 1: // ASCII字符(1字节)
*pOutput = *pInput;
utfbytes = 1;
break;
case 2: // 2字节UTF-8
b1 = *pInput;
b2 = *(pInput + 1);
if ((b2 & 0xE0) != 0x80) // 验证后续字节格式
return 0;
*pOutput = (b1 << 6) + (b2 & 0x3F);
*(pOutput+1) = (b1 >> 2) & 0x07;
break;
case 3: // 3字节UTF-8(常用汉字)
b1 = *pInput;
b2 = *(pInput + 1);
b3 = *(pInput + 2);
if (((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80))
return 0;
*pOutput = (b2 << 6) + (b3 & 0x3F);
*(pOutput+1) = (b1 << 4) + ((b2 >> 2) & 0x0F);
break;
case 4: // 4字节UTF-8
b1 = *pInput;
b2 = *(pInput + 1);
b3 = *(pInput + 2);
b4 = *(pInput + 3);
if (((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) || ((b4 & 0xC0) != 0x80))
return 0;
*pOutput = (b3 << 6) + (b4 & 0x3F);
*(pOutput+1) = (b2 << 4) + ((b3 >> 2) & 0x0F);
*(pOutput+2) = ((b1 << 2) & 0x1C) + ((b2 >> 4) & 0x03);
break;
case 5: // 5字节UTF-8
b1 = *pInput;
b2 = *(pInput + 1);
b3 = *(pInput + 2);
b4 = *(pInput + 3);
b5 = *(pInput + 4);
if (((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) ||
((b4 & 0xC0) != 0x80) || ((b5 & 0xC0) != 0x80))
return 0;
*pOutput = (b4 << 6) + (b5 & 0x3F);
*(pOutput+1) = (b3 << 4) + ((b4 >> 2) & 0x0F);
*(pOutput+2) = (b2 << 2) + ((b3 >> 4) & 0x03);
*(pOutput+3) = (b1 << 6);
break;
case 6: // 6字节UTF-8
b1 = *pInput;
b2 = *(pInput + 1);
b3 = *(pInput + 2);
b4 = *(pInput + 3);
b5 = *(pInput + 4);
b6 = *(pInput + 5);
if (((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) ||
((b4 & 0xC0) != 0x80) || ((b5 & 0xC0) != 0x80) ||
((b6 & 0xC0) != 0x80))
return 0;
*pOutput = (b5 << 6) + (b6 & 0x3F);
*(pOutput+1) = (b5 << 4) + ((b6 >> 2) & 0x0F);
*(pOutput+2) = (b3 << 2) + ((b4 >> 4) & 0x03);
*(pOutput+3) = ((b1 << 6) & 0x40) + (b2 & 0x3F);
break;
default:
return 0;
}
return utfbytes;
}
/**
* Unicode转UTF-8(单字符)
* @param unic Unicode编码值
* @param pOutput 输出缓冲区
* @param outSize 缓冲区大小
* @return 转换后的字节数,失败返回0
*/
int enc_unicode_to_utf8_one(unsigned long unic, unsigned char *pOutput,
int outSize)
{
assert(pOutput != NULL);
assert(outSize >= 6);
if (unic <= 0x0000007F) // ASCII范围
{
*pOutput = (unic & 0x7F); // 0xxxxxxx
return 1;
}
else if (unic >= 0x00000080 && unic <= 0x000007FF) // 2字节UTF-8
{
*(pOutput+1) = (unic & 0x3F) | 0x80;
*pOutput = ((unic >> 6) & 0x1F) | 0xC0; // 110xxxxx 10xxxxxx
return 2;
}
else if (unic >= 0x00000800 && unic <= 0x0000FFFF) // 3字节UTF-8
{
*(pOutput+2) = (unic & 0x3F) | 0x80;
*(pOutput+1) = ((unic >> 6) & 0x3F) | 0x80;
*pOutput = ((unic >> 12) & 0x0F) | 0xE0; // 1110xxxx 10xxxxxx 10xxxxxx
return 3;
}
else if (unic >= 0x00010000 && unic <= 0x001FFFFF) // 4字节UTF-8
{
*(pOutput+3) = (unic & 0x3F) | 0x80;
*(pOutput+2) = ((unic >> 6) & 0x3F) | 0x80;
*(pOutput+1) = ((unic >> 12) & 0x3F) | 0x80;
*pOutput = ((unic >> 18) & 0x07) | 0xF0; // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
return 4;
}
else if (unic >= 0x00200000 && unic <= 0x03FFFFFF) // 5字节UTF-8
{
*(pOutput+4) = (unic & 0x3F) | 0x80;
*(pOutput+3) = ((unic >> 6) & 0x3F) | 0x80;
*(pOutput+2) = ((unic >> 12) & 0x3F) | 0x80;
*(pOutput+1) = ((unic >> 18) & 0x3F) | 0x80;
*pOutput = ((unic >> 24) & 0x03) | 0xF8; // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
return 5;
}
else if (unic >= 0x04000000 && unic <= 0x7FFFFFFF) // 6字节UTF-8
{
*(pOutput+5) = (unic & 0x3F) | 0x80;
*(pOutput+4) = ((unic >> 6) & 0x3F) | 0x80;
*(pOutput+3) = ((unic >> 12) & 0x3F) | 0x80;
*(pOutput+2) = ((unic >> 18) & 0x3F) | 0x80;
*(pOutput+1) = ((unic >> 24) & 0x3F) | 0x80;
*pOutput = ((unic >> 30) & 0x01) | 0xFC; // 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
return 6;
}
return 0;
}
/**
* 初始化字库
* @param info 字库信息结构体
*/
void init_utf8(UTF8_INFO *info)
{
int ret = 0;
int fd = open(info->path, O_RDONLY); // 打开字库文件
if (-1 == fd)
{
exit(1);
}
struct stat st;
ret = stat(info->path, &st); // 获取文件信息
if (-1 == ret)
{
printf("get zi ku file size error");
exit(1);
}
// 分配内存并读取字库文件
if (NULL == info->g_ziku_data)
{
info->g_ziku_data = malloc(st.st_size);
}
ret = read(fd, info->g_ziku_data, st.st_size); // 读取字库数据
if (ret <= 0)
{
printf("read utf-8 info error!");
exit(1);
}
info->zimo_size = st.st_size / 65536; // 计算每个字的字节数(假设65536个字)
close(fd); // 关闭文件
}
/**
* 清理字库资源
* @param info 字库信息结构体
*/
void uninit_utf8(UTF8_INFO *info)
{
free(info->g_ziku_data); // 释放字库内存
}
/**
* 根据Unicode编码获取字模数据
* @param info 字库信息结构体
* @param out Unicode编码
* @return 字模数据指针
*/
unsigned char* get_utf_data(UTF8_INFO *info, int out)
{
// 计算字模位置:每个字的大小 = width * height / 8(字节)
unsigned char* temp = info->g_ziku_data + out * info->width * info->height / 8;
return temp;
}
5. main.c
cpp
#include <stdio.h>
#include "framebuffer.h"
// "普"字的24x24点阵字模数据
unsigned char pu[24 * 24 / 8] = {
/*-- 文字: 普 --*/
/*-- 仿宋18; 此字体下对应的点阵为:宽x高=24x24 --*/
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0xC3, 0x80, 0x00, 0xE3, 0x00,
0x00, 0x77, 0x00, 0x00, 0xFF, 0xF0, 0x0F, 0xFF, 0xC0, 0x00, 0x7E, 0xE0,
0x06, 0x7E, 0xE0, 0x03, 0x7F, 0xC0, 0x03, 0xFF, 0x80, 0x01, 0x7F, 0xFC,
0x7F, 0xFF, 0xE0, 0x3E, 0x00, 0x80, 0x03, 0xFF, 0xC0, 0x03, 0xFF, 0xC0,
0x03, 0x81, 0xC0, 0x03, 0xFF, 0xC0, 0x01, 0xFF, 0x80, 0x01, 0x81, 0x80,
0x01, 0xFF, 0x80, 0x03, 0xFF, 0x80, 0x01, 0x81, 0x80, 0x00, 0x00, 0x00
};
int main(int argc, const char *argv[])
{
// 1. 初始化UTF-8字库
UTF8_INFO utf8_info;
bzero(&utf8_info, sizeof(UTF8_INFO)); // 清零初始化
strcpy(utf8_info.path, ZIKUK_FILE_BIG); // 设置字库路径
utf8_info.width = 32; // 字模宽度32像素
utf8_info.height = 32; // 字模高度32像素
init_utf8(&utf8_info); // 加载字库到内存
// 2. 初始化framebuffer
int fb_fd = init_fb("/dev/fb0");
if (-1 == fb_fd)
{
return -1;
}
// 3. 绘制各种图形和文字
draw_clear(0x00F08080); // 清屏(淡紫色)
draw_point(400, 300, 0x00ffffff); // 绘制单个白色像素点
draw_rectangle(100, 100, 100, 200, 0x00ff0000); // 红色矩形
draw_x_line(200, 200, 300, 300, 0x0000ffff); // 青色斜线
draw_x_line(200, 200, 300, 100, 0x0000ffff); // 青色斜线
draw_x_line(300, 300, 100, 100, 0x00ff00ff); // 紫色斜线
draw_circle(200, 200, 200, 0x00ffffff); // 白色圆形
sleep(2); // 等待2秒
draw_bmp(0, 0, "./res/1.bmp", 800, 600); // 显示BMP图片
draw_word(100, 100, pu, 24 / 8, 24, 0x00ff0000); // 显示"普"字(红色)
draw_utf8_str(&utf8_info, 100, 100, "hello",
0x00ff0000, 0x00ffffff); // 显示文字标题(红色)
// 4. 清理资源
uninit_fb(fb_fd); // 关闭framebuffer
return 0;
}
三、详细解释
1. 核心原理
1.1 Framebuffer机制
-
Framebuffer 是Linux内核中的一种显示驱动框架,将显示设备抽象为连续的内存区域
-
应用程序通过内存映射(mmap)直接操作显存,实现高效的图形绘制
-
支持多种颜色格式:RGB888(32位)、RGB565(16位)等
1.2 颜色编码
-
RGB888 :32位颜色,格式为
0x00RRGGBB(高8位通常为0) -
RGB565 :16位颜色,格式为
RRRRRGGGGGGBBBBB -
BMP图片为24位格式,需要转换为对应格式
1.3 坐标系统
-
原点 (0,0) 在屏幕左上角
-
x轴向右递增,y轴向下递增
-
物理分辨率 vs 虚拟分辨率:虚拟分辨率可以大于物理分辨率,实现滚动
2. 关键数据结构
2.1 fb_var_screeninfo(来自Linux内核)
struct fb_var_screeninfo {
__u32 xres; // 可见分辨率宽度
__u32 yres; // 可见分辨率高度
__u32 xres_virtual; // 虚拟分辨率宽度
__u32 yres_virtual; // 虚拟分辨率高度
__u32 bits_per_pixel; // 每像素位数(16或32)
// ... 其他字段
};
2.2 UTF8_INFO(自定义字库结构)
typedef struct {
char path[256]; // 字库文件路径
unsigned width; // 单个字符宽度(像素)
unsigned height; // 单个字符高度(像素)
unsigned zimo_size; // 每个字模字节数
unsigned char* g_ziku_data; // 字库数据指针
} UTF8_INFO;
3. 主要函数详解
3.1 初始化流程
int fd = open("/dev/fb0", O_RDWR); // 1. 打开设备文件
ioctl(fd, FBIOGET_VSCREENINFO, &vinf); // 2. 获取屏幕信息
pmem = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); // 3. 内存映射
3.2 像素绘制原理
// 对于RGB888格式(32位色):
unsigned int *p = pmem;
*(p + y * vinf.xres_virtual + x) = color;
// 对于RGB565格式(16位色):
unsigned short *p = pmem;
*(p + y * vinf.xres_virtual + x) = color;
3.3 中文显示流程
UTF-8字符 → Unicode编码 → 字库中查找字模 → 绘制点阵
↓ ↓ ↓ ↓
"中" (0xE4B8AD) → 0x4E2D → 字模数据地址 → 屏幕显示
4. 编译和运行
4.1 编译命令
# 编译所有源文件
gcc -o graphics_demo main.c framebuffer.c utf.c -lm
# 带调试信息
gcc -o graphics_demo main.c framebuffer.c utf.c -lm -g -Wall
4.2 运行准备
# 1. 确保有framebuffer设备
ls -l /dev/fb*
# 2. 准备字库文件(放到程序目录)
cp ziku_file ./ziku2_w32_h32
# 3. 准备BMP图片
mkdir res
cp image.bmp ./res/1.bmp
# 4. 运行程序(可能需要root权限)
sudo ./graphics_demo
5. 注意事项
5.1 权限问题
-
直接操作
/dev/fb0需要root权限 -
可以将用户加入
video组避免使用sudo:sudo usermod -a -G video $USER
5.2 性能优化
-
当前实现为教学用途,未做优化
-
实际应用中可优化:
-
批量绘制代替单点绘制
-
使用DMA加速
-
双缓冲减少闪烁
-
5.3 限制和扩展
-
当前限制:
-
只支持BMP 24位位图
-
直线算法精度有限
-
没有图形填充功能
-
-
扩展方向:
-
添加填充矩形、填充圆形
-
支持更多图片格式(PNG、JPEG)
-
添加抗锯齿功能
-
实现图形用户界面(GUI)
-
6. 应用场景
-
嵌入式系统显示:无GUI环境的Linux设备
-
信息显示系统:工业控制、监控系统
-
教育演示:计算机图形学教学
-
轻量级GUI基础:可作为简单GUI框架的基础
这个图形库虽然简单,但涵盖了底层图形编程的核心概念,是学习Linux下图形编程的良好起点。