一褪测、前言
最近項目中有需要壓縮GIF的需求,最開始時試圖使用FFmpeg通過降低GIF的分辨率和幀率的來減少GIF文件體積,但實際測試下來涵卵,大多數(shù)情況下壓縮效果并不理想,甚至會出現(xiàn)降低分辨率后導出的GIF甚至比原文件還大的情況荒叼。
故選擇放棄FFmpeg轿偎,經(jīng)過大量的查詢資料,發(fā)現(xiàn)如果想要壓縮GIF大致有以下幾個途徑:
1.由于 GIF 支持全局調(diào)色盤和局部調(diào)色盤被廓,在沒有局部調(diào)色盤的時候會用放在文件頭中的全局調(diào)色盤坏晦。所以對于顏色變化不大的 GIF,可以將顏色放入全局調(diào)色盤中嫁乘,去除局部調(diào)色盤昆婿。
- 對于顏色較少的 GIF,將調(diào)色盤大小減少蜓斧,比如從 256 種減少到 128 種等仓蛆。
3.對于背景一致,畫面中有一部分元素在變化的 GIF挎春,可以將多個元素和背景分開存儲看疙,然后加上如何還原的信息
4.對于背景一致,畫面中有一部分元素在動的 GIF直奋,可以和前面一幀比較狼荞,將不動的部分透明化
5.對于幀數(shù)很多的 GIF,可以抽取中間部分的幀帮碰,減少幀數(shù)
6.對于每幀分辨率很高的 GIF相味,將每幀的分辨率減小
而正如前文所述,抽幀(減小幀率)和減少分辨率有時候效果并不是很好殉挽,而且對于圖片的質(zhì)量損耗較大丰涉。
對于1拓巧,2條途徑依然可以使用FFmpeg實現(xiàn),但是效果也不理想一死,并且處理起來比較復雜肛度。故剩余的處理方式只剩3和4了。但是正如上文作者所說:“在移動端投慈,除非將 ImageMagick 或者 gifsicle 移植到 iOS&Android 上承耿,要實現(xiàn)前面 4 個方法是比較困難的∥泵海”
作者提到了兩個程序:ImageMagick和gifsicle加袋,經(jīng)過查詢ImageMagick已支持安卓,gifsicle尚未支持抱既。但是ImageMagick目前對于安卓的支持較差职烧,限制較多:
Requires API >= 24 (>= Nougat)
Currently, only arm64-v8a is supported
并且其庫過于龐大:
對于我來說只需要它的壓縮動圖功能,卻需要添加這么大的庫防泵,性價比過低蚀之。
不過如果讀者有需要的可以試試,項目地址:ImageMagick
也就是說捷泞,現(xiàn)在對于我來說只剩下gifsicle可用足删,但是gifsicle尚未提供安卓可用版本。
gifsicle項目地址:gifsicle
如何編譯 gifsicle 使其在安卓上可用便是本文想要探討的問題锁右。
二失受、gifsicle編譯方案
原計劃是在gifsicle之上使用jni封裝使其可以在安卓中調(diào)用其接口,但是通過對gifsicle的源碼以及issus研究骡湖,發(fā)現(xiàn)該項目只支持直接編譯成可執(zhí)行文件贱纠,且修改較為困難。這也是為什么至今沒有移植到安卓上的原因响蕴。
github中關于將gifsicle移植成庫的討論
雖說不能直接移植成安卓的 .so 庫谆焊,但是即使是編譯成可執(zhí)行文件也可以通過
Runtime.getRuntime().exec(cmd, envp)
使用該庫,唯一需要注意的是浦夷,在安卓10中可能會禁止執(zhí)行外部可執(zhí)行庫:
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.
不過經(jīng)過實際測試辖试,只要是打包進apk中且命名形如 libxxx.so 的可執(zhí)行文件依舊可以使用。
確定好如何編譯后下面就開始編譯
三劈狐、開始編譯
1.編譯前準備
參考:
想請教關于gifsicle在android上編譯的問題
seven332/gifsicle
1.編譯環(huán)境
我使用的是WSL2 Linux+NDK r21b
Linux版本如下(因為電腦上正好裝著Kali所以就用Kali了罐孝,一般用Ubuntu就行)
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
依據(jù)官方文檔,首先生成 config
autoreconf -i
根據(jù)需要執(zhí)行 configure 肥缔,因為我只需要壓縮gif功能莲兢,所以其他模塊就不需要編譯了:
./configure --disable-gifview --disable-gifdiff
此時目錄中應該已經(jīng)生成了一個 config.h 文件。
==切記不要執(zhí)行 make 和 install==
4.編寫 Android.mk 文件
在代碼根目錄中新建一個 Android.mk 文件,內(nèi)容如下:
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 文件改艇,內(nèi)容如下:
APP_ABI := all
APP_PLATFORM := android-16
NDK_TOOLCHAIN_VERSION := clang
其他東西不需理會收班,記住 APP_ABI := all 表示需要編譯的CPU ABI 版本即可,這里寫的是所有版本都編譯一份谒兄,你也可以指定只編譯特定的版本摔桦,如 APP_ABI := armeabi-v7a
5.準備編譯
將代碼目錄更名為 jni
否則ndk不會將該項目識別為ndk項目并且編譯。
做完上述步驟后的完整目錄結構如下:
2.開始編譯
1.首先確保你的ndk已經(jīng)安裝并且已配置環(huán)境承疲,否則將會
-bash: ndk-build: command not found
安裝ndk并配置環(huán)境可參考:
VMware安裝Ubuntu教程邻耕,Linux下搭建Android開發(fā)環(huán)境
中關于NDK的介紹(AS,SDK等不需要裝)
2.切換至 jni 文件夾的上層目錄:
cd ../
如圖:3.編譯64位的so(arm64-v8a, x86_64)
首先修改 Application.mk 文件的 APP_ABI 為 arm64-v8a, x86_64
vim ./jni/Application.mk
修改后如圖:
修改后執(zhí)行:
ndk-build
如圖即為編譯成功:
生成的so庫在 ./libs 目錄下
4.編譯32位so(如:armeabi-v7a)
如3中所說燕鸽,首先修改 Application.mk 文件的 APP_ABI 為 armeabi-v7a
修改 ./jni/config.h 文件
vim ./jni/config.h
找到
#define SIZEOF_UNSIGNED_LONG 8
修改為
#define SIZEOF_UNSIGNED_LONG 4
如圖:
切記一定要修改不然會報錯如下:
之后執(zhí)行
ndk-build
即可兄世。
注意: 上述編譯32位時如果不修改 ./jni/config.h 會出現(xiàn)報錯的情況,根據(jù)我猜測應該時由于我使用的是 Linux 的 automake 工具生成的 config.h 文件绵咱,也就是說這個config.h 文件是適用于編譯成我使用 automake 的系統(tǒng)(kali)的配置文件碘饼,而非用于我生成安卓可執(zhí)行文件的配置文件熙兔。
根據(jù)報錯信息找到源碼出錯地方如下:
根據(jù)源碼中注釋的描述悲伶,此處代碼是用于檢測 Windos 下編譯腳本是否出錯的問題(即是否在 64位 系統(tǒng)使用了 32位 腳本進行編譯或者反之)
根據(jù)源碼來說,此處并未有除檢測外實際作用住涉,故我直接根據(jù)指示將 config.h 中的 UNSIGNED_LONG 值設置為4來規(guī)避檢測麸锉。
雖然樣能夠編譯成功,初步測試使用也沒有問題舆声,但是我也不確定是否會影響到 gifsicle 其他功能的使用花沉。
所以讀者如有需要使用,還需自行測試是否存在問題媳握,如果有人知道其中的問題也希望能不吝賜教碱屁,十分感謝。
四蛾找、使用方法
1.復制庫
將編譯成功的庫復制到您的安卓項目文件
項目根目錄\app\src\main\jniLibs
下娩脾,并且改名為 libgifsicle.so:
一定要記得改名,否則安裝時不會被系統(tǒng)復制至可執(zhí)行文件目錄下打毛。
2.使用
代碼中已用注釋說明各個語句的作用柿赊。
val gifsicle = File(File(applicationInfo.nativeLibraryDir), "libgifsicle.so") //可執(zhí)行文件地址安裝后形如:/data/app/com.equationl.myapplication-wZxpZo7IgVPNv3jvY0S8QA==/lib/arm/libgifsicle.so
if (!gifsicle.canExecute()) { //無法執(zhí)行該執(zhí)行文件
Log.e("el", "startCustomizeCompress: can't excute")
}
val envp = arrayOf("LD_LIBRARY_PATH=" + File(applicationInfo.nativeLibraryDir)) //設置環(huán)境
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 更改顏色數(shù)為256 按第3級別優(yōu)化并輸出至緩存目錄下 result.gif (詳細請自己看 gifsicle 的文檔)
Log.i("el", "startCustomizeCompress: envp=${envp[0]}\ncmd=$cmd")
val process = Runtime.getRuntime().exec(cmd, envp) //開始執(zhí)行命令
try {
if (process.waitFor() != 0) { //如果執(zhí)行成功會返回 0幻枉,不成功返回非0
Log.e("el", "startCustomizeCompress: running error process.waitFor() != 0")
}
else {
Log.i("el", "Success!")
}
} catch (e: InterruptedException) {
e.printStackTrace()
}
關于 gifsicle 的使用方法請自行查看官方文檔
@equationl 原創(chuàng)
@email:equationl@qq.com