找回密码
 立即注册
首页 业界区 业界 在Android中使用libpng

在Android中使用libpng

郗燕岚 前天 15:24
最近在使用Android的Bitmap.compress方法保存4K png图片时,发现其耗时在1秒钟以上,通过询问deepseek得知相比Bitmap.compress,使用libpng提升png图片的保存速度。接下来本文将阐述在Android中如何集成libpng,以及在使用过程中遇到的问题和最终的对比测试结果。
编译libpng

使用AndroidStudio创建Native C++项目或者Android Native Library模块,然后将下载libpng解压到对应的src/main/cpp目录下,与CMakeLists.txt在同级目录下,如:
  1. src/main/cpp
  2. ├── CMakeLists.txt
  3. ├── libpng
复制代码
在libpng的libpng16分支中已经提供了CMakeLists.txt文件,因此在Android的CMakeLists.txt中添加子路径:
  1. add_subdirectory(libpng)
复制代码
同时添加头文件路径:
  1. include_directories(libpng)
复制代码
build后就可以在build/intermediates/cxx目录下找到编译出来的libpng16.so文件。
接下来在kotlin文件中添加保存png图片的接口:
  1. class PNG {
  2.     companion object {
  3.         init {
  4.             System.loadLibrary("png-jni")
  5.         }
  6.         external fun save(bitmap: Bitmap, filepath: String): Boolean
  7.     }
  8. }
复制代码
在c/c++文件中添加native实现:
  1. extern "C"
  2. JNIEXPORT jboolean JNICALL
  3. Java_com_ihuntto_libpng_PNG_00024Companion_save(JNIEnv *env, jobject thiz, jobject bitmap,
  4.                                                 jstring file_path) {
  5.     const char *path = env->GetStringUTFChars(file_path, nullptr);
  6.     if (path == nullptr) {
  7.         return JNI_FALSE;
  8.     }
  9.     // 获取 Bitmap 信息
  10.     AndroidBitmapInfo info;
  11.     if (AndroidBitmap_getInfo(env, bitmap, &info) < 0) {
  12.         env->ReleaseStringUTFChars(file_path, path);
  13.         return JNI_FALSE;
  14.     }
  15.     if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
  16.         // 需要 RGBA_8888 格式
  17.         env->ReleaseStringUTFChars(file_path, path);
  18.         return JNI_FALSE;
  19.     }
  20.     // 锁定 Bitmap 像素
  21.     void *pixels;
  22.     if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0) {
  23.         env->ReleaseStringUTFChars(file_path, path);
  24.         return JNI_FALSE;
  25.     }
  26.     FILE *fp = fopen(path, "wb");
  27.     if (!fp) {
  28.         AndroidBitmap_unlockPixels(env, bitmap);
  29.         env->ReleaseStringUTFChars(file_path, path);
  30.         return JNI_FALSE;
  31.     }
  32.     png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
  33.     if (!png) {
  34.         fclose(fp);
  35.         AndroidBitmap_unlockPixels(env, bitmap);
  36.         env->ReleaseStringUTFChars(file_path, path);
  37.         return JNI_FALSE;
  38.     }
  39.     png_infop info_ptr = png_create_info_struct(png);
  40.     if (!info_ptr) {
  41.         png_destroy_write_struct(&png, nullptr);
  42.         fclose(fp);
  43.         AndroidBitmap_unlockPixels(env, bitmap);
  44.         env->ReleaseStringUTFChars(file_path, path);
  45.         return JNI_FALSE;
  46.     }
  47.     if (setjmp(png_jmpbuf(png))) {
  48.         png_destroy_write_struct(&png, &info_ptr);
  49.         fclose(fp);
  50.         AndroidBitmap_unlockPixels(env, bitmap);
  51.         env->ReleaseStringUTFChars(file_path, path);
  52.         return JNI_FALSE;
  53.     }
  54.     png_init_io(png, fp);
  55.     // 设置 PNG 头信息
  56.     int color_type = PNG_COLOR_TYPE_RGBA;
  57.     png_set_IHDR(png, info_ptr, info.width, info.height, 8, color_type,
  58.                  PNG_INTERLACE_NONE,
  59.                  PNG_COMPRESSION_TYPE_BASE,
  60.                  PNG_FILTER_TYPE_BASE);
  61.     png_write_info(png, info_ptr);
  62.     // 写入图像数据
  63.     png_bytep *row_pointers = new png_bytep[info.height];
  64.     for (int y = 0; y < info.height; y++) {
  65.         row_pointers[y] = static_cast<png_bytep>(pixels) + y * info.stride;
  66.     }
  67.     png_write_image(png, row_pointers);
  68.     png_write_end(png, nullptr);
  69.     // 清理资源
  70.     delete[] row_pointers;
  71.     png_destroy_write_struct(&png, &info_ptr);
  72.     fclose(fp);
  73.     AndroidBitmap_unlockPixels(env, bitmap);
  74.     env->ReleaseStringUTFChars(file_path, path);
  75.     return JNI_TRUE;
  76. }
复制代码
上述实现代码由deepseek提供
最后需要在CMakeLists.txt链接libpng库:
  1. target_link_libraries(${CMAKE_PROJECT_NAME}
  2.         # List libraries link to the target library
  3.         android
  4.         png_shared
  5.         jnigraphics
  6.         log)
复制代码
注意libpng的链接目标是png_shared,而不是png或png16,因为libpng的CMakeLists.txt中编译的库目标名称为png_shared,输出库文件名称为libpng16.so,因此不要链接错了,否则会编译报错。
现在就可以通过PNG.save()完成libpng的图片保存目标了。
对比测试

为了对比Bitmap.compress和libpng,增加一段对比测试代码:
  1. //omit other code
  2. GlobalScope.launch(Dispatchers.IO) {
  3.     var time = System.currentTimeMillis()
  4.     val bitmap = createColorNoiseBitmap(binding.root.width, binding.root.height)
  5.     val sb = StringBuilder()
  6.     sb.append("create bitmap used ${System.currentTimeMillis() - time}ms\n")
  7.     time = System.currentTimeMillis()
  8.     externalCacheDir?.absolutePath?.let { cacheDir ->
  9.         try {
  10.             BufferedOutputStream(FileOutputStream(cacheDir + File.separatorChar + "noise1.png")).use {
  11.                 bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
  12.             }
  13.             sb.append("bitmap compress used ${System.currentTimeMillis() - time}ms\n")
  14.             time = System.currentTimeMillis()
  15.             PNG.save(bitmap, cacheDir + File.separatorChar + "noise0.png")
  16.             sb.append("libpng save used ${System.currentTimeMillis() - time}ms\n")
  17.         } catch (e: IOException) {
  18.             e.printStackTrace()
  19.         }
  20.     }
  21.     //omit other code
  22. }
复制代码
序号图片分辨率Bitmap.compresslibpng11080*22538.46MB/323ms8.46MB/563ms在Build Variants为debug模式时,libpng的速度比Bitmap.compress的速度要慢,上表只列出了一次测试结果,多次测试后也是libpng的速度慢,但两者保存的图片大小是一致的。接下来看看是否能提升一下libpng的保存速度。
libpng优化


  • Build Variants改为release模式。
序号\耗时(ms)Bitmap.compresslibpng13183032310301331828842992925317282
测试的图片分辨率都是1080*2253,不再单独列出。
现在libpng的速度已经快于Bitmap.compress,但相差不大。

  • 设置libpng速度优先:
  1.     // 1. 设置最快的压缩级别
  2.     png_set_compression_level(png, Z_BEST_SPEED);
  3.     // 2. 禁用所有过滤器(最快)
  4.     png_set_filter(png, PNG_FILTER_TYPE_BASE, PNG_FILTER_NONE);
  5.     // 3. 设置压缩策略为最快
  6.     png_set_compression_strategy(png, Z_DEFAULT_STRATEGY);
复制代码
序号\耗时(ms)Bitmap.compresslibpng13242332327175331421043032055306211此时libpng已经明显快于Bitmap.compress了,耗时约为Bitmap.compress的三分之二。

  • 开启png硬件优化
在CMakeLists.txt中添加:
  1. set(PNG_HARDWARE_OPTIMIZATIONS ON)
复制代码
不过这个是默认开启的,添加后实际无差异。

  • 其他编译优化
在CMakeLists.txt中添加:
  1. set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -ffast-math -fno-rtti -fno-exceptions")
  2. set(PNG_STATIC OFF) # 不编译静态库
  3. set(PNG_TESTS OFF) # 不编译测试程序
复制代码
第一个是设置Release的编译优化,经过实际测试几乎无优化;后两个主要可以提升编译速度。
目前从测试结果来看,libpng相比于Android自带的Bitmap.compress带来的速度提升有限,并且还会增加apk的大小,是否需要使用需要根据项目实际情况来评估。
完整项目代码已上传Github。
参考

[1] deepseek
[2] Android Developer API reference
[3] libpng

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册