正点原子Linux学习笔记(六)在 LCD 上显示 jpeg 图像

在 LCD 上显示 jpeg 图像

  • 20.1 JPEG 简介
  • 20.2 libjpeg 简介
  • 20.3 libjpeg 移植
    • 下载源码包
    • 编译源码
    • 安装目录下的文件夹介绍
    • 移植到开发板
  • 20.4 libjpeg 使用说明
    • 错误处理
    • 创建解码对象
    • 设置数据源
    • 读取 jpeg 文件的头信息
    • 设置解码处理参数
    • 开始解码
    • 读取数据
    • 结束解码
    • 释放/销毁解码对象
  • 20.5 libjpeg 应用编程
  • 20.6 总结

我们常用的图片格式有很多,一般最常用的有三种:JPEG(或 JPG)、PNG、BMP。上一章给大家介绍了如何在 LCD 上显示 BMP 图片,详细介绍了 BMP 图像的格式;BMP 图像虽然没有失真、并且解析简单,但是由于图像数据没有进行任何压缩,因此,BMP 图像文件所占用的存储空间很大,不适合存储在磁盘设备中。

而 JPEG(或 JPG)、PNG 则是经过压缩处理的图像格式,将图像数据进行压缩编码,大大降低了图像文件的大小,适合存储在磁盘设备中,所以很常用。本章我们就来学习如何在 LCD 屏上显示 jpeg 图像,下一章将向大家介绍如何在 LCD 屏上显示 png 图像。

本章将会讨论如下主题。
⚫ JPEG 简介;
⚫ libjpeg 库简介;
⚫ libjpeg 库移植;
⚫ 使用 libjpeg 库函数对 JPEG 图像进行解码;

20.1 JPEG 简介

JPEG(Joint Photographic Experts Group)是由国际标准组织为静态图像所建立的第一个国际数字图像压缩标准,也是至今一直在使用的、应用最广的图像压缩标准。
JPEG 由于可以提供有损压缩,因此压缩比可以达到其他传统压缩算法无法比拟的程度;JPEG 虽然是有损压缩,但这个损失的部分是人的视觉不容易察觉到的部分,它充分利用了人眼对计算机色彩中的高频信息部分不敏感的特点,来大大节省了需要处理的数据信息。
JPEG 压缩文件通常以.jpg 或.jpeg 作为文件后缀名,关于 JPEG 压缩标准就给大家介绍这么多,这些内容都是笔者从网络上截取下来的,对此感兴趣的读者可以自行从网络上查阅这些信息。

20.2 libjpeg 简介

JPEG 压缩标准使用了一套压缩算法对原始图像数据进行了压缩得到.jpg 或.jpeg 图像文件,如果想要在LCD 上显示.jpg 或.jpeg 图像文件,则需要对其进行解压缩、以得到图像的原始数据,譬如 RGB 数据。

既然压缩过程使用了算法,那对.jpg 或.jpeg 图像文件进行解压同样也需要算法来处理,当然,笔者并不会教大家如何编写解压算法,这些算法的实现也是很复杂的,笔者肯定不会,自然教不了大家!但是,我们可以使用别人写好的库、调用别人写好的库函数来解压.jpg 或.jpeg 图像文件,也就是本小节要向大家介绍的 libjpeg 库。

libjpeg 是一个完全用 C 语言编写的函数库,包含了 JPEG 解码(解压缩)、JPEG 编码(创建压缩)和其他的 JPEG 功能的实现。可以使用 libjpeg 库对.jpg 或.jpeg 压缩文件进行解压或者生成.jpg 或.jpeg 压缩文件。

libjpeg 是一个开源 C 语言库,我们获取到它的源代码。

20.3 libjpeg 移植

下载源码包

首先,打开 http://www.ijg.org/files/链接地址,如下所示:
在这里插入图片描述
目前最新的一个版本是 v9d,对应的年份为 2020 年,这里我们选择一个适中的版本,笔者以 v9b 为例,对应的文件名为 jpegsrc.v9b.tar.gz,点击该文件即可下载。

其实开发板出厂系统中已经移植了 libjpeg 库,但是版本太旧了!所以这里我们选择重新移植。下载后如下所示:
在这里插入图片描述

编译源码

将 jpegsrc.v9b.tar.gz 压缩包文件拷贝到 Ubuntu 系统用户家目录下,如下所示:
在这里插入图片描述
执行命令解压:

tar -xzf jpegsrc.v9b.tar.gz

在这里插入图片描述
解压成功之后会生成 jpeg-9b 文件夹,也就是 libjpeg 源码文件夹。
编译之前,在家目录下的 tools 文件夹中创建一个名为 jpeg 的文件夹,该目录作为 libjpeg 库的安装目录:
在这里插入图片描述
回到家目录下,进入到 libjpeg 源码目录 jpeg-9b 中,该目录下包含的内容如下所示:
在这里插入图片描述
接下来对 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 的安装目录。
在这里插入图片描述
接着执行 make 命令编译工程:

make

在这里插入图片描述
编译完成之后,执行命令安装 libjpeg:

make install

在这里插入图片描述
命令执行完成之后,我们的 libjpeg 也就安装成功了!

安装目录下的文件夹介绍

进入到 libjpeg 安装目录:
在这里插入图片描述
与 tslib 库安装目录下的包含的文件夹基本相同(除了没有 etc 目录),bin 目录下包含一些测试工具;include 目录下包含头文件;lib 目录下包含动态链接库文件。
进入到 include 目录下:
在这里插入图片描述
在这个目录下包含了 4 个头文件,在应用程序中,我们只需包含 jpeglib.h 头文件即可!进入到 lib 目录下:
在这里插入图片描述
libjpeg.so 和 libjpeg.so.9 都是符号链接,指向 libjpeg.so.9.2.0。

移植到开发板

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

tar -czf lib.tar.gz ./*

在这里插入图片描述
再将 lib.tar.gz 压缩文件拷贝到开发板 Linux 的用户家目录下,在解压之前,将开发板出厂系统中已经移植的 libjpeg 库删除,执行命令:

rm -rf /usr/lib/libjpeg.*

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

接着我们将 lib.tar.gz 压缩文件解压到开发板 Linux 系统/usr/lib 目录下:

tar -xzf lib.tar.gz -C /usr/lib

在这里插入图片描述
解压成功之后,接着执行 libjpeg 提供的测试工具,看看我们移植成功没:djpeg --help
在这里插入图片描述
djpeg 是编译 libjpeg 源码得到的测试工具(在 libjpeg 安装目录下的 lib 目录中),当执行命令之后,能够成功打印出这些信息就表示我们的移植成功了!

20.4 libjpeg 使用说明

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 解码对象和错误处理对象。

错误处理

使用 libjpeg 库函数的时候难免会产生错误,所以我们在使用 libjpeg 解码之前,首先要做好错误处理。在 libjpeg 库中,实现了默认错误处理函数,当错误发生时,譬如如果内存不足、文件格式不对等,则会 libjpeg实现的默认错误处理函数,默认错误处理函数将会调用 exit()结束束整个进程;当然,我们可以修改错误处理的方式,libjpeg 提供了接口让用户可以注册一个自定义错误处理函数。
错误处理对象使用 struct jpeg_error_mgr 结构体描述,该结构体内容如下所示:

示例代码 20.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_MAX 200 /* 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_MAX 80
 union {
 int i[8];
 char s[JMSG_STR_PARM_MAX];
 } 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;

创建解码对象

要使用 libjpeg 解码 jpeg 数据,这步是必须要做的。

jpeg_create_decompress(&cinfo);

在创建解码对象之后,如果解码结束或者解码出错时,需要调用 jpeg_destroy_decompress 销毁/释放解码对象,否则将会内存泄漏。

设置数据源

也就是设置需要进行解码的 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 数据源还可以来自内存中、而不一定的是文件流。

读取 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;

设置解码处理参数

在进行解码之前,我们可以对一些解码参数进行设置,这些参数都有一个默认值,调用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;

开始解码

经过前面的参数设置,我们可以开始解码了,调用 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()的方法来实现。

读取数据

接下来就可以读取解码后的数据了,数据是按照行读取的,解码后的数据按照从左到右、从上到下的顺序存储,每个像素点对应的各颜色或灰度通道数据是依次存储,譬如一个 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 
}

结束解码

解码完毕之后调用 jpeg_finish_decompress()函数:

jpeg_finish_decompress(&cinfo);

释放/销毁解码对象

当解码完成之后,我们需要调用 jpeg_destroy_decompress()函数销毁/释放解码对象:

jpeg_destroy_decompress(&cinfo);

20.5 libjpeg 应用编程

通过上小节的介绍,我们已经知道了如何使用 libjpeg 提供的库函数来解码.jpg/.jpeg 图像,本小节进行实战,对一个指定的 jpeg 图像进行解码,显示在 LCD 屏上,示例代码如下所示:

示例代码 20.5.1 libjpeg 应用程序示例代码
#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++)
 fb_line_buf[i] = ((jpeg_line_buf[i].red & 0xF8) << 8) |
 ((jpeg_line_buf[i].green & 0xFC) << 3) |
 ((jpeg_line_buf[i].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[0]);
 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[1]);
 /* 退出 */
 munmap(screen_base, screen_size); //取消映射
 close(fd); //关闭文件
 exit(EXIT_SUCCESS); //退出进程
}

代码就不再讲解了,前面的内容看懂了,代码自然就能看懂!在 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

在这里插入图片描述
编译的时候需要指定头文件的路径、库文件的路径以及需要链接的库文件,与编译 tslib 应用程序是一样的道理。
将编译得到的可执行文件和一个.jpg/.jpeg 图像文件拷贝到开发板 Linux 系统的用户家目录下,执行测试程序:
在这里插入图片描述
此时 LCD 屏上便会显示这张图片,如下所示(执行测试程序之前,建议关闭出厂系统的 Qt GUI 应用程序):
在这里插入图片描述

20.6 总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/608571.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

30分钟彻底了解Flutter整个渲染流程(超详细)

30分钟彻底了解Flutter整个渲染流程[超详细] 从运行第一行代码出发WidgetsFlutterBinding初始化了一堆娃 三个中流砥柱SchedulerBindingRendererBindingWidgetsBinding 申请Vsync流程下发Vsync承接Vsync 从运行第一行代码出发 void main() {runApp(const MyApp()); }void runA…

卡码网模拟笔试题第十六期 |

A、构造二阶行列式 数字不大&#xff0c;直接四重循环暴力枚举 #include <iostream> using namespace std;int main() {int x;cin >> x;for (int i 1; i < 20; i) {for (int j 1; j < 20;j) {for (int x1 1;x1 < 20;x1) {for (int y 1;y<20;y){if…

2023-2024年家电行业报告合集(精选51份)

家电行业报告/方案&#xff08;精选51份&#xff09; 2023-2024年 报告来源&#xff1a;2023-2024年家电行业报告合集&#xff08;精选51份&#xff09; 【以下是资料目录】 空气炸锅出海品牌策划创意全案【家电出海】【品牌全案】 卡萨帝潮流消费品生活家电音乐节活动方案…

44.乐理基础-音符的组合方式-附点

内容参考于&#xff1a; 三分钟音乐社 首先如下图&#xff0c;是之前的音符&#xff0c;但是它不全&#xff0c;比如想要一个三拍的音符改怎样表示&#xff1f; 在简谱中三拍&#xff0c;在以四分音符为一拍的情况下&#xff0c;在后面加两根横线就可以了&#xff0c;称为附点…

山东齐鲁文化名人颜廷利:教育的本质区别重点是什么

教育的本质区别重点是‘方式’&#xff0c; 现在的教育却成为了一种‘形式’&#xff1b; 教育的核心价值关键载于‘实践’&#xff0c; 当前我们的教育观念却变成了消耗‘时间’&#xff1b; ‘读书’的原则在于‘堵疏’&#xff0c;作为汉语‘堵疏’一词&#xff0c;顾名思义…

亚马逊是如何铺设多个IP账号实现销量大卖的?

一、针对亚马逊平台机制&#xff0c;如何转变思路&#xff1f; 众所周知&#xff0c;一个亚马逊卖家只能够开一个账号&#xff0c;一家店铺&#xff0c;这是亚马逊平台明确规定的。平台如此严格限定&#xff0c;为的就是保护卖家&#xff0c;防止卖家重复铺货销售相同的产品&a…

多线程学习Day07

共享模型之不可变 从一个日期转换的问题开始 Slf4j(topic "c.Test1") public class Test1 {public static void main(String[] args) {SimpleDateFormat sdf new SimpleDateFormat("yyyy-MM-dd");for (int i 0; i < 10; i) {new Thread(() -> {…

使用GitLab自带的CI/CD功能在本地部署.Net8项目(二)

前置内容&#xff1a; 通过Docker Compose部署GitLab和GitLab Runner&#xff08;一&#xff09; 目录 一、创建代码仓库 二、创建GitLabRunner 三、注册Runner 四、配置Runner&#xff0c;绑定宿主Docker 五、创建.Net8WebApi项目进行测试 六、总结 一、创建代码仓库 …

Qt---项目的创建及运行

一、创建第一个Qt程序 1. 点击创建项目后&#xff0c;选择项目路径以及给项目起名称 名称&#xff1a;不能有中文、不能有空格 路径&#xff1a;不能有中文路径 2. 默认创建有窗口类myWidget&#xff0c;基类有三种选择&#xff1a;QWidget、QMainWindow、QDialog 3. m…

【C++11】线程库 | 互斥量 | 原子性操作 | 条件变量

文章目录 一、线程库 - thread1. 线程对象的构造方式无参构造带可变参数包的构造移动构造 2. thread类的成员函数thread::detach()thread::get_id()thread::join()thread::joinable() 线程函数参数的问题 二、互斥量库 - mutex标准库提供的四种互斥锁1. std::mutex2. std::recu…

【Ubuntu18.04+melodic】抓取环境设置

UR5_gripper_camera_gazebo&#xff08;无moveit&#xff09; 视频讲解 B站-我要一米八了-抓取不止&#xff01;Ubuntu 18.04下UR5机械臂搭建Gazebo环境&#xff5c;开源分享 运行步骤 1.创建工作空间 catkin_make2.激活环境变量 source devel/setup.bash3.1 rviz下查看模…

Oracle 修改数据库的字符集

Oracle 修改数据库的字符集 alter system enable restricted session; alter database "cata" character set ZHS16CGB231280; alter database "cata" national character set ZHS16CGB231280; alter system enable restricted session; alter database…

使用动态种子的DGA:DNS流量中的意外行为

Akamai研究人员最近在域名系统&#xff08;DNS&#xff09;流量数据中观察到&#xff1a;使用动态种子的域名生成算法&#xff08;Domain Generation Algorithm&#xff0c;DGA&#xff09;的实际行为&#xff0c;与对算法进行逆向工程推测的预期行为之间存在一些差异。也就是说…

C++ 基础 输入输出

一 C 的基本IO 系统中的预定义流对象cin和cout: 输入流&#xff1a;cin处理标准输入&#xff0c;即键盘输入&#xff1b; 输出流&#xff1a;cout处理标准输出&#xff0c;即屏幕输出&#xff1b; 流&#xff1a;从某种IO设备上读入或写出的字符系列 使用cin、cout这两个流对…

在Ubuntu上安装Anaconda之后,启动失败

为了方便管理Pythonu环境&#xff0c;在Ubuntu的Docker容器中安装了Anaconda&#xff0c;安装完成&#xff0c;启动时出现如下错误&#xff1a; conda activate xxx usage: conda [-h] [--no-plugins] [-V] COMMAND ... conda: error: argument COMMAND: invalid choice: acti…

Linux的基础IO:文件描述符 重定向本质

目录 前言 文件操作的系统调用接口 open函数 close函数 write函数 read函数 注意事项 文件描述符-fd 小补充 重定向 文件描述符的分配原则 系统调用接口-dup2 缓冲区 缓冲区的刷新策略 对于“2”的理解 小补充 前言 在Linux中一切皆文件&#xff0c;打开文件…

springcloud服务间调用 feign 的使用

引入依赖包 <!-- 服务调用feign --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>创建调用外部服务的接口 需要使用的地方注入 使用 启动类增…

CTFHUB-技能树-Web题-RCE(远程代码执行)-eval执行

CTFHUB-技能树-Web题-RCE&#xff08;远程代码执行&#xff09; 文章目录 CTFHUB-技能树-Web题-RCE&#xff08;远程代码执行&#xff09;eval执行解题方法&#xff1a;构造网址&#xff0c;查找当前目录文件并没有发现flag,接着查看上一级目录接着查看上一级接着查看上一级目录…

luceda ipkiss教程 66:金属线的钝角转弯

案例分享&#xff1a;金属线的135度转弯&#xff1a; 所有代码如下&#xff1a; from si_fab import all as pdk import ipkiss3.all as i3 from ipkiss.geometry.shape_modifier import __ShapeModifierAutoOpenClosed__ from numpy import sqrtclass ShapeManhattanStub(__…

一种快速H.264 NALU快速搜索算法

1. 引言 在播放H.264码流的时候,进行NALU的搜索的效率高低影响着系统的性能。有采用普通逐字节搜索的算法,有利用cpu的simd的单指令多数据操作的并行功能进行搜索的算法,今天要介绍的是一个非常简单而且高效的快速搜索算法,而且不需要利用simd指令,搜索的速度甚至快于我之…