关键思路
启动应用 -> 创建全屏透明窗口 -> 监听鼠标事件 -> 拖动绘制选区 -> 释放鼠标
↓ ↑ ↓
隐藏窗口 -> 截取选区图像 -> 转换格式 -> 复制到剪贴板 -> 关闭窗口
具体代码
最新代码请参考链接https://gitee.com/likexiang/source-code-records/blob/master/JAVA/ScreenCapture.java
package sample;
// JavaFX核心库导入
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
// AWT相关库导入(用于截图和剪贴板操作)
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.image.BufferedImage;
import java.io.IOException;
// JavaFX快捷键组合支持
import javafx.scene.input.KeyCombination;
/**
* 屏幕截图工具 - 支持区域选择并复制到剪贴板
* 主要功能:
* 1. 全屏透明覆盖层
* 2. 鼠标拖拽选择矩形区域
* 3. 实时显示选区尺寸
* 4. 截图复制到系统剪贴板
* 5. ESC键退出截图模式
*/
public class ScreenCapture extends Application {
// 成员变量声明
private Point2D origin; // 记录鼠标按下时的起始坐标
private Pane root; // 根布局容器
private Canvas canvas; // 用于绘制选择框的画布
private Label sizeLabel; // 显示选区尺寸的标签
private GraphicsContext gc; // 画布绘图上下文
private Stage primaryStage; // 主舞台(窗口)
/**
* JavaFX应用入口方法
* @param primaryStage 主舞台对象
*/
@Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
setupUI(); // 初始化UI组件
setupStage(); // 配置舞台属性
setupEventHandlers(); // 设置事件监听
}
/**
* 初始化用户界面组件
*/
private void setupUI() {
root = new Pane();
// 创建覆盖整个屏幕的Canvas
canvas = new Canvas(getTotalScreenWidth(), getTotalScreenHeight());
gc = canvas.getGraphicsContext2D(); // 获取绘图上下文
// 初始化尺寸标签样式
sizeLabel = new Label();
sizeLabel.setTextFill(Color.WHITE); // 白色文字
// 半透明黑色背景(rgba最后0.5表示50%透明度)
sizeLabel.setStyle("-fx-background-color: rgba(0, 0, 0, 0.1); -fx-padding: 2 5;");
// 将组件添加到根容器
root.getChildren().addAll(canvas, sizeLabel);
}
/**
* 配置舞台属性
*/
private void setupStage() {
// 创建透明场景
Scene scene = new Scene(root);
// 舞台初始化设置
primaryStage.initStyle(StageStyle.TRANSPARENT); // 透明无边框样式
primaryStage.setScene(scene); // 设置场景到舞台
primaryStage.setOpacity(0.2);
primaryStage.setX(0);
primaryStage.setY(0);
primaryStage.show(); // 显示窗口
}
/**
* 设置事件处理器
*/
private void setupEventHandlers() {
// 鼠标按下事件:记录起始点
root.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.PRIMARY) { // 左键点击
origin = new Point2D(e.getScreenX(), e.getScreenY());
}
});
// 鼠标拖动事件:实时绘制选择框
root.setOnMouseDragged(e -> {
if (origin != null) {
clearCanvas(); // 清除旧绘制
drawSelectionBox(origin, new Point2D(e.getScreenX(), e.getScreenY()));
updateSizeLabel(origin, new Point2D(e.getScreenX(), e.getScreenY()));
}
});
// 鼠标释放事件:执行截图操作
root.setOnMouseReleased(e -> {
if (origin != null) {
try {
captureSelectedArea(origin, new Point2D(e.getScreenX(), e.getScreenY()));
} catch (AWTException ex) {
ex.printStackTrace();
}
primaryStage.close(); // 关闭窗口
}
});
// 键盘事件:ESC键退出
root.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ESCAPE) {
primaryStage.close();
}
});
}
/**
* 绘制选择框和半透明遮罩
* @param start 起点坐标
* @param end 终点坐标
*/
private void drawSelectionBox(Point2D start, Point2D end) {
// 计算选区坐标和尺寸
double x = Math.min(start.getX(), end.getX());
double y = Math.min(start.getY(), end.getY());
double width = Math.abs(end.getX() - start.getX());
double height = Math.abs(end.getY() - start.getY());
// 绘制半透明填充(20%不透明度)
gc.setFill(Color.rgb(0, 0, 0, 0.6));
gc.fillRect(x, y, width, height);
// 绘制红色边框
gc.setStroke(Color.RED);
gc.setLineWidth(1);
// +0.5偏移量用于消除抗锯齿导致的模糊
gc.strokeRect(x + 0.5, y + 0.5, width - 1, height - 1);
}
/**
* 更新尺寸标签的位置和内容
* @param start 起点坐标
* @param end 终点坐标
*/
private void updateSizeLabel(Point2D start, Point2D end) {
// 计算宽高
int width = (int) Math.abs(end.getX() - start.getX());
int height = (int) Math.abs(end.getY() - start.getY());
sizeLabel.setText(width + " x " + height);
// 将标签定位在选区右下角
double labelX = Math.max(start.getX(), end.getX()) - sizeLabel.getWidth() - 5; // 右侧留5像素
double labelY = Math.max(start.getY(), end.getY()) - sizeLabel.getHeight() - 5; // 底部留5像素
sizeLabel.setLayoutX(labelX);
sizeLabel.setLayoutY(labelY);
}
/**
* 捕获选定区域并复制到剪贴板
* @param start 起点坐标
* @param end 终点坐标
*/
private void captureSelectedArea(Point2D start, Point2D end) throws AWTException {
// 计算截图区域参数
int x = (int) Math.min(start.getX(), end.getX());
int y = (int) Math.min(start.getY(), end.getY());
int width = (int) Math.abs(end.getX() - start.getX());
int height = (int) Math.abs(end.getY() - start.getY());
if (width > 0 && height > 0) {
primaryStage.hide(); // 隐藏窗口避免影响截图
try {
Robot robot = new Robot(); // 创建AWT机器人
// 截取屏幕区域
BufferedImage screenshot = robot.createScreenCapture(
new java.awt.Rectangle(x, y, width, height));
screenshot = convertToARGB(screenshot); // 转换图像格式
copyImageToClipboard(screenshot); // 复制到剪贴板
System.out.println("截图已复制到剪贴板,尺寸:" + width + "x" + height);
} finally {
primaryStage.close(); // 确保窗口关闭
}
}
}
/**
* 转换图像为ARGB格式(支持透明度)
* @param src 原始图像
* @return 转换后的图像
*/
private static BufferedImage convertToARGB(BufferedImage src) {
// 创建新图像(TYPE_INT_ARGB支持透明度)
BufferedImage dest = new BufferedImage(
src.getWidth(),
src.getHeight(),
BufferedImage.TYPE_INT_ARGB
);
// 绘制原始图像到新图像
Graphics2D g = dest.createGraphics();
g.drawImage(src, 0, 0, null);
g.dispose();
return dest;
}
/**
* 复制图像到系统剪贴板
* @param image 要复制的图像
*/
private void copyImageToClipboard(BufferedImage image) {
TransferableImage transferable = new TransferableImage(image); // 创建可传输对象
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); // 获取剪贴板
try {
clipboard.setContents(transferable, null); // 设置剪贴板内容
} catch (IllegalStateException e) {
System.err.println("剪贴板访问失败: " + e.getMessage());
}
}
/**
* 自定义可传输图像类(实现剪贴板传输协议)
*/
private static class TransferableImage implements Transferable {
private final BufferedImage image;
public TransferableImage(BufferedImage image) {
this.image = image;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] { DataFlavor.imageFlavor }; // 声明支持图像格式
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return DataFlavor.imageFlavor.equals(flavor); // 仅支持图像格式
}
@Override
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
if (!isDataFlavorSupported(flavor)) {
throw new UnsupportedFlavorException(flavor); // 抛出格式不支持异常
}
return image; // 返回图像对象
}
}
//============ 辅助方法 ============//
/**
* 清空画布内容
*/
private void clearCanvas() {
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
}
/**
* 获取多显示器总宽度
*/
private double getTotalScreenWidth() {
return Screen.getScreens().stream()
.mapToDouble(s -> s.getBounds().getMaxX())
.max()
.orElse(0);
}
/**
* 获取多显示器总高度
*/
private double getTotalScreenHeight() {
return Screen.getScreens().stream()
.mapToDouble(s -> s.getBounds().getMaxY())
.max()
.orElse(0);
}
/**
* 程序入口
*/
public static void main(String[] args) {
launch(args); // 启动JavaFX应用
}
}