在安卓项目中使用gifsicle编辑GIF动图-Android NDK 编译 gifsicle 为可执行文件记录

一、前言

最近项目中有需要压缩GIF的需求,最开始时试图使用FFmpeg通过降低GIF的分辨率和帧率的来减少GIF文件体积,但实际测试下来,大多数情况下压缩效果并不理想,甚至会出现降低分辨率后导出的GIF甚至比原文件还大的情况。
故选择放弃FFmpeg,经过大量的查询资料,发现如果想要压缩GIF大致有以下几个途径:

参考文章

1.由于 GIF 支持全局调色盘和局部调色盘,在没有局部调色盘的时候会用放在文件头中的全局调色盘。所以对于颜色变化不大的 GIF,可以将颜色放入全局调色盘中,去除局部调色盘。
2. 对于颜色较少的 GIF,将调色盘大小减少,比如从 256 种减少到 128 种等。
3.对于背景一致,画面中有一部分元素在变化的 GIF,可以将多个元素和背景分开存储,然后加上如何还原的信息
4.对于背景一致,画面中有一部分元素在动的 GIF,可以和前面一帧比较,将不动的部分透明化
5.对于帧数很多的 GIF,可以抽取中间部分的帧,减少帧数
6.对于每帧分辨率很高的 GIF,将每帧的分辨率减小

而正如前文所述,抽帧(减小帧率)和减少分辨率有时候效果并不是很好,而且对于图片的质量损耗较大。
对于1,2条途径依然可以使用FFmpeg实现,但是效果也不理想,并且处理起来比较复杂。故剩余的处理方式只剩3和4了。但是正如上文作者所说:“在移动端,除非将 ImageMagick 或者 gifsicle 移植到 iOS&Android 上,要实现前面 4 个方法是比较困难的。”
作者提到了两个程序:ImageMagick和gifsicle,经过查询ImageMagick已支持安卓,gifsicle尚未支持。但是ImageMagick目前对于安卓的支持较差,限制较多:

Requires API >= 24 (>= Nougat)
Currently, only arm64-v8a is supported

并且其库过于庞大:1
对于我来说只需要它的压缩动图功能,却需要添加这么大的库,性价比过低。
不过如果读者有需要的可以试试,项目地址:ImageMagick
也就是说,现在对于我来说只剩下gifsicle可用,但是gifsicle尚未提供安卓可用版本。
gifsicle项目地址:gifsicle
如何编译 gifsicle 使其在安卓上可用便是本文想要探讨的问题。

二、gifsicle编译方案

原计划是在gifsicle之上使用jni封装使其可以在安卓中调用其接口,但是通过对gifsicle的源码以及issus研究,发现该项目只支持直接编译成可执行文件,且修改较为困难。这也是为什么至今没有移植到安卓上的原因。
github中关于将gifsicle移植成库的讨论
虽说不能直接移植成安卓的 .so 库,但是即使是编译成可执行文件也可以通过

Runtime.getRuntime().exec(cmd, envp)

使用该库,唯一需要注意的是,在安卓10中可能会禁止执行外部可执行库:

Android 10 includes the following security changes.
Removed execute permission for app home directory
Untrusted apps that target Android 10 cannot invoke exec() on files within the app’s home directory. This execution of files from the writable app home directory is a W^X violation. Apps should load only the binary code that’s embedded within an app’s APK file.
In addition, apps that target Android 10 cannot in-memory modify executable code from files which have been opened with dlopen(). This includes any shared object (.so) files with text relocations.

不过经过实际测试,只要是打包进apk中且命名形如 libxxx.so 的可执行文件依旧可以使用。
确定好如何编译后下面就开始编译

三、开始编译

1.编译前准备

参考:
想请教关于gifsicle在android上编译的问题
seven332/gifsicle
1.编译环境
我使用的是WSL2 Linux+NDK r21b
Linux版本如下(因为电脑上正好装着Kali所以就用Kali了,一般用Ubuntu就行)
2
2.安装依赖
因为编译前需要 automake 生成 config.h 所以需要安装以下依赖(已安装请忽略)

sudo apt-get install autoconf automake libtool
sudo apt-get install libffi-dev

2.下载代码
编译前首先将 gifsicle 下载下来
这里直接 clone 官方仓库:

git clone https://github.com/kohler/gifsicle.git

切进代码目录

cd gifsicle

3.生成 config.h
3
依据官方文档,首先生成 config

autoreconf -i

根据需要执行 configure ,因为我只需要压缩gif功能,所以其他模块就不需要编译了:

./configure --disable-gifview --disable-gifdiff

此时目录中应该已经生成了一个 config.h 文件。
切记不要执行 make 和 install

4.编写 Android.mk 文件
在代码根目录中新建一个 Android.mk 文件,内容如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := gifsicle
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_SRC_FILES := \
src/clp.c  \
src/fmalloc.c   \
src/giffunc.c  \
src/gifread.c   \
src/gifsicle.c  \
src/gifunopt.c   \
src/gifwrite.c  \
src/merge.c   \
src/optimize.c   \
src/quantize.c   \
src/support.c   \
src/xform.c  

LOCAL_CFLAGS := -DHAVE_CONFIG_H
 
include $(BUILD_EXECUTABLE)

再新建一个 Application.mk 文件,内容如下:

APP_ABI := all

APP_PLATFORM := android-16

NDK_TOOLCHAIN_VERSION := clang

其他东西不需理会,记住 APP_ABI := all 表示需要编译的CPU ABI 版本即可,这里写的是所有版本都编译一份,你也可以指定只编译特定的版本,如 APP_ABI := armeabi-v7a

5.准备编译
将代码目录更名为 jni
否则ndk不会将该项目识别为ndk项目并且编译。
做完上述步骤后的完整目录结构如下:
4

2.开始编译

1.首先确保你的ndk已经安装并且已配置环境,否则将会

-bash: ndk-build: command not found

安装ndk并配置环境可参考:
VMware安装Ubuntu教程,Linux下搭建Android开发环境
中关于NDK的介绍(AS,SDK等不需要装)

2.切换至 jni 文件夹的上层目录:

cd ../

如图:8
3.编译64位的so(arm64-v8a, x86_64)
首先修改 Application.mk 文件的 APP_ABI 为 arm64-v8a, x86_64

vim ./jni/Application.mk

修改后如图:
99
修改后执行:

ndk-build

如图即为编译成功:
8976545
生成的so库在 ./libs 目录下
34667
4.编译32位so(如:armeabi-v7a)
如3中所说,首先修改 Application.mk 文件的 APP_ABI 为 armeabi-v7a
4565
修改 ./jni/config.h 文件

vim ./jni/config.h

找到

#define SIZEOF_UNSIGNED_LONG 8

修改为

#define SIZEOF_UNSIGNED_LONG 4

如图:
123
切记一定要修改不然会报错如下
1312
之后执行

ndk-build

即可。

注意: 上述编译32位时如果不修改 ./jni/config.h 会出现报错的情况,根据我猜测应该时由于我使用的是 Linux 的 automake 工具生成的 config.h 文件,也就是说这个config.h 文件是适用于编译成我使用 automake 的系统(kali)的配置文件,而非用于我生成安卓可执行文件的配置文件。
根据报错信息找到源码出错地方如下:
53535
根据源码中注释的描述,此处代码是用于检测 Windos 下编译脚本是否出错的问题(即是否在 64位 系统使用了 32位 脚本进行编译或者反之)
根据源码来说,此处并未有除检测外实际作用,故我直接根据指示将 config.h 中的 UNSIGNED_LONG 值设置为4来规避检测。
虽然样能够编译成功,初步测试使用也没有问题,但是我也不确定是否会影响到 gifsicle 其他功能的使用。
所以读者如有需要使用,还需自行测试是否存在问题,如果有人知道其中的问题也希望能不吝赐教,十分感谢。

四、使用方法

1.复制库

将编译成功的库复制到您的安卓项目文件

项目根目录\app\src\main\jniLibs

下,并且改名为 libgifsicle.so
在这里插入图片描述
一定要记得改名,否则安装时不会被系统复制至可执行文件目录下。

2.使用

代码中已用注释说明各个语句的作用。

val gifsicle = File(File(applicationInfo.nativeLibraryDir), "libgifsicle.so")   //可执行文件地址安装后形如:/data/app/com.equationl.myapplication-wZxpZo7IgVPNv3jvY0S8QA==/lib/arm/libgifsicle.so
if (!gifsicle.canExecute()) {   //无法执行该执行文件
    Log.e("el", "startCustomizeCompress: can't excute")
}

val envp = arrayOf("LD_LIBRARY_PATH=" + File(applicationInfo.nativeLibraryDir))  //设置环境
val cmd = String.format(Locale.US, "%s -i %s -k 256 -O3 -o %s",
        gifsicle.path, File(externalCacheDir, "test.gif").toString(), File(externalCacheDir, "result.gif").toString())  //设置命令,此处作用为将缓存目录下的 test.gif 更改颜色数为256 按第3级别优化并输出至缓存目录下 result.gif (详细请自己看 gifsicle 的文档)
Log.i("el", "startCustomizeCompress: envp=${envp[0]}\ncmd=$cmd")
val process = Runtime.getRuntime().exec(cmd, envp) //开始执行命令
try {
    if (process.waitFor() != 0) {  //如果执行成功会返回 0,不成功返回非0
        Log.e("el", "startCustomizeCompress: running error process.waitFor() != 0")
    }
    else {
        Log.i("el", "Success!")
    }
} catch (e: InterruptedException) {
    e.printStackTrace()
}

关于 gifsicle 的使用方法请自行查看官方文档

@equationl 原创
@email:equationl@qq.com

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值