day69 SQLite3動態庫移植 + BMP圖像解析顯示 + 進度條控件設計與動態文本管理


一、SQLite3 動態庫移植流程回顧與驗證

1. 移植步驟總結

  1. 下載源碼
  • 從官網或可信源獲取 SQLite3 源碼壓縮包(如 sqlite-autoconf-xxxxxx.tar.gz)。
  1. 解壓源碼
tar -zxvf sqlite-autoconf-xxxxxx.tar.gz
cd sqlite-autoconf-xxxxxx
  1. 配置編譯環境
    使用 ./configure 命令,關鍵參數如下:
./configure \
  CC=arm-linux-gnueabihf-gcc \      # 指定交叉編譯器
  --host=arm-linux \                # 指定目標平台
  --prefix=/your/absolute/path      # 指定安裝路徑(必須為絕對路徑!)

⚠️ 注意:--prefix 必須使用絕對路徑,否則 make install 無法正確部署。

  1. 編譯與安裝
make
make install
  • 生成的動態庫(如 libsqlite3.so)將被安裝到 --prefix 指定目錄下的 lib/ 中。
  1. 部署到開發板
  • 將生成的 libsqlite3.so 及其符號鏈接文件(共約4個相關文件)完整拷貝到開發板的 /lib/usr/lib
  • 重要:若開發板原已有 libsqlite3.so必須先刪除舊文件,避免衝突。
  1. 驗證移植結果
  • 編寫一個獨立的測試程序(不要直接在 SQLite 源碼目錄下運行示例),鏈接 -lsqlite3 並執行。
  • 若程序能正常運行,説明移植成功。

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. 準備測試圖片

  1. 任選一張圖片(如 JPG),用 Windows 畫圖 打開。
  2. 調整尺寸為 800×600 像素(或更小),且寬高均為 4 的整數倍
  3. 另存為 24 位 BMP 格式(如 test.bmp)。
  4. 拷貝到 Linux 虛擬機。

3. BMP 解析核心代碼(含內存對齊)

#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 顯示優化:整塊讀取 + 座標映射

問題:逐像素讀取效率極低

原始方式(低效):

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 開銷巨大,畫面刷新卡頓。
優化方案:一次性讀取全部像素數據
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)在左上角
  • 映射公式:
int screen_x = j + x0;                     // 橫座標正常偏移
int screen_y = (ih.biheight - i - 1) + y0; // 縱座標翻轉 + 偏移

5. 多照片顯示動圖效果

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. 結構體定義

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)。
  • 進度條
  1. 計算當前進度比例:
    bar_len = (value - min) / (max - min) * (width - 2*border_width)
  2. 逐列繪製豎線:
  • bar_len 列:進度條顏色
  • 剩餘列:背景色

3. 完整功能演示(fb.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. 屏幕背景備份與恢復機制

新增全局備份緩衝區
// 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 實現
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. 進度條控件增強(支持動態百分比)

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   // 顏色
    );
}
使用示例
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);
}

五、關鍵經驗總結

  1. 內存對齊是二進制文件解析的常見陷阱,務必使用 #pragma pack
  2. 組件化設計(如 bar_t)極大提升代碼複用性與可維護性。
  3. 批量 I/O 是性能優化的核心手段,避免高頻小數據讀寫。
  4. 座標系轉換(BMP vs 屏幕)需顯式處理,避免圖像倒置。
  5. 多實例支持:通過動態分配結構體,避免全局變量限制。
  6. 局部更新:通過背景備份與恢復,實現高效、無閃爍的動態UI更新,這是所有GUI系統的基礎。