day69 SQLite3动态库移植 + BMP图像解析显示 + 进度条控件设计与动态文本管理
一、SQLite3 动态库移植流程回顾与验证
1. 移植步骤总结
-
下载源码
- 从官网或可信源获取 SQLite3 源码压缩包(如
sqlite-autoconf-xxxxxx.tar.gz)。
- 从官网或可信源获取 SQLite3 源码压缩包(如
-
解压源码
bashtar -zxvf sqlite-autoconf-xxxxxx.tar.gz cd sqlite-autoconf-xxxxxx -
配置编译环境
使用
./configure命令,关键参数如下:bash./configure \ CC=arm-linux-gnueabihf-gcc \ # 指定交叉编译器 --host=arm-linux \ # 指定目标平台 --prefix=/your/absolute/path # 指定安装路径(必须为绝对路径!)⚠️ 注意:
--prefix必须使用绝对路径 ,否则make install无法正确部署。 -
编译与安装
bashmake make install- 生成的动态库(如
libsqlite3.so)将被安装到--prefix指定目录下的lib/中。
- 生成的动态库(如
-
部署到开发板
- 将生成的
libsqlite3.so及其符号链接文件(共约4个相关文件)完整拷贝 到开发板的/lib或/usr/lib。 - 重要 :若开发板原已有
libsqlite3.so,必须先删除旧文件,避免冲突。
- 将生成的
-
验证移植结果
- 编写一个独立的测试程序 (不要直接在 SQLite 源码目录下运行示例),链接
-lsqlite3并执行。 - 若程序能正常运行,说明移植成功。
- 编写一个独立的测试程序 (不要直接在 SQLite 源码目录下运行示例),链接
2. 注意事项
- 动态库优于静态库 :项目中优先使用动态库(
.so),便于更新与节省空间。 - 保留符号链接属性 :拷贝时建议将整个
lib/目录打包(如tar),再在开发板解压,确保链接关系不丢失。 - 流程标准化:本次移植流程适用于其他开源库(如 zlib、curl 等),需形成通用方法论。
二、BMP 图像格式解析与显示
1. BMP 文件结构(以 24 位真彩色为例)
| 部分 | 大小 | 说明 |
|---|---|---|
| 文件头(File Header) | 14 字节 | 包含类型("BM")、文件总大小、数据偏移量等 |
| 信息头(Info Header) | 40 字节 | 包含宽、高、位深度(如24)、压缩方式(0=无压缩)等 |
| 调色板(Color Table) | 可选 | 24位图无调色板 |
| 像素数据(Pixel Data) | 可变 | 从文件偏移 54 字节 开始;按 BGR 顺序存储;从左下角开始逐行向上(底行 → 首行) |
📌 关键点:
- 像素排列:行首 → 行尾(每行从左到右)
- 行顺序:底行 → 首行(文件先存最下面一行)
- 字节序:小端(Little-Endian)
2. 准备测试图片
- 任选一张图片(如 JPG),用 Windows 画图 打开。
- 调整尺寸为 800×600 像素 (或更小),且宽高均为 4 的整数倍。
- 另存为 24 位 BMP 格式 (如
test.bmp)。 - 拷贝到 Linux 虚拟机。
3. BMP 解析核心代码(含内存对齐)
c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
// 打包结构体,确保字节对齐为1字节(匹配BMP格式的紧凑布局)
#pragma pack(push, 1)
typedef struct __file_head {
unsigned short bfType; // BMP标识,应为"BM"(0x4D42)
unsigned int bfSize; // 文件总大小(注释:wen jian da xiao)
unsigned int bfReserved1; // 保留字段1
unsigned int bfReserved2; // 保留字段2
unsigned int bfOffBits; // 像素数据相对于文件头的偏移量
} file_head_t;
typedef struct __info_head {
unsigned int biSize; // 信息头大小(应为40字节)
unsigned int biWidth; // 图像宽度(注释:width)
unsigned int biHeight; // 图像高度(注释:height)
unsigned short biPlanes; // 颜色平面数(固定为1)
unsigned short biBitCount; // 每像素位数(注释:bits_per_pixel)
unsigned int biCompression;// 压缩方式(0为无压缩)
unsigned int biSizeImage; // 像素数据大小
unsigned int biXPelsPerMeter; // 水平分辨率
unsigned int biYPelsPerMeter; // 垂直分辨率
unsigned int biClrUsed; // 使用的颜色数
unsigned int biClrImportant;// 重要颜色数
} info_head_t;
#pragma pack(pop)
int main(int argc, const char *argv[]) {
file_head_t fh;
info_head_t ih;
// 打开BMP文件(请确保1.bmp存在于上级目录)
int fd = open("../1.bmp", O_RDONLY);
if (fd < 0) {
perror("open file failed");
return -1;
}
// 读取文件头
if (read(fd, &fh, sizeof(fh)) != sizeof(fh)) {
perror("read file head failed");
close(fd);
return -1;
}
// 读取信息头
if (read(fd, &ih, sizeof(ih)) != sizeof(ih)) {
perror("read info head failed");
close(fd);
return -1;
}
// 打印文件头大小(验证结构体打包是否正确)
printf("sizeof file_head: %ld\n", sizeof(fh));
// 打印BMP标识(应为"BM")
printf("type: %c%c\n", ((char *)&fh.bfType)[0], ((char *)&fh.bfType)[1]);
// 打印文件总大小、宽度、高度、每像素位数
printf("size: %d\n", fh.bfSize);
printf("width: %d\n", ih.biWidth);
printf("height: %d\n", ih.biHeight);
printf("bitcount: %d\n", ih.biBitCount);
// 关闭文件
close(fd);
return 0;
}
代码说明:
-
使用
#pragma pack(push, 1)和#pragma pack(pop)强制结构体按 1 字节对齐,避免编译器填充导致结构体大小错误。 -
依次读取 BMP 文件头和信息头,并打印关键字段。
-
理想输出示例:
sizeof file_head: 14 type: BM size: 1728054 width: 800 height: 600 bitcount: 24
4. BMP 显示优化:整块读取 + 坐标映射
问题:逐像素读取效率极低
原始方式(低效):
c
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
read(fd, &b, 1); // 每次读1字节,共需 width*height*3 次系统调用
read(fd, &g, 1);
read(fd, &r, 1);
draw_point(...);
}
}
- 对于 800×480 图像,需 1,152,000 次
read(),I/O 开销巨大,画面刷新卡顿。
优化方案:一次性读取全部像素数据
c
int color_size = ih.biwidth * ih.biheight * ih.bibitcount / 8;
unsigned char *data = malloc(color_size);
read(fd, data, color_size); // 1次系统调用读完所有像素
// 内存中顺序解析
unsigned char *p = data;
for (i = 0; i < ih.biheight; i++) {
for (j = 0; j < ih.biwidth; j++) {
c.col.b = *p++; // B
c.col.g = *p++; // G
c.col.r = *p++; // R
draw_point(j + x0, ih.biheight - i - 1 + y0, c.l);
}
}
free(data);
- 效果:画面刷新流畅,无逐行扫描卡顿。
坐标映射说明:
-
BMP 像素数据存储顺序:从左下角开始,逐行向上(底行 → 首行)。
-
屏幕坐标系:原点(0,0)在左上角。
-
映射公式:
cint screen_x = j + x0; // 横坐标正常偏移 int screen_y = (ih.biheight - i - 1) + y0; // 纵坐标翻转 + 偏移
5. 多照片显示动图效果
c
while(1)
{
int i = 0;
for(i = 0; i < 23; i++)
{
char path[100] = {0};
sprintf(path, "./1/0e2db04a70904fcb911ee50436982e12a4odeoSJdCTgJ07X-%d.bmp", i);
show_bmp(path, 100, 100);
usleep(200 * 1000);
}
}
代码说明:
- 循环读取 23 张 BMP 图片,依次显示在屏幕 (100,100) 位置。
- 每张图片显示 200ms(
usleep(200 * 1000))。 - 实现简单帧动画效果。
三、进度条控件(bar)设计与实现
1. 结构体定义
c
typedef struct __tag_bar
{
unsigned int x0;
unsigned int y0;
unsigned int width;
unsigned int height;
unsigned int border_width;
unsigned int border_color;
unsigned int bar_bg_color;
unsigned int bar_color;
unsigned int min_value;
unsigned int max_value;
unsigned int value;
unsigned int digit_color; // 新增:百分比数字的颜色
} bar_t;
2. 绘制逻辑
- 边框 :循环绘制多层矩形(层数 =
border_width)。 - 进度条 :
- 计算当前进度比例:
bar_len = (value - min) / (max - min) * (width - 2*border_width) - 逐列绘制竖线:
- 前
bar_len列:进度条颜色 - 剩余列:背景色
- 前
- 计算当前进度比例:
3. 完整功能演示(fb.c)
c
int main() {
fb_init();
screen_clear();
show_bmp("./2.bmp", 0, 0); // 显示背景图
lcd_draw_circle(400, 240, 100, 0xff0000); // 画圆
lcd_show_string(100, 100, 200, 50, 32, "hello", 0xff00ff); // 显示文字
// 创建并配置两个进度条
bar_t *b1 = create_bar();
bar_t *b2 = create_bar();
set_bar_pos(b2, 20, 400);
set_bar_size(b2, 600, 20);
set_bar_border(b2, 4, 0xc0c0c0); // 灰色边框
set_bar_range(b2, 0, 200);
set_bar_color(b2, 0xffffff); // 白色进度条
set_bar_bg_color(b2, 0); // 黑色背景
set_bar_digit_color(b2, 0); // 黑色数字
// 动态更新
int i = 0;
while(1) {
set_bar_value(b1, i);
set_bar_value(b2, 2*i);
bar_display(b1);
bar_display(b2);
usleep(100 * 1000);
if (++i > 100) i = 0;
}
// 清理资源
del_bar(b1); del_bar(b2);
fb_deinit();
return 0;
}
四、动态文本"重影"问题与解决方案
1. 问题引出
- 现象:在进度条上直接显示百分比数字(如 "50%")后,当进度值更新时,新数字会直接覆盖在旧数字之上,导致显示混乱("重影")。
- 低效方案:每次更新都清屏并重绘整个界面。效率极低,不适用于复杂UI。
- 正确思路 :采用局部背景恢复技术。
2. 屏幕背景备份与恢复机制
新增全局备份缓冲区
c
// framebuffer.c
static unsigned char * p_mem; // 帧缓冲映射地址
static unsigned char * p_bk; // 新增:背景备份缓冲区
int fb_init(void) {
// ... 初始化 p_mem ...
p_bk = malloc(fb_size); // 分配备份内存
return 0;
}
void fb_deinit(void) {
free(p_bk); // 释放备份内存
// ...
}
核心API
save_mem(void):在完成初始界面绘制后,调用此函数将当前屏幕内容完整备份到p_bk。recovery_mem(x0, y0, width, height):在绘制动态内容前,调用此函数将指定矩形区域从p_bk恢复到p_mem。
recovery_mem 实现
c
void recovery_mem(unsigned int x0, unsigned int y0, unsigned int width, unsigned int height) {
for(int i = 0; i < height; i++) {
unsigned char * p_src = p_bk + (info.xres_virtual * (y0 + i) + x0) * (info.bits_per_pixel / 8);
unsigned char * p_dst = p_mem + (info.xres_virtual * (y0 + i) + x0) * (info.bits_per_pixel / 8);
for(int j = 0; j < width * (info.bits_per_pixel / 8); j++) {
*p_dst++ = *p_src++;
}
}
}
3. 进度条控件增强(支持动态百分比)
c
void bar_display(bar_t * bar) {
// ... 绘制边框和进度条 ...
// 1. 格式化百分比字符串
char str[5] = {0};
int percentage = (int)((100.0 * (bar->value - bar->min_value)) / (bar->max_value - bar->min_value));
sprintf(str, "%d%%", percentage);
// 2. 恢复数字区域的背景 (关键步骤!)
recovery_mem(bar->x0, bar->y0 - 20, bar->width + 100, 20);
// 3. 在恢复后的干净区域上绘制新数字
lcd_show_string(
bar->x0 + bar_len, // X坐标:进度条当前长度处
bar->y0 - 20, // Y坐标:进度条上方
strlen(str) * 16, 50, // 宽高(足够容纳字符串)
16, // 字体大小
str, // 要显示的字符串
bar->digit_color // 颜色
);
}
使用示例
c
fb_init();
screen_clear();
show_bmp("./2.bmp", 0, 0); // 1. 绘制背景图
save_mem(); // 2. 保存此时的完整背景
// ... 其他静态UI绘制 ...
while(1) {
bar_display(b1);
bar_display(b2);
usleep(100 * 1000);
}
五、关键经验总结
- 内存对齐 是二进制文件解析的常见陷阱,务必使用
#pragma pack。 - 组件化设计 (如
bar_t)极大提升代码复用性与可维护性。 - 批量 I/O 是性能优化的核心手段,避免高频小数据读写。
- 坐标系转换(BMP vs 屏幕)需显式处理,避免图像倒置。
- 多实例支持:通过动态分配结构体,避免全局变量限制。
- 局部更新:通过背景备份与恢复,实现高效、无闪烁的动态UI更新,这是所有GUI系统的基础。
💡 关键提醒 :务必理解
save_mem和recovery_mem的调用时机。save_mem应在所有静态背景 绘制完成后调用一次;recovery_mem则应在每次绘制动态前景前调用。