需求
使用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