应用——Linux Framebuffer 图形库显示

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. 应用场景

  1. 嵌入式系统显示:无GUI环境的Linux设备

  2. 信息显示系统:工业控制、监控系统

  3. 教育演示:计算机图形学教学

  4. 轻量级GUI基础:可作为简单GUI框架的基础

这个图形库虽然简单,但涵盖了底层图形编程的核心概念,是学习Linux下图形编程的良好起点。

相关推荐
阿巴~阿巴~几秒前
IPv4地址的边界与智慧:特殊用途、枯竭挑战与应对策略全景解析
运维·服务器·网络·网络协议·tcp/ip·ipv4·ipv4地址枯竭
im_AMBER几秒前
Leetcode 97 移除链表元素
c++·笔记·学习·算法·leetcode·链表
噎住佩奇几秒前
Shell提示符变为bash-4.2
linux·ssh
开开心心就好3 分钟前
系统清理工具清理缓存日志,启动卸载管理
linux·运维·服务器·神经网络·cnn·pdf·1024程序员节
Moresweet猫甜3 分钟前
Ubuntu LVM引导丢失紧急救援:完整恢复指南
linux·运维·数据库·ubuntu
海奥华23 分钟前
Golang Channel 原理深度解析
服务器·开发语言·网络·数据结构·算法·golang
Jasmine_llq4 分钟前
《P3200 [HNOI2009] 有趣的数列》
java·前端·算法·线性筛法(欧拉筛)·快速幂算法(二进制幂)·勒让德定理(质因子次数统计)·组合数的质因子分解取模法
AI视觉网奇5 分钟前
ue 动画重定向 实战笔记2026
笔记·ue5
代码游侠5 分钟前
学习笔记——MQTT协议
开发语言·笔记·php
love530love6 分钟前
Flash Attention 2.8.3 在 Windows + RTX 3090 上成功编译与运行复盘笔记(2026年1月版)
人工智能·windows·笔记·python·flash_attn·flash attention·z-image