第三阶段应用层——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缩放时,部分缩放比例是达不到预期的效果
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章