LCD螢幕顯示PNG影像

cyMessi發表於2024-09-01

正點原子LCD螢幕顯示PNG影像

本文概要

這段時間在學習正點原子的IMX6ULL開發板,在應用程式設計中有一個程式碼練習是需要在LCD螢幕上顯示PNG影像,但由於我的螢幕引數和教程中的有些出入,於是經過自己查閱和修改,終於成功在自己的LCD螢幕上顯示PNG影像。

LCD 螢幕引數

我的LCD引數如下所示:

這裡面出現一個問題,我的螢幕引數顯示的是32位,但是實際上RGBA的位數只有24位。那麼多出來的8位是什麼呢?起初我以為是透明度A,但是引數顯示的是透明度所佔位數為0。

後面我猜測這8位可能不是RGBA中的一種,而是用來資料對齊的一種方式。由於和我們的程式碼主體無關,所以後面也就不管它了。

總而言之,這幾個引數就是LCD顯示中最重要的引數:

螢幕解析度:1024*600

畫素深度:32位

畫素格式:RGB888

程式碼流程

zlib和libpng庫

zlib和libpng庫是PNG影像的解析庫,在Linux系統中直接下載原始碼然後編譯移植到開發板檔案系統即可。

zlib庫下載地址: https://www.zlib.net/fossils/

libpng庫下載地址:https://github.com/glennrp/libpng/releases

其中libpng也有幫助文件:http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf

這裡我使用的是zlib-1.2.10 和 libpng-1.6.35,版本根據自己的需求選擇。

解析影像流程

使用libpng庫解析影像的流程基本是固定的,無非就是:

  1. 獲取檔案描述符:開啟PNG影像檔案獲取檔案描述符

  2. 解碼物件:使用png_create_read_struct函式建立png_structp結構體指標,即為解碼物件。解碼物件在後續操作中就相當於一個控制代碼的作用;

  3. 影像資訊物件:使用png_create_info_struct函式建立png_infop結構體指標,即為影像資訊物件,用於儲存影像的寬、高等屬性;

  4. 錯誤處理:使用setjmp(png_jmpbuf(png_ptr))設定錯誤處理,這裡setjmp函式的作用就是設定錯誤跳點,當傳送錯誤時就跳到這裡來執行後續函式;

  5. 指定資料來源:使用png_init_io函式設定資料來源,表明我們要解碼的就是這個影像檔案;

  6. 讀取影像資訊:這裡使用png_read_png()函式來直接讀取PNG影像資訊,這個函式會自動開闢一個記憶體空間用來儲存影像資訊。因為這個記憶體資訊和png_structp結構體物件繫結了,所以後續可以用png_get_rows()函式來獲取該記憶體地址;

  7. 獲取影像資料: 使用png_get_rows()函式獲取影像資料,並返回一個png_bytep型別的指標,指向的是影像資料中每一行的地址,所以該指標指向的是一個陣列,陣列每個元素是影像資料中一行的資料,元素大小為image_width*bytes_per_pixel/8

  8. 轉換為RGB888格式:這裡外層迴圈的作用是遍歷影像資料的每一行,記憶體迴圈的作用是遍歷影像資料的每一列。並且用指標png_bytep px指向影像資料中每3個資料為一組的地址,指標unsigned char *dst 指向影像視訊記憶體地址中每4個資料為一組的地址,然後對RGB進行資料轉換,這裡為什麼是將RGB的順序互換,我也不太清楚,可能與圖片原先的RGB格式有關,反正只要轉換為LCD螢幕的RGB格式就好;

點選檢視程式碼
/* 將24位RGB影像轉換為32位RGB888格式 */
for (int y = 0; y < image_height; y++) {
    for (int x = 0; x < image_width; x++) {
        png_bytep px = &(row_pointers[y][x * 3]);
        unsigned char *dst = &image_data[(y * image_width + x) * 4];
        dst[0] = px[2]; // R
        dst[1] = px[1]; // G
        dst[2] = px[0]; // B
        dst[3] = 0xFF; // A (Alpha channel set to 255)
    }
}
  1. 分配記憶體並寫入視訊記憶體:使用malloc()函式分配記憶體,並使用memcpy()函式將影像資料寫入記憶體中;
點選檢視程式碼
/* 將影像資料寫入視訊記憶體 */
for (int y = 0; y < image_height && y < screen_height; y++) {
    memcpy(screen_base + y * line_length, image_data + y * image_width * 4, image_width * 4);
}
  1. 釋放記憶體:使用free()函式釋放記憶體。

最終顯示效果

顯示效果如下:

總結與對比

相較於教程的程式碼流程,卡住我的主要就是理解我的LCD螢幕為32位的含義所在,不是RGBA,而是有8位作為資料對齊。以及還有轉化為RGB888格式的程式碼,但是這個我是交給Copilot自動生成了,在這裡狠狠安利一波Copilot!!

其實理解了程式碼的含義也不難,最重要的就是掌握好libpng庫的使用框架和API,當然還有影像的儲存方式:影像的image_width和image_height指的是影像的寬和高分別有多少個畫素點,而影像的儲存是按行優先排列的,所以用png_get_rows()函式獲取的是指向影像資料一行一行地址的指標,其實可以當作是一個二維矩陣來看,所以透過兩層迴圈也就可以遍歷影像資料了。

相關文章