數碼相冊——在LCD上顯示JPG圖片
- 硬件平臺:韋東山嵌入式Linxu開發板(S3C2440.v3)
- 軟件平臺:運行於VMware Workstation 12 Player下UbuntuLTS16.04_x64 系統
- 參考資料:《嵌入式Linux應用開發手冊》、《嵌入式Linux應用開發手冊第2版》
- 開發環境:Linux 3.4.2內核、arm-linux-gcc 4.3.2工具鏈
目錄
一、如何在LCD上顯示一張圖片
對於一張JPG圖片,實際上是由多個不同顏色的像素點所組成的,對於LCD實際上也是由多個像素點所組成的,當我們得到JPG圖片上的每個像素的顏色信息,按照行列(在內存中表示爲二維數組)的方式存儲在一塊內存空間中,這個內存空間我們稱之爲Framebuffer
顯存。
- 在存儲圖像信息到顯存之前,顯存(自行分配)與LCD控制器,通過設置構成一個映射關係(硬件自動完成),關係建立之後,LCD控制器就有能力自動從顯存中讀取像素數據傳輸給LCD驅動器。
- 在我們把圖像信息存儲到顯存後想要在LCD上顯示出來,我們只關心在軟件方面實現LCD上的像素信息的提取與顯示,圖片信息的規劃等等。
對於一個JPG圖片,當我們使用二進制軟件打開它時,會發現實際上就是一個二進制文件,只是通過了一定的格式顯示爲圖片。
也就是說,實際上JPG圖片是通過二進制存儲信息,具體可以看這個博客【jpeg圖片格式詳解】。當我們把這些而二進制信息解析出來時,把所需要的RGB數據發送給LCD,就可以顯示一張JPG圖片了。
那麼如何解析呢?對於寫應用程序的我們,肯定需要使用一些開源的庫來對JPEG文件進行處理,解壓出我們所需要的原始RGB數據,比較常用的就是libjpeg-turbo
了。
二、libjpeg-turbo
的介紹與使用
通過查看官方資料可知:
1、介紹
libjpeg-turbo
是JPEG圖像編解碼器,它使用SIMD指令(MMX,SSE2,AVX2,NEON,AltiVec)來加速x86,x86-64,ARM和PowerPC系統上的基線JPEG壓縮和解壓縮,以及在x86,x86-64,ARM和PowerPC系統上的漸進JPEG壓縮x86和x86-64系統。在這樣的系統上, libjpeg-turbo
通常是 libjpeg
的 2-6倍,其他所有條件都相同。在其他類型的系統上,憑藉其高度優化的霍夫曼編碼例程, libjpeg-turbo仍然可以大大優於libjpeg。
2、使用
閱讀官方libjpeg.txt
文件可以得到如下信息:
- 分配和初始化一個
decompression
結構體
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
...
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
- 提定源文件
FILE * infile;
...
if ((infile = fopen(filename, "rb")) == NULL) {
fprintf(stderr, "can't open %s\n", filename);
exit(1);
}
jpeg_stdio_src(&cinfo, infile);
- 用
jpeg_read_header
獲得jpg信息,信息存儲在cinfo
這個結構體中的成員變量中
jpeg_read_header(&cinfo, TRUE);
-
設置解壓參數,比如放大、縮小
對於這個參數,可以通過設置cinfo
結構體的成員scale_num/scale_denom
構成縮放因子,根據縮放因子的大小實現放大或縮小。
注意:縮放因子默認爲1/1,或者沒有縮放。目前,僅支持比例縮放M/8是M從1到16,還是M的簡化分數如1/2,3/4等)。 -
啓動解壓:
jpeg_start_decompress
jpeg_start_decompress(&cinfo);
- 循環調用
jpeg_read_scanlines
while (scan lines remain to be read)
jpeg_read_scanlines(...);
- 完成解壓
jpeg_finish_decompress(&cinfo);
- 釋放
decompression
結構體
jpeg_destroy_decompress(&cinfo);
三、程序編寫
流程:
- 初始化LCD
- 清屏
- 分配和初始化一個
decompression
結構體 - 提定源文件
- 用
jpeg_read_header
獲得jpg信息並打印源信息 - 設置解壓參數,比如放大、縮小
- 啓動解壓
- 打印輸出圖片信息
- 計算輸出圖片一行數據的長度
- 分配顯存
- 循環調用
jpeg_read_scanlines
讀出輸出圖片每一行的信息並顯示在LCD中 - 釋放顯存
- 完成解壓
- 釋放
decompression
結構體
/**
* @file show_jpg2rgb.c
* @brief 把jpg格式的文件使用libjpeg庫解壓出原始的rgb信息
* @version 1.0 (版本聲明)
* @author Dk
* @date July 6,2020
*/
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include "jpeglib.h"
#define FB_DEVICE_NAME "/dev/fb0"
#define DBG_PRINTF printf
static int s_FBFD;
static int s_ScreenSize;
static int s_LineWidth;
static int s_PixelWidth;
static struct fb_var_screeninfo s_tVar;
static struct fb_fix_screeninfo s_tFix;
static unsigned char *s_pFbmem;
/**
* @Description: LCD設備初始化函數
* @return 0 - 初始化成功, -1 - 初始化失敗
*/
static int FBDeviceInit(void)
{
/* 打開設備:支持讀寫 */
s_FBFD = open(FB_DEVICE_NAME, O_RDWR);
if (s_FBFD < 0) {
DBG_PRINTF("can not open %s , err code :%d\n", FB_DEVICE_NAME, s_FBFD);
return -1;
}
/* 獲得可變信息 */
if (ioctl(s_FBFD, FBIOGET_VSCREENINFO, &s_tVar) < 0) {
DBG_PRINTF("can not get s_tVar\n");
return -1;
}
/* 獲得固定信息 */
if (ioctl(s_FBFD, FBIOGET_FSCREENINFO, &s_tFix) < 0) {
DBG_PRINTF("can not get s_tVar\n");
return -1;
}
s_ScreenSize = s_tVar.xres * s_tVar.yres * s_tVar.bits_per_pixel / 8; // 屏幕總像素所佔的字節數
s_LineWidth = s_tVar.xres * s_tVar.bits_per_pixel / 8; // 每行像素所佔的字節數
s_PixelWidth = s_tVar.bits_per_pixel / 8; // 每個像素所佔字節數
/* 直接映射到內存的Framebuffer */
s_pFbmem = (unsigned char *)mmap(NULL, s_ScreenSize, PROT_READ | PROT_WRITE, MAP_SHARED, s_FBFD, 0);
if (s_pFbmem == (unsigned char *)-1) {
DBG_PRINTF("can not mmap\n");
return -1;
}
return 0;
}
/**
* @Description: LCD顯示像素函數,根據傳入的信息,在(PenX, PenY)位置顯示Color
* @param PenX - 像素X座標, PenY - 像素Y座標, Color - 像素RGB值
* @return 0 - 成功, -1 - 不支持該bpp
*/
static int FBShowPixel(int PenX, int PenY, unsigned int Color)
{
unsigned char *pPen8;
unsigned short *pPen16;
unsigned int *pPen32;
unsigned int red, green, blue;
if ((PenX >= s_tVar.xres) || (PenY >= s_tVar.yres))
{
DBG_PRINTF("out of region\n");
return -1;
}
/* 該座標在內存中對應像素的位置 */
pPen8 = s_pFbmem + PenY * s_LineWidth + PenX * s_PixelWidth;
pPen16 = (unsigned short *)pPen8;
pPen32 = (unsigned int *)pPen8;
switch (s_tVar.bits_per_pixel) {
case 8:
*pPen8 = (unsigned char)Color;
break;
case 16:
/* RGB:565 */
red = ((Color >> 16) & 0xff) >> 3;
green = ((Color >> 8 ) & 0xff) >> 2;
blue = ((Color >> 0 ) & 0xff) >> 3;
*pPen16 = (red << 11) | (green << 5) | blue;
break;
case 32:
*pPen32 = Color;
break;
default:
DBG_PRINTF("can not surport %d bpp\n", s_tVar.bits_per_pixel);
return -1;
}
return 0;
}
/**
* @Description: 清屏函數,整個屏幕顯示BackColor
* @param BackColor - 所要顯示的顏色RGB值
* @return 0 - 成功, -1 - 不支持該bpp
*/
static int FBCleanScreen(unsigned int BackColor)
{
int i;
unsigned char *pPen8;
unsigned short *pPen16;
unsigned int *pPen32;
unsigned int red, green, blue;
pPen8 = s_pFbmem;
pPen16 = (unsigned short *)pPen8;
pPen32 = (unsigned int *)pPen8;
switch (s_tVar.bits_per_pixel) {
case 8:
memset(pPen8, BackColor, s_ScreenSize);
break;
case 16:
/* RGB:565 */
red = ((BackColor >> 16) & 0xffff) >> 3;
green = ((BackColor >> 8 ) & 0xffff) >> 2;
blue = ((BackColor >> 0 ) & 0xffff) >> 3;
BackColor = (red << 11) | (green << 5) | blue;
for (i = 0; i < s_ScreenSize;) {
*pPen16 = BackColor;
pPen16++;
i += 2;
}
break;
case 32:
for (i = 0; i < s_ScreenSize;) {
*pPen32 = BackColor;
pPen32++;
i += 4;
}
break;
default:
DBG_PRINTF("can not surport %dbpp\n", s_tVar.bits_per_pixel);
return -1;
}
return 0;
}
/**
* @Description: LCD顯示一行的像素,在y行,x座標範圍爲xStart~xEnd,顯示pRGBArray
* @param xStart - LCD開始顯示位置, xEnd - 結束位置, y - 行, pRGBArray - 存儲一行像素RGB的數組,
* @return 0 - 顯示成功,-1 - 顯示失敗
*/
static int FBShowLine(int xStart, int xEnd, int y, unsigned char *pRGBArray)
{
int i;
int x;
unsigned int color;
if (y >= s_tVar.yres)
return -1;
if (xStart >= s_tVar.xres)
return -1;
if (xEnd >= s_tVar.xres)
xEnd = s_tVar.xres;
i = xStart * 3; //偏移量,每個顏色在pRGBArray在佔據3個字節
for (x = xStart; x < xEnd; x++) {
/* 0xRRGGBB */
color = (pRGBArray[i] << 16) + (pRGBArray[i+1] << 8) + (pRGBArray[i+2] << 0);
i += 3;
FBShowPixel(x, y, color);
}
return 0;
}
int main(int argc, char **argv)
{
int row_stride;
unsigned char *buffer;
FILE *infile;
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
if (argc != 2) {
DBG_PRINTF("Usage: \n");
DBG_PRINTF("%s <jpg_file>\n", argv[0]);
return -1;
}
/* 初始化LCD */
if (FBDeviceInit()) {
DBG_PRINTF("err FBDeviceInit()\n");
return -1;
}
/* 清屏 */
FBCleanScreen(0);
/* 分配和初始化一個decompression結構體 */
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
/* 指定源文件 */
if ((infile = fopen(argv[1], "rb")) == NULL) {
fprintf(stderr, "can't open %s\n", argv[1]);
return -1;
}
jpeg_stdio_src(&cinfo, infile);
/* 用jpeg_read_header獲得jpg信息 */
jpeg_read_header(&cinfo, TRUE);
/* 打印源信息 */
printf("source image_width :%d\n", cinfo.image_width);
printf("source image_height:%d\n", cinfo.image_height);
printf("source num_components:%d\n", cinfo.num_components);
/* 設置解壓參數,比如放大、縮小
* 縮放因子:scale_num/scale_denom = 1/2
* 範圍:M/8 with all M from 1 to 16
*/
printf("enter M/N\n");
scanf("%d/%d", &cinfo.scale_num, &cinfo.scale_denom);
printf("scale to: %d/%d\n", cinfo.scale_num, cinfo.scale_denom);
/* 啓動解壓 */
jpeg_start_decompress(&cinfo);
/* 打印輸出信息 */
printf("out image_width :%d\n", cinfo.output_width);
printf("out image_height:%d\n", cinfo.output_height);
printf("out num_components:%d\n", cinfo.output_components);
row_stride = cinfo.output_width * cinfo.output_components; //一行的數據長度
/* 分配一個buffer */
buffer = malloc(row_stride);
if (buffer == NULL) {
DBG_PRINTF("buffer malloc err\n");
return -1;
}
/* 循環調用jpeg_read_scanlines,一行一行的獲得解壓數據 */
while (cinfo.output_scanline < cinfo.output_height) {
(void)jpeg_read_scanlines(&cinfo, &buffer, 1);
/* 寫到LCD中 */
FBShowLine(0, cinfo.image_width, cinfo.output_scanline, buffer);
}
/* 釋放空間 */
free(buffer);
/* 完成解壓 */
jpeg_finish_decompress(&cinfo);
/* 釋放decompression結構體 */
jpeg_destroy_decompress(&cinfo);
return 0;
}
四、編譯與運行
1、前期準備
1.1 解壓libjpeg庫
執行tar xzf libjpeg-turbo-1.2.1.tar.gz
1.2 配置
進入到/libjpeg-turbo-1.2.1
目錄下,執行mkdir tmp
新建一個文件夾
執行./configure --prefix=/work/tools/libjpeg-turbo-1.2.1/tmp/ --host=arm-linux
,設置安裝目錄
1.3 安裝庫
執行make
執行make install
1.4 移動頭文件和庫文件到交叉編譯工具鏈對應文件夾中
安裝好的libjpeg需要把它的/include和/lib
目錄下的所有文件,分別移動到/4.3.2/arm-none-linux-gnueabi/libc/usr/include
和/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib
先進入到/tmp
目錄下
命令如下:
- 移動
到/lib
目錄文件,執行sudo cp *so* /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib -d
- 移動
/include
目錄文件,執行sudo cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include -d
1.5 移動庫文件的.so
文件到開發板根文件系統的/lib
目錄下
先進入到/tmp/
目錄下
命令如下:
- 移動到
/lib
目錄文件,執行cp *so* /work/nfs_root/fs_mini_mdev_new/lib -d
2、編譯運行
2.1 交叉編譯jpg2rgb.c
文件
執行arm-linux-gcc -o show_jpg2rgb show_jpg2rgb.c -ljpeg
,生成可執行文件show_jpg2rgb
2.2 把可執行文件和.jpe
圖片文件傳輸到開發板的根文件系統中
2.3 執行
顯示圖片的原始比例是800 × 600,LCD分辨率480 × 272
執行./show_jpg2rgb 1.jpg
,後根據提示信息輸入縮放因子
- 縮放因子1/1
- 縮放因子1/2
- 縮放因子1/3
- 縮放因子1/4
- 縮放因子1/8
- 縮放因子15/8
通過顯示的情況與源數據和輸出數據可以瞭解到,使用libjpeg-turbo
縮放時,部分縮放比例是達不到預期的效果。