考虑到偶尔会有使用Gif动图进行演示截图的情况,选择自己制作了一款简单的截屏生成Gif的小工具
需要使用到hutool工具包
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.8.15version>
dependency>
主要用到的技术点有:
(1)Java自带的截屏函数
(2)hutool里的将图片转为gif图片的函数
(3)监听全局键盘事件
(4)Swing界面的简单绘制
package com.boot.util;
import cn.hutool.core.img.gif.AnimatedGifEncoder;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author bbyh
* @date 2023/12/19 13:59
*/
public class ScreenToGifFrame extends JFrame {
private static final String IMG = "img";
private static String RESULT_DIR = System.getProperty("user.dir") + File.separator + IMG;
private static final List<String> RESULT_IMG_LIST = new ArrayList<>(10);
private static Integer SEQUENCE = 1;
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd hh-mm-ss");
private static final Integer COUNT_DOWN = 3;
private static final JLabel RESULT_DISPLAY = new JLabel();
private static final JFrame COUNT_FRAME = new JFrame();
private static final JLabel COUNT_DOWN_LABEL = new JLabel("4");
private static Boolean STATUS = false;
private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(5, 10, 1000, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), new ThreadPoolExecutor.AbortPolicy());
static {
COUNT_FRAME.setSize(300, 150);
COUNT_FRAME.setLocationRelativeTo(null);
COUNT_FRAME.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
COUNT_FRAME.setTitle("倒计时");
COUNT_FRAME.setLayout(new GridLayout(1, 1));
COUNT_FRAME.add(COUNT_DOWN_LABEL);
COUNT_DOWN_LABEL.setFont(new Font("楷体", Font.BOLD, 50));
COUNT_DOWN_LABEL.setHorizontalAlignment(SwingConstants.CENTER);
RESULT_DISPLAY.setFont(new Font("楷体", Font.BOLD, 14));
File file = new File(IMG);
if (!file.exists()) {
if (!file.mkdir()) {
RESULT_DISPLAY.setText(IMG + "文件夹,存放录屏临时文件夹创建失败");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
}
}
}
public ScreenToGifFrame() {
setTitle("将屏幕录制变为Gif");
setSize(600, 150);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel screenIntervalLabel = new JLabel("录屏间隔(ms):");
JTextField screenIntervalInput = new JTextField(20);
screenIntervalInput.setText("300");
JButton startButton = new JButton("开始录制");
JButton stopButton = new JButton("结束录制");
JButton resultDirButton = new JButton("保存目录");
JButton specificationButton = new JButton("使用说明");
setLayout(new GridLayout(2, 1));
JPanel screenIntervalPanel = new JPanel();
JPanel buttonPanel = new JPanel();
add(screenIntervalPanel);
add(buttonPanel);
screenIntervalPanel.add(screenIntervalLabel);
screenIntervalPanel.add(screenIntervalInput);
buttonPanel.add(startButton);
buttonPanel.add(stopButton);
buttonPanel.add(resultDirButton);
buttonPanel.add(specificationButton);
Font font = new Font("楷体", Font.BOLD, 20);
screenIntervalLabel.setFont(font);
screenIntervalInput.setFont(font);
startButton.setFont(font);
stopButton.setFont(font);
resultDirButton.setFont(font);
specificationButton.setFont(font);
startButton.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
start(screenIntervalInput);
}
});
stopButton.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
stop();
}
});
Toolkit.getDefaultToolkit().addAWTEventListener(event -> {
if (event.getID() == KeyEvent.KEY_PRESSED) {
KeyEvent e = (KeyEvent) event;
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
stop();
} else if (e.getKeyCode() == KeyEvent.VK_TAB) {
start(screenIntervalInput);
}
}
}, AWTEvent.KEY_EVENT_MASK);
JFileChooser chooser = new JFileChooser("./");
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
resultDirButton.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
RESULT_DIR = chooser.getSelectedFile().getPath();
RESULT_DISPLAY.setText("您当前设置的输出目录为:" + RESULT_DIR);
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
chooser.setVisible(false);
}
}
});
specificationButton.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
RESULT_DISPLAY.setText("在点击开始或者按下Tab键3秒后,按照指定间隔截屏,点击结束或者按下ESC键后生成Gif图片到指定目录下,默认在当前运行目录下");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
}
});
}
private void stop() {
STATUS = false;
EXECUTOR.execute(this::generateGif);
RESULT_DISPLAY.setText("正在生成Gif,请稍候");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
}
private void start(JTextField screenIntervalInput) {
COUNT_FRAME.setVisible(true);
COUNT_FRAME.setAlwaysOnTop(true);
for (int i = COUNT_DOWN; i > 0; i--) {
COUNT_DOWN_LABEL.setText(i + "");
COUNT_FRAME.update(COUNT_FRAME.getGraphics());
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
COUNT_FRAME.setVisible(false);
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
EXECUTOR.execute(() -> {
STATUS = true;
int screenInterval = 0;
boolean isScreenIntervalValid = true;
try {
screenInterval = Integer.parseInt(screenIntervalInput.getText());
} catch (NumberFormatException ex) {
isScreenIntervalValid = false;
}
if (!isScreenIntervalValid || screenInterval <= 0) {
RESULT_DISPLAY.setText("设置的录屏间隔需要为正整数");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
return;
}
while (STATUS) {
screenCapture();
try {
Thread.sleep(screenInterval);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
});
}
private void generateGif() {
try {
AnimatedGifEncoder animatedGifEncoder = new AnimatedGifEncoder();
animatedGifEncoder.start(Files.newOutputStream(Paths.get(RESULT_DIR + File.separator + FORMAT.format(new Date()) + ".gif")));
animatedGifEncoder.setDelay(500);
animatedGifEncoder.setRepeat(0);
for (String imgName : RESULT_IMG_LIST) {
File file = new File(imgName);
if (file.exists()) {
BufferedImage image = ImageIO.read(file);
animatedGifEncoder.addFrame(image);
if (!file.delete()) {
RESULT_DISPLAY.setText("录屏临时文件删除失败");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
}
}
}
animatedGifEncoder.finish();
RESULT_DISPLAY.setText("Gif生成成功");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private void screenCapture() {
try {
Robot robot = new Robot();
Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
Rectangle rectangle = new Rectangle(dimension);
BufferedImage bufferedImage = robot.createScreenCapture(rectangle);
String captureName = RESULT_DIR + File.separator + "screenshot" + SEQUENCE + ".jpg";
SEQUENCE++;
RESULT_IMG_LIST.add(captureName);
ImageIO.write(bufferedImage, "jpg", new File(captureName));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public static void main(String[] args) {
new ScreenToGifFrame().setVisible(true);
}
}
之前的代码含有不少隐藏bug,我对此进行了一些简单的修正,下面是修正后的工具类源码
package com.boot.util;
import cn.hutool.core.img.gif.AnimatedGifEncoder;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author bbyh
* @date 2023/12/19 13:59
*/
public class ScreenToGifFrame extends JFrame {
private static final String IMG = "img";
private static final String TEMP = "temp";
private static String RESULT_DIR = System.getProperty("user.dir") + File.separator + IMG;
private static final String RESULT_TEMP_DIR = System.getProperty("user.dir") + File.separator + IMG + File.separator + TEMP;
private static final List<String> RESULT_IMG_LIST = new ArrayList<>(200);
private static Integer SEQUENCE = 1;
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
private static final Integer COUNT_DOWN = 3;
private static final JLabel RESULT_DISPLAY = new JLabel();
private static final JFrame COUNT_FRAME = new JFrame();
private static final JLabel COUNT_DOWN_LABEL = new JLabel("4");
private static Boolean STATUS = false;
private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(5, 10, 1000, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), new ThreadPoolExecutor.AbortPolicy());
static {
COUNT_FRAME.setSize(300, 150);
COUNT_FRAME.setLocationRelativeTo(null);
COUNT_FRAME.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
COUNT_FRAME.setTitle("倒计时");
COUNT_FRAME.setLayout(new GridLayout(1, 1));
COUNT_FRAME.add(COUNT_DOWN_LABEL);
COUNT_DOWN_LABEL.setFont(new Font("楷体", Font.BOLD, 50));
COUNT_DOWN_LABEL.setHorizontalAlignment(SwingConstants.CENTER);
RESULT_DISPLAY.setFont(new Font("楷体", Font.BOLD, 14));
checkDir();
}
private static void checkDir() {
File file = new File(IMG);
if (!file.exists()) {
if (!file.mkdir()) {
RESULT_DISPLAY.setText(IMG + "文件夹,存放录屏临时文件夹创建失败");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
}
}
file = new File(IMG + File.separator + TEMP);
if (!file.exists()) {
if (!file.mkdir()) {
RESULT_DISPLAY.setText(IMG + "文件夹,存放录屏临时文件夹创建失败");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
}
}
}
public ScreenToGifFrame() {
setTitle("将屏幕录制变为Gif");
setSize(800, 150);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel screenIntervalLabel = new JLabel("录屏间隔(ms):");
JTextField screenIntervalInput = new JTextField(20);
screenIntervalInput.setText("100");
JButton startButton = new JButton("开始录制");
JButton stopButton = new JButton("结束录制");
JButton resultDirButton = new JButton("保存目录");
JButton clearTempDirButton = new JButton("清空临时目录");
JButton specificationButton = new JButton("使用说明");
setLayout(new GridLayout(2, 1));
JPanel screenIntervalPanel = new JPanel();
JPanel buttonPanel = new JPanel();
add(screenIntervalPanel);
add(buttonPanel);
screenIntervalPanel.add(screenIntervalLabel);
screenIntervalPanel.add(screenIntervalInput);
buttonPanel.add(startButton);
buttonPanel.add(stopButton);
buttonPanel.add(resultDirButton);
buttonPanel.add(clearTempDirButton);
buttonPanel.add(specificationButton);
Font font = new Font("楷体", Font.BOLD, 20);
screenIntervalLabel.setFont(font);
screenIntervalInput.setFont(font);
startButton.setFont(font);
stopButton.setFont(font);
resultDirButton.setFont(font);
clearTempDirButton.setFont(font);
specificationButton.setFont(font);
startButton.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
start(screenIntervalInput);
}
});
stopButton.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
stop();
}
});
Toolkit.getDefaultToolkit().addAWTEventListener(event -> {
if (event.getID() == KeyEvent.KEY_PRESSED) {
KeyEvent e = (KeyEvent) event;
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
stop();
} else if (e.getKeyCode() == KeyEvent.VK_TAB) {
start(screenIntervalInput);
}
}
}, AWTEvent.KEY_EVENT_MASK);
JFileChooser chooser = new JFileChooser("./");
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
resultDirButton.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
RESULT_DIR = chooser.getSelectedFile().getPath();
RESULT_DISPLAY.setText("您当前设置的输出目录为:" + RESULT_DIR);
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
chooser.setVisible(false);
}
}
});
clearTempDirButton.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
File dir = new File(RESULT_TEMP_DIR);
if (dir.exists()) {
File[] files = dir.listFiles();
if (files != null) {
boolean deleteSuccess = true;
for (File file : files) {
if (!file.delete()) {
deleteSuccess = false;
RESULT_DISPLAY.setText("录屏临时文件删除失败");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
}
}
if (deleteSuccess) {
RESULT_IMG_LIST.clear();
RESULT_DISPLAY.setText("录屏临时文件删除成功");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
}
}
}
}
});
specificationButton.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
RESULT_DISPLAY.setText("在点击开始或者按下Tab键3秒后,按照指定间隔截屏,点击结束或者按下ESC键后生成Gif图片到指定目录下,默认在当前运行目录下");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
}
});
}
private void stop() {
STATUS = false;
EXECUTOR.execute(this::generateGif);
RESULT_DISPLAY.setText("正在生成Gif,请稍候");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
}
private void start(JTextField screenIntervalInput) {
COUNT_FRAME.setVisible(true);
COUNT_FRAME.setAlwaysOnTop(true);
for (int i = COUNT_DOWN; i > 0; i--) {
COUNT_DOWN_LABEL.setText(i + "");
COUNT_FRAME.update(COUNT_FRAME.getGraphics());
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
COUNT_FRAME.setVisible(false);
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
EXECUTOR.execute(() -> {
STATUS = true;
int screenInterval = 0;
boolean isScreenIntervalValid = true;
try {
screenInterval = Integer.parseInt(screenIntervalInput.getText());
} catch (NumberFormatException ex) {
isScreenIntervalValid = false;
}
if (!isScreenIntervalValid || screenInterval <= 0) {
RESULT_DISPLAY.setText("设置的录屏间隔需要为正整数");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
return;
}
SEQUENCE = 0;
RESULT_IMG_LIST.clear();
while (STATUS) {
screenCapture();
try {
Thread.sleep(screenInterval);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
});
}
private void generateGif() {
checkDir();
try {
AnimatedGifEncoder animatedGifEncoder = new AnimatedGifEncoder();
animatedGifEncoder.start(Files.newOutputStream(Paths.get(RESULT_DIR + File.separator + FORMAT.format(new Date()) + ".gif")));
animatedGifEncoder.setDelay(300);
animatedGifEncoder.setRepeat(0);
for (String imgName : RESULT_IMG_LIST) {
File file = new File(imgName);
if (file.exists()) {
BufferedImage image = ImageIO.read(file);
animatedGifEncoder.addFrame(image);
}
}
animatedGifEncoder.finish();
RESULT_DISPLAY.setText("Gif生成成功");
JOptionPane.showMessageDialog(null, RESULT_DISPLAY);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private void screenCapture() {
checkDir();
try {
Robot robot = new Robot();
Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
Rectangle rectangle = new Rectangle(dimension);
BufferedImage bufferedImage = robot.createScreenCapture(rectangle);
String captureName = RESULT_TEMP_DIR + File.separator + "screenshot" + System.currentTimeMillis() + SEQUENCE + ".jpg";
SEQUENCE++;
RESULT_IMG_LIST.add(captureName);
ImageIO.write(bufferedImage, "jpg", new File(captureName));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public static void main(String[] args) {
new ScreenToGifFrame().setVisible(true);
}
}