桌面图片标记查看器 —— 双屏工作流的理想伴侣
ForeastDraw 是一个轻量级桌面应用,专为双显示器场景设计。在副屏展示参考图片的同时,可以直接在图片上绘制标记、添加文字、测量标注,而标记数据独立存储,永不修改原图。
核心场景:
- 游戏攻略参考(副屏放地图/技能树,主屏游戏)
- 设计稿对照(副屏放设计图并标注问题点)
- 教程学习(副屏放教程图并做笔记)
- 任何需要"一边看图一边工作"的场景
- 🖌️ 画笔:自由绘制折线
- ⭕ 圆形:拖动绘制椭圆/圆形
- 📏 直线:绘制直线段
- 📐 标尺:带端点标记的测量线,可添加标注文字
- ✏️ 文字:点击添加文本标注
- 🎨 颜色自定义:5个预设色 + 颜色选择器
- 📐 4档粗细:适应不同标注需求
- ↩️ 撤销/恢复:每张图独立的操作历史
- 💾 自动保存:标记数据实时保存到 JSON 文件
- 📤 导出图片:将标记合并到图片,弹出「另存为」自选路径导出为 PNG(默认下载文件夹,记住上次目录)
- 🗂️ 多文件夹管理:左侧树形结构,展开/收起
- 👁️ 标记开关:快速隐藏/显示所有标记
- 🔍 缩放平移:滚轮缩放,左键 / 中键 / 空格+左键拖动平移
- ✋ 移动标记:
Ctrl(macOS 为⌘)+ 拖动标记调整位置(悬停显示抓取点) - 🪟 窗口置顶:一键让窗口浮在最前,双屏参考更方便
- 🔄 自动更新:基于 GitHub Releases 检查并提示新版本
- ✅ 标记存储在
.foreastdraw.json,绝不写入图片文件 - ✅ 使用归一化坐标,缩放平移时标记严格跟随
- ✅ 文件夹监听,外部修改图片自动同步
- ✅ 应用内重命名图片,标记数据自动迁移
| 层级 | 技术 |
|---|---|
| 框架 | Tauri 2 |
| 后端 | Rust (notify 文件监听) |
| 前端 | TypeScript + Vite |
| 渲染 | Canvas 2D |
无 UI 框架:整个前端约 1500 行纯 TypeScript,启动快、占用低。
- Node.js >= 18
- Rust >= 1.70
- pnpm / npm / yarn
macOS 开发需要 Xcode Command Line Tools:
xcode-select --installnpm installnpm run tauri dev热重载启动,修改前端代码自动刷新。
npm run tauri build本地构建的输出位置(x.x.x 为版本号):
| 平台 | 产物 | 路径 |
|---|---|---|
| Windows | 免安装 exe | src-tauri/target/release/ForeastDraw.exe |
| Windows | NSIS 安装包 | src-tauri/target/release/bundle/nsis/ForeastDraw_x.x.x_x64-setup.exe |
| macOS | 应用本体 | src-tauri/target/release/bundle/macos/ForeastDraw.app |
| macOS | 磁盘镜像 | src-tauri/target/release/bundle/dmg/ForeastDraw_x.x.x_*.dmg |
| Linux | AppImage | src-tauri/target/release/bundle/appimage/*.AppImage |
| Linux | deb 包 | src-tauri/target/release/bundle/deb/*.deb |
| Linux | rpm 包 | src-tauri/target/release/bundle/rpm/*.rpm |
默认只构建当前系统对应的产物。指定特定格式用
npx tauri build --bundles <targets>,例如--bundles nsis或--bundles appimage,deb,rpm。
仓库配置了三条独立的 workflow,均可在 GitHub 的 Actions 页手动触发(workflow_dispatch,可选填 Release 标签,留空则用 package.json 版本号,如 0.2.0)。三者用同一个 tag,产物会合并进同一个 Release。
| Workflow | Runner | 产物(发布名统一为 ForeastDraw-<tag>-<平台>-<架构>.*) |
|---|---|---|
build-windows.yml |
windows-latest |
免安装 -portable.exe、NSIS -setup.exe |
build-macos.yml |
macos-latest |
通用二进制 .dmg、.app.tar.gz(已签名 + 公证 + 装订) |
build-linux.yml |
ubuntu-22.04 |
.AppImage、.deb、.rpm |
应用启动后可通过菜单栏「检查更新」比对 GitHub 最新 Release 版本(走 GitHub API,无需自建服务器)。
build-macos.yml 会自动导入证书、签名、公证(notarization)并装订(staple)。需要在仓库 Settings → Secrets and variables → Actions 配置以下 secrets:
| Secret | 说明 |
|---|---|
APPLE_CERTIFICATE |
Developer ID Application 证书(.p12 的 base64) |
APPLE_CERTIFICATE_PASSWORD |
导出 .p12 时设置的密码 |
APPLE_SIGNING_IDENTITY |
形如 Developer ID Application: Name (TEAMID) |
KEYCHAIN_PASSWORD |
任意强随机串(用于创建临时 keychain) |
APPLE_API_ISSUER |
App Store Connect API 的 Issuer ID |
APPLE_API_KEY |
API Key ID(10 位) |
APPLE_API_KEY_P8 |
API Key .p8 文件的完整文本内容 |
GITHUB_TOKEN 由 Actions 自动注入,无需手动配置。Windows / Linux 构建不需要任何 secret。
Tauri 2 依赖 webkit2gtk-4.1,构建基线为 Ubuntu 22.04(glibc 2.35),因此运行环境要求:
- ✅ 支持:Ubuntu 22.04+、Debian 12+、Fedora 36+、以及基于较新内核/glibc 的银河麒麟 V10 SP3 等
⚠️ 受限:CentOS Stream 9(glibc 2.34)可能因 glibc 略低而无法运行- ❌ 不支持:CentOS 7(glibc 2.17)及基于其构建的旧版麒麟 —— glibc 过低,Tauri 2 官方不支持
三种 Linux 产物中,AppImage 通用性最好(自带大部分依赖,免安装双击运行);.deb / .rpm 依赖系统已安装 webkit2gtk,分别面向 Debian 系(Ubuntu / 银河麒麟)与 RHEL 系(CentOS / 中标麒麟)。
npm run build输出到 dist/ 目录。
npx tsc --noEmitForeastDraw/
├── src/ # 前端源码
│ ├── main.ts # 主入口 + 事件编排
│ ├── render.ts # Canvas 渲染逻辑
│ ├── store.ts # 标记数据管理(按文件夹分组)
│ ├── viewport.ts # 视图变换(fit/zoom/center)
│ ├── imagecache.ts # ImageBitmap LRU 缓存
│ ├── tree.ts # 左侧文件夹树
│ ├── menu.ts # 右键菜单 + 浮层
│ ├── ui.ts # DOM 工具函数
│ ├── api.ts # Tauri IPC 封装
│ ├── types.ts # 共享类型定义
│ └── style.css # 全局样式
├── src-tauri/ # Rust 后端
│ ├── src/
│ │ ├── lib.rs # 8 个 Tauri 命令 + 文件监听
│ │ └── main.rs # 入口(调用 lib.rs)
│ ├── Cargo.toml # Rust 依赖
│ ├── tauri.conf.json # Tauri 配置
│ └── capabilities/ # 权限配置
├── index.html # HTML 模板
├── vite.config.ts # Vite 配置
└── package.json # npm 依赖
ForeastDraw 采用平移优先交互:左键默认平移,按住空格切换到绘制。可在设置(S 键)中反转。
移动标记的修饰键按平台区分:macOS 用
⌘(Command),Windows / Linux 用Ctrl。下表以Ctrl为例,Mac 上请替换为⌘。
默认模式(平移优先):
| 按键 | 功能 |
|---|---|
| 主操作 | |
| 左键拖动 | 平移画布 |
| 空格 + 左键 | 绘制当前工具 |
| Ctrl / ⌘ + 左键拖动 | 移动已有标记 |
| 中键拖动 | 平移(台式机便利) |
| 右键 | 打开菜单 |
| 工具切换 | |
| P | 画笔工具 |
| O | 圆形工具 |
| L | 直线工具 |
| T | 文字工具 |
| R | 标尺工具 |
| 颜色/粗细 | |
| Q / W | 切换到预设颜色 1 / 2 |
| [ / ] | 减细 / 加粗 |
| 导航 | |
| ← / → | 上一张 / 下一张 |
| PgUp / PgDn | 上一张 / 下一张 |
| Home / End | 第一张 / 最后一张 |
| 视图 | |
| 0 | 适应窗口 |
| 9 | 实际大小(100%) |
| + / - | 放大 / 缩放 |
| 滚轮 | 鼠标位置缩放 |
| 标记操作 | |
| H | 显示 / 隐藏标记 |
| Ctrl / ⌘ + Z | 撤销 |
| Ctrl / ⌘ + Shift + Z | 恢复 |
| Del / Backspace | 清理当前图片全部标记 |
| 文件操作 | |
| F2 | 重命名图片 |
| E | 导出图片(含标记,弹出「另存为」选择路径) |
| 其他 | |
| S | 打开设置(可反转平移/绘制) |
| F | 全屏 |
| Esc | 关闭菜单/对话框 |
反转模式(勾选「反转平移 / 绘制」后):
- 左键拖动 → 绘制当前工具
- 空格 + 左键 → 平移画布
- 其他快捷键不变
应用菜单栏还提供「检查更新」「帮助」「关于」以及「文件 → 打开文件夹 / 关闭当前文件夹」。
每个文件夹的标记存储在 .foreastdraw.json:
{
"version": 1,
"images": {
"example.jpg": {
"mtime": 1234567890,
"strokes": [
{
"tool": "ellipse",
"color": "#ff3b30",
"width": 0.004,
"points": [[0.3, 0.4], [0.6, 0.7]]
},
{
"tool": "text",
"color": "#76b900",
"width": 0.002,
"points": [[0.5, 0.5]],
"text": "重要标记"
}
]
}
}
}关键设计:
width是图片宽度的比例(如 0.004 = 0.4%),不是像素值points是归一化坐标(0..1),与分辨率无关mtime用于检测图片是否被外部修改过
-
标记坐标归一化 — 存储 0..1 的相对坐标,渲染时乘以图片尺寸。这样换分辨率、换缩放都不会错位。
-
单 transform 管理 — 图片和标记共用一个 canvas transform,所以标记"跟随"不是计算出来的,而是结构上不可能不跟随。别把它们拆成两次 transform。
-
文件夹分组存储 —
store.ts的Map<dir, FolderMarks>,撤销栈、脏标记都是按文件夹分组的。 -
改名竞态处理 —
fs::rename会触发文件监听,dropMissing会把旧文件名当删除。所以store.rename必须在 400ms 防抖窗口内完成(doRename有注释标了顺序)。 -
右键菜单平台差异 — macOS 在 pointerdown 触发 contextmenu,Windows 在 pointerup,所以全局禁掉 contextmenu 事件,由指针状态机自己开菜单。
- 在
types.ts的Stroke["tool"]联合类型中添加 - 在
render.ts的path()函数中添加渲染分支 - 在
main.ts的pointermove/pointerup中处理交互 - 在
index.html添加工具按钮 - 在
handleLetterKeys添加快捷键
# 只做类型检查
npx tsc --noEmit
# 只构建前端
npx vite build
# 只检查 Rust
cargo check --manifest-path src-tauri/Cargo.toml
# 查看 Tauri 日志
npm run tauri dev # 控制台会输出 Rust 端日志Q: 标记位置偏移了?
A: 可能是图片被外部软件修改了尺寸。应用会 toast 提示"图片尺寸已变化"。
Q: 外部改名的图片标记丢失?
A: 这是设计行为。标记按文件名追踪,外部改名视为删除。请在应用内改名(F2)。
Q: 能导入 Photoshop/Figma 的标注吗?
A: 目前不支持。标记格式是应用专用的。
MIT License
ForeastDraw, for Foreast Yao.
Copyright © 2026 iwpz. All rights reserved.