MF99 coding 💻

keep learning; keep coding;

製作 fastboot 的 Boot logo

這陣子接到了新工作,繼上次製作並修改了 開機動畫(Boot animation) 之後,這次是修改 開機圖示(Boot Logo) 也就是一般剛開機的時候第一個會看到的靜態畫面,然後才是開機動畫。

簡單來說,Android系統的開機流程首先是 bootloader 啟動,然後是底層的 Linux kernel起,最後是 Android 的 Dalvik (Java VM,未來應該會換成 ART)

由於 bootloader 啟動很快,只是負責把預先設定好的東西載入到記憶體而已,所以基本上還來不及感受他的存在之後就結束了,然後由於啟動 Linux kernel 需要一點時間,而且很多基本的模組還沒有準備好,所以這時候就會顯示開機圖示。 接著開到 Android VM 的時候,由於 Linux 基本上已經開好了,所以就能夠運行比較複雜一點的開機動畫。

開機圖示跟動畫的畫面,有些廠商會做的不一樣,有些會做的像是一樣,不過有一點不會變得是:開機圖示會是靜態的畫面,而開機動畫則是會動的(類似GIF的感覺,就是簡單的連續畫面重複播放)。

由於 Linux 在啟動的時候,無法處理 File IO 以及 image decode 這種複雜動作,所以其實開機圖示其實並不是一張 PNG 圖放進去叫他秀在螢幕上這麼單純,所以這邊就記錄一下如何從一張圖製作開機的 Logo。

這邊先說明一下,由於顯示 Boot logo 的部份是 BSP 的開機流程中的某一個程式來負責,所以基本上在各個硬體平台上的實作方式會不太一樣,放的位置也不太一樣,但是基本上最終結果應該都是把圖檔用 ARGB 的格式, 然後把 raw data 儲存在某個 header 檔,然後再透過開機流程的程式去讀取,並顯示。

以下是以 nVidia 平台為例子,各家平台可能會有些許差異,但是本文最後產出的 byte array 應該是都通用的。

轉換過程的步驟如下:

1. 將原始圖檔(假設為 PNG)先行上下翻轉
2. 將圖檔轉換成 32bits (ARGB8888) 格式的 BMP 檔
3. 移除 BMP 檔的 header,讓他變成純 raw file
4. 將 raw file 轉成16進位的 C header 格式
5. 將16進位的資料中的 ARGB 分離
6. 最後生成所需要的 byte array

1. 將原始圖檔(假設為 PNG)先行上下翻轉

這部份至少NV的平台上需要,猜想是因為他畫圖的座標系是從左下到右上 (原點在左下角),跟一般上層的繪圖座標系(原點在右上角)剛好上下相反,所以在讀取資料並依序畫圖的時候,會從左下角開始畫,所以必須要把原始圖檔先上下翻轉,否則畫出來的結果會跟預期的上下顛倒。

Note. 這部份是 NV 平台的限制,其他硬體平台不一定要做這個動作。

2. 將圖檔轉換成 32bits (ARGB8888) 格式的 BMP

基本上一般的繪圖軟體應該都能夠輸出,或是另存成 BMP檔,但是不見得每個軟體都能明確的指定格式。 由於 BMP 檔區有著以下格式:

  • 16bits: RGB565 (R=5bit, G=6bit, B=5bit)
  • 24bits: RGB888
  • 32bits: ARGB8888 (XRGB8888)

而我們這邊需要的是 ARGB8888 的格式,如果你不確定你的繪圖軟體是否能正確的輸出到正確的格式,或是根本沒有選項的話,這邊提供一個 Open source 的工具可以把 PNG 轉成 32bit 的 BMP 格式。 PNGtoBMP32 (雖然直接下載的 binary 是 Windows 的,但是他有提供 open source,如果有需要也可以自行編譯)

3. 移除 BMP 檔的 header,讓他變成純 raw file

由於檔案本身還是有他自己的 header,裡面儲存一些給 OS 使用的 metadata,而這些資訊是我們不需要的,幸好 BMP 的檔案 header 長度固定為 54 bytes,所以可以透過下面的指令來去除。

$ dd if=in.bmp of=out.raw bs=1 skip=54

4. 將 raw file 轉成16進位的 C header 格式

接著利用 xxd 指令,把 raw file 轉換成 16進位的 C header。

$ xxd -i out.raw > converter.h

5. 將16進位的資料中的 ARGB 分離

這部份請先參考以下程式碼

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "converter.h"
#define OUT_FILE "out.txt"

int main() {
    int count = 0;
    int i = 0;
    unsigned char slot[4];
    unsigned char last[4];
    int entries = 0;
    FILE *out = fopen(OUT_FILE, "w+");

    /* Process beginning 3 bytes first */
    memcpy(last, out_raw, sizeof(last));
    count = 1;
    i = 4;

    while (i < out_raw_len) {
        memcpy(slot, out_raw + i, sizeof(slot));
        if ((memcmp(last, slot, sizeof(last)) == 0) && count < 65535) {
            ++count;
        } else {
            fprintf(out, "0x%.4x, 0x%.2x, 0x%.2x, 0x%.2x, 0x%.2x,",
                count,
                last[0],
                last[1],
                last[2],
                last[3]);
            count = 1;
            memcpy(last, slot, sizeof(last));
            ++entries;

            if (entries == 4) {
                fprintf(out, "\n");
                entries = 0;
            } else
                fprintf(out, " ");
        }
        i += 4;
    }
    fprintf(out, "\n");
    fclose(out);
    return 0;
}

基本上這是一個小工具程式,可以把剛剛產出的 converter.h 裡面的資料中的 ARGB 分離,並且加上一些所需的資訊並以16進位的格式輸出。 關鍵的地方在輸出的部份

fprintf(out, "0x%.4x, 0x%.2x, 0x%.2x, 0x%.2x, 0x%.2x,",
                count,
                last[0],
                last[1],
                last[2],
                last[3]);

在這裡由於 NV 平台的需求,這裡的 array 格式為 {a0, a1, a2, a3, a4}

a0: 要畫的pixel的數目,長為2個byte
a1: B
a2: G
a3: R
a4: A

所以如果您的實作硬體平台的格式不同的話,基本上只要調整輸出的這部份就可以了。

接著就直接編譯、執行這個 converter.c

$ gcc converter.c

$ ./a.out

6. 最後生成所需要的 byte array

接下來是 NV 平台相關的部份,由於真正所需的 C header 需要一些其他資訊,所以這個產出的 out.txt 主要也是方便計算這些資訊,所以還不算是最終可以使用的版本。 NV平台除了 byte array 之外,還需要以下資訊:

  • width: 圖片的寬
  • height: 圖片的高
  • stride: 圖片的寬度*4
  • byte: 這邊就要計算整個 array 的 byte 數,這部份剛才的 out.txt 中已經擺好了位置,每一行都是4個 Pixel,而每個 Pixel 的格式為 0x004d, 0x00, 0x00, 0x00, 0xff,所以就是6個 byte。 所以基本上完整一行就是 24 bytes,所以基本上只要計算 out.txt 中包含完整 4 Pixel 的行數,加上最後零散的 byte 數,就會是總 byte 數。 假設 out.txt 有3521行,但是最後一行只有2個 Pixel,所以總 byte 數就是 (3520*24 + 2*6) = 84492
  • bpp: 固定為24
  • data[]: 這邊就是要貼上完整的 byte array

由於剛才的 out.txt為了計算 byte 方便,有故意在每4個 Pixel 的部份斷行,所以要先透過下面的指令去除斷行後再貼過來

$ cat out.txt | tr '\n' ' ' > out.h

這篇有點又臭又長,而且又很多是跟硬體平台相關的東西,本來實在是不太想寫,但是想到這個過程有點複雜,而且未來用到的機會或許還不小,所以就還是耐著性子把它寫出來了。