百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

正点原子I.MX6U嵌入式Linux C应用编 第二十二章 在LCD上显示png图片

toyiye 2024-06-22 20:06 9 浏览 0 评论

在LCD上显示png图片

上一章介绍了如何使用libjpeg库对jpeg图像进行解码、并显示到LCD屏上,除了jpeg图像之外,png图像也很常见,那本章我们就来学习如何对png图像进行解码、并显示到LCD屏上。

本章将会讨论如下主题。

  • PNG简介;
  • libpng库简介;
  • libpng库移植;
  • 使用libpng库函数对PNG图像进行解码;

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格式就介绍这么多。

libpng简介

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

zlib移植

zlib其实是一套包含了数据压缩算法的函式库,此函数库为自由软件,是一套免费、开源的C语言函数库,所以我们可以获取到它源代码。

libpng依赖于zlib库,所以要想移植libpng先得移植zlib库才可以,zlib也好、libpng也好,其实移植过程非常简单,无非就是下载源码、编译源码这样的一些工作,那本小节就向大家介绍如何移植zlib。

在移植之前,先给大家说明一下,我们的开发板出厂系统都是已经移植好了这些库,其实是可以直接使用的,但是作为学习,必须要自己亲自把这些库给移植到开发板,这是非常重要的!

下载源码包

我们可以进入到https://www.zlib.net/fossils/这个链接地址下载zlib源码包:

图 22.3.1 zlib源码下载链接

往下翻,找到一个合适的版本,这里我们就选择1.2.10版本的zlib:

图 22.3.2 选择一个版本

点击文件名就可以下载了,下载成功之后就会得到.tar.gz格式的压缩文件:

图 22.3.3 zlib-1.2.10.tar.gz文件

编译源码

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

tar -xzf zlib-1.2.10.tar.gz

图 22.3.4 将zlib压缩文件解压

解压之后就会得到zlib-1.2.10文件夹,这就是zlib的源代码目录。

在编译zlib之前,我们先在tools目录下创建一个名为zlib的文件夹,作为zlib库的安装目录:

图 22.3.5 创建zlib文件夹

接着我们进入到zlib的源码目录zlib-1.2.10,如下所示:

图 22.3.6 zlib源码目录下的文件

同样也是执行三部曲:配置、编译、安装,一套流程下来就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库的安装目录。

图 22.3.7 配置zlib源码工程

配置完成之后,直接make编译:

make

图 22.3.8 make编译zlib

编译完成之后,接着执行make install安装即可!

make install

图 22.3.9

安装目录下的文件夹介绍

进入到zlib库的安装目录:

图 22.3.10 zlib安装目录下的文件夹

头文件目录include以及库文件目录lib。

至此,zlib库就已经编译好了,接下来我们需要把编译得到的库文件拷贝到开发板。

移植到开发板

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

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

删除之后,再将我们编译得到的zlib库文件拷贝到开发板/usr/lib目录,拷贝库文件时,需要注意符号链接的问题,不能破坏原有的符号链接。

拷贝过去之后,开发板/usr/lib目录下就应该存在这些库文件,如下所示:

图 22.3.11 开发板/usr/lib目录下的zlib库文件

libpng移植

移植好zlib库之后,接着我们开始移植libpng。

下载源码包

首先下载libpng源码包,进入https://github.com/glennrp/libpng/releases链接地址,如下:

图 22.4.1 libpng源码下载链接

我们直接下载这个最新的版本1.6.35,点击下面的Source Code(tar.gz)压缩文件进行下载:

图 22.4.2 选择1.6.35版本tar.gz压缩文件

下载完成之后,就会得到libpng的源码包:

图 22.4.3 libpng源码包

编译源码

将下载的libpng-1.6.35.tar.gz压缩包文件拷贝到Ubuntu系统的用户家目录下,接着将其解压:

图 22.4.4 将libpng-1.6.35.tar.gz解压

解压之后得到libpng-1.6.35文件夹,这便是libpng的源码目录。

在编译libpng之前,先在tools目录下创建一个名为png的文件夹,作为libpng库的安装目录:

图 22.4.5 创建png目录

接着我们进入到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"

图 22.4.6 导出环境变量

接着执行下面这条命令对libpng源码工程进行配置:

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

--prefix选项指定libpng的安装目录,将家目录下的tools/png作为libpng的安装目录。

图 22.4.7 配置libpng源码工程

接着执行make进行编译:

make

图 22.4.8 make编译

最后执行make install安装即可!

make install

图 22.4.9 make install安装

安装目录下的文件夹介绍

进入到libpng安装目录:

图 22.4.10 libpng安装目录下的文件夹

同样包含了bin、include、lib这些目录。

移植到开发板

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

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

删除之后,再将编译得到的libpng库文件拷贝到开发板/usr/lib目录,拷贝库文件时,需要注意符号链接的问题,不能破坏原有的符号链接。

拷贝过去之后,开发板/usr/lib目录下就应该存在这些库文件,如下所示:

图 22.4.11 开发板/usr/lib目录下的libpng相关库文件

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()函数执行是否成功!

该函数有B 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()是初次返回还是后续的“伪”返回,这里大家要注意!

好,那么关于这两个函数就向大家介绍这么多,我们来看一个例子:

本例程源码对应的路径为:开发板光盘->11、Linux C应用编程例程源码->22_libpng->setjmp.c

示例代码 22.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系统下编译运行,运行结果如下所示:

图 22.5.1 测试程序运行结果

打印结果就不再分析了,上面给大家讲解地非常清楚了。

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只是对low-level方式进行了一个封装,使用high-level接口非常方便只需一个函数即可,但缺点是灵活性不高、被限定了;而low-level接口恰好相反,灵活性高、但需要用户调用多个API;所以具体使用哪种方式要看你的需求。

high-level接口

通常在满足以下两个条件时使用high-level接口:

  • 用户的内存空间足够大,可以一次性存放整个png文件解码后的数据;
  • 数据输出格式限定为libpng预定义的数据转换格式。

在满足以上两个条件时,可以使用high-level接口,libpng预定义数据转换类型包括:

libpng预定义转换类型

说明

PNG_TRANSFORM_IDENTITY

No transformation

PNG_TRANSFORM_STRIP_16

Strip 16-bit samples to 8 bits

PNG_TRANSFORM_STRIP_ALPHA

Discard the alpha channel

PNG_TRANSFORM_PACKING

Expand 1, 2 and 4-bit samples to bytes

PNG_TRANSFORM_PACKSWAP

Change order of packed pixels to LSB first

PNG_TRANSFORM_EXPAND

Perform set_expand()

PNG_TRANSFORM_INVERT_MONO

Invert monochrome images

PNG_TRANSFORM_SHIFT

Normalize pixels to the sBIT depth

PNG_TRANSFORM_BGR

Flip RGB to BGR, RGBA to BGRA

PNG_TRANSFORM_SWAP_ALPHA

Flip RGBA to ARGB or GA to AG

PNG_TRANSFORM_INVERT_ALPHA

Change alpha from opacity to transparency

PNG_TRANSFORM_SWAP_ENDIAN

Byte-swap 16-bit samples

PNG_TRANSFORM_GRAY_TO_RGB

Expand grayscale samples to RGB (or GA to RGBA)

表 22.5.1 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);

第一个参数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);

libpng应用编程

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

本例程源码对应的路径为:开发板光盘->11、Linux C应用编程例程源码->22_libpng->show_png_image.c

示例代码 22.6.1 libpng应用编程示例代码
/***************************************************************
Copyright ? ALIENTEK Co., Ltd. 1998-2021. All rights reserved.
文件名 : show_png_image.c
作者 : 邓涛
版本 : V1.0
描述 : libpng使用实战
其他 : 无
论坛 : www.openedv.com
日志 : 初版 V1.0 2021/6/15 邓涛创建
***************************************************************/
#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

图 22.6.1 编译示例代码

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

将编译得到的可执行文件拷贝到开发板Linux系统的用户家目录下,并准备一个png文件,接着执行测试程序(执行测试程序前,先关闭出厂系统的Qt GUI应用程序):

图 22.6.2 执行测试程序

可以看到打印出了一些警告信息,原因是新版本的libpng增强了检查,发出了警告;不过这并不影响我们的使用,可以忽略。

此时开发板LCD上会显示我们指定的png图像,如下所示:

图 22.6.3 LCD显示png图像

本章内容到此结束!

相关推荐

落叶知秋的图片爬取(落叶知秋的图片有哪些?)

importrequestsfrombs4importBeautifulSoupimporttimeimportjsonpathimportjsonfromurllib.parsei...

小心有毒!长沙海关查获藏匿在“巧克力威化涂层”中的大麻

来源:海关发布近日,长沙黄花机场海关对一票申报为“巧克力威化涂层”的进境快件进行机检查验时,在包裹内查获封装于各独立威化饼干包装袋中的大麻230克。另从其他申报为“巧克力、儿童早餐谷物”的快件中查获藏...

钧正平:编造传播这种谣言,荒谬(钧正公司)

来源:钧正平工作室官方微博【钧评编造传播这种谣言,荒谬!】目前,乌克兰安全形势还在迅速变化之中,各方面安全风险上升。相关事件网上热度极高,倍受瞩目。然而,有一些人却借机大肆制造散播一些低级谣言,比如...

幸运角色过去了,谈一谈DNF起源的元素

总的来说伤害比上个版本强太多了,打卢克每日和团本明显能感觉的到。目前打团B套+圣耀稍微打造下应该都能随便二拖了。组队基本上都是秒秒秒(以前得强力辅助,现在随便带个毒奶都行)。单刷除了王座和顶能源阿斯兰...

DNF元素超大凉打桩测试(把括号的伤害加起来好像比较正常)

最近修练场的二觉老是很奇怪,发现以前都是习惯性先减抗然后丢二觉,结果伤害。。。直接丢二觉就正常了下面是其他技能伤害,没达到BUG线,估计问题不大。装备打造方面:全身红字加起来353(41*5+74*2...

ANSYS接触和出图技巧(ansys rough接触)

1.ANSYS后处理时如何按灰度输出云图?1)你可以到utilitymenu-plotctrls-style-colors-windowcolors试试2)直接utilitymenu-plotctr...

ANSYS有限元使用经验总结-后处理(4)

28.求塑性极限荷载时,结构的变形应该较大,建议把大变形打开。...

CFopen21.1、CFopen21.2都来了(cfile open)

[呲牙][赞][加油]

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

取消回复欢迎 发表评论:

请填写验证码