Java創建GIF圖,帶透明背景

需求

使用Java將序列圖轉換爲GIF動圖,如果序列圖本身包含alpha透明通道,生成的GIF圖也要保留透明通道。
對於序列圖轉換爲GIF動圖的需求,如果數量比較大,對性能要求較高,建議直接使用專業的處理工具,比如ImageMagickGraphicsMagick 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
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章