需求是大大的減小多媒體簡訊圖片的體積,可以發送更多數量的多媒體簡訊圖片,圖片的內容是報表,顏色單一。最開始把透明色去掉了,位深從32->24,但是最近測試發現多媒體簡訊圖片體積還是太大了,這就需要去學習圖片壓縮的知識了
PNG格式有8位元、24位元、32位元三種形式,其中8位元PNG支援兩種不同的透明形式(索引透明和alpha透明),24位元PNG不支援透明,32位元PNG在24位元基礎上增加了8位元透明通道,因此可展現256級透明程度。位深度越大圖片體積越大,能夠表示的色彩越豐富。
png有種索引彩色模式,採用8位元調色板將RGB彩色影象轉換爲索引彩色影象。影象中儲存的不再是各個畫素的彩色資訊,而是從影象中挑選出來的具有代表性的顏色編號,每一編號對應一種顏色,影象的數據量也因此減少,這對彩色影象的傳播非常有利。
通過索引png,可以大大減小圖片體積,如果圖片中的顏色種類小於256,那麼就可以使用索引格式的PNG,如果圖片原本的顏色大於256種,那麼可以通過向量量化的方法來建立一個索引PNG格式(有失真壓縮)
上面只是一個簡單的介紹,如果要深入瞭解可以看看:
https://www.jianshu.com/p/5ad19825a3d0
https://www.jianshu.com/p/324744087e24
我這裏的場景非常適合png索引圖模式,因爲圖片內容是表格報表,顏色最多隻有4種,遠遠小於索引圖的256色,如果顏色豐富的那種照片之類的還是用jpg壓縮吧。。。
這裏重點介紹png量化轉索引圖
看網上框架有:tinypng、pngquant、OpenViewerFX6.6.14等等
根據資料顯示,tinypng、pngquant、ImageAlpha、pngnq都是有失真壓縮,基本採用的都是quantization演算法,將32位元的PNG圖片轉換爲8位元的PNG圖片,減少圖片的顏色數;pngcrush、optipng、pngout、advpng都是無失真壓縮,採用的都是基於LZ/Huffman的DEFLATE演算法,減少圖片IDAT
chunk區域的數據。一般有失真壓縮的壓縮率會大大高於無失真壓縮。
我這裏使用開源的pngquant框架和OpenViewerFX6.6.14來操作
<dependency>
<groupId>org.jpedal</groupId>
<artifactId>OpenViewerFX</artifactId>
<version>6.6.14</version>
</dependency>
PngCompressor.compress(inputStream, outputStream);
這個的使用極其方便,就一句,input進output出,就是不像pngquant有很多參數可以選,如果只是簡單的壓縮,這個就足夠了
28k(24位元) -> 12.6k(8位元),發現已經成功的轉爲了索引圖
使用pngquant框架,這是一款比較流行的png壓縮開源框架,它是一種有失真壓縮,並將其轉爲索引圖,這種轉換大大減小了檔案大小(通常高達70%),並保留了完全的alpha透明度。生成的影象與所有web瀏覽器和操作系統相容。
它是c語言寫的,這裏麻煩一點,需要使用jni的方式呼叫,這裏就當練習下jni使用了
官方參考文件: https://pngquant.org/lib/
首先去https://github.com/ImageOptim/libimagequant下載原始碼,發現它是基於makefile構建的.
文件說使用make java就可以構建了,dll的話就是make java-dll
本地環境: centOS7.3 64位元、win7 64位元、jdk1.8、gcc9.3.0、GNU make4.3
在linux下構建比較簡單,直接cd到目錄下去make java
遇到的問題有:
1、configure檔案格式的問題,把內容複製下來重新copy一份即可
2、bad value錯誤
make clean
make distclean
./configure --extra-cflags="-fPIC"
make java
帶上fPIC再試一次就可以了
編譯完成後就有了libimagequant.jnilib檔案
當然,在linux上我們需要的是libimagequant.so而不是libimagequant.jnilib(這是macOS上使用的)
Make java的時候輸出的最後一個命令,我們copy下來修改一下
gcc -g -fno-math-errno -funroll-loops -fomit-frame-pointer -Wall -std=c99 -I. -O3 -DNDEBUG -DUSE_SSE=1 -msse -mfpmath=sse -Wno-unknown-pragmas -fexcess-precision=fast -fPIC -lm -I'/usr/lib/jvm/java-1.8.0-openjdk/include' -I'/usr/lib/jvm/java-1.8.0-openjdk/include/linux' -I'/usr/lib/jvm/java-1.8.0-openjdk/include/win32' -I'/usr/lib/jvm/java-1.8.0-openjdk/include/darwin' -shared -o libimagequant.so org/pngquant/PngQuant.c libimagequant.a
把目標檔案改爲.so,重新編譯就可以了
在win下構建可以用於本地開發測試,所以我這裏打一個dll
Win下使用cygwin就可以使用make命令構建了,當然,用visual studio也可以,還能避免一些坑。。。
Cygwin是一個可原生執行於Windows系統上的POSXI相容環境,使用它就可以用到常用的ls、pwd、make、gcc等命令,也是推薦的c++開發工具鏈
遇到的問題有:
1、Configure檔案格式不對,這個解決辦法跟linux下一樣
2、__int64定義錯誤
這種解決方法是不對滴,爲了引出問題還是記錄一下過程
發現報錯了,因爲gnu中沒有 __int64,而參照的win版本的jdk又有這個玩意
當時的解決方法是修改jni_md.h的34行,加上#ifdef else語句判斷一下當前環境。或者gcc編譯參數中加入宏定義-D__int64=’long long’
我直接修改了編譯參數,在Makefile裏面
java-dll:
$(MAKE) CFLAGS="$(CFLAGS) -DIMAGEQUANT_EXPORTS" $(JNIDLL)
$(JNIDLL) $(JNIDLLIMP): $(JAVAHEADERS) $(OBJS) org/pngquant/PngQuant.c
$(CC) -D__int64='long long' -fPIC -shared -I. $(JAVAINCLUDE) -o $(JNIDLL) $^ $(LDFLAGS) -Wl,--out-implib,$(JNIDLLIMP),--output-def,$(JNIDLLDEF)
在這兩處加上-D__int64=’long long’,給__int64這種型別一個宏定義,編譯倒是成功編譯了,就是執行時有錯誤
3、jni執行時error
這一步是在程式執行中報錯的,可以先看下面 下麪jin的使用再看這步報錯
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0000000180136797, pid=9848, tid=0x0000000000001c3c
#
# JRE version: Java(TM) SE Runtime Environment (8.0_191-b12) (build 1.8.0_191-b12)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# C [cygwin1.dll+0xf6797]
……
Stack: [0x0000000002580000,0x0000000002680000], sp=0x000000000267f6a0, free space=1021k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C [cygwin1.dll+0xf6797]
C [cygwin1.dll+0x9431f]
Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j org.pngquant.PngQuant.liq_attr_create()J+0
j org.pngquant.PngQuant.<init>()V+5
j com.ai.base.tool.pngquant.PngQuantTest.main([Ljava/lang/String;)V+4
v ~StubRoutines::call_stub
在呼叫create的時候直接error了,說是cygwin1.dll裏面報錯,這個cygwin1.dll是POSIX系統呼叫的模擬層,使用cygwin構建的所有程式都依賴了cygwin1.dll,所以這裏不是關鍵。
在看下面 下麪,報錯是在liq_attr_create方法中,就是編譯出來的dll有問題,不能正常執行。
解決方法是:換用MinGW編譯器,MinGW相比CygWin/gcc來講,更加貼近win32。因爲它幾乎支援所有的Win32API。
直接修改configure檔案,大概在17行的位置
# make gcc default compiler unless CC is already set
CC=${CC:-x86_64-w64-mingw32-gcc}
把編譯器換成這個,我這裏是用的cygwin的元件,如果沒有的話,去setup一遍cygwin,把這個元件選擇安裝上
然後重新編譯一次,編譯過後體積大了100多k,不過終於能正常執行了
關於編譯好的pngquant動態鏈接庫檔案,文章最後有下載
Jni這裏就不多介紹了
在pngquant原始碼中,lib\org\pngquant目錄下找到java檔案
這個makefile指令碼會呼叫javah生成標頭檔案
它長這個樣子,可以發現是javah自動生成的,下面 下麪的方法名是根據:Java+包名+類名+方法名生成的
所以如果生成好了,在專案中參照時不要修改包名,不然找不到方法
如果遇到找不到方法,那麼就確認一下動態鏈接庫的方法名是否正確
通過nm命令檢視,它可以列舉檔案中的符號,當然也可以看到方法名
我這裏的包結構是這樣的
Win下System.loadLibrary的是imagequant.dll(不帶lib字首),linux下就是libimagequant.so(帶lib字首)
public class PngQuantTest {
public static void main(String[] args) {
BufferedImage newImg;
PngQuant pngQuant = new PngQuant();
try {
newImg = pngQuant.getRemapped(ImageIO.read(new File("D:\\test\\92.png")));
ImageIO.write(newImg, "png", new File("D:\\test\\new_92.png"));
} catch (IOException e) {
e.printStackTrace();
}
pngQuant.close();
}
}
最後寫一個測試方法,執行新增jvm參數: -Djava.library.path=./lib,然後測試成功!
關於jni還有個比jni效能好一點的javacpp有興趣可以看看
https://github.com/bytedeco/javacpp