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
在这里插入图片描述

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