在以前的文章中介绍了 stb_image
图像库,还顺带提到了 libpng 和 libjpeg ,这篇文章就是介绍如何在 Android 平台上用 CMake 编译 libpng 动态库以及 libpng 使用实践。html
【简单易用的图像解码库介绍 —— stb_image】android
https://glumes.com/post/android/stb-image-introduce/git
<!--more-->github
libpng 的官方介绍网站以下:数组
http://www.libpng.org/pub/png/libpng.html
下载地址网站以下:微信
https://sourceforge.net/projects/libpng/files/
博客中使用的版本是 1.6.37 ,也是目前最新的版本了。函数
关于 libpng 的编译网上已经有很多博客教程了,但有的是基于 Linux,有的是基于 Android.mk 的,本文会介绍如何在 Android Studio 上经过 CMake 来编译 Android 的动态库。post
在 libpng 的源代码中,就提供了 CMakeLists.txt 文件用以说明如何编译,可是却不能直接用在 Android 平台上,不过能够借鉴其源码做为参考。学习
因为 CMake 跨平台编译的特性,通常大型项目代码编译都会针对平台作适配,常见代码结构以下:优化
if (CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" OR CMAKE_SYSTEM_PROCESSOR MATCHES "^aarch64") set(libpng_arm_sources arm/arm_init.c arm/filter_neon.S arm/filter_neon_intrinsics.c arm/palette_neon_intrinsics.c) // 定义宏 add_definitions(-DPNG_ARM_NEON_OPT=2) endif ()
这段代码就是判断系统处理器平台,不一样平台所须要编译的代码不同。而 libpng 会有这样的适配,主要是由于它用到了 neon 相关优化,该优化主要是用在 filter
操做方面。
// libpng 使用 neon 优化加速的方法 void png_read_filter_row_up_neon(png_row_infop row_info, png_bytep row, png_const_bytep prev_row) { png_bytep rp = row; png_bytep rp_stop = row + row_info->rowbytes; png_const_bytep pp = prev_row; png_debug(1, "in png_read_filter_row_up_neon"); for (; rp < rp_stop; rp += 16, pp += 16) { uint8x16_t qrp, qpp; qrp = vld1q_u8(rp); qpp = vld1q_u8(pp); qrp = vaddq_u8(qrp, qpp); vst1q_u8(rp, qrp); } }
经过查看 libpng 源代码,要启用 neon 优化,还必须经过 add_definitions 方法定义 DPNG_ARM_NEON_OPT 宏的值为 2 ,不然在源码中会认为不须要使用 neon 。
要使用 neon 编译,还须要指定编译器相关参数:
set_property(SOURCE ${libpng_arm_sources} APPEND_STRING PROPERTY COMPILE_FLAGS " -mfpu=neon")
不过看到网上一些 libpng 编译文章,基本没提到 neon 相关东西,估计这个优化加速功能用不上吧。
可是,能够在个人 Demo 上看到如何启用 neon 去编译,之后也会写专门的文章来介绍 neon 的使用~~
libpng 动态库编译还依赖 zlip 库,要是在其余平台上须要单独下载这个库,可是 Android 上就不须要了,由于 Android 编译环境自己就提供了这个库,就像咱们使用 log 库同样。
// 指定要编译的 so 依赖哪些其余的 so , z 就是 zlib 库 target_link_libraries(png z log )
Android 编译环境中 z
就是 zlip 库了。
其余的就是源码编译了,主要是 add_library 方法的使用,要指定好须要编译的源文件。
具体有哪些源文件须要添加到编译中,仍是请参考以下连接,就不贴具体代码了,减小文章篇幅。
https://github.com/glumes/InstantGLSL/blob/master/instantglsl/src/main/cpp/libpng/CMakeLists.txt
完成上述三个过程后,就可以编译出 libpng 的动态库了,实际编译过程仍是参考项目代码吧。
编译是小事,重点在使用~~~
以解码 png 图片获取像素内容为例:
首先是初始化 libpng ,获得 png_structp
结构体。它能够说是表明了 libpng 上下文,在方法调用时都须要把它做为第一个参数传入。
// 传 nullptr 的参数是用来自定义错误处理的,这里不须要 png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
因为是读取,方法名中带有 read ,若是是写入,那就是 png_create_write_struct 方法了。
因为在建立 png 变量时,用来自定义错误处理的参数都传了 nullptr,因此须要设置错误返回点,这样当 libpng 发生错误时,程序将回到这个调用点,这时候能够作一些清理工做:
if (setjmp(png_jmpbuf(png))) { png_destroy_read_struct(&png, nullptr, nullptr); fclose(fp); return; }
libpng 提供了 png_sig_cmp 方法来检查文件是否 png 格式。
#define PNG_BYTES_TO_CHECK 4 char buf[PNG_BYTES_TO_CHECK]; // 读取 buffer if (fread(buf, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK) { return; } // 判断 if (!png_sig_cmp(reinterpret_cast<png_const_bytep>(buf), 0, PNG_BYTES_TO_CHECK)) { // 返回值不等于 0 则是 png 文件格式 }
若是调用了该方法,须要经过 png_set_sig_bytes 方法告诉 libpng 该跳过相应的数据,不然会出现黑屏,或者经过 rewind 方法重置文件指针。
首先建立 png_infop 结构体来表明图像信息:
png_infop infop = png_create_info_struct(png);
而后是设置图像的数据源,前提是要获得文件路径:
// 根据文件路径打开文件 FILE *fp = fopen(mFileName.c_str(), "rb"); // 设置图像数据源 png_init_io(png, fp);
接下来是读取信息:
png_read_png(png, infop, (PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND),
经过以下方法,能获得图像具体某方面信息:
mWidth = png_get_image_width(png, info); mHeight = png_get_image_height(png, info); mColorType = png_get_color_type(png, info); mBitDepth = png_get_bit_depth(png, info);
固然也能够经过 png_get_IHDR 方法去得到信息
png_get_IHDR(png, infop, &mWidth, &mHeight, &mBitDepth, &mColorType, &mInterlaceType, &mCompressionType, &mFilterType);
经过 png_get_rows 方法按行获取全部的数据,而后赋值到像素指针上去。
// 表明像素内容的指针 unsigned char *mPixelData; // 获取每行的字节数量 unsigned int row_bytes = png_get_rowbytes(png, infop); mPixelData = new unsigned char[row_bytes * mHeight]; png_bytepp rows = png_get_rows(png, infop); // 逐行读取,并填充到像素指针上去 for (int i = 0; i < mHeight; ++i) { memcpy(mPixelData + (row_bytes * i), rows[i], row_bytes); }
其实在前面的 png_read_png 方法中就已经获得了全部的像素内容,保存在 infop 变量的 row_pointers 中,具体的实现以下:
经过 png_read_image 方式读取像素内容:
// row_pointers 当成了一维指针数组 png_bytep *row_pointers; row_pointers = (png_bytep *) malloc(sizeof(png_bytep) * mHeight); for (int y = 0; y < mHeight; y++) { row_pointers[y] = (png_byte *) malloc(png_get_rowbytes(png, info)); } png_read_image(png, row_pointers);
最后,别忘了调用 png_read_end 方法结束读取。
有了像素内容,就能够作一个常见的渲染操做了,将像素内容渲染绘制到纹理上。
最后介绍如何根据像素内容去保存图片,在 libpng 中也提供了相应的方法调用,流程就是以下方法:
png = png_create_write_struct() infop = png_create_info_struct() // 关联数据源,png 和要写入的文件 png_init_io(png,fp) // 设置 infop 相关参数,表明最好要生成的图片文件相关信息 png_set_IHDR() // 写入图片信息 png_write_info(png, infop); // 写入图片像素内容 png_write_image(png, row_pointers); // 结束写入 png_write_end(png, NULL);
流程和读取像素内容刚好相反。
其中 png 变量要经过 png_create_write_struct 建立。
infop 变量仍是 png_create_info_struct 方法建立。
接下来就是设置图片信息,写入图片信息,写入像素内容,具体的代码实践能够参考个人代码示例。
最后,在 libpng 的源代码中,也提供了丰富的示例,通常这种开源库都会提供相应的 test 代码,经过 test 代码基本都能找到相应的函数调用。
libpng 的官网示例地址以下:
http://www.libpng.org/pub/png/libpng-manual.txt
有疑问的话,基本均可以在这个上面找到答案。
Google Jetpack 新组件 CameraX 介绍与实践
最近新开了一个知识星球【图形/图像/音视频交流】,若是有什么疑问,欢迎在知识星球中讨论。
星球除了探讨,更主要是起到知识点沉淀的做用,我会把【OpenGLES技术交流群】中你们的探讨相应同步到星球中,做为内容沉淀。
另外,微信公众号推广的二维码也更新了一波,【纸上浅谈·多媒体开发札记】,会更加专一分享多媒体开发中的技术学习和实践积累。