做一个努力的人好处在于,人人见了你都会想帮你。但如果你自己不先做出一点努力的样子,人家想拉你一把,都不知你的手在哪里。
GIF的压缩,对大多数程序开发者来说是一大难点,要么无法压缩,要么压缩之后GIF失去动画效果。这是因为GIF不像其他格式图片,GIF是由一帧一帧的图片合成,所以在进行压缩的时候,就不能简简单单的按照其他图片压缩方式那样处理。
那么究竟该如何处理GIF的压缩呢?
GIF是由一帧一帧合成,那么压缩过程中可以先将GIF拆分成一帧一帧,然后在一帧一帧的压缩,最后将压缩结果合成新的GIF,这样就能起到压缩的效果。
标题中提到的animated-gif-lib.jar是用来拆分和合成GIF的帮助类,主要用到它提供的GifDecoder和AnimatedGifEncoder。
Java SDK是用来负责压缩每帧数据,只针对于质量压缩。
animated-gif-lib.jar是专门针对GIF的工具类,可使用maven导入工程,也可去官网下载。
<dependency>
<groupId>com.madgag</groupId>
<artifactId>animated-gif-lib</artifactId>
<version>1.2</version>
</dependency>
1、拆分GIF
拆分GIF需要借助GifDecoder:
GifDecoder decoder = new GifDecoder();
int status = decoder.read(imagePath);// imagePath源文件路径
if (status != GifDecoder.STATUS_OK) {
throw new IOException("read image " + imagePath + " error!");
}
通过GifDecoder就可以获取每帧数据:
int frameCount = decoder.getFrameCount();// 获取GIF有多少个frame
for (int i = 0; i < frameCount; i++) {
BufferedImage bufferedImage = decoder.getFrame(i);
......
}
2、合成GIF
合成Gif主要是AnimatedGifEncoder来实现:
AnimatedGifEncoder encoder = new AnimatedGifEncoder();
encoder.start(outputPath);// 设置合成位置
int frameCount = decoder.getFrameCount();// 获取GIF有多少个frame
for (int i = 0; i < frameCount; i++) {
......
encoder.addFrame(zoomImage);// 合成
}
encoder.finish();
首先要获取图片的BufferedImage流,即读取图片内存数据。
File file = new File(imagePath);// imagePath源文件路径
BufferedImage bufferedImage = ImageIO.read(file);// 获取BufferedImage流
其次获取图片文件的ImageWriter,来实现图片文件的重构。
// 得到指定Format图片的writer
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");// 得到迭代器
ImageWriter writer = (ImageWriter) iter.next(); // 得到writer
接下来就是比较核心的内容,压缩图片。
// 得到指定writer的输出参数设置(ImageWriteParam)
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // 设置可否压缩
iwp.setCompressionQuality(quality); // 设置压缩质量参数,0~1,1为最高质量
iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
ColorModel colorModel = ColorModel.getRGBdefault();
// 指定压缩时使用的色彩模式
iwp.setDestinationType(new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(16, 16)));
// 开始打包图片,写入byte[]
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流
IIOImage iIamge = new IIOImage(bufferedImage, null, null);
// 此处因为ImageWriter中用来接收write信息的output要求必须是ImageOutput
// 通过ImageIo中的静态方法,得到byteArrayOutputStream的ImageOutput
writer.setOutput(ImageIO.createImageOutputStream(byteArrayOutputStream));
writer.write(null, iIamge, iwp);
最后获取压缩图片的ByteArrayOutputStream,转化成图片文件。
// 获取压缩后的btye
byte[] tempByte = byteArrayOutputStream.toByteArray();
// 创建输出文件,outputPath输出文件路径,imgStyle目标文件格式(png)
File outFile = new File(outputPath + "." + imgStyle);
FileOutputStream fos = new FileOutputStream(outFile);
fos.write(tempByte);
fos.close();
最后将上面的思路代码封装成zoomBufferedImageByQuality(BufferedImage bufferedImage, float quality)方法,以方便后面进行调用。
GIF压缩质量,尺寸不变
/**
* @param imagePath 原图片路径地址,如:F:\\a.png
* @param imgStyle 目标文件类型
* @param quality 输出的图片质量,范围:0.0~1.0,1为最高质量。
* @param outputPath 输出文件路径(不带后缀),如:F:\\b,默认与原图片路径相同,为空时将会替代原文件
* @throws IOException
*/
public void zoomGifByQuality(String imagePath, String imgStyle, float quality, String outputPath) throws IOException {
// 防止图片后缀与图片本身类型不一致的情况
outputPath = outputPath + "." + imgStyle;
// GIF需要特殊处理
GifDecoder decoder = new GifDecoder();
int status = decoder.read(imagePath);
if (status != GifDecoder.STATUS_OK) {
throw new IOException("read image " + imagePath + " error!");
}
// 拆分一帧一帧的压缩之后合成
AnimatedGifEncoder encoder = new AnimatedGifEncoder();
encoder.start(outputPath);// 设置合成位置
encoder.setRepeat(decoder.getLoopCount());// 设置GIF重复次数
int frameCount = decoder.getFrameCount();// 获取GIF有多少个frame
for (int i = 0; i < frameCount; i++) {
encoder.setDelay(decoder.getDelay(i));// 设置GIF延迟时间
BufferedImage bufferedImage = decoder.getFrame(i);
// 利用java SDK压缩BufferedImage
byte[] tempByte = zoomBufferedImageByQuality(bufferedImage, quality);
ByteArrayInputStream in = new ByteArrayInputStream(tempByte);
BufferedImage zoomImage = ImageIO.read(in);
encoder.addFrame(zoomImage);// 合成
}
encoder.finish();
File outFile = new File(outputPath);
BufferedImage image = ImageIO.read(outFile);
ImageIO.write(image, outFile.getName(), outFile);
}
GIF压缩尺寸大小
/**
* @param imagePath 原图片路径地址,如:F:\\a.png
* @param imgStyle 目标文件类型
* @param width 目标文件宽
* @param height 目标文件高
* @param outputPath 输出文件路径(不带后缀),如:F:\\b,默认与原图片路径相同,为空时将会替代原文件
* @throws IOException
*/
public void zoomGifBySize(String imagePath, String imgStyle, int width, int height, String outputPath) throws IOException {
// 防止图片后缀与图片本身类型不一致的情况
outputPath = outputPath + "." + imgStyle;
// GIF需要特殊处理
GifDecoder decoder = new GifDecoder();
int status = decoder.read(imagePath);
if (status != GifDecoder.STATUS_OK) {
throw new IOException("read image " + imagePath + " error!");
}
// 拆分一帧一帧的压缩之后合成
AnimatedGifEncoder encoder = new AnimatedGifEncoder();
encoder.start(outputPath);
encoder.setRepeat(decoder.getLoopCount());
for (int i = 0; i < decoder.getFrameCount(); i++) {
encoder.setDelay(decoder.getDelay(i));// 设置播放延迟时间
BufferedImage bufferedImage = decoder.getFrame(i);// 获取每帧BufferedImage流
BufferedImage zoomImage = new BufferedImage(width, height, bufferedImage.getType());
Image image = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
Graphics gc = zoomImage.getGraphics();
gc.setColor(Color.WHITE);
gc.drawImage(image, 0, 0, null);
encoder.addFrame(zoomImage);
}
encoder.finish();
File outFile = new File(outputPath);
BufferedImage image = ImageIO.read(outFile);
ImageIO.write(image, outFile.getName(), outFile);
}
以上便是封装的GIF压缩质量,和GIF压缩尺寸的方法,当然网上也有一些针对GIF处理的一些框架或者算法,这里写的方法仅供参考,个人不是很推荐GIF进行压缩,因为GIF在合成的时候自身已经进行了一次压缩,而且GIF压缩过程比较耗性能。
微信公众号:伴职创作