跳到正文

PhotoCraft

一个逐项对着 Photoshop 做的图像编辑器,从零用 Rust 写成,所有操作都是命令,菜单、命令行、控制通道和 agent 驱动的是同一个引擎。

Screenshot of PhotoCraft
编辑截图, 10 Oct 2026PhotoCraft ↗

这是什么

这是给那些已经熟悉 Photoshop、但不想按月租用它的人做的图像编辑器。菜单、快捷键、面板的排布都按 Adobe 的方式来,文档可以打开、分层、加蒙版、修图、排版、挂上实时调整图层,再原样写回分层 PSD。底下没有 webview、没有 Electron、也没有 C++:一套纯数据的文档模型,一个所有界面都按 id 调用的命令注册表,一个充当参照的 CPU 合成器和一个对着它测的 GPU 合成器,以及一个自己解析 ICC 配置文件的色彩引擎。它作为签过名的桌面应用发布在 macOS、Windows、Linux 和 FreeBSD 上,同样的 Rust 也编译成 WebAssembly 跑在浏览器里。项目自称早期 alpha,并且给自己的完成度标了数字。

谁做的一个 2021 年创建的组织账号,56 个公开仓库、25,251 个关注者。它用同一套文风规范发布七个桌面应用:PhotoCraft、VectorCraft、FilmCraft、LightCraft、PdfCraft、EffectCraft、DesignCraft,外加托管式的图像视频工作室 ArtCraft。PhotoCraft 仓库在 1,256 次提交里点名了 184 个账号;最多的是 @echelon,240 次提交、281,036 行新增,另外 228 条提交尾注把这个账号写成共同作者。contributors/people.toml 存着十几位贡献者自己选择的署名,文件里要求每个人只添加自己那一条。

它是怎么搭起来的

组成 · 6

组织方式是:引擎是产品,界面只是它的一个客户端。文档是纯数据——图层是一个枚举,成员包括像素、调整、填充、文字、形状和智能对象,像素放在 Arc 指向的 256 像素瓦片里,于是给文档拍一张快照的代价是每层一个指针,撤销、自动保存和后台任务都能不加锁地读它。所有对用户可见的东西都只存在于一个命令注册表里:菜单项、快捷键、命令面板、录制下来的动作、命令行工具、只监听回环地址的 JSON 控制通道,以及一个 MCP 服务器,解析到的是同一个命令 id 和同一份带类型的参数——这也是 agent 能在没有屏幕的情况下驱动它的原因。位深和色彩模式在每一个表面上都是运行时数据,所以 8 位 RGB 文档和 32 位浮点 CMYK 文档走的是同一套代码。两个合成器并行维护:CPU 那个是参照,wgpu 那个对着它测。外壳刻意做薄,是 eframe 上的 egui,没有 webview,整个项目里没有一行 JavaScript;同一批 crate 还编译成 WebAssembly,这一点由构建强制而不是靠自觉。

crates/geom、color、cms、raster
地基:几何、像素格式以及作用在其上的混合数学、一个自己解析配置文件并构建变换与 LUT 的 ICC 色彩引擎,以及所有 crate 都经由它分配内存的写时复制瓦片存储。
crates/psd 与 crates/codecs
不依赖工作区里任何东西的格式 crate,因此每个都能单独发布和单独阅读。PSD 那个就是整个 Photoshop 文件:文件头、图层记录与蒙版、色彩模式数据、描述符与标记块读取器,以及笔刷(.abr)、色板(.aco)、渐变(.grd)、图案和 TIFF 的解析器。
crates/doc
只装数据、不装别的文档模型:图层、组、剪贴、像素与矢量蒙版、调整图层、图层样式、带智能滤镜的智能对象、alpha 与专色通道、参考线和切片。不做渲染,不带工具箱,不碰 I/O。
crates/ops、paint、algo、text、vector
历史记录与日志、带动态参数且笔画可确定性重放的笔刷引擎、图像算法、文字引擎,以及路径与形状及其布尔运算。
crates/compose、gpu、format
充当参照的 CPU 合成器、在 Metal/Vulkan/DX12/WebGPU 上绘制画布并对着 CPU 版测试的 wgpu 合成器,以及原生格式 .pcraft——文档结构新增字段却没有映射时会编译失败,因此保存不可能悄悄丢掉一个字段。
crates/engine 与 crates/ui-egui
会话与命令注册表:约 700 条命令,每条都声明了 id、名称、菜单路径、默认快捷键、参数说明、启用条件和执行闭包;以及只持有视图状态、其余一律转发的 egui 外壳。crates/automation 提供 MCP 服务器,apps/photocraft、apps/photocraft-cli 和 apps/photocraft-web 是同一个引擎的三扇门。

取舍,以及另一种做法

  • 让每一个用户可见的操作都是注册表里的一条命令 替代 先在界面里实现功能,之后再考虑暴露

    这是 AGENTS.md 的第 1 条规则,理由也一并写着:UI、CLI、控制通道和 MCP 都按 id 分发,所以写成命令的功能在上线当天就能被 agent 和脚本驱动;而直接用 Photoshop 菜单目录里的 id,菜单项不需要写任何界面代码就会亮起来。

  • 让位深和色彩模式在每一个表面上都是运行时数据 替代 以 8 位 RGB 为核心,高位数路径事后补

    架构文档开篇就是一节「Avoiding GIMP's hole」,逐条算给 GIMP 看:把 8 位瓦片写死在核心里,代价是另起一套引擎 GEGL,2000 年前后开始,到 2018 年的 2.10 才真正完成。PhotoCraft 因此把 PixelFormat { depth, model, alpha } 放在每个表面上,禁止公开引擎 API 里出现 u8,并从第一天起就在 8、16、32 位上跑同一套黄金测试。

  • 把 CPU 合成器留作 GPU 合成器的参照 替代 只做 GPU 路径并相信它

    两个合成器互相对测,由 CPU 那个裁定 GPU 那个对不对。项目对外声称两者一致到 1/255,而必须一直维持这句话的是 crates/gpu/tests/parity.rs。

  • 照 Adobe 公布的规范写 PSD crate 替代 读另一份该格式的实现

    贡献指南写明了来源,也写明了随之而来的规则:可以研究行为,不可以复制代码,实现来自公开规范。也正因为如此,没被建模的块在往返保存时会被原样带过去,而不是丢掉。

  • 在非测试代码里禁止 panic 与 unsafe 替代 把崩溃当成报一个修一个的 bug

    工作区在干净的 crate 里禁止整个 panic 家族(unwrap、expect、panic!、unimplemented! 及其同类),除隔离出来的手写笔输入 crate 外一律禁止 unsafe,另有一个集成测试拿对抗性参数去轰每一条命令。给的理由是:用户把无法重做的工作交给了这个程序。

  • 把引擎和外壳一起编译成 WebAssembly 替代 只做原生应用

    外壳以下的 crate 不许用工具箱,碰文件系统的代码放在 cfg(not(target_arch = "wasm32")) 后面,构建会拿 wasm 目标检查整个下层。浏览器版就是同一个应用,体积约束直接写进 Cargo profile:fat LTO、按体积优化,同时把做像素的 crate 钉回 opt-level 3,这一步把 wasm 从 26.3 MB 降到 18.8 MB。

依据storytold/photocraft 的 AGENTS.md、ROADMAP.md、docs/architecture.md、docs/target-app-parity.md、docs/gaps.md、docs/scorecard.md、docs/parity-checklist.md、docs/contributing.md、docs/development.md、docs/releasing.md、README.md、ATTRIBUTION.md、Cargo.toml、contributors/contributors.json、.github/workflows/ci.yml 与 release.yml、scorecard/、perf/budgets.toml,以及 crates/ 下的代码,2026-10-11 读取。

制作过程

5 个阶段
  1. 01

    这 18.5 MB 里是什么

    我在 2026-10-11 克隆下来的这棵树里有 1,053 个 .rs 文件、18,512,884 字节 Rust 代码,共 408,084 行。GitHub 自己的语言统计和这个数字只差零头(Rust 18,510,175 字节),排在第二的 WGSL 着色器只有 53 KB。体量集中在两处:crates/ui-egui 有 129,589 行 egui 界面,crates/engine 另有 87,855 行命令。再往下,algo(42,272)、io(26,545)、codecs(16,154)、psd(13,813)、compose(11,146)和 raw(9,818)才是图像处理所在,那里的文件名是算法而不是管道:nonlocal.rs、inpaint.rs、matting.rs、puppet.rs、liquify.rs、seam.rs、poisson.rs、panorama.rs、camera_raw.rs。整棵树大约三分之一是测试:行内 #[cfg(test)] 模块里 83,731 行,tests/ 目录里另有 61,398 行,511 个文件里合计 5,733 个 #[test],其中 511 个文件自带行内测试模块。没有留空的地方:unimplemented!() 在整个仓库里出现零次,而 clippy 被配置为连同其他标记「尚未完成」的宏一起禁止它们。单文件最大的是 5,921 行的 crates/ui-egui/src/canvas.rs,而它不是一张表。这 18.5 MB 不包含 crates/ui-egui/src/i18n/ 下十六份界面翻译(约 1.9 MB 的 .tsv),也不含 assets/ 下 4.5 MB 的图标、字体和一份 SCOWL 词表。仓库自己也数过:2026-10-10 重写的 docs/architecture.md 写着约 393,000 行 Rust、约 1,020 个文件、约 5,460 个测试函数,是同一棵树早几个小时的测量。同一天的 README 上写的是「more than 1,700 tests」。

  2. 02

    开头 34 次提交加了 175,558 行

    仓库创建于 2026-09-30,它的第二次提交叫 one-shot,动了 241 个文件,加了 42,038 行。接下来三次一样:continued wip(181 个文件,+37,493 行)、more algorithms, progress(213 个文件,+43,275 行)、Analysis, notes, proof views, workspaces (+ in-progress work of other agents)(162 个文件,+34,026 行)。这四次提交合起来约 157,000 行,2026-10-03 之前的 34 次提交合计 175,558 行。从那之后历史才开始像一份仓库:小提交、一行标题、一段正文、一个 PR 号。 这套做法写在树里。AGENTS.md 开头就说明它是给 AI agent 看的指南,第六节标题是「Parallel agents」:让每个 agent 设 CARGO_TARGET_DIR=target/agent-<name>,免得抢 Cargo 的构建锁;改动要小,因为共享文件就是争用点;新命令写进新模块而不是把共享文件撑大;并提醒每个构建目录大约占 10 GB。它指向 log/devlog.md,说这是下一个 agent 接手的方式,理由是会话会因为崩溃和上下文上限突然结束;而 log/ 在 gitignore 里,所以那份记录不在克隆里。这个应用家族共用的规范放在一个叫 craftrules 的同级 checkout 里,文档三次重复说明它不公开。 剩下的答案在提交尾注里。main 上 1,256 次提交中,898 次带 Co-authored-by:其中 782 次点名某个 Claude 模型,31 次点名 Copilot,228 次点名维护者本人。换个说法:大约每三次提交里有两次把功劳记在模型头上。项目自己的计量单位也跟着来。ROADMAP.md 把到 beta 的工作量估成 1,100 到 1,800 个 Opus 5.5 agent 小时,标定方法一栏写着这些小时数来自本仓库已合并的 PR。

  3. 03

    clean room 这个说法能核到哪一步

    说法写在仓库简介里:Photoshop 的 clean-room 重写。仓库对它的处理是一条规则。AGENTS.md 第 3 条说,研究 Photoshop 和其他专有编辑器时只看行为与观感,绝不复制它们的代码、着色器、配置文件或素材,实现来自公开规范,并点名 Adobe 的 PSD 规范、ICC 规范和混合模式用的 ISO 32000。 其中一部分可以从树里核。PSD 支持是独立 crate,不依赖工作区里任何东西,它的测试跑在按 sha256 钉在 xtask/src/corpus_pins.rs 里的语料上:项目自己用 Photoshop 生成的对照文件,加上 psd-tools、ag-psd 和 PngSuite。色彩引擎自己解析和写出 ICC 配置文件,而它唯一随附的 CMYK 配置文件是合成的,由 crates/cms/src/synth.rs 生成、以 CC0 发布。ATTRIBUTION.md 为每一个非代码素材留一行,写清路径、标题、作者、来源和许可,而那些行都很无聊:ISC 和 MIT 的 Lucide 与 Feather 图标、OFL 的 Inter 与 JetBrains Mono、一份 SCOWL 词表。要抄,抄出来的痕迹就在这些文件里。 到对标目标这一步,就无法只靠仓库审计了。docs/target-app-parity.md 说 Photoshop 2026 是在不启动的情况下检查的:Info.plist 里的文档类型、各格式插件 PiPL 资源里的读写标志、.lproj 语言包、Default Keyboard Shortcuts.kys,以及主二进制里的字符串。crates/ui-egui/src/menu_catalog.rs 里那棵 628 项的菜单树也是照着 Photoshop 的菜单写的。读一份买来的软件包不等于读源码,但读者没法从这里核实这条界线。 这个问题挂在 issue 上,而项目没有回答。#341 由 @soapdog 于 2026-10-06 提出,问的是:由训练语料无从审计的模型生成的代码,还能不能叫 clean room。它下面 13 条评论全部来自外部账号,争论在「模型的训练集无法审计」和「人的记忆同样无法审计」之间来回,也在「拿这份代码去比对泄露的 Adobe 源码」和「无法证明一个否定命题」之间来回。仓库不在这个讨论里。另一条更安静,同样没人回:#2332(2026-10-10)指出部分主题用的 Adwaita 调色板来自 LGPL 的桌面样式表,与项目自己那条「随附素材必须使用宽松许可」的规则冲突。

  4. 04

    628 个菜单项,以及这个数字量的是什么

    仓库自己生成成绩单,而它比 README 苛刻。cargo xtask parity 把 Photoshop 的菜单树和活着的命令注册表对一遍,写出 docs/parity-checklist.md,另有一个测试把结果压在 627 的下限之上,防止回退。当前文件写着 628 项菜单全部有活的命令,并逐菜单列出:File 52、Edit 75、Image 61、Layer 161、Type 44、Select 25、Filter 75、View 74、Window 61。README 随后用自己的话说明这个数字值多少:Photoshop 的每个菜单项都接上了一条命令,而这量的是接线,不是行为。 成绩单里可测的那一半在 docs/scorecard.md,编号的缺口清单在 docs/gaps.md,两者互相吻合。工具箱 68 件里做了 53 件,缺的 15 件有名有姓:画板、单行与单列选框、透视裁剪、框架、颜色取样器、颜色替换、历史记录艺术画笔、自由钢笔与弯度钢笔、三个锚点工具,以及两个文字蒙版工具。面板 35 个里做了 30 个。文件格式在 Photoshop 自己支持的方向上是 31 个里的 13 个,另有 4 个部分支持。149 项首选项里有 46 项什么都不做。 PSD 是树里三个数字互相打架的地方。成绩单的对照测试把 PhotoCraft 的渲染和 Photoshop 自己的合并图像比较:Photoshop 生成的对照文件 256 个里对上 133 个(52%),项目自己的 io 语料 170 个里 146 个(86%),psd-tools 集合 309 个里 236 个(76%)。README 主推的却是往返渲染:psd-tools 的 309 个文件里 307 个一致——那是另一个更容易的问题。官网写的是「134 of 135 real-world test files round-trip byte for byte」,又是第三种测法。三个数字同一天都挂在项目自己的材料上。 性能对着 perf/budgets.toml 里的预算测,25 项里达标 3 项。带图层样式的图层移动要 321 ms,预算是 25 ms;改不透明度 276 ms,预算 20 ms;粘贴一张 1200 万像素的图 1.2 s,预算 100 ms。人工智能是唯一一间空着的房间:树里根本没有 ML 运行时,所以选择与去除靠的是经典算法(最大流图割、非局部块补全、PatchMatch 式修补),而生成式填充、Neural Filters 和天空替换是缺席,不是近似实现。 把这些加起来,项目自己的答案就在 roadmap 第一屏:功能广度约 76%,「能用于真实工作」约 45%,给出区间 40 到 50,阶段是 alpha,而挡住 beta 的只有一类问题——Photoshop 不肯稳定地重新打开 PhotoCraft 写出的文件(#1281、#2469)。

  5. 05

    它跑得起来,以及怎么确认这一点

    构建说明是四行:克隆,然后 cargo run --release -p photocraft -- image.psd。工作区要 Rust 1.95 和 edition 2024,锁文件钉住 728 个包,Type 工具用的字体是第二个仓库的可选 checkout,不是这个仓库里的文件。十一天里发了八个版本,从 2026-10-02 的 v0.1.0 到 2026-10-10 的 v0.6.0;最新版挂了 24 个文件:Windows 的 x64、arm64、x86 各有 MSI 和便携 ZIP,macOS 有通用版已公证的 DMG 和配套命令行工具,Linux 的 x86_64 与 aarch64 各有 AppImage、Flatpak、deb、rpm 和 tarball,FreeBSD 14 有一个按 /usr/local 铺开的 tarball,还有一个 11 MB 的 WebAssembly 静态站点。v0.6.0 的文件被下载了 73,049 次,其中 Windows x64 安装包占了 24,040 次。 这个安装包我是自己验的,没有采信 README。对下载下来的 MSI 跑 Get-AuthenticodeSignature,结果是 Valid:签名者为佐治亚州亚特兰大的 CN=Learning Machines Inc,签发者一栏是 Microsoft ID Verified CS EOC CA 03,并由微软的公共时间戳服务加戳,所以叶子证书过期之后签名仍然有效。而项目自己的缺口清单在同一天还把「Windows code signing(签名材料尚未取得)」列为缺失,发布清单里也还写着签名材料不存在时会跳过签名并给出警告。已发布的文件说的不是这个。 需要提醒的地方 README 和 docs/gaps.md 都写了,不用等人踩坑。在 Wayland 会话里把文件拖到窗口上打不开,因为底下的 winit 0.30 在那里没有拖放(#386),README 直接把绕法写在正文里。FreeBSD 版在虚拟机里构建,以纯 tarball 发布,背后没有代码签名机制。没有应用内更新检查,也没有应用内报错入口。卖下载的那个官网慢了一个版本:2026-10-11 它还在提供 0.5.0 的安装包、还要求 Rust 1.90,而仓库已经发布 0.6.0 一天了。 持续集成不是摆设。八个 workflow、6,583 次运行;ci.yml 会跑格式化、clippy、Linux 与 Windows 上的全工作区测试、一遍不带可选 HEIC 解码器的测试、一遍带字体输入的测试、分层检查、wasm 检查和语料测试,macOS 留给发布流水线,因为这家组织把五个 macOS 名额留给签名。GitHub 的 API 显示,这个 workflow 最近 60 次已完成的运行是 3 次通过、22 次失败、23 次被取消、12 次在等维护者批准 fork 的工作流。被取消的里面有 12 次是推到 main,而 workflow 文件里留着一句注释,说 main 上的运行从不取消,因为早先的策略在合并挨得近时会让 main 处于未验证状态。

他会告诉你什么

  • clean room 这个问题开着,也没人回答。#341 于 2026-10-06 提出,13 条评论全部来自外部账号,没有一条来自项目;一个训练语料无人能审计的模型,无法为自己的来源背书,而仓库也没有反驳这一点。#2332(2026-10-10)指出部分主题背后的 Adwaita 调色板来自 LGPL 样式表,与项目自己那条「随附素材须为宽松许可」的规则冲突。两条都没有回复。许可是 MIT 或 Apache-2.0 二选一,但有一处例外:ArtCraft 的名称、字标和 logo 是商标,只能原样使用、且只能作为本仓库的一部分使用,所以 fork 必须把它们去掉。
  • 按它自己的测量,这就是早期 alpha,而且测得很具体:「能用于真实工作」约 45%,功能广度约 76%,25 项性能预算里 22 项没达标,149 项首选项里 46 项接了等于没接,68 件工具缺 15 件,所有 AI 功能都没有 ML 运行时,另有一类被 roadmap 称作堵住 beta 的问题——Photoshop 不肯稳定地重新打开 PhotoCraft 写出的文件(#1281、#2469)。alpha 关卡被描述为通过,依据是六条核心工作流,其中四条只是深度不足。
  • README 上的数字是整个仓库最弱的一环,其中三个在同一天里与代码树或彼此矛盾。它写「more than 1,700 tests」,而源码里有 5,733 个 #[test]、同一天的架构文档写的是约 5,460。它的 Windows 下载表说安装包已代码签名,而 docs/gaps.md 仍把 Windows 签名列为缺失,实际发布出去的 MSI 验出来是签了名的。它主推的 PSD 成绩 309 里 307,旁边放着成绩单的 256 里 133(对照 Photoshop 自己的渲染)和官网的 135 里 134,这是三个不同的问题。
  • 体量和深度不是同时到的。项目最爱引用的那个数字(菜单覆盖)一天之内从 224 项涨到 532 项,第四天(2026-10-03)就满了;而真正面对用户的修补要等 0.5 和 0.6 到了人手里才开始。要判断这个项目现在是什么状态,该看的是 issue 列表。
  • issue 列表才是这个东西的诚实描述。2026-10-11 这天 GitHub 在仓库上记着 784 个打开项;同一天上午把列表翻完,得到 557 条 issue 和 228 条 PR,比计数器多一个——两个数相隔几分钟读到。十一天里提了 1,276 条 issue、关了 718 条,而开着的那些标题是针对两个已发布版本的用户反馈,不是许愿:Wacom 笔偏移几个像素、大笔刷下延迟、阿拉伯语图层名显示成豆腐块、150 层的文档崩掉、Linux AppImage 上 CMYK 坏掉。项目自己排出的工作清单把这条积压算成 250 到 400 agent 小时,是 PSD 保真度和性能之后最大的一项。
  • 持续集成不支持那个测试数。主 workflow 最近 60 次已完成的运行里,3 次通过、22 次失败、23 次被取消、12 次在等维护者批准 fork 的工作流。真正还管用的检查是克隆下来自己构建:release、语料钉子和一个签过名的安装包都在,而 cargo run --release -p photocraft 就是这个主张的四行版本。

相关档案

全部档案 →