使用 Tauri 作为应用技术栈来实现截图功能并不占优势。
Tauri 最大的特色是跨平台,一份代码,多端应用。然而,截图这个功能本身就不可太可能跨得了平台,因为每个平台都有自己的独特 API 实现。比如,macOS 上,需要调用 cocoa 框架的接口来实现。并且,macOS上是需要应用申请屏幕录制权限的。如果用户不授权,那么应用只能获取到应用自己的截图以及空桌面截图。在 linux 上,我们可能还得考虑是在 X server 上实现还是 wayland 上实现。
另外一个问题,是 Tauri 的传输效率。我们知道,Tauri 使用 Command 指令来交互前端与Rust 。在 Tauri v1 时代,Command 指令只支持序列化的文本传输信息,因为 Tauri 默认使用 serde::Serialize 处理数据。这对传输二进制数据来说,效率慢的非常可怕。
好在,Tauri v2 的数据已经支持 Raw:
pub enum InvokeBody {
/// Json payload.
Json(JsonValue),
/// Bytes payload.
Raw(Vec<u8>),
}
但是对于前后端来说,还是有一些传输数据的开销在,只是不那么另人无法接受了。
为什么这对于截图来说很重要?你试试打开微信的截图,微信的截图功能在截图前会有一个鼠标跟随预览功能(类似放大镜效果),方便用户精准定位截图的开始点。
Tauri 要怎么实现截图?
截图软件本质上就是一个透明的,置顶的,全屏的应用。
在 tauri.conf.json 里,添加如下配置:
"app": {
"windows": [
{
"title": "jietu",
"width": 800,
"height": 600,
"resizable": false,
"alwaysOnTop": true,
"decorations": false,
"fullscreen": true,
"shadow": false,
"transparent": true
}
],
"security": {
"csp": "default-src 'self' 'unsafe-inline'; connect-src 'self' ipc://localhost"
}
},
开发阶段,最好设置 "fullscreen": false,,不然不好调试 。
前端也要设置背景透明色:
html,body,#root,.container {
@apply bg-transparent;
}
剩下的,我们需要在 Rust 层实现各个平台的截图功能。 并在前端层实现截图上的交互。
如果我们自己在Rust 层实现三大平台的截图,代码量是比较大的,同时你还得非常了解 windows api、cocoa 框架等。幸运的是,有人把这部分脏活给干了:
cargo add xcap
放大镜(截图预览开始点)
下面这个方法就实现了放大镜功能,我们需要获取鼠标的坐标点,并以这个坐标点为原点,获取一个200x200 的截图:
#[tauri::command]
fn xcap_start(x:u32,y:u32) -> Result<Response, String> {
let monitors = Monitor::all().map_err(|e| e.to_string())?;
let monitor = monitors
.into_iter()
.find(|m| m.is_primary().unwrap_or(false))
.expect("No primary monitor found");
let region_width = 200u32;
let region_height = 200u32;
let start = Instant::now();
let image = monitor.capture_region(x, y, region_width, region_height).map_err(|e| e.to_string())?;
println!(
"Time to record region of size {}x{}: {:?}",
image.width(),
image.height(),
start.elapsed()
);
// 将图像数据转换为字节数组返回给前端
let width = image.width();
let height = image.height();
let mut buffer = Cursor::new(Vec::new());
let encoder = image::codecs::png::PngEncoder::new(&mut buffer);
let rgba_image = image::DynamicImage::ImageRgba8(image).into_rgba8();
encoder.write_image(rgba_image.as_bytes(), width, height, image::ExtendedColorType::Rgba8)
.map_err(|e| e.to_string())?;
Ok(Response::new(buffer.into_inner()))
}
前端则使用一个全屏画布来实现截图页面,我习惯使用 konva 。
“tsx
鼠标位置: (${mousePosition.x}, ${mousePosition.y})} fontSize={15} x={20} y={20} fill=“white” />
{image && (
<Image
x={300}
y={50}
width={200}
height={200}
image={image}
/>
)}
</Layer>
</Stage>
在最外层的容器上添加鼠标事件:
const [imagePath, setImagePath] = useState<string | null>(null);
// 使用节流优化截图调用
const capsview = useRef(throttle(async (x: number, y: number) => {
try {
// 更新鼠标位置状态
setMousePosition({ x, y });
// 调用Rust命令获取截图数据
const result: Uint8Array = await invoke("xcap_start",{x,y});
const blob = new Blob([new Uint8Array(result).buffer], { type: "image/png" });
setImagePath(URL.createObjectURL(blob));
console.log("截图成功获取");
} catch (error) {
console.error("截图失败:", error);
}
}, 30)).current; // 限制为每30ms最多执行一次
这里的 30ms 是有说法的。因为 Rust 层在 windows 平台上截图一个 200x200大小的图片耗时(使用的是 pnpm tauri dev, 这是未优化过的,理论上优化过的应该更少耗时)如下:
Time to record region of size 200x200: 16.8005ms
Time to record region of size 200x200: 16.9432ms
Time to record region of size 200x200: 16.5475ms
Time to record region of size 200x200: 15.8173ms
Time to record region of size 200x200: 17.1305ms
Time to record region of size 200x200: 16.2595ms
Time to record region of size 200x200: 16.0607ms
Time to record region of size 200x200: 16.7726ms
Time to record region of size 200x200: 16.3253ms
Time to record region of size 200x200: 15.9661ms
Time to record region of size 200x200: 17.0717ms
Time to record region of size 200x200: 15.809ms
Time to record region of size 200x200: 12.2509ms
Time to record region of size 200x200: 19.8513ms
Time to record region of size 200x200: 17.0228ms
Time to record region of size 200x200: 14.8592ms
Time to record region of size 200x200: 15.5001ms
Time to record region of size 200x200: 15.9599ms
Time to record region of size 200x200: 16.6687ms
Time to record region of size 200x200: 15.5552ms
Time to record region of size 200x200: 15.9622ms
再叠加前后端传输以及一些处理等,30ms 是比较合适的间隔。
这时候截图前的放大镜效果最核心的功能就已经实现出来了,细节什么的,只是UI 问题而已。
同样地,选取区域截图功能,你甚至改改上面的xcap_start 就能使用,至于截图后要加画笔功能、文本功能、画框功能什么的,都属于前端的范畴,直接使用 konva 的api 就能轻松搞定.