预备接手表情包处理业务,前期处理并不复杂,流程包括 : GIF动图与视频的解帧 , 逐帧处理, 组合各帧得到新的GIF. 经过调研, 整合了ffmpeg的Java CV 可完美处理解帧 , animated-gif-lib 组件包含gif生成的成熟方案 , 进而问题解决.
animated-gif-lib + Java CV
animated-gif-lib.jar是用来拆分和合成GIF的工具包,主要用到其中的GifDecoder/AnimatedGifEncoder.
Java CV 常用于音频/图片等处理,其中整合了常用的c++类库,例如音频处理的ffmpeg,且可与Open CV配合使用.这里主要用到FFmpegFrameGrabber来取帧/Java2DFrameConverter来类型转换.
其实,GifDecoder也可以完成对GIF的解帧,但无法对视频进行操作,且实际使用中发现各帧颜色处理上有偏差,但并不影响最后新GIF的合成.综上,为了代码的复用性,采用Java CV来解帧,只使用其中AnimatedGifEncoder来完成合成GIF的操作.
代码实现
解帧,FFmpegFrameGrabber获取GIF总帧数时异常(),故而采用GifDecoder获取
String gifPath = "/home/lab/test/11.gif";
String dirPath= "/home/lab/test/gif/";//用以解帧
FFmpegFrameGrabber grabberGif = newFFmpegFrameGrabber(gifPath);
grabberGif.start();
Frame frame ;//用以获取GIF总帧数
GifDecoder decoder = newGifDecoder();int status =decoder.read(gifPath);if (status !=GifDecoder.STATUS_OK) {throw new IOException("read image " + gifPath + " error!");
}//类型转换,Frame -> BufferedImage
Java2DFrameConverter converter = newJava2DFrameConverter();int frameCount =decoder.getFrameCount();for (int i = 0 ; i < frameCount ; i++) {
String fileName= dirPath + "img_" + i + ".jpg";
File outPut= newFile(fileName);
frame=grabberGif.grabImage();if (frame != null) {
ImageIO.write(converter.getBufferedImage(frame),"jpg",outPut);
}
}
grabberGif.stop();
合成GIF
int frameRate = 20;//新GIF总帧数
String resGif = "/home/lab/test/22.gif";
FileOutputStream targetFile= new FileOutputStream(resGif); //目标文件流
int margin = 2; //间隔帧数
AnimatedGifEncoder en = newAnimatedGifEncoder();
en.setFrameRate(frameRate);
en.start(targetFile);for (int i = 0; i < frameRate; i++) {
en.addFrame(converter.convert(grabberGif.grabImage()));
grabberGif.setFrameNumber(grabberGif.getFrameNumber()+margin);
}
en.finish();
grabberGif.stop();
targetFile.close();
原GIF倒序得到新GIF
String gifPath = "/home/lab/test/11.gif";//用以解帧
FFmpegFrameGrabber grabberGif = newFFmpegFrameGrabber(gifPath);
grabberGif.start();//用以获取GIF总帧数
GifDecoder decoder = newGifDecoder();int status =decoder.read(gifPath);if (status !=GifDecoder.STATUS_OK) {throw new IOException("read image " + gifPath + " error!");
}//类型转换,Frame -> BufferedImage
Java2DFrameConverter converter = newJava2DFrameConverter();int frameCount =decoder.getFrameCount();
String resGif= "/home/lab/test/22.gif";
FileOutputStream targetFile= new FileOutputStream(resGif); //目标文件流
AnimatedGifEncoder en = newAnimatedGifEncoder();
en.setFrameRate(frameCount);
en.start(targetFile);for (int i = frameCount - 1; i >= 0; i--) {
grabberGif.setFrameNumber(i);
en.addFrame(converter.convert(grabberGif.grabImage()));
}
en.finish();
grabberGif.stop();
targetFile.close();
基于GifDecoder和AnimatedGifEncoder实现的gif倒序
String outputPath = "/home/lab/test/001.gif";
String imagePath= "/home/lab/test/33.gif";
GifDecoder decoder= newGifDecoder();int status =decoder.read(imagePath);if (status !=GifDecoder.STATUS_OK) {throw new IOException("read image " + imagePath + " error!");
}//拆分一帧一帧的压缩之后合成
AnimatedGifEncoder encoder = newAnimatedGifEncoder();
encoder.start(outputPath);
encoder.setRepeat(decoder.getLoopCount());for (int i = decoder.getFrameCount() -1; i >= 0; i--) {
encoder.setDelay(decoder.getDelay(i));//设置播放延迟时间
BufferedImage bufferedImage = decoder.getFrame(i);//获取每帧BufferedImage流
int height =bufferedImage.getHeight();int width =bufferedImage.getWidth();
BufferedImage zoomImage= newBufferedImage(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= newFile(outputPath);
BufferedImage image=ImageIO.read(outFile);
ImageIO.write(image, outFile.getName(), outFile);
视频转gif
String videpPath = "/home/lab/test/t1.mp4";
String gifPath= "/home/lab/test/test.gif";
FileOutputStream targetFile= newFileOutputStream(gifPath);
FFmpegFrameGrabber grabber= newFFmpegFrameGrabber(videpPath);
grabber.start();
Frame frame;int frames =grabber.getLengthInFrames();
AnimatedGifEncoder encoder= newAnimatedGifEncoder();
encoder.setFrameRate(frames);
encoder.start(targetFile);
Java2DFrameConverter converter= newJava2DFrameConverter();for (int i = 0 ; i < frames ; i++) {
encoder.setDelay((int) grabber.getDelayedTime());
grabber.setFrameNumber(i);
frame=grabber.grabImage();
encoder.addFrame(converter.convert(frame));
}
encoder.finish();
targetFile.close();
grabber.close();
pom依赖
因 javacv-platform依赖过重,实际引入的时候推荐指定系统版本的即可.开发机为64位Ubuntu,依赖如下
org.bytedeco
javacv
1.4.3
org.bytedeco.javacpp-presets
opencv
3.4.3-1.4.3
linux-x86_64
org.bytedeco.javacpp-presets
ffmpeg
4.0.2-1.4.3
linux-x86_64
org.bytedeco
javacpp
1.4.3
org.bytedeco.javacpp-presets
ffmpeg
4.0.2-1.4.3
linux-x86_64
com.madgag
animated-gif-lib
1.4