第三階段應用層——1.8 數碼相冊—在LCD上顯示JPG圖片

數碼相冊——在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顯存

  1. 存儲圖像信息到顯存之前顯存(自行分配)與LCD控制器,通過設置構成一個映射關係(硬件自動完成),關係建立之後,LCD控制器有能力自動從顯存中讀取像素數據傳輸給LCD驅動器
  2. 在我們把圖像信息存儲到顯存後想要在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文件可以得到如下信息:
在這裏插入圖片描述

  1. 分配和初始化一個decompression結構體
	struct jpeg_decompress_struct cinfo;
	struct jpeg_error_mgr jerr;
	...
	cinfo.err = jpeg_std_error(&jerr);
	jpeg_create_decompress(&cinfo);
  1. 提定源文件
	FILE * infile;
	...
	if ((infile = fopen(filename, "rb")) == NULL) {
	    fprintf(stderr, "can't open %s\n", filename);
	    exit(1);
	}
	jpeg_stdio_src(&cinfo, infile);
  1. jpeg_read_header獲得jpg信息,信息存儲在cinfo這個結構體中的成員變量
	jpeg_read_header(&cinfo, TRUE);
  1. 設置解壓參數,比如放大、縮小
    對於這個參數,可以通過設置cinfo結構體的成員scale_num/scale_denom構成縮放因子,根據縮放因子的大小實現放大或縮小
    注意:縮放因子默認爲1/1,或者沒有縮放。目前,僅支持比例縮放M/8是M從1到16,還是M的簡化分數如1/2,3/4等)。

  2. 啓動解壓:jpeg_start_decompress

	jpeg_start_decompress(&cinfo);
  1. 循環調用jpeg_read_scanlines
	while (scan lines remain to be read)
		jpeg_read_scanlines(...);
  1. 完成解壓
	jpeg_finish_decompress(&cinfo);
  1. 釋放decompression結構體
	jpeg_destroy_decompress(&cinfo);

三、程序編寫

流程:

  1. 初始化LCD
  2. 清屏
  3. 分配和初始化一個decompression結構體
  4. 提定源文件
  5. jpeg_read_header獲得jpg信息並打印源信息
  6. 設置解壓參數,比如放大、縮小
  7. 啓動解壓
  8. 打印輸出圖片信息
  9. 計算輸出圖片一行數據的長度
  10. 分配顯存
  11. 循環調用jpeg_read_scanlines
    讀出輸出圖片每一行的信息並顯示在LCD中
  12. 釋放顯存
  13. 完成解壓
  14. 釋放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縮放時,部分縮放比例是達不到預期的效果
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章