正点原子Linux学习笔记(七)在 LCD 上显示 png 图片

在 LCD 上显示 png 图片

  • 21.1 PNG 简介
  • 21.2 libpng 简介
  • 21.3 zlib 移植
    • 下载源码包
    • 编译源码
    • 安装目录下的文件夹介绍
    • 移植到开发板
  • 21.4 libpng 移植
    • 下载源码包
    • 编译源码
    • 安装目录下的文件夹介绍
    • 移植到开发板
  • 21.5 libpng 使用说明
    • libpng 的数据结构
    • 创建和初始化 png_struct 对象
    • 创建和初始化 png_info 对象
    • 设置错误返回点
    • 指定数据源
    • 读取 png 图像数据并解码
    • 读取解码后的数据
    • 结束销毁对象
  • 21.6 libpng 应用编程
    • 二级目录
      • 三级目录

上一章介绍了如何使用 libjpeg 库对 jpeg 图像进行解码、并显示到 LCD 屏上,除了 jpeg 图像之外,png图像也很常见,那本章我们就来学习如何对 png 图像进行解码、并显示到 LCD 屏上。本章将会讨论如下主题。
⚫ PNG 简介;
⚫ libpng 库简介;
⚫ libpng 库移植;
⚫ 使用 libpng 库函数对 PNG 图像进行解码;

21.1 PNG 简介

以下的这些内容都是从网络上截取下来的。PNG(便携式网络图形格式 PortableNetwork Graphic Format,简称 PNG)是一种采用无损压缩算法的位图格式,其设计目的是试图替代 GIF 和 TIFF 文件,同时增加一些 GIF 文件所不具备的特性。PNG 使用从LZ77 派生的无损数据压缩算法,它压缩比高,生成文件体积小,并且支持透明效果,所以被广泛使用。
特点
无损压缩:PNG 文件采用 LZ77 算法的派生算法进行压缩,其结果是获得高的压缩比,不损失数据。它利用特殊的编码方法标记重复出现的数据,因而对图像的颜色没有影响,也不可能产生颜色的损失,这样就可以重复保存而不降低图像质量。
体积小:在保证图片清晰、逼真、不失真的前提下,PNG 使用从 LZ77 派生的无损数据压缩算法,它压缩比高,生成文件体积小; ⚫ 索引彩色模式:PNG-8 格式与 GIF 图像类似,同样采用 8 位调色板将 RGB 彩色图像转换为索引彩色图像。图像中保存的不再是各个像素的彩色信息,而是从图像中挑选出来的具有代表性的颜色编号,每一编号对应一种颜色,图像的数据量也因此减少,这对彩色图像的传播非常有利。
更优化的网络传输显示:PNG 图像在浏览器上采用流式浏览,即使经过交错处理的图像会在完全下载之前提供浏览者一个基本的图像内容,然后再逐渐清晰起来。它允许连续读出和写入图像数据,这个特性很适合于在通信过程中显示和生成图像。
支持透明效果:PNG 可以为原图像定义 256 个透明层次,使得彩色图像的边缘能与任何背景平滑地融合,从而彻底地消除锯齿边缘。这种功能是 GIF 和 JPEG 没有的。关于 PNG 格式就介绍这么多。

21.2 libpng 简介

对于 png 图像,我们可以使用 libpng 库对其进行解码,跟 libjpeg 一样,它也是一套免费、开源的 C 语言函数库,支持对 png 图像文件解码、编码等功能。

21.3 zlib 移植

zlib 其实是一套包含了数据压缩算法的函式库,此函数库为自由软件,是一套免费、开源的 C 语言函数库,所以我们可以获取到它源代码。
libpng 依赖于 zlib 库,所以要想移植 libpng 先得移植 zlib 库才可以,zlib 也好、libpng 也好,其实移植过程非常简单,无非就是下载源码、编译源码这样的一些工作,那本小节就向大家介绍如何移植 zlib。在移植之前,先给大家说明一下,我们的开发板出厂系统都是已经移植好了这些库,其实是可以直接使用的,但是作为学习,必须要自己亲自把这些库给移植到开发板,这是非常重要的!

下载源码包

我们可以进入到 https://www.zlib.net/fossils/这个链接地址下载 zlib 源码包:
在这里插入图片描述往下翻,找到一个合适的版本,这里我们就选择 1.2.10 版本的 zlib:
在这里插入图片描述
点击文件名就可以下载了,下载成功之后就会得到.tar.gz 格式的压缩文件:
在这里插入图片描述

编译源码

将下载的 zlib-1.2.10.tar.gz 压缩文件拷贝到 Ubuntu 系统的用户家目录下,然后将其解压开:

tar -xzf zlib-1.2.10.tar.gz

在这里插入图片描述
解压之后就会得到 zlib-1.2.10 文件夹,这就是 zlib 的源代码目录。
在编译 zlib 之前,我们先在 tools 目录下创建一个名为 zlib 的文件夹,作为 zlib 库的安装目录:
在这里插入图片描述
接着我们进入到 zlib 的源码目录 zlib-1.2.10,如下所示:
在这里插入图片描述
同样也是执行三部曲:配置、编译、安装,一套流程下来就 OK 了!
在此之前,先对交叉编译工具的环境进行初始化,使用 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

执行下面这条命令对 zlib 工程进行配置:

./configure --prefix=/home/dt/tools/zlib/

–prefix 选项指定 zlib 库的安装目录,将家目录下的 tools/zlib 作为 zlib 库的安装目录。
在这里插入图片描述
配置完成之后,直接 make 编译:

make

在这里插入图片描述
编译完成之后,接着执行 make install 安装即可!

make ins

在这里插入图片描述

安装目录下的文件夹介绍

进入到 zlib 库的安装目录:
在这里插入图片描述
头文件目录 include 以及库文件目录 lib。
至此,zlib 库就已经编译好了,接下来我们需要把编译得到的库文件拷贝到开发板。

移植到开发板

进入到 zlib 安装目录下,将 lib 目录下的所有动态链接库文件拷贝到开发板 Linux 系统/usr/lib 目录;注意在拷贝之前,需要先将出厂系统中原有的 zlib 库文件删除,在开发板 Linux 系统下执行命令:

rm -rf /usr/lib/libz.* /lib/libz.*

删除之后,再将我们编译得到的 zlib 库文件拷贝到开发板/usr/lib 目录,拷贝库文件时,需要注意符号链接的问题,不能破坏原有的符号链接。
拷贝过去之后,开发板/usr/lib 目录下就应该存在这些库文件,如下所示:

在这里插入图片描述

21.4 libpng 移植

下载源码包

首先下载 libpng 源码包,进入 https://github.com/glennrp/libpng/releases 链接地址,如下:
在这里插入图片描述
下载完成之后,就会得到 libpng 的源码包:
在这里插入图片描述

编译源码

将下载的 libpng-1.6.35.tar.gz 压缩包文件拷贝到 Ubuntu 系统的用户家目录下,接着将其解压:
在这里插入图片描述
解压之后得到 libpng-1.6.35 文件夹,这便是 libpng 的源码目录。
在编译 libpng 之前,先在 tools 目录下创建一个名为 png 的文件夹,作为 libpng 库的安装目录:
在这里插入图片描述
接着我们进入到 libpng 源码目录下,同样也是执行三部曲:配置、编译、安装,一套流程下来就 OK 了!
在此之前,先对交叉编译工具的环境进行初始化,使用 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

libpng 依赖于 zlib 库,前面我们已经将 zlib 库编译成功了,但是我们得告知编译器 zlib 库的安装目录,这样编译器才能找到 zlib 的库文件以及头文件,编译 libpng 的时才不会报错。
执行下面这三条命令,将 zlib 库安装目录下的 include 和 lib 路径导出到环境变量:

export LDFLAGS="${LDFLAGS} -L/home/dt/tools/zlib/lib"
export CFLAGS="${CFLAGS} -I/home/dt/tools/zlib/include"
export CPPFLAGS="${CPPFLAGS} -I/home/dt/tools/zlib/include"

在这里插入图片描述
接着执行下面这条命令对 libpng 源码工程进行配置:

./configure --prefix=/home/dt/tools/png --host=arm-poky-linux-gnueabi

–prefix 选项指定 libpng 的安装目录,将家目录下的 tools/png 作为 libpng 的安装目录。
在这里插入图片描述接着执行 make 进行编译:

make

在这里插入图片描述
最后执行 make install 安装即可!

make install

在这里插入图片描述

安装目录下的文件夹介绍

进入到 libpng 安装目录:
在这里插入图片描述

移植到开发板

进入到 libpng 安装目录,将 bin 目录下的所有测试工具拷贝到开发板 Linux 系统/usr/bin 目录;将 lib 目录下的所有库文件拷贝到 Linux 系统/usr/lib 目录,注意在拷贝之前,先将开发板出厂系统中已经移植好的libpng 库文件删除,执行下面这条命令:

rm -rf /lib/libpng* /usr/lib/libpng*

删除之后,再将编译得到的 libpng 库文件拷贝到开发板/usr/lib 目录,拷贝库文件时,需要注意符号链接的问题,不能破坏原有的符号链接。
拷贝过去之后,开发板/usr/lib 目录下就应该存在这些库文件,如下所示:
在这里插入图片描述

21.5 libpng 使用说明

本小节向大家简单地介绍如何使用 libpng 对 png 图像进行解码,libpng 除了解码功能之外,还包含编码功能,也就是创建 png 压缩文件,当然,这个笔者就不再介绍了。libpng 官方提供一份非常详细地使用文档,笔者也是参考了这份文档给大家进行介绍的,这份文档的链接地址如下:
http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf
http://www.libpng.org/pub/png/libpng-manual.txt
这两份文档的内容是一样的,第一份是 pdf 文档、第二份是 txt 文档,如果大家想更加深入的了解、学习,那么可以查阅这份文档

libpng 的数据结构

首先,使用 libpng 库需要包含它的头文件<png.h>。png.h 头文件中包含了 API、数据结构的申明,libpng中有两个很重要的数据结构体:png_struct 和 png_info。png_struct 作为 libpng 库函数内部使用的一个数据结构体,除了作为传递给每个 libpng 库函数调用的第一个变量外,在大多数情况下不会被用户所使用。使用 libpng 之前,需要创建一个 png_struct 对象并对其进行初始化操作,该对象由 libpng 库内部使用,调用 libpng 库函数时,通常需要把这个对象作为参数传入。png_info 数据结构体描述了 png 图像的信息,在以前旧的版本中,用户可以直接访问 png_info 对象中的成员,譬如查看图像的宽、高、像素深度、修改解码参数等;然而,这往往会导致出现一些问题,因此新的版本中专门开发了一组 png_info 对象的访问接口:get 方法 png_get_XXX 和 set 方法 png_set_XXX,建议大家通过 API 来访问这些成员

创建和初始化 png_struct 对象

首先第一步是创建 png_struct 对象、并对其进行初始化操作,使用 png_create_read_struct()函数创建一个 png_struct 对象、并完成初始化操作,read 表示我们需要创建的是一个用于 png 解码的 png_struct 对象;同理可以使用 png_create_write_struct()创建一个用于 png 编码的 png_struct 对象。
png_create_read_struct 函数原型如下所示:

png_structp png_create_read_struct(png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, 
png_error_ptr warn_fn);

它的是返回值是一个 png_structp 指针,指向一个 png_struct 对象;所以 png_create_read_struct()函数创建 png_struct 对象之后,会返回一个指针给调用者,该指针指向所创建的 png_struct 对象。但如果创建对象失败,则会返回 NULL,所以调用者可以通过判断返回值是否为 NULL 来确定 png_create_read_struct()函数执行是否成功!

该函数有 4 个参数,第一个参数 user_png_ver 指的是 libpng 的版本信息,通常将其设置为PNG_LIBPNG_VER_STRING,这是 png.h 头文件中定义的一个宏,其内容便是 libpng 的版本号信息,如下:

#define PNG_LIBPNG_VER_STRING "1.6.35"

创建、初始化 png_struct 对象时,调用者可以指定自定义的错误处理函数和自定义的警告处理函数,通过参数 error_fn 指向自定义的错误处理函数、通过参数 warn_fn 指向自定义的警告处理函数,而参数 error_ptr表示传递给这些函数所使用的数据结构的指针;当然也可将它们设置为 NULL,表示使用 libpng 默认的错误处理函数以及警告函数。使用示例如下:

png_structp png_ptr = NULL;
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
return -1;

创建和初始化 png_info 对象

png_info数据结构体描述了png图像的信息,同样也需要创建png_info对象,调用png_create_info_struct()函数创建一个 png_info 对象,其函数原型如下所示:

png_infop png_create_info_struct(png_const_structrp png_ptr);

该函数返回一个 png_infop 指针,指向一个 png_info 对象,所以 png_create_info_struct()函数创建 png_info对象之后,会将它的指针返回给调用者;如果创建失败,则会返回 NULL,所以调用者可以通过判断返回值是否为 NULL 来确定函数调用是否成功!
该函数有一个参数,需要传入一个png_struct对象的指针,内部会将它们之间建立关联,当销毁png_struct对象时、也可将 png_info 对象销毁。使用示例如下:

png_infop info_ptr = NULL;
info_ptr = png_create_info_struct(png_const_structrp png_ptr);
if (NULL == info_ptr) {
png_destroy_read_struct(&png_ptr, NULL, NULL);
return -1;
}

png_destroy_read_struct()函数用于销毁 png_struct 对象的函数,后面再给大家介绍。

设置错误返回点

调用 png_create_read_struct()函数创建 png_struct 对象时,调用者可以指定一个自定义的错误处理函数,当 libpng 工作发生错误时,它就会执行这个错误处理函数;但如果调用者并未指定自定义的错误处理函数,那么 libpng 将会使用默认的错误处理函数,其实默认的错误处理函数会执行一个跳转动作,跳转到程序中的某一个位置,我们把这个位置称为错误返回点。

这样,当调用者未指定自定义错误处理函数时,当 libpng 遇到错误时,它会执行默认错误处理函数,而默认错误处理函数会跳转到错误返回点,通常这个错误返回点就是在我们程序中的某个位置,我们期望libpng 发生错误时能够回到我们的程序中,为什么要这样做呢?因为发生错误时不能直接终止退出,而需要执行释放、销毁等清理工作,譬如前面创建的 png_struct 和 png_info 对象,需要销毁,避免内存泄漏。

那如何在我们的程序中设置错误返回点呢?在此之前,笔者需要向大家介绍两个库函数:setjmp 和longjmp。

setjmp 和 longjmp
在 C 语言中,在一个函数中执行跳转,我们可以使用 goto 语句,笔者也经常使用 goto 语句,尤其是在开发驱动程序时;但 goto 语句只能在一个函数内部进行跳转,不能跨越函数,譬如从 func1()函数跳转到func2()函数,如果想要实现这种跨越函数间的跳转,在 Linux 下,我们可以使用库函数 setjmp 和 longjmp。
setjmp 函数用于设置跳转点,也就是跳转位置;longjmp 执行跳转,那么它会跳转到 setjmp 函数所设置的跳转点,来看看这两个函数的原型:

#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);

可以看到 setjmp 和 longjmp 函数都有一个 env 参数,这是一个 jmp_buf 类型的参数,jmp_buf 是一种特殊类型,当调用 setjmp()时,它会把当前进程环境的各种信息保存到 env 参数中,而调用 longjmp()也必须指定相同的参数,这样才可跳转到 setjmp 所设置的跳转点。

从编程角度来看,调用 longjmp()函数后,看起来就和第二次调用 setjmp()返回时完全一样,可以通过检查 setjmp()函数的返回值,来区分 setjmp()是初次调用返回还是第二次“返回”,初始调用返回值为 0,后续“伪”返回的返回值为 longjmp()调用中参数 val 所指定的任意值,通过对 val 参数使用不同的值,可以区分出程序中跳转到同一位置的多个不同的起跳位置。

所以,通常情况下,调用 longjmp()时,不会将参数 val 设置为 0,这样将会导致无法区分 setjmp()是初次返回还是后续的“伪”返回,这里大家要注意!好,那么关于这两个函数就向大家介绍这么多,我们来看一个例子:

示例代码 21.5.1 setjmp/longjmp 函数使用示例
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
static jmp_buf buf;
static void hello(void) {
 printf("hello world!\n");
 longjmp(buf,1);
 printf("Nice to meet you!\n");
}
int main(void) {
 if(0 == setjmp(buf)) {
 printf("First return\n");
 hello();
 }
 else
 printf("Second return\n");
 exit(0);
}

我们直接在 Ubuntu 系统下编译运行,运行结果如下所示:
在这里插入图片描述
打印结果就不再分析了,上面给大家讲解地非常清楚了。
libpng 设置错误返回点
libpng 库默认也使用 setjmp/longjmp 这两个库函数组合来处理发生错误时的跳转,当 libpng 遇到错误时,执行默认错误处理函数,默认错误处理函数会调用 longjmp()来进行跳转,所以我们需要使用 setjmp()来 为 libpng 设置一个错误返回点。设置方法如下:

/* 设置错误返回点 */
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return -1;
}

png_jmpbuf()函数可以获取到 png_struct 对象中的 jmp_buf 变量,那么后续 libpng 库调用 longjmp 执行跳转时也是使用这个变量。我们可以在错误返回点执行一些清理工作。

指定数据源

也就是指定需要进行解码的 png 图像,通常可以使用多种方式来指定数据源,譬如文件输入流、内存中的数据流等,这里笔者以文件输入流为例。
libpng 提供了 png_init_io()函数,png_init_io()可以指定数据源,该数据源以文件输入流的方式提供,来看看函数原型:

png_init_io(png_structrp png_ptr, png_FILE_p fp);

第一个参数是 png_ptr,指向 png_struct 对象;而第二个参数 fp 则是一个 png_FILE_p 类型指针,其实就是标准 I/O 中的 FILE *指针。所以由此可知,我们需要先使用 fopen()函数将 png 文件打开,然后得到指向该文件的 FILE *类型指针。

使用示例如下:

FILE *png_file = NULL;
/* 打开 png 文件 */
png_file = fopen("image.png", "r"); //以只读方式打开
if (NULL == png_file) {
perror("fopen error");
return -1;
}
/* 指定数据源 */
png_init_io(png_ptr, png_file);

读取 png 图像数据并解码

从 png 文件中读取数据并进行解码,将解码后的图像数据存放在内存中,待用户读取。关于这一步的操作,libpng 提供了两种方式去处理:high-level 接口处理和 low-level 接口处理。其实 high-level 只是对 lowlevel 方式进行了一个封装,使用 high-level 接口非常方便只需一函数即可,但缺点是灵活性不高、被限定了;而 low-level 接口恰好相反,灵活性高、但需要用户调用多个 API;所以具体使用哪种方式要看你的需求。

high-level 接口
通常在满足以下两个条件时使用 high-level 接口:
⚫ 用户的内存空间足够大,可以一次性存放整个 png 文件解码后的数据;
⚫ 数据输出格式限定为 libpng 预定义的数据转换格式。
在满足以上两个条件时,可以使用 high-level 接口,libpng 预定义数据转换类型包括:
在这里插入图片描述
后面的注释说明大家自己去翻译,我怕翻译错了,把你们带入坑!
这些转换当中,还不包括背景颜色设置(透明图)、伽马变换、抖动和填充物等,使用 high-level 接口只能使用以上这些预定义的转换类型,而其它的配置则保持默认。

high-level 接口只需要使用一个函数 png_read_png(),调用该函数将一次性把整个 png 文件的图像数据解码出来、将解码后的数据存放在内存中,如下所示:

png_read_png(png_structrp png_ptr, png_inforp info_ptr, int transforms, png_voidp params);

g_read_png(png_structrp png_ptr, png_inforp info_ptr, int transforms, png_voidp params);第一个参数 png_ptr 为指向 png_struct 对象的指针,第二个参数 info_ptr 为指向 png_info 对象的指针;而第三个参数 transforms 为整型参数,取值为上表所列出的 libpng 预定义的数据转换类型,可以使用 or(C语言的或 | 运算符)组合多个转换类型。使用示例如下:

png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_ALPHA, NULL);

该函数相当于调用一系列 low-level 函数(下文将会介绍),调用顺序如下所示:
⑴、调用 png_read_info 函数获得 png 图像信息;
⑵、根据参数 transforms 所指定的转换类型对数据输出转换格式进行设置;
⑶、调用 png_read_image 一次性把整个 png 文件的图像数据解码出来、并将解码后的数据存放在内存中。
⑷、调用 png_read_end 结束解码。

low-level 接口
使用 low-level 接口,需要用户将函数 png_read_png()所做的事情一步一步执行:
a)、读取 png 图像的信息
首先我们要调用 png_read_info()函数获取 png 图像的信息:

png_read_info(png_ptr, info_ptr);

该函数会把 png 图像的信息读入到 info_ptr 指向的 png_info 对象中。
b)、查询图像的信息
前面提到 png_read_info()函数会把 png 图像的信息读入到 png_info 对象中,接下来我们可以调用 libpng提供的 API 查询这些信息。

unsigned int width = png_get_image_width(png_ptr, info_ptr); //获取 png 图像的宽度
unsigned int height = png_get_image_height(png_ptr, info_ptr); //获取 png 图像的高度
unsigned char depth = png_get_bit_depth(png_ptr, info_ptr); //获取 png 图像的位深度
unsigned char color_type = png_get_color_type(png_ptr, info_ptr); //获取 png 图像的颜色类型

color type 在 png.h 头文件中定义,如下所示:

/* These describe the color_type field in png_info. */
/* color type masks */
#define PNG_COLOR_MASK_PALETTE 1
#define PNG_COLOR_MASK_COLOR 2
#define PNG_COLOR_MASK_ALPHA 4
/* color types. Note that not all combinations are legal */
#define PNG_COLOR_TYPE_GRAY 0
#define PNG_COLOR_TYPE_PALETTE (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE)
#define PNG_COLOR_TYPE_RGB (PNG_COLOR_MASK_COLOR)
#define PNG_COLOR_TYPE_RGB_ALPHA (PNG_COLOR_MASK_COLOR | 
PNG_COLOR_MASK_ALPHA)
#define PNG_COLOR_TYPE_GRAY_ALPHA (PNG_COLOR_MASK_ALPHA)
/* aliases */
#define PNG_COLOR_TYPE_RGBA PNG_COLOR_TYPE_RGB_ALPHA
#define PNG_COLOR_TYPE_GA PNG_COLOR_TYPE_GRAY_ALPHA

c)、设置解码输出参数(转换参数)
这步非常重要,用户可以指定数据输出转换的格式,比如 RGB888,BGR888、ARGB8888 等数据输出格式,libpng 提供了很多 set 方法(png_set_xxxxx 函数)来实现这些设置,例如如下代码:

unsigned char depth = png_get_bit_depth(png_ptr, info_ptr);
unsigned char color_type = png_get_color_type(png_ptr, info_ptr);
if (16 == depth)
png_set_strip_16(png_ptr); //将 16 位深度转为 8 位深度
if (8 > depth)
png_set_expand(png_ptr); //如果位深小于 8,则扩展为 24-bit RGB
if (PNG_COLOR_TYPE_GRAY_ALPHA == color_type)
png_set_gray_to_rgb(png_ptr); //如果是灰度图,则转为 RGB

关于这些函数的作用和使用方法,大家可以打开 libpng 的头文件 png.h 进行查看,每个函数它都有相应的注释信息以及参数列表。如上我们列举了几个 png_set_xxx 转换函数,这种转换函数还很多,这里便不再一一进行介绍,具体请查看 libpng 的使用手册以了解他们的作用。

虽然 libpng 提供了很多转换函数,可以调用它们对数据的输出格式进行设置,但是用户的需求是往往无限的,很多输出格式 libpng 并不是原生支持的,譬如 YUV565、RGB565、YUYV 等,为了解决这样的问题,libpng 允许用户设置自定义转换函数,可以让用户注册自定义转换函数给 libpng 库,libpng 库对输出数据进行转换时,会调用用户注册的自定义转换函数进行转换。

调用者通过 png_set_read_user_transform_fn()函数向 libpng 注册一个自定义转换函数,另外调用者还可以通过 png_set_user_transform_info()函数告诉 libpng 自定义转换函数的用户自定义数据结构和输出数据的详细信息,比如颜色深度、颜色通道(channel)等等。关于这些内容,大家自己去查阅 libpng 的使用帮助文档。

d)、更新 png 数据的详细信息
经过前面的设置之后,信息肯定会有一些变化,我们需要调用 png_read_update_info 函数更新信息:

png_read_update_info(png_ptr, info_ptr);

该函数将会更新保存在 info_ptr 指向的 png_info 对象中的图像信息。

e)、读取 png 数据并解码
前面设置完成之后,接下来便可对 png 文件的数据进行解码了。调用 png_read_image()函数可以一次性把整个 png 文件的图像数据解码出来、并将解码后的数据存放在用户提供的内存区域中,使用示例如下:

png_read_image(png_ptr, row_pointers);

该函数无返回值,参数 png_ptr 指向 png_struct 对象;第二个参数 row_pointers 是一个 png_bytepp 类型的指针变量,也就是 unsigned char **,是一个指针数组,如下所示:

png_bytep row_pointers[height];

调用该函数,需要调用者提供足够大的内存空间,可以保存整个图像的数据,这个内存空间的大小通常是解码后数据的总大小;调用者分配内存空间后,需要传入指向每一行的指针数组,如下所示:

png_bytep row_pointers[height] = {0};
size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);//获取每一行数据的字节大小
int row;
/* 为每一行数据分配一个缓冲区 */
for (row = 0; row < height; row++)
row_pointers[row] = png_malloc(png_ptr, rowbytes);
png_read_image(png_ptr, row_pointers);

Tips:png_malloc()函数是 libpng 提供的一个 API,其实就等价于库函数 malloc。除了 png_read_image()函数之外,我们也可以调用 png_read_rows()一次解码 1 行或多行数据、并将解码后的数据存放在用于提供的内存区域中,譬如:

size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);//获取每一行数据的字节大小
png_bytep row_buf = png_malloc(png_ptr, rowbytes);//分配分缓冲、用于存储一行数据
int row;
for (row = 0; row < height; row++) {
png_read_rows(png_ptr, &row_buf, NULL, 1);//每次读取、解码一行数据(最后一个数字 1 表示每次 1 行)
/* 对这一行数据进行处理: 譬如刷入 LCD 显存进行显示 */
do_something();
}

png_read_rows 会自动跳转处理下一行数据。
由此可知,在 low-level 接口,调用 png_read_image()或 png_read_rows()函数都需要向 libpng 提供用于存放数据的内存区域。但是在 high-level 接口中,调用 png_read_png()时我们并不需要自己分配缓冲区,png_read_png()函数内部会自动分配一块缓冲区,那我们如何获取到它分配的缓冲区呢?通过 png_get_rows()函数得到,下小节介绍。

f)、png_read_end()结束读取、解码
当整个 png 文件的数据已经读取、解码完成之后,我们可以调用 png_read_end()结束,代码如下:

png_read_end(png_ptr, info_ptr);

读取解码后的数据

解码完成之后,我们便可以去获取解码后的数据了,要么那它们做进一步的处理、要么直接刷入显存显示到 LCD 上;对于 low-level 方式,存放图像数据的缓冲区是由调用者分配的,所以直接从缓冲区中获取数据即可!

对于 high-level 方式,存放图像数据的缓冲区是由 png_read_png()函数内部所分配的,并将缓冲区与png_struct 对象之间建立了关联,我们可以通过 png_get_rows()函数获取到指向每一行数据缓冲区的指针数组,如下所示:

png_bytepp row_pointers = NULL;
row_pointers = png_get_rows(png_ptr, info_ptr);//获取到指向每一行数据缓冲区的指针数组

当我们销毁 png_struct 对象时,由 png_read_png()所分配的缓冲区也会被释放归还给操作系统。

结束销毁对象

调用 png_destroy_read_struct()销毁 png_struct 对象,该函数原型如下所示:

void png_destroy_read_struct(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr);

使用方法如下:

png_destroy_read_struct(png_ptr, info_ptr, NULL);

21.6 libpng 应用编程

经过上一小节的介绍后,相信大家已经知道了如何使用 libpng 库对 png 图像进行解码,本小节我们将进行实战,使用 libpng 库对一张指定的 png 图像进行解码,并在 LCD 上显示图像。示例代码如下所示:

示例代码 21.6.1 libpng 应用编程示例代码
#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 <png.h>
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_png_image(const char *path) {
 png_structp png_ptr = NULL;
 png_infop info_ptr = NULL;
 FILE *png_file = NULL;
 unsigned short *fb_line_buf = NULL; //行缓冲区:用于存储写入到 LCD 显存的一行数据
 unsigned int min_h, min_w;
 unsigned int valid_bytes;
 unsigned int image_h, image_w;
 png_bytepp row_pointers = NULL;
 int i, j, k;
 /* 打开 png 文件 */
 png_file = fopen(path, "r"); //以只读方式打开
 if (NULL == png_file) {
 perror("fopen error");
 return -1;
 }
 /* 分配和初始化 png_ptr、info_ptr */
 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
 if (!png_ptr) {
 fclose(png_file);
 return -1;
 }
 info_ptr = png_create_info_struct(png_ptr);
 if (!info_ptr) {
 png_destroy_read_struct(&png_ptr, NULL, NULL);
 fclose(png_file);
 return -1;
 }
 /* 设置错误返回点 */
 if (setjmp(png_jmpbuf(png_ptr))) {
 png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
 fclose(png_file);
 return -1;
 }
 /* 指定数据源 */
 png_init_io(png_ptr, png_file);
 /* 读取 png 文件 */
 png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_ALPHA, NULL);
 image_h = png_get_image_height(png_ptr, info_ptr);
 image_w = png_get_image_width(png_ptr, info_ptr);
 printf("分辨率: %d*%d\n", image_w, image_h);
 /* 判断是不是 RGB888 */
 if ((8 != png_get_bit_depth(png_ptr, info_ptr)) &&
 (PNG_COLOR_TYPE_RGB != png_get_color_type(png_ptr, info_ptr))) {
 printf("Error: Not 8bit depth or not RGB color");
 png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
 fclose(png_file);
 return -1;
 }
 /* 判断图像和 LCD 屏那个的分辨率更低 */
 if (image_w > width)
 min_w = width;
 else
 min_w = image_w;
 if (image_h > height)
 min_h = height;
 else
 min_h = image_h;
 valid_bytes = min_w * bpp / 8;
 /* 读取解码后的数据 */
 fb_line_buf = malloc(valid_bytes);
 row_pointers = png_get_rows(png_ptr, info_ptr);//获取数据
 unsigned int temp = min_w * 3; //RGB888 一个像素 3 个 bit 位
 for(i = 0; i < min_h; i++) {
 // RGB888 转为 RGB565
 for(j = k = 0; j < temp; j += 3, k++)
 fb_line_buf[k] = ((row_pointers[i][j] & 0xF8) << 8) |
 ((row_pointers[i][j+1] & 0xFC) << 3) |
 ((row_pointers[i][j+2] & 0xF8) >> 3);
 memcpy(screen_base, fb_line_buf, valid_bytes);//将一行数据刷入显存
 screen_base += width; //定位到显存下一行
 }
 /* 结束、销毁/释放内存 */
 png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
 free(fb_line_buf);
 fclose(png_file);
 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 <png_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_png_image(argv[1]);
 /* 退出 */
 munmap(screen_base, screen_size); //取消映射
 close(fd); //关闭文件
 exit(EXIT_SUCCESS); //退出进程
}

代码不再进行讲解,示例代码中所使用到的函数都已经给大家介绍过,上述示例代码使用的是 high-level接口处理方式,直接调用了 png_read_png,一次性把整个 png 文件的数据解码出来,由于得到的数据是RGB888 格式,所以我们需要将其转为 RGB565,转换完成之后将其刷入到显存中。

接下来我们编译示例代码,这里要注意下,使用交叉编译器编译代码时,需要指定 libpng 库和 zlib 库,如下所示:

${CC} -o testApp testApp.c -I/home/dt/tools/png/include -L/home/dt/tools/png/lib -L/home/dt/tools/zlib/lib -
lpng -lz

在这里插入图片描述
使用-I 选项指定 libpng 的头文件(也就是安装目录下的 include 目录),不需要指定 zlib 的头文件;使用了两次-L 选项,分别指定了 libpng 和 zlib 的库目录(也就是安装目录下的 lib 目录);再使用-l 选项指定需要链接的库(z 表示 libz.so、png 表示 libpng.so)。

将编译得到的可执行文件拷贝到开发板 Linux 系统的用户家目录下,并准备一个 png 文件,接着执行测试程序(执行测试程序前,先关闭出厂系统的 Qt GUI 应用程序):
在这里插入图片描述
可以看到打印出了一些警告信息,原因是新版本的 libpng 增强了检查,发出了警告;不过这并不影响我们的使用,可以忽略。
此时开发板 LCD 上会显示我们指定的 png 图像,如下所示:
在这里插入图片描述
本章内容到此结束!

二级目录

三级目录

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

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

相关文章

win11个性化锁屏界面怎么关闭?

win11个性化锁屏界面关闭方法对于win11用户来说&#xff0c;关闭个性化锁屏界面是一个常见问题。本文将由php小编苹果详细介绍如何执行此操作&#xff0c;分步指导并提供操作截图。继续阅读以了解具体步骤。 win11个性化锁屏界面关闭方法 第一步&#xff0c;点击底部Windows图…

企信通_企信通短信群发平台

现代社会&#xff0c;随着互联网技术的快速发展&#xff0c;传统的营销方式已经无法满足企业对于市场开拓和客户沟通的需求。群发作为一种高效、低成本的营销手段&#xff0c;逐渐成为了众多企业的首选。而在众多群发平台中&#xff0c;嘀迈信息企信通公司凭借其稳定可靠的服务…

GM EPUB Reader Pro for Mac:专业电子书阅读工具

GM EPUB Reader Pro是一款适用于Mac的专业EPUB阅读软件。它为用户提供了优质的阅读体验和丰富的功能。 GM EPUB Reader Pro支持EPUB格式&#xff0c;这是一种广泛使用的电子书格式&#xff0c;常用于小说、教育书籍、期刊等。您可以通过该软件打开和阅读EPUB文件&#xff0c;享…

提取网页元数据的Python库之lassie使用详解

概要 Lassie是一个用于提取网页元数据的Python库,它能够智能地抓取网页的标题、描述、关键图像等内容。Lassie的设计目的是为了简化从各种类型的网页中提取关键信息的过程,适用于需要预览链接内容的应用场景。 安装 安装Lassie非常简单,可以通过Python的包管理器pip进行安…

WPS二次开发系列:一文快速了解WPS SDK功能场景

作者持续关注 WPS二次开发专题系列&#xff0c;持续为大家带来更多有价值的WPS开发技术细节&#xff0c;如果能够帮助到您&#xff0c;请帮忙来个一键三连&#xff0c;更多问题请联系我&#xff08;QQ:250325397&#xff09; 目录 SDK功能介绍 功能详解&#xff1a; 打开文档…

Windows系统完全卸载删除 Node.js (包含控制面板找不到node.js选项情况)

1.打开cmd命令行窗口&#xff0c;输入npm cache clean --force 回车执行 2.打开控制面板&#xff0c;在控制面板中把Node.js卸载 移除之后检查环境变量是否也移除&#xff1a;点击Path&#xff0c;点击编辑。 把环境变量中和node有关的全部移除&#xff0c;然后点击确定。 3.重…

WEB基础--JDBC基础

JDBC简介 JDBC概述 数据库持久化介绍 jdbc是java做数据库持久化的规范&#xff0c;持久化(persistence)&#xff1a;把数据保存到可掉电式存储设备(断电之后&#xff0c;数据还在&#xff0c;比如硬盘&#xff0c;U盘)中以供之后使用。大多数情况下&#xff0c;特别是企业级…

Gartner发布准备应对勒索软件攻击指南:勒索软件攻击的三个阶段及其防御生命周期

攻击者改变了策略&#xff0c;在某些情况下转向勒索软件。安全和风险管理领导者必须通过提高检测和预防能力来为勒索软件攻击做好准备&#xff0c;同时还要改进其事后应对策略。 主要发现 勒索软件&#xff08;无加密的数据盗窃攻击&#xff09;是攻击者越来越多地使用的策略。…

SpringBoot启动流程分析之创建SpringApplication对象(一)

SpringBoot启动流程分析之创建SpringApplication对象(一) 目录&#xff1a; 文章目录 SpringBoot启动流程分析之创建SpringApplication对象(一)1、SpringApplication的构造方法1.1、推断应用程序类型1.2、设置Initializers1.3、设置Listener1.4、推断main方法所在类 流程分析…

Seata之XA 模式的使用

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 Seata 是一款开源的…

计算机专业,求你别再玩了,我都替你们着急

明确学习目标和方向&#xff1a;确定自己希望在计算机领域的哪个方向深入发展&#xff0c;如前端开发、后端开发、数据库管理、人工智能等。根据目标方向&#xff0c;制定详细的学习计划&#xff0c;确保所学知识与未来职业方向相匹配。 【PDF学习资料文末获取】 扎实基础知识…

变配电工程 变配电室智能监控系统 门禁 视频 环境 机器人

一、方案背景 要真正了解无人值守配电房的运行模式&#xff0c;我们必须对“无人值守”这一概念有准确的理解。它并不意味着完全没有工作人员管理&#xff0c;而是通过技术设备和人机协作来确保配电房的正常运行。 利用变配电室智能监控系统&#xff0c;可以实时获得配电室各…

【优选算法】—Leetcode—11—— 盛最多水的容器

1.题目 11. 盛最多水的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#…

滑动窗口详解

目录 一、滑动窗口的特定步骤&#xff1a; 二、题目解析 1、⻓度最⼩的⼦数组---点击跳转题目 3、最⼤连续 1 的个数 III----点击跳转题目 4、将 x 减到 0 的最⼩操作数----点击跳转题目 5、⽔果成篮----点击跳转题目 滑动窗口是双指针算法中细分的一种&#xff0c;它由暴…

【AutoGPT】踩坑帖(follow李鱼皮)

本文写于2024年5月7日 参考视频&#xff1a;AutoGPT傻瓜式使用教程真实体验&#xff01; 对应文章&#xff1a;炸裂的AutoGPT&#xff0c;帮我做了个网站&#xff01; 平台&#xff1a;GitPod 云托管服务 原仓库已经改动很大&#xff0c;应使用的Repo为&#xff1a;Auto-GPT-ZH…

java后端15问!

前言 最近一位粉丝去面试一个中厂&#xff0c;Java后端。他说&#xff0c;好几道题答不上来&#xff0c;于是我帮忙整理了一波答案 G1收集器JVM内存划分对象进入老年代标志你在项目中用到的是哪种收集器&#xff0c;怎么调优的new对象的内存分布局部变量的内存分布Synchroniz…

中职大数据专业介绍:大数据技术应用

近年来&#xff0c;人工智能在经济发展、社会进步、国际政治经济格局等方面已经产生重大而深远的影响。规划纲要对“十四五”及未来十余年我国人工智能的发展目标、核心技术突破、智能化转型与应用&#xff0c;以及保障措施等多个方面都作出了部署。 据2020年全国教育事业发展统…

运用分支结构与循环结构写一个猜拳小游戏

下面我们运用平常所学的知识来写一个小游戏&#xff0c;这样能够加强我们学习的趣味性&#xff0c;并且能够更加的巩固我们所学的知识。 游戏代码&#xff1a; 直接放代码&#xff1a;&#xff08;手势可以使用数字来代替&#xff0c;比如0对应石头&#xff0c;1对应剪刀&…

Qexo:让你的静态博客动起来

Qexo是一个强大而美观的在线静态博客编辑器&#xff0c;它不仅限于编辑&#xff0c;而是将静态博客提升到新的高度。通过GPL3.0开源协议&#xff0c;Qexo提供了一个集编辑、管理、扩展于一体的平台&#xff0c;让静态博客也能拥有动态的元素。无论你是Hexo、Hugo还是Valaxy的用…

【论文阅读】<YOLOP: You Only Look Once for PanopticDriving Perception>

Abstract 全视驾驶感知系统是自动驾驶的重要组成部分。一个高精度的实时感知系统可以帮助车辆在驾驶时做出合理的决策。我们提出了一个全视驾驶感知网络&#xff08;您只需寻找一次全视驾驶感知网络&#xff08;YOLOP&#xff09;&#xff09;&#xff0c;以同时执行交通目标检…
最新文章