在 LCD 上显示 png 图片
PNG 简介
无损压缩:PNG 使用 LZ77 派生算法进行无损压缩,确保图像质量不受损,且压缩比高
体积小:通过高压缩比,PNG 文件体积小,适合网络传输
索引彩色模式:PNG-8 格式采用8位调色板,将RGB图像转换为索引彩色图像,减少数据量
优化的网络传输显示:PNG 支持流式浏览,允许图像在未完全下载前显示基本内容,适合网络通信
支持透明效果:PNG 提供256级透明度,使图像边缘与背景平滑融合,这是GIF和JPEG不具备的特性
libpng 简介
libpng 是用于处理 PNG 图像的库
功能:支持对 PNG 图像文件进行解码和编码
性质:免费、开源的 C 语言函数库
对比:与 libjpeg 类似
zlib 移植
zlib 简介
-
zlib 是一个包含数据压缩算法的函数库,是免费、开源的 C 语言库
-
libpng 依赖:libpng 依赖于 zlib 库,因此在移植 libpng 之前需要先移植 zlib
下载源码包
-
选择 1.2.10 版本的 zlib
编译源码
-
将下载的 zlib-1.2.10.tar.gz 压缩文件拷贝到 Ubuntu 系统的用户家目录下
-
tar -xzf zlib-1.2.10.tar.gz
- 解压之后就会得到 zlib-1.2.10 文件夹,这就是 zlib 的源代码目录
-
先在 tools 目录下创建一个名为 zlib 的文件夹,作为 zlib 库的安装目录
-
配置
-
先对交叉编译工具的环境进行初始化
- source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
-
对 zlib 工程进行配置
-
./configure --prefix=/home/alientek/tools/zlib/
-
--prefix 选项指定 zlib 库的安装目录
-
-
-
编译
- make
-
安装
- make install
安装目录下的文件夹介绍
-
头文件目录 include
-
库文件目录 lib
移植到开发板
-
将出厂系统中原有的 zlib 库文件删除
- rm -rf /usr/lib/libz.* /lib/libz.*
-
将编译得到的 zlib 库文件拷贝到开发板/usr/lib 目录,拷贝库文件时,需要注意符号
链接的问题,不能破坏原有的符号链接
- 开发板/usr/lib 目录下的 zlib 库文件
libpng 移植
下载源码包
编译源码
-
将下载的 libpng-1.6.35.tar.gz 压缩包文件拷贝到 Ubuntu 系统
-
解压:tar -xzf libpng-1.6.35.tar.gz
-
解压之后得到 libpng-1.6.35 文件夹,这便是 libpng 的源码目录
-
在编译 libpng 之前,先在 tools 目录下创建一个名为 png 的文件夹,作为 libpng 库的安装目录
-
配置
-
先对交叉编译工具的环境进行初始化
- source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
-
得告知编译器 zlib 库的安装目录,将 zlib 库安装目录下的 include 和 lib 路径导出到环境变量
- export LDFLAGS=" L D F L A G S − L / h o m e / d t / t o o l s / z l i b / l i b " e x p o r t C F L A G S = " {LDFLAGS} -L/home/dt/tools/zlib/lib" export CFLAGS=" LDFLAGS−L/home/dt/tools/zlib/lib"exportCFLAGS="{CFLAGS} -I/home/dt/tools/zlib/include"
export CPPFLAGS="${CPPFLAGS} -I/home/dt/tools/zlib/include"
- export LDFLAGS=" L D F L A G S − L / h o m e / d t / t o o l s / z l i b / l i b " e x p o r t C F L A G S = " {LDFLAGS} -L/home/dt/tools/zlib/lib" export CFLAGS=" LDFLAGS−L/home/dt/tools/zlib/lib"exportCFLAGS="{CFLAGS} -I/home/dt/tools/zlib/include"
-
对 libpng 源码工程进行配置
-
./configure --prefix=/home/alientek/tools/png --host=arm-poky-linux-gnueabi
-
--prefix 选项指定 zlib 库的安装目录
-
-
-
编译
- make
-
安装
- make install
安装目录下的文件夹介绍
- 同样包含了 bin、include、lib 这些目录
移植到开发板
-
将开发板出厂系统中已经移植好的
libpng 库文件删除
- rm -rf /lib/libpng* /usr/lib/libpng*
-
将编译得到的 libpng 库文件拷贝到开发板/usr/lib 目录
- 开发板/usr/lib 目录下的 libpng 相关库文件
libpng 使用说明
libpng 还包含编码功能,但本文不作介绍。libpng 官方提供了详细使用文档
-
PDF 文档:http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf
-
TXT 文档:
libpng 的数据结构
-
头文件:使用 libpng 需要包含头文件 <png.h>,其中包含 API 和数据结构的声明
-
关键数据结构:libpng 有两个重要的数据结构体:png_struct 和 png_info
-
png_struct
-
用于 libpng 函数内部
-
作为传递给每个 libpng 函数调用的第一个变量
-
用户需要创建并初始化这个对象,但通常不会直接使用
-
-
png_info
-
描述 PNG 图像的信息
-
旧版本中,用户可以直接访问 png_info 成员,如图像宽、高、像素深度等
-
新版本中,建议通过 png_get_XXX 和 png_set_XXX 接口来访问和修改 png_info 成员,以避免问题
-
创建和初始化 png_struct 对象
-
使用 png_create_read_struct() 创建一个用于 PNG 解码的 png_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);
-
第一个参数 user_png_ver 通常设置为 PNG_LIBPNG_VER_STRING(png.h 头文件中定义的一个宏),包含 libpng 的版本信息
- #define PNG_LIBPNG_VER_STRING "1.6.35"
-
error_ptr、error_fn、warn_fn 参数用于自定义错误处理和警告处理函数,也可设置为 NULL,使用 libpng 默认的处理函数
-
返回一个指向 png_struct 对象的指针(png_structp)
-
如果创建失败,返回 NULL,通过判断返回值是否为 NULL 来确认函数执行是否成功
-
示例:
png_structp png_ptr = NULL;
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
return -1;
-
-
使用 png_create_write_struct() 创建一个用于 PNG 编码的 png_struct 对象
创建和初始化 png_info 对象
-
创建 png_info 对象
-
使用 png_create_info_struct() 函数创建 png_info 对象
-
png_infop png_create_info_struct(png_const_structrp png_ptr);
-
需要传入一个 png_struct 对象的指针,内部会建立 png_struct 和 png_info 对象之间的关联
-
返回一个指向 png_info 对象的指针(png_infop)
-
如果创建成功,返回指向 png_info 对象的指针。
如果创建失败,返回 NULL,调用者可通过判断返回值是否为 NULL 来确认函数调用是否成功
-
-
当销毁 png_struct 对象时,也可以销毁 png_info 对象
-
示例:
png_infop info_ptr = NULL;
info_ptr = png_create_info_struct(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() 时,可以指定一个自定义的错误处理函数
-
如果未指定自定义错误处理函数,libpng 会使用默认的错误处理函数
-
-
默认错误处理函数
- 默认的错误处理函数会执行一个跳转动作,跳转到程序中的某个位置,这个位置称为错误返回点
-
错误返回点
-
当 libpng 遇到错误时,若使用默认错误处理函数,它会跳转到错误返回点
-
错误返回点是程序中预先设定的一个位置,通常用于执行清理工作,如释放和销毁 png_struct 和 png_info 对象,避免内存泄漏
-
-
避免直接终止
- 发生错误时,不直接终止程序,而是跳转到错误返回点执行必要的清理工作
-
设置错误返回点的方法
-
使用 setjmp 和 longjmp 库函数来设置错误返回点
-
函数跳转
-
在 C 语言中,goto 语句用于函数内部跳转,但不能跨越函数
-
使用 setjmp 和 longjmp 库函数可以实现跨越函数的跳转
-
-
setjmp 函数
- setjmp(jmp_buf env) 用于设置跳转点,保存当前进程环境信息到 env 参数中
-
longjmp 函数
-
longjmp(jmp_buf env, int val) 用于执行跳转,跳转到 setjmp 设置的跳转点
-
longjmp 调用后,类似于第二次调用 setjmp 返回
-
返回值区分
-
setjmp 初次调用返回 0
-
longjmp 调用后,setjmp 返回 val 参数指定的值,用于区分不同的起跳位置
-
-
-
val 参数设置
- 通常 longjmp 调用时,val 参数不为 0,以区分 setjmp 的初次返回和后续"伪"返回
-
-
编程举例
-
代码
- #include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
- #include <stdio.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);
}
- 验证
-
- libpng 设置错误返回点
- 错误处理机制:
libpng 库使用 setjmp/longjmp 组合处理错误跳转
- 错误返回点设置:
使用 setjmp() 为 libpng 设置错误返回点
- 错误处理流程:
当 libpng 遇到错误时,默认错误处理函数调用 longjmp() 进行跳转。
需要在代码中设置错误返回点,以便在错误发生时进行处理
- /* 设置错误返回点 */
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return -1;
}
指定数据源
-
需要指定进行解码的 PNG 图像,数据源可以是文件输入流、内存中的数据流等,这里以文件输入流为例
-
png_init_io() 函数
-
ibpng 提供 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 文件
- 从 PNG 文件中读取数据并解码,将解码后的图像数据存放在内存中供用户读取
-
处理方式
-
High-level 接口:封装了 low-level 接口,使用方便,只需一个函数,但灵活性不高
-
Low-level 接口:灵活性高,但需要用户调用多个 API
-
-
high-level 接口
-
High-level 接口使用条件
-
内存空间足够大,可一次性存放解码后的数据
-
数据输出格式为 libpng 预定义的格式
- libpng 预定义的数据转换类型
-
-
High-level 接口限制
- 预定义的转换类型,不包括背景颜色设置、伽马变换、抖动和填充物
-
High-level 接口函数
-
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 预定义的转换类型
-
-
函数调用示例
- png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_ALPHA, NULL);
-
-
High-level 接口调用顺序
-
调用 png_read_info 获取 PNG 图像信息
-
根据 transforms 设置数据输出格式
-
调用 png_read_image 解码并存放数据
-
调用 png_read_end 结束解码
-
-
-
low-level 接口
-
使用 low-level 接口,需要用户将函数 png_read_png()所做的事情一步一步执行
-
1、读取 PNG 图像信息
-
调用 png_read_info() 获取 PNG 图像信息并存入 png_info 对象
-
png_read_info(png_ptr, info_ptr);
-
-
2、查询图像信息
-
使用 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 头文件中定义,如上所示:
-
-
3、设置解码输出参数
-
根据需要调用 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 中可以查看每个函数的注释信息和参数列表
-
具体使用方法和更多转换函数请参考 libpng 的使用手册
-
-
自定义转换函数
-
libpng 提供了多种转换函数,但不支持所有可能的输出格式(如 YUV565、RGB565、YUYV)
-
用户可以设置自定义转换函数来解决这些问题
-
-
注册自定义转换函数
-
使用 png_set_read_user_transform_fn() 注册自定义转换函数
-
使用 png_set_user_transform_info() 提供自定义转换函数所需的用户数据结构和输出数据详细信息(如颜色深度和颜色通道)
-
有关自定义转换函数和其他详细信息,可以查阅 libpng 的使用帮助文档
-
-
-
4、更新 PNG 数据信息
-
调用 png_read_update_info() 更新 png_info 对象中的图像信息
-
png_read_update_info(png_ptr, info_ptr);
-
-
5、读取并解码 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];
-
无返回值
-
-
需要提供足够大的内存空间来保存解码后的数据,并通过 row_pointers 数组传入每一行的指针
-
-
内存分配
-
调用 png_read_image() 一次性解码整个 PNG 文件的数据并存入用户提供的内存区域
-
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);
-
使用 png_malloc() 为每一行数据分配缓冲区
-
png_malloc() 是 libpng 提供的 API,等价于 malloc()
-
-
也可以调用 png_read_rows()一次解码 1 行或多行数据、并将解码
后的数据存放在用于提供的内存区域中
-
ize_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 接口中,需要手动分配内存区域
-
在 high-level 接口中,调用 png_read_png() 时 libpng 会自动分配缓冲区
-
-
-
6、结束解码
-
调用 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 应用编程
c
#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); //退出进程
}
编写流程和上面介绍的libpng 使用说明一致
-
使用的是 high-level
接口处理方式
-
解码得到的数据是 RGB888 格式,需要转换为 RGB565 格式
-
转换后的数据刷入显存中进行显示
编译代码
- ${CC} -o testApp testApp.c -I/home/alientek/tools/png/include -L/home/alientek/tools/png/lib -L/home/alientek/tools/zlib/lib -
lpng -lz
执行测试
-
看到打印出了一些警告信息,原因是新版本的 libpng 增强了检查,发出了警告;不影响
我们的使用,可以忽略