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

server/2024/9/22 22:47:20/

在 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.ppmy.cn/server/40481.html

相关文章

【OceanBase诊断调优】—— checksum error ret=-4103 问题排查

适用版本 OceanBase 数据库所有版本。 什么是 checksum data checksum&#xff1a;一个 SSTable 中所有宏块内存二进制计算出来的 checksum 值。反映了宏块中的数据和数据分布情况。如果宏块中数据一致但是数据分布不一致&#xff0c;计算出来的 checksum 也不相等。 column…

分享5个免费AI写作软件

在数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;正以惊人的速度渗透到我们生活的方方面面&#xff0c;而写作领域也不例外。AI写作工具的出现&#xff0c;不仅改变了传统的写作流程&#xff0c;更在创意表达、文本生成、语言校正等方面展现了其独特的优势。这些工…

py黑帽子学习笔记_环境准备

1 下载os装os 下载一个kali虚机镜像然后用虚机管理软件创虚机&#xff0c;装完如下图&#xff0c;我用的版本是2024.1的版本kali-linux-2024.1-installer-amd64&#xff0c;可以从镜像站下载&#xff0c;官网下的慢还断网Index of /kali-images/kali-2024.1/ | 清华大学开源软…

Vue3实战笔记(16)—pinia基本用法--Getter

文章目录 前言一、pinia的getter简单理解二、访问其他 store 的 getter总结 前言 在 Pinia 中&#xff0c;getter 类似于 Vuex 中的 getter&#xff0c;允许你从 store 中派生出一些状态&#xff0c;而不需要修改原始状态。这使得我们可以创建基于现有状态的计算属性。 一、pi…

[C/C++] -- 观察者模式

观察者模式是一种行为型设计模式&#xff0c;用于定义对象间的一种一对多的依赖关系&#xff0c;使得当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都会得到通知并自动更新。 观察者模式涉及以下几个角色&#xff1a; 主题&#xff08;Subject&#xff09;&…

【linux软件基础知识】-cdev_alloc

struct cdev *cdev_alloc(void) {struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);if <

oracle 行转列及列转行

行转列 使用pivot函数实现 行转列 with temp as( select 四川省 nation ,成都市 city,第一 ranking from dual union all select 四川省 nation ,绵阳市 city,第二 ranking from dual union all select 四川省 nation ,德阳市 city,第三 ranking from dual union all select 四…

利用爬虫解决数据采集难题

文章目录 安装为什么选择 BeautifulSoup 和 requests&#xff1f;安装 BeautifulSoup 和 requests解决安装问题 示例总结 在现代信息时代&#xff0c;数据是企业决策和发展的关键。然而&#xff0c;许多有用的数据分散在网络上&#xff0c;且以各种格式和结构存在&#xff0c;因…