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

一、前言

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

参考文章

1.由于 GIF 支持全局调色盘和局部调色盘,在没有局部调色盘的时候会用放在文件头中的全局调色盘。所以对于颜色变化不大的 GIF,可以将颜色放入全局调色盘中,去除局部调色盘。

  1. 对于颜色较少的 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容