需求
使用Java将序列图转换为GIF动图,如果序列图本身包含alpha透明通道,生成的GIF图也要保留透明通道。
对于序列图转换为GIF动图的需求,如果数量比较大,对性能要求较高,建议直接使用专业的处理工具,比如ImageMagick, GraphicsMagick ,FFmpeg 等,不仅可以转换成GIF,还可以转换出压缩率更高的webp动图。
但是吧,有时候只需要一个很小的功能,引入一个大块头的组件,就有点杀鸡用牛刀了。故这里介绍下怎么用简单的Java代码实现序列图到GIF图的转换。
实现代码
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 序列图转gif图工具类
*
* @author lixk
*/
public class Gif {
private ImageWriter writer;
private ImageWriteParam params;
private IIOMetadata metadata;
/**
* 创建GIF图构造方法
*
* @param outputStream 输出流
* @param imageType 图片类型
* @param delay 序列帧间隔延迟,单位:毫秒
* @param loop 是否循环播放
* @throws IOException
*/
private Gif(ImageOutputStream outputStream, int imageType, int delay, boolean loop) throws IOException {
this.writer = ImageIO.getImageWritersBySuffix("gif").next();
this.params = writer.getDefaultWriteParam();
this.metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromBufferedImageType(imageType), params);
//配置元数据
this.configureRootMetadata(delay, loop);
//设置输出流
writer.setOutput(outputStream);
writer.prepareWriteSequence(null);
}
/**
* 配置元数据
*
* @param delay 延迟,单位:毫秒
* @param loop 是否循环播放
* @throws IIOInvalidTreeException
*/
private void configureRootMetadata(int delay, boolean loop) throws IIOInvalidTreeException {
String metaFormatName = metadata.getNativeMetadataFormatName();
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(metaFormatName);
IIOMetadataNode graphicsControlExtensionNode = getNode(root, "GraphicControlExtension");
graphicsControlExtensionNode.setAttribute("disposalMethod", "none");
graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE");
graphicsControlExtensionNode.setAttribute("transparentColorFlag", "FALSE");
graphicsControlExtensionNode.setAttribute("delayTime", Integer.toString(delay / 10));
graphicsControlExtensionNode.setAttribute("transparentColorIndex", "0");
IIOMetadataNode appExtensionsNode = getNode(root, "ApplicationExtensions");
IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension");
child.setAttribute("applicationID", "NETSCAPE");
child.setAttribute("authenticationCode", "2.0");
int loopContinuously = loop ? 0 : 1;
child.setUserObject(new byte[]{0x1, (byte) (loopContinuously & 0xFF), (byte) ((loopContinuously >> 8) & 0xFF)});
appExtensionsNode.appendChild(child);
metadata.setFromTree(metaFormatName, root);
}
/**
* 获取元数据节点
*
* @param rootNode 根节点
* @param nodeName 节点名称
* @return IIOMetadataNode
*/
private static IIOMetadataNode getNode(IIOMetadataNode rootNode, String nodeName) {
int nNodes = rootNode.getLength();
for (int i = 0; i < nNodes; i++) {
if (rootNode.item(i).getNodeName().equalsIgnoreCase(nodeName)) {
return (IIOMetadataNode) rootNode.item(i);
}
}
IIOMetadataNode node = new IIOMetadataNode(nodeName);
rootNode.appendChild(node);
return (node);
}
/**
* 将图片数据写入序列帧
*
* @param img 图片数据
* @throws IOException
*/
public void writeToSequence(RenderedImage img) throws IOException {
writer.writeToSequence(new IIOImage(img, null, metadata), params);
}
public void close() throws IOException {
writer.endWriteSequence();
}
/**
* 将序列图数据转换成gif图并写入输出流
*
* @param images 输入序列图
* @param outputStream 图像输出流
* @param delay 帧延迟,单位:毫秒
* @param loop 是否循环播放
* @param width 宽度
* @param height 高度
*/
public static void convert(BufferedImage[] images, ImageOutputStream outputStream, int delay, boolean loop, Integer width, Integer height) {
//图像类型
int imageType = images[0].getType();
//缩放参数
double sx = width == null ? 1.0 : ((double) width / images[0].getWidth());
double sy = height == null ? 1.0 : ((double) height / images[0].getHeight());
AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(sx, sy), null);
try {
Gif gif = new Gif(outputStream, imageType, delay, loop);
for (BufferedImage image : images) {
gif.writeToSequence(op.filter(image, null));
}
gif.close();
outputStream.close();
} catch (Exception e) {
throw new RuntimeException("GIF convert error", e);
}
}
/**
* 将序列图数据转换成gif图并写入输出流
*
* @param images 输入序列图
* @param outputStream 图像输出流
* @param delay 帧延迟,单位:毫秒
* @param loop 是否循环播放
*/
public static void convert(BufferedImage[] images, ImageOutputStream outputStream, int delay, boolean loop) {
Gif.convert(images, outputStream, delay, loop, null, null);
}
/**
* 将序列图转换成gif图
*
* @param imagePaths 输入序列图路径
* @param gifPath gif图路径
* @param delay 帧延迟,单位:毫秒
* @param loop 是否循环播放
* @param width 宽度
* @param height 高度
*/
public static void convert(String[] imagePaths, String gifPath, int delay, boolean loop, Integer width, Integer height) {
try {
BufferedImage[] images = new BufferedImage[imagePaths.length];
for (int i = 0; i < imagePaths.length; i++) {
images[i] = ImageIO.read(new File(imagePaths[i]));
}
FileImageOutputStream fileImageOutputStream = new FileImageOutputStream(new File(gifPath));
convert(images, fileImageOutputStream, delay, loop, width, height);
} catch (Exception e) {
throw new RuntimeException("GIF convert error", e);
}
}
/**
* 将序列图转换成gif图
*
* @param imagePaths 输入序列图路径
* @param gifPath gif图路径
* @param delay 帧延迟,单位:毫秒
* @param loop 是否循环播放
*/
public static void convert(String[] imagePaths, String gifPath, int delay, boolean loop) {
Gif.convert(imagePaths, gifPath, delay, loop, null, null);
}
}
测试代码
public class GifTest {
public static void main(String[] args) {
//图1
String[] images = new String[]{"E:/图片/图标/叶子.png", "E:/图片/图标/four_leaf_clover.png", "E:/图片/图标/Monster.png"};
Gif.convert(images, "E:/output1.gif", 300, true);
List<String> list = new ArrayList<>();
//图2
for (int i = 1; i <= 5; i++) {
list.add(String.format("F:/video/images/%04d.jpg", i));
}
Gif.convert(list.toArray(new String[]{}), "E:/output2.gif", 100, true, 192, 108);
}
}
输出结果
output1.gif
output2.gif