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

news2024/12/23 11:13:50

在 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,那本章内容到此结束!大家加油!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[华为OD]C卷 BFS 亲子游戏 200

题目&#xff1a; 宝宝和妈妈参加亲子游戏&#xff0c;在一个二维矩阵&#xff08;N*N&#xff09;的格子地图上&#xff0c;宝宝和妈妈抽签决定各自 的位置&#xff0c;地图上每个格子有不同的Q糖果数量&#xff0c;部分格子有障碍物。 游戏规则Q是妈妈必须在最短的时间&a…

力扣刷题第1天:消失的数字

大家好啊&#xff0c;从今天开始将会和大家一起刷题&#xff0c;从今天开始小生也会开辟新的专栏。&#x1f61c;&#x1f61c;&#x1f61c; 目录 第一部分&#xff1a;题目描述 第二部分&#xff1a;题目分析 第三部分&#xff1a;解决方法 3.1 思路一&#xff1a;先排序…

WebView基础知识以及Androidx-WebKit的使用

文章目录 摘要WebView基础一、启动调整模式二、WebChromeClient三、WebViewClient四、WebSettings五、WebView和Native交互 Androidx-WebKit一、启动安全浏览服务二、设置代理三、安全的 WebView 和 Native 通信支持四、文件传递五、深色主题的支持六、JavaScript and WebAssem…

邮件大附件系统如何进行安全、高效的大附件发送?

邮件大附件系统是一套解决传统电子邮件系统&#xff0c;在发送大文件时遇到限制的解决方案。由于传统电子邮件系统通常对附件大小有限制&#xff0c;这使得发送大文件变得困难。邮件大附件系统通过各种技术手段&#xff0c;允许用户发送超过传统限制的大文件&#xff0c;通常在…

【随笔】Git 高级篇 -- 上传命令的参数 (下)git push(三十七)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

目标检测——低光可见光-红外配对数据集

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …

TypeScript学习日志-第二十四天(webpack构建ts+vue3)

webpack构建tsvue3 一、构建项目目录 如图&#xff1a; shim.d.ts 这个文件用于让ts识别.vue后缀的 后续会说 并且给 tsconfig.json 增加配置项 "include": ["src/**/*"] 二、基础构建 安装依赖 安装如下依赖&#xff1a; npm install webpack -D …

11.偏向锁原理及其实战

文章目录 偏向锁原理及其实战1.偏向锁原理2.偏向锁案例代码演示2.1.偏向锁案例代码2.2.1.无锁情况下状态2.1.2.偏向锁状态2.1.3.释放锁后的状态 2.2.偏向锁的膨胀和撤销2.2.1.偏向锁撤销的条件2.2.2.偏向锁的撤销 2.2.3.偏向锁的膨胀 2.3.全局安全点原理和偏向锁撤销性能问题2.…

在R的 RGui中,使用devtools 安装trajeR

创建于&#xff1a;2024.5.5 文章目录 1. 报错信息2. 尝试使用指定的清华镜像&#xff0c;没有解决3. 找到原因&#xff1a;官网把包删除了4. 尝试从网上下载&#xff0c;然后安装。没有成功5. 使用devtools安装5.1 尝试直接安装&#xff1a;install.packages("devtools&q…

OpenCV | 项目 | 虚拟绘画

OpenCV | 项目 | 虚拟绘画 捕捉摄像头 如果在虚拟机中运行&#xff0c;请确保虚拟机摄像头打开。 #include<opencv2/opencv.hpp>using namespace cv; using namespace std;int main() {VideoCapture cap(0);Mat img;while(1) {cap.read(img);imshow("Image"…

JetBrains的Java集成开发环境IntelliJ 2024.1版本在Windows/Linux系统的下载与安装配置

目录 前言一、IntelliJ在Windows安装二、IntelliJ在Linux安装三、Windows下使用配置四、Linux下使用配置总结 前言 ​ “ IntelliJ IDEA Ultimate是一款功能强大的Java集成开发环境&#xff08;IDE&#xff09;。它提供了丰富的功能和工具&#xff0c;可以帮助开发人员更高效地…

labview技术交流-将时间字符串转换成时间格式

应用场景 我们在数据库中设计了datetime类型的字段&#xff0c;比如字段名就叫“保存时间”&#xff0c;当我们使用labview将表中数据读取出来后datetime类型的数据是以字符串的格式显示的。而我们想计算两条数据“保存时间”的间隔时间时&#xff0c;用字符串类型自然是没法计…

uniapp读取项目本地文件/json文件/txt文件

uniapp读取项目本地文件/json文件/txt文件 文件必须放在static目录下 方法&#xff1a; /*** 访问static里面的文件* param url 文件路径 必须在static目录下*/ function localFetch(url) {return new Promise((resolve, reject) > {plus.io.resolveLocalFileSystemURL(_ww…

OmniReader Pro mac激活版:智慧阅读新选择,开启高效学习之旅

在追求知识的道路上&#xff0c;一款优秀的阅读工具是不可或缺的。OmniReader Pro作为智慧阅读的新选择&#xff0c;以其独特的功能和卓越的性能&#xff0c;为您开启高效学习之旅。 OmniReader Pro具备高效的文本识别和处理技术&#xff0c;能够快速准确地提取文档中的关键信息…

PXE批量部署,一键安装配置多台Linux系统

目录 一、PXE批量部署的优点 二、搭建PXE远程安装服务器 1. 实验初始化设置 2. 一键安装软件包 3. 复制 vmlinuz、initrd.img、pxelinux.0文件 4. 配置PE启动菜单配置文件 5. 修改配置文件&#xff0c; 启动各个软件服务 6. kickstart自动应答文件修改启动菜单配置文件…

(一)Linux的vim编辑器的使用

一.vim编辑器 Vim 是从 vi 发展出来的一个文本编辑器。代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。简单的来说, vi 是老式的字处理器,不过功能已经很齐全了,但是还是有可以进步的地方。 vim 则可以说是程序开发者的一项很好用的工具。 二…

不抽象:Increase API 设计原则

原文&#xff1a;Increase - 2024.04.26 &#xff08;注&#xff1a;Increase 是一家提供金融技术服务的公司。&#xff09; API 资源是 API 的实体或对象。决定如何为这些实体命名和建模可以说是设计 API 最难也是最重要的部分。您所公开的资源组织了用户对您的产品如何工作…

什么才是正确的领域驱动实现架构?

作为一种系统建模方法&#xff0c;DDD同样涉及系统的体系架构设计。区别于分布式、事件驱动、消息总线等架构设计方法&#xff0c;DDD中的架构设计关注前面各章所介绍的聚合、实体、值对象、领域事件、应用服务以及资源库之间的交互方式和风格&#xff0c;并在设计思想上有其独…

揭秘设计师必备神器:情绪板是什么?

每个伟大的设计项目都从一点灵感开始。无论你是在设计网站、应用程序&#xff0c;还是想重新装修房子&#xff0c;情绪板都可以帮助你激发创造力&#xff0c;甚至情绪板也可以决定UI界面是否成功。本文将分享什么是情绪板&#xff0c;为什么需要情绪板&#xff0c;以及如何充分…

Linux下多线程相关概念

thread 1.什么是线程1.1 线程优缺点1.2 线程异常1.3 线程用途 2. 进程和线程区别3. 线程控制3.1 POSIX线程库3.2 pthread_create()3.3 线程ID3.4 线程ID地址空间布局pthread_self() 3.5 线程终止pthread_exit函数pthread_cancle函数 3.6 线程等待3.7 分离线程__thread修饰全局变…