正点原子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 80union {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;elsemin_w = cinfo.output_width;if (cinfo.output_height > height)min_h = height;elsemin_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 数据转为 RGB565for (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.mzph.cn/web/8918.shtml

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

相关文章

【动态规划】子数组、子串系列I|最大子数组和|环形子数组的最大和|乘积最大子数组|乘积为正数的最长子数组长度

一、最大子数组和 最大子数组和 算法原理&#xff1a; &#x1f4a1;细节&#xff1a; 1.返回值为dp表每个位置的最大值&#xff0c;而不是只看最后一个位置&#xff0c;因为可能最后一个位置都不选 2.可以直接在填dp表的时候就进行返回值的比较 3.如果初始化选择多开一个位…

2024最新版JavaScript逆向爬虫教程-------基础篇之无限debugger的原理与绕过

目录 一、无限debugger的原理与绕过1.1 案例介绍1.2 实现原理1.3 绕过debugger方法1.3.1 禁用所有断点1.3.2 禁用局部断点1.3.3 替换文件1.3.4 函数置空与hook 二、补充2.1 改写JavaScript文件2.2 浏览器开发者工具中出现的VM开头的JS文件是什么&#xff1f; 一、无限debugger的…

520送男士内裤给男朋友好吗?五大男士内裤测评种草

相信有很多朋友都选在520这个特殊的日子里为心爱的人挑选一份特别的礼物吧&#xff01;如果送礼给男朋友或老公&#xff0c;一份实用的礼物肯定是最佳选择哦&#xff01;很多男性朋友每条内裤都穿很久&#xff0c;如果给男朋友挑选合适的男士内裤&#xff0c;也是一种关心体贴的…

[华为OD]BFS C卷 200 智能驾驶

题目&#xff1a; 有一辆汽车需要从m*n的地图的左上角(起点)开往地图的右下角(终点)&#xff0c;去往每一个地区都需 要消耗一定的油量&#xff0c;加油站可进行加油 请你计算汽车确保从起点到达终点时所需的最少初始油量说明&#xff1a; (1)智能汽车可以上下左右四个方向…

C++ 复习2 输入输出 基本数据类型

输入输出 标准输出流 ( cout ) cout 代表标准输出流&#xff0c;通常用于向屏幕输出数据。 使用操作符 << &#xff08;插入操作符&#xff09;向 cout 发送数据。 例如&#xff0c; std::cout << "Hello, world!" << std::endl; 会在屏幕上打印 …

本地搭建AI环境

本地搭建AI 这几天刚刚看到好兄弟分享的一段关于本地搭建AI的短视频&#xff0c;于是我按照视频里的讲解&#xff0c;进行了实践。感觉非常棒&#xff01;&#xff01;&#xff0c;马上整理成文字与大家分享一下。 在本地启动并运行大型语言模型&#xff0c;运行llama3、phi3…

自然语言处理(NLP)技术有哪些运用?

目录 一、自然语言处理&#xff08;NLP&#xff09;技术有哪些运用&#xff1f; 二、Python进行文本的情感分析 1、NLTK库: 2、TextBlob库: 三、错误排除 一、自然语言处理&#xff08;NLP&#xff09;技术有哪些运用&#xff1f; 自然语言处理&#xff08;NLP&#xff09…

区块链 | NFT 水印:Review on Watermarking Techniques(一)

&#x1f34d;原文&#xff1a;Review on Watermarking Techniques Aiming Authentication of Digital Image Artistic Works Minted as NFTs into Blockchains 1 应用于 NFT 的水印技术 常见的水印技术类型可以分为&#xff1a; 可见 v i s i b l e \mathsf{visible} visi…

循环神经网络(RNN)

大家好&#xff0c;这里是七七&#xff0c;这两天在写关于神经网络相关的知识&#xff0c;面对的是有一定基础的读者哦。 一、RNN核心思想 RNN的核心思想就是曾经的输入造成的影响&#xff0c;会以致影响之后的输入&#xff0c;即隐含层的输出取决于历史数据的全部输入。 三个…

Infuse for Mac激活版:高清影音播放软件

对于热爱影音娱乐的Mac用户来说&#xff0c;Infuse for Mac是一个不容错过的选择。它以其简洁的操作界面和强大的播放功能&#xff0c;为用户带来了全新的影音播放体验。 Infuse for Mac支持广泛的音视频格式&#xff0c;无需额外转换&#xff0c;即可轻松播放您喜爱的影片。无…

Mybatis Plus二级缓存 使用@CacheNamespace 失效@CacheNamespace和@CacheNamespaceRef

1、注解 CacheNamespace(flushInterval 100000,eviction LruCache.class,readWrite false,size 1024)2、xml配置 <cache eviction "LRU" flushInterval "100000" readOnly "true" size "1024"/> 二级缓存&#xff0c;配置文…

【Linux 性能详解】CPU性能分析工具篇

目录 uptime mpstat 实时监控 查看特定CPU核心 pidstart 监控指定进程 组合多个监控类型 监控线程资源 按用户过滤进程 vmstart 用途 基本用法 输出字段 perf execsnoop dstat 通俗解释 技术层面解释 使用示例 总结 uptime uptime 是一个在 Linux 和 Unix…

上班不想用脑子写代码了怎么办?那就试试Baidu Comate啊宝贝

本文目录 前言1、视频编程实战1.1、熟悉代码库中的代码1.2、参考现有代码编写新代码 2、下载使用教程3、使用体验3.1、AutoWork 产品测评3.2、解决有关ajax请求后重定向问题3.3、询问编程相关知识3.3.1、cookie和session的区别与联系3.3.2、数据库中主键外键的相关知识 4、问题…

基于EBAZ4205矿板的图像处理:12图像二值化(阈值可调)

基于EBAZ4205矿板的图像处理&#xff1a;12图像二值化(阈值可调) 我的项目是基于EBAZ4205矿板的阈值可调的图像阈值二值化处理&#xff0c;可以通过按键调整二值化的阈值&#xff0c;key1为阈值加1&#xff0c;key4为阈值减1&#xff0c;key2为阈值加10&#xff0c;key5为阈值…

【bug记录】清除僵尸进程,释放GPU显存

目录 1. 为什么会出现这种情况&#xff1f;2. 解决方案方法一&#xff1a;使用 fuser 命令方法二&#xff1a; 3. 小贴士 在进行深度学习或其他需要GPU支持的任务时&#xff0c;我们有时会发现虽然没有可见的进程在执行&#xff0c;但GPU资源却意外地被占用。这种情况往往会阻碍…

AI换脸原理(4)——人脸对齐(关键点检测)参考文献2DFAN:代码解析

注意,本文属于人脸关键点检测步骤的论文,虽然也在人脸对齐的范畴下。 1、介绍 在本文中,重点介绍了以下几项创新性的成果,旨在为人脸关键点检测领域带来新的突破。 首先,成功构建了一个卓越的2D人脸关键点检测基线模型。这一模型不仅集成了目前最优的关键点检测网络结构,…

sqlite3命令行工具无法退出问题处理

一、背景&#xff1a; 软件使用的后台数据库为sqlite&#xff0c;linux主机系统层面使用sqlite3命令行工具登录数据库后&#xff0c;无法执行sql脚本&#xff0c;无法退出sqlite3。无法执行ctrlc&#xff0c;执行ctrlz后sqlite3前台进程被中断&#xff0c;但是该进程没有退出。…

Spring-依赖查找

依赖查找 根据名称进行查找 实时查找 BeanFactory beanFactory new ClassPathXmlApplicationContext("beans.xml"); Object bean beanFactory.getBean("personHolder"); System.out.println(bean);xml如下: <bean id"person" class&qu…

运维自动化工具:Ansible 概念与模块详解

目录 前言 一、运维自动化工具有哪些 二、Ansible 概述 1、Ansible 概念 2、Ansible 特点 3、Ansible 工作流程 4、Ansible 架构 4.1 Ansible 组成 4.2 Ansible 命令执行来源 5、Ansible 的优缺点 三、Ansible 安装部署 1、环境部署 2、管理节点安装 Ansible 3、…

Golang | Leetcode Golang题解之第75题颜色分类

题目&#xff1a; 题解&#xff1a; func sortColors(nums []int) {p0, p2 : 0, len(nums)-1for i : 0; i < p2; i {for ; i < p2 && nums[i] 2; p2-- {nums[i], nums[p2] nums[p2], nums[i]}if nums[i] 0 {nums[i], nums[p0] nums[p0], nums[i]p0}} }