正点原子 发表于 2021-8-25 15:40:35

《I.MX6U嵌入式Linux C应用编程指南》第二十一章 在LCD上显示jpeg

1)实验平台:正点原子i.MX6ULL Linux阿尔法开发板
2)章节摘自【正点原子】I.MX6U嵌入式Linux C应用编程指南
3)购买链接:https://item.taobao.com/item.htm?&id=603672744434
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyz-i.mx6ull.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子Linux技术交流群:1027879335







第二十一章 在LCD上显示jpeg图像
       我们常用的图片格式有很多,一般最常用的有三种:JPEG(或JPG)、PNG、BMP。上一章给大家介绍了如何在LCD上显示BMP图片,详细介绍了BMP图像的格式;BMP图像虽然没有失真、并且解析简单,但是由于图像数据没有进行任何压缩,因此,BMP图像文件所占用的存储空间很大,不适合存储在磁盘设备中。
而JPEG(或JPG)、PNG则是经过压缩处理的图像格式,将图像数据进行压缩编码,大大降低了图像文件的大小,适合存储在磁盘设备中,所以很常用。本章我们就来学习如何在LCD屏上显示jpeg图像,下一章将向大家介绍如何在LCD屏上显示png图像。
本章将会讨论如下主题。
JPEG简介;
libjpeg库简介;
libjpeg库移植;
使用libjpeg库函数对JPEG图像进行解码;

1.1JPEG简介
       JPEG(Joint Photographic Experts Group)是由国际标准组织为静态图像所建立的第一个国际数字图像压缩标准,也是至今一直在使用的、应用最广的图像压缩标准。
       JPEG由于可以提供有损压缩,因此压缩比可以达到其他传统压缩算法无法比拟的程度;JPEG虽然是有损压缩,但这个损失的部分是人的视觉不容易察觉到的部分,它充分利用了人眼对计算机色彩中的高频信息部分不敏感的特点,来大大节省了需要处理的数据信息。
       JPEG压缩文件通常以.jpg或.jpeg作为文件后缀名,关于JPEG压缩标准就给大家介绍这么多,这些内容都是笔者从网络上截取下来的,对此感兴趣的读者可以自行从网络上查阅这些信息。
1.2libjpeg简介
       JPEG压缩标准使用了一套压缩算法对原始图像数据进行了压缩得到.jpg或.jpeg图像文件,如果想要在LCD上显示.jpg或.jpeg图像文件,则需要对其进行解压缩、以得到图像的原始数据,譬如RGB数据。
       既然压缩过程使用了算法,那对.jpg或.jpeg图像文件进行解压同样也需要算法来处理,当然,笔者并不会教大家如何编写解压算法,这些算法的实现也是很复杂的,笔者肯定不会,自然教不了大家!但是,我们可以使用别人写好的库、调用别人写好的库函数来解压.jpg或.jpeg图像文件,也就是本小节要向大家介绍的libjpeg库。
       libjpeg是一个完全用C语言编写的函数库,包含了JPEG解码(解压缩)、JPEG编码(创建压缩)和其他的JPEG功能的实现。可以使用libjpeg库对.jpg或.jpeg压缩文件进行解压或者生成.jpg或.jpeg压缩文件。
libjpeg是一个开源C语言库,我们获取到它的源代码。
1.3libjpeg移植
1.3.1下载源码包
首先,打开http://www.ijg.org/files/链接地址,如下所示:

图 21.3.1 libjpeg下载链接页面
       目前最新的一个版本是v9d,对应的年份为2020年,这里我们选择一个适中的版本,笔者以v9b为例,对应的文件名为jpegsrc.v9b.tar.gz,点击该文件即可下载。
       其实开发板出厂系统中已经移植了libjpeg库,但是版本太旧了!所以这里我们选择重新移植。
下载后如下所示:

图 21.3.2 jpegsrc.v9b.tar.gz源码包
1.3.2编译源码
       将jpegsrc.v9b.tar.gz压缩包文件拷贝到Ubuntu系统用户家目录下,如下所示:

图 21.3.3 将压缩包文件拷贝到Ubuntu系统
执行命令解压:
tar -xzf jpegsrc.v9b.tar.gz

图 21.3.4 将jpegsrc.v9b.tar.gz文件解压
       解压成功之后会生成jpeg-9b文件夹,也就是libjpeg源码文件夹。
       编译之前,在家目录下的tools文件夹中创建一个名为jpeg的文件夹,该目录作为libjpeg库的安装目录:

图 21.3.5 创建jpeg文件夹
       回到家目录下,进入到libjpeg源码目录jpeg-9b中,该目录下包含的内容如下所示:

图 21.3.6 libjpeg源码目录下的文件
       接下来对libjpeg源码进行交叉编译,跟编译tslib时步骤一样,包含三个步骤:
       配置工程;
       编译工程;
       安装;
       一套流程下来非常地快!没有任何难点。在此之前,先对交叉编译工具的环境进行初始化,使用source执行交叉编译工具安装目录下的environment-setup-cortexa7hf-neon-poky-linux-gnueabi脚本文件(如果已经初始化过了,那就不用再进行初始化了):
source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
执行下面这条命令对libjpeg工程进行配置:
./configure --host=arm-poky-linux-gnueabi --prefix=/home/dt/tools/jpeg/
       大家可以执行./configure --help查看它的配置选项以及含义,--host选项用于指定交叉编译得到的库文件是运行在哪个平台,通常将--host设置为交叉编译器名称的前缀,譬如arm-poky-linux-gnueabi-gcc前缀就是arm-poky-linux-gnueabi;--prefix选项则用于指定库文件的安装路径,将家目录下的tools/jpeg目录作为libjpeg的安装目录。

图 21.3.7 配置工程
接着执行make命令编译工程:
make

图 21.3.8 编译工程
编译完成之后,执行命令安装libjpeg:
make install

图 21.3.9 安装
命令执行完成之后,我们的libjpeg也就安装成功了!
1.3.3安装目录下的文件夹介绍
进入到libjpeg安装目录:

图 21.3.10 安装目录下的文件夹
与tslib库安装目录下的包含的文件夹基本相同(除了没有etc目录),bin目录下包含一些测试工具;include目录下包含头文件;lib目录下包含动态链接库文件。
进入到include目录下:

图 21.3.11 头文件
在这个目录下包含了4个头文件,在应用程序中,我们只需包含jpeglib.h头文件即可!
进入到lib目录下:

图 21.3.12 库文件
libjpeg.so和libjpeg.so.9都是符号链接,指向libjpeg.so.9.2.0。
1.3.4移植到开发板
开发板出厂系统已经移植了libjpeg库,前面给大家提到过,只是移植的版本太低了,所以这里不打算使用出厂系统移植的libjpeg库,而使用21.3.2小节交叉编译好的libjpeg库。
进入到libjpeg安装目录下,将bin目录下的所有测试工具拷贝到开发板Linux系统/usr/bin目录;将lib目录下的所有库文件拷贝到开发板Linux系统/usr/lib目录。
拷贝lib目录下的库文件时,需要注意符号链接的问题,不能破坏原有的符号链接;可以将lib目录下的所有文件打包成压缩包的形式,譬如进入到lib目录,执行命令:
tar -czf lib.tar.gz ./*

图 21.3.13 将lib目录下的所有文件打包
再将lib.tar.gz压缩文件拷贝到开发板Linux的用户家目录下,在解压之前,将开发板出厂系统中已经移植的libjpeg库删除,执行命令:
rm -rf /usr/lib/libjpeg.*

图 21.3.14 删除出厂系统原有的libjpeg库
Tips:注意!当出厂系统原有的libjpeg库被删除后,将会导致开发板下次启动后,出厂系统的Qt GUI应用程序会出现一些问题,原本显示图片的位置变成了空白,显示不出来了!原因在于Qt程序处理图片(对jpeg图片解码)时,它的底层使用到了libjpeg库,而现在我们将出厂系统原有的libjpeg库给删除了,自然就会导致Qt GUI应用程序中图片显示不出来(无法对jpeg图片进行解码)!
这个跟具体的libjpeg版本绑定起来的,即使我们将21.3.2小节编译得到的库文件拷贝到/usr/lib目录下,也是无济于事,因为版本不同,这里大家知道就行。
接着我们将lib.tar.gz压缩文件解压到开发板Linux系统/usr/lib目录下:
tar -xzf lib.tar.gz -C /usr/lib

图 21.3.15 将lib.tar.gz解压到/usr/lib目录
解压成功之后,接着执行libjpeg提供的测试工具,看看我们移植成功没:
djpeg --help

图 21.3.16 测试libjpeg移植是否成功
djpeg是编译libjpeg源码得到的测试工具(在libjpeg安装目录下的lib目录中),当执行命令之后,能够成功打印出这些信息就表示我们的移植成功了!
1.4libjpeg使用说明
libjpeg提供JPEG解码、JPEG编码和其他的JPEG功能的实现,本小节我们只给大家介绍如何使用libjpeg提供的库函数对.jpg/.jpeg进行解码(解压),得到RGB数据。
首先,使用libjpeg库需要在我们的应用程序中包含它的头文件jpeglib.h,该头文件包含了一些结构体数据结构以及API接口的申明。先来看看解码操作的过程:
⑴、创建jpeg解码对象;
⑵、指定解码数据源;
⑶、读取图像信息;
⑷、设置解码参数;
⑸、开始解码;
⑹、读取解码后的数据;
⑺、解码完毕;
⑻、释放/销毁解码对象。
以上便是整个解码操作的过程,用libjpeg库解码jpeg数据的时候,最重要的一个数据结构为struct jpeg_decompress_struct结构体,该数据结构记录着jpeg数据的详细信息,也保存着解码之后输出数据的详细信息。除此之外,还需要定义一个用于处理错误的对象,错误处理对象是一个struct jpeg_error_mgr结构体变量。
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
以上就定义了JPEG解码对象和错误处理对象。
1.4.1错误处理
       使用libjpeg库函数的时候难免会产生错误,所以我们在使用libjpeg解码之前,首先要做好错误处理。在libjpeg库中,实现了默认错误处理函数,当错误发生时,譬如如果内存不足、文件格式不对等,则会libjpeg实现的默认错误处理函数,默认错误处理函数将会调用exit()结束束整个进程;当然,我们可以修改错误处理的方式,libjpeg提供了接口让用户可以注册一个自定义错误处理函数。
       错误处理对象使用struct jpeg_error_mgr结构体描述,该结构体内容如下所示:
示例代码 21.4.1 struct jpeg_error_mgr结构体
/* Error handler object */
struct jpeg_error_mgr {
    /* Error exit handler: does not return to caller */
    JMETHOD(noreturn_t, error_exit, (j_common_ptr cinfo));
    /* Conditionally emit a trace or warning message */
    JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level));
    /* Routine that actually outputs a trace or error message */
    JMETHOD(void, output_message, (j_common_ptr cinfo));
    /* Format a message string for the most recent JPEG error or message */
    JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer));
#define JMSG_LENGTH_MAX200    /* recommended size of format_message buffer */
    /* Reset error state variables at start of a new image */
    JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo));

    /* The message ID code and any parameters are saved here.
   * A message can have one string parameter or up to 8 int parameters.
   */
    int msg_code;
#define JMSG_STR_PARM_MAX80
    union {
      int i;
      char s;
    } msg_parm;

    /* Standard state variables for error facility */
    int trace_level;            /* max msg_level that will be displayed */

    /* For recoverable corrupt-data errors, we emit a warning message,
   * but keep going unless emit_message chooses to abort.emit_message
   * should count warnings in num_warnings.The surrounding application
   * can check for bad data by seeing if num_warnings is nonzero at the
   * end of processing.
   */
    long num_warnings;            /* number of corrupt-data warnings */

    /* These fields point to the table(s) of error message strings.
   * An application can change the table pointer to switch to a different
   * message list (typically, to change the language in which errors are
   * reported).Some applications may wish to add additional error codes
   * that will be handled by the JPEG library error mechanism; the second
   * table pointer is used for this purpose.
   *
   * First table includes all errors generated by JPEG library itself.
   * Error code 0 is reserved for a "no such error string" message.
   */
    const char * const * jpeg_message_table; /* Library errors */
    int last_jpeg_message;    /* Table contains strings 0..last_jpeg_message */
    /* Second table can be added by application (see cjpeg/djpeg for example).
   * It contains strings numbered first_addon_message..last_addon_message.
   */
    const char * const * addon_message_table; /* Non-library errors */
    int first_addon_message;      /* code for first string in addon table */
    int last_addon_message;       /* code for last string in addon table */
};
error_exit函数指针便指向了错误处理函数。使用libjpeg库函数jpeg_std_error()会将libjpeg错误处理设置为默认处理方式。如下所示:
//初始化错误处理对象、并将其与解压对象绑定
cinfo.err = jpeg_std_error(&jerr);
如果我们要修改默认的错误处理函数,可这样操作:
void my_error_exit(struct jpeg_decompress_struct *cinfo)
{
      /* ... */
}

cinfo.err.error_exit = my_error_exit;
1.4.2创建解码对象
要使用libjpeg解码jpeg数据,这步是必须要做的。
jpeg_create_decompress(&cinfo);
在创建解码对象之后,如果解码结束或者解码出错时,需要调用jpeg_destroy_decompress销毁/释放解码对象,否则将会内存泄漏。
1.4.3设置数据源
也就是设置需要进行解码的jpeg文件,使用jpeg_stdio_src()函数设置数据源:
FILE *jpeg_file = NULL;

//打开.jpeg/.jpg图像文件
jpeg_file = fopen("./image.jpg", "r");   //只读方式打开
if (NULL == jpeg_file) {
      perror("fopen error");
      return -1;
}

//指定图像文件
jpeg_stdio_src(&cinfo, jpeg_file);
待解码的jpeg文件使用标准I/O方式fopen将其打开。除此之外,jpeg数据源还可以来自内存中、而不一定的是文件流。
1.4.4读取jpeg文件的头信息
这个和创建解码对象一样,是必须要调用的,是约定,没什么好说的。因为在解码之前,需要读取jpeg文件的头部信息,以获取该文件的信息,这些获取到的信息会直接赋值给cinfo对象的某些成员变量。
jpeg_read_header(&cinfo, TRUE);
调用jpeg_read_header()后,可以得到jpeg图像的一些信息,譬如jpeg图像的宽度、高度、颜色通道数以及colorspace等,这些信息会赋值给cinfo对象中的相应成员变量,如下所示:
cinfo.image_width                  //jpeg图像宽度
cinfo.image_height                  //jpeg图像高度
cinfo.num_components               //颜色通道数
cinfo.jpeg_color_space                //jpeg图像的颜色空间
支持的颜色包括如下几种:
/* Known color spaces. */
typedef enum {
      JCS_UNKNOWN,         /* error/unspecified */
      JCS_GRAYSCALE,          /* monochrome */
      JCS_RGB,                  /* red/green/blue, standard RGB (sRGB) */
      JCS_YCbCr,            /* Y/Cb/Cr (also known as YUV), standard YCC */
      JCS_CMYK,                     /* C/M/Y/K */
      JCS_YCCK,            /* Y/Cb/Cr/K */
      JCS_BG_RGB,                   /* big gamut red/green/blue, bg-sRGB */
      JCS_BG_YCC            /* big gamut Y/Cb/Cr, bg-sYCC */
} J_COLOR_SPACE;
1.4.5设置解码处理参数
在进行解码之前,我们可以对一些解码参数进行设置,这些参数都有一个默认值,调用jpeg_read_header()函数后,这些参数被设置成相应的默认值。
直接对cinfo对象的成员变量进行修改即可,这里介绍两个比较有代表性的解码处理参数:
输出的颜色(cinfo.out_color_space):默认配置为RGB颜色,也就是JCS_RGB;
图像缩放操作(cinfo.scale_num和cinfo.scale_denom):libjpeg可以设置解码出来的图像的大小,也就是与原图的比例。使用scale_num和scale_denom两个参数,解出来的图像大小就是scale_num/scale_denom,JPEG当前仅支持1/1、1/2、1/4、和1/8这几种缩小比例。默认是1/1,也就是保持原图大小。譬如要将输出图像设置为原图的1/2大小,可进行如下设置:
cinfo.scale_num=1;
cinfo.scale_denom=2;
1.4.6开始解码
经过前面的参数设置,我们可以开始解码了,调用jpeg_start_decompress()函数:
jpeg_start_decompress(&cinfo);
在完成解压缩操作后,会将解压后的图像信息填充至cinfo结构中。譬如,输出图像宽度cinfo.output_width,输出图像高度cinfo.output_height,每个像素中的颜色通道数cinfo.output_components(比如灰度为1,全彩色RGB888为3)等。
一般情况下,这些参数是在jpeg_start_decompress后才被填充到cinfo中的,如果希望在调用jpeg_start_decompress之前就获得这些参数,可以通过调用jpeg_calc_output_dimensions()的方法来实现。
1.4.7读取数据
接下来就可以读取解码后的数据了,数据是按照行读取的,解码后的数据按照从左到右、从上到下的顺序存储,每个像素点对应的各颜色或灰度通道数据是依次存储,譬如一个24-bit RGB真彩色的图像中,一行的数据存储模式为B,G,R,B,G,R,B,G,R,...。
libjpeg默认解码得到的图像数据是BGR888格式,即R颜色在低8位、而B颜色在高8位。可以定义一个BGR888颜色类型,如下所示:
typedef struct bgr888_color {
    unsigned char red;
    unsigned char green;
    unsigned char blue;
} __attribute__ ((packed)) bgr888_t;
每次读取一行数据,计算每行数据需要的空间大小,比如RGB图像就是宽度×3(24-bit RGB真彩色一个像素3个字节),灰度图就是宽度×1(一个像素1个字节)。
bgr888_t *line_buf = malloc(cinfo.output_width * cinfo.output_components);
以上我们分配了一个行缓冲区,它的大小为cinfo.output_width * cinfo.output_components,也就是输出图像的宽度乘上每一个像素的字节大小。我们除了使用malloc分配缓冲区外,还可以使用libjpeg的内存管理器来分配缓冲区,这个不再介绍!
缓冲区分配好之后,接着可以调用jpeg_read_scanlines()来读取数据,jpeg_read_scanlines()可以指定一次读多少行,但是目前该函数还只能支持一次只读1行;函数如下所示:
jpeg_read_scanlines(&cinfo, &buf, 1);
1表示每次读取的行数,通常都是将其设置为1。
cinfo.output_scanline表示接下来要读取的行对应的索引值,初始化为0(表示第一行)、1表示第二行等,每读取一行数据,该变量就会加1,所以我们可以通过下面这种循环方式依次读取解码后的所有数据:
while(cinfo.output_scanline < cinfo.output_height)
{
      jpeg_read_scanlines(&cinfo, buffer, 1);
      //do something
}
1.4.8结束解码
解码完毕之后调用jpeg_finish_decompress()函数:
jpeg_finish_decompress(&cinfo);
1.4.9释放/销毁解码对象
当解码完成之后,我们需要调用jpeg_destroy_decompress()函数销毁/释放解码对象:
jpeg_destroy_decompress(&cinfo);
1.5libjpeg应用编程
通过上小节的介绍,我们已经知道了如何使用libjpeg提供的库函数来解码.jpg/.jpeg图像,本小节进行实战,对一个指定的jpeg图像进行解码,显示在LCD屏上,示例代码如下所示:
本例程源码对应的路径为:开发板光盘->11、Linux C应用编程例程源码->21_libjpeg->show_jpeg_image.c。
示例代码 21.5.1 libjpeg应用程序示例代码
<font size="4">/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2021. All rights reserved.
文件名 : show_jpeg_image.c
作者 : 邓涛
版本 : V1.0
描述 : libjpeg使用实战
其他 : 无
论坛 : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
日志 : 初版 V1.0 2021/6/15 邓涛创建
***************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <jpeglib.h>

typedef struct bgr888_color {
    unsigned char red;
    unsigned char green;
    unsigned char blue;
} __attribute__ ((packed)) bgr888_t;

static int width;                     //LCD X分辨率
static int height;                      //LCD Y分辨率
static unsigned short *screen_base = NULL;      //映射后的显存基地址
static unsigned long line_length;       //LCD一行的长度(字节为单位)
static unsigned int bpp;    //像素深度bpp

static int show_jpeg_image(const char *path)
{
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE *jpeg_file = NULL;
    bgr888_t *jpeg_line_buf = NULL;   //行缓冲区:用于存储从jpeg文件中解压出来的一行图像数据
    unsigned short *fb_line_buf = NULL; //行缓冲区:用于存储写入到LCD显存的一行数据
    unsigned int min_h, min_w;
    unsigned int valid_bytes;
    int i;

    //绑定默认错误处理函数
    cinfo.err = jpeg_std_error(&jerr);

    //打开.jpeg/.jpg图像文件
    jpeg_file = fopen(path, "r");   //只读方式打开
    if (NULL == jpeg_file) {
      perror("fopen error");
      return -1;
    }

    //创建JPEG解码对象
    jpeg_create_decompress(&cinfo);

    //指定图像文件
    jpeg_stdio_src(&cinfo, jpeg_file);

    //读取图像信息
    jpeg_read_header(&cinfo, TRUE);
    printf("jpeg图像大小: %d*%d\n", cinfo.image_width, cinfo.image_height);

    //设置解码参数
    cinfo.out_color_space = JCS_RGB;//默认就是JCS_RGB
    //cinfo.scale_num = 1;
    //cinfo.scale_denom = 2;

    //开始解码图像
    jpeg_start_decompress(&cinfo);

    //为缓冲区分配内存空间
    jpeg_line_buf = malloc(cinfo.output_components * cinfo.output_width);
    fb_line_buf = malloc(line_length);

    //判断图像和LCD屏那个的分辨率更低
    if (cinfo.output_width > width)
      min_w = width;
    else
      min_w = cinfo.output_width;

    if (cinfo.output_height > height)
      min_h = height;
    else
      min_h = cinfo.output_height;

    //读取数据
    valid_bytes = min_w * bpp / 8;//一行的有效字节数 表示真正写入到LCD显存的一行数据的大小
    while (cinfo.output_scanline < min_h) {

      jpeg_read_scanlines(&cinfo, (unsigned char **)&jpeg_line_buf, 1);//每次读取一行数据

      //将读取到的BGR888数据转为RGB565
      for (i = 0; i < min_w; i++)
</font><span style="font-style: italic;"><span style="font-style: normal;"><font size="4">            fb_line_buf = ((jpeg_line_buf.red & 0xF8) << 8) |
                  ((jpeg_line_buf</font></span><span style="font-style: normal;"><font size="4">.green & 0xFC) << 3) |
                  ((jpeg_line_buf</font></span><span style="font-style: normal;"><font size="4">.blue & 0xF8) >> 3);

      memcpy(screen_base, fb_line_buf, valid_bytes);
      screen_base += width;//+width定位到LCD下一行显存地址的起点
    }

    //解码完成
    jpeg_finish_decompress(&cinfo); //完成解码
    jpeg_destroy_decompress(&cinfo);//销毁JPEG解码对象、释放资源

    //关闭文件、释放内存
    fclose(jpeg_file);
    free(fb_line_buf);
    free(jpeg_line_buf);
    return 0;
}

int main(int argc, char *argv[])
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd;

    /* 传参校验 */
    if (2 != argc) {
      fprintf(stderr, "usage: %s <jpeg_file>\n", argv);
      exit(-1);
    }

    /* 打开framebuffer设备 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
      perror("open error");
      exit(EXIT_FAILURE);
    }

    /* 获取参数信息 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

    line_length = fb_fix.line_length;
    bpp = fb_var.bits_per_pixel;
    screen_size = line_length * fb_var.yres;
    width = fb_var.xres;
    height = fb_var.yres;

    /* 将显示缓冲区映射到进程地址空间 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void *)screen_base) {
      perror("mmap error");
      close(fd);
      exit(EXIT_FAILURE);
    }

    /* 显示BMP图片 */
    memset(screen_base, 0xFF, screen_size);
    show_jpeg_image(argv);

    /* 退出 */
    munmap(screen_base, screen_size);//取消映射
    close(fd);//关闭文件
    exit(EXIT_SUCCESS);    //退出进程
}</font></span></span>
代码就不再讲解了,前面的内容看懂了,代码自然就能看懂!
在while循环中,通过jpeg_read_scanlines()每次读取一行数据,注意,jpeg_read_scanlines()函数的第二个参数是一个unsigned char **类型指针。读取到数据之后,需要将其转为RGB565格式,因为我们这个开发板出厂系统,LCD是RGB565格式的显示设备,这个转化非常简单,没什么可说的,懂的人自然懂!
编译上述代码:
${CC} -o testApp testApp.c -I /home/dt/tools/jpeg/include -L /home/dt/tools/jpeg/lib -ljpeg

图 21.5.1 编译示例代码
编译的时候需要指定头文件的路径、库文件的路径以及需要链接的库文件,与编译tslib应用程序是一样的道理。
将编译得到的可执行文件和一个.jpg/.jpeg图像文件拷贝到开发板Linux系统的用户家目录下,执行测试程序:

图 21.5.2 执行应用程序
此时LCD屏上便会显示这张图片,如下所示(执行测试程序之前,建议关闭出厂系统的Qt GUI应用程序):

图 21.5.3 LCD显示效果
1.6总结
关于本章的内容就向大家介绍这么多,libjpeg除了JPEG解码功能外,还可以实现JPEG编码以及其它一些JPEG功能,大家可以自己去学习、去摸索一下,笔者不可能把所有API都给你讲一遍,这是不现实的,譬如后面会给大家介绍音频应用编程,用到了alsa-lib库,这个库估计包含了几百个API,你说我会一个一个给你讲吗?所以这是不可能的事情,大家应该学习的是一种方法,在原有内容的基础上进行扩展,学习更多的用法,而不仅限于本书中的这些内容。
libjpeg提供的API其实并不是很多,大家可以打开它的头文件jpeglib.h,大致去浏览一下,其实从它函数的命名上可以看出它的一个大致作用,再结合注释信息基本可以确定函数的功能,除此之外,这些函数库都会提供一些示例代码供用户参考。笔者也曾尝试找了找libjpeg官方的帮助文档,但是很遗憾未能找到!不知是官方没有出帮助文档还是笔者找的方法不对,总之,笔者确实没找到,如果有哪位读者找到了,那么希望可以通知到笔者,我会把它的链接地址写入本书,供读者查阅!
OK,那本章内容到此结束!大家加油!
页: [1]
查看完整版本: 《I.MX6U嵌入式Linux C应用编程指南》第二十一章 在LCD上显示jpeg