java中png转索引图压缩图片

背景

需求是大大的减小彩信图片的体积,可以发送更多数量的彩信图片,图片的内容是报表,颜色单一。最开始把透明色去掉了,位深从32->24,但是最近测试发现彩信图片体积还是太大了,这就需要去学习图片压缩的知识了

png格式简介

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来操作

使用OpenViewerFX

<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(JNI调用)

使用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下构建

在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下构建

在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调用

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

关于pngquant的jni动态链接库文件下载

https://download.csdn.net/download/w57685321/12706816

猜你喜欢

转载自blog.csdn.net/w57685321/article/details/107959988
今日推荐