PhotoCraft 贡献指南从 Never crash 铁律到新增一条引擎命令的完整路径【免费下载链接】photocraftPhotoCraft: An open-source, photo editor written by scratch in pure Rust项目地址: https://gitcode.com/gh_mirrors/pho/photocraftPhotoCraft 是纯 Rust 编写、以 1:1 对齐 Photoshop 为目标的图像编辑器本文讲清 Rust 开源项目如何贡献到它资产与许可证边界、Never crash 铁律、命令注册主流程与合入门禁。读完即可独立提交一条可运行、可撤销、Agent 可调用的引擎命令。语言/运行时仓库只允许 Rust禁止 JavaScript 与 TypeScript。Web 端是同一套 Rust 代码经trunkwasm-bindgen编译到 WebAssembly 的产物唯一的 JS 是 wasm-bindgen 自动生成的胶水代码绝不应手写。桌面端是原生 egui/eframe 加 wgpuAGENTS.md 明确禁止引入 Tauri、Electron 或任何 webview 壳也禁止把这类依赖加进构建步骤或打包目标。许可证与资产贡献代码采用 MIT OR Apache-2.0 双许可证根目录提供 LICENSE-MIT、LICENSE-APACHE 与 NOTICE。资产规则分四类全部登记与变更必须在同一个提交中完成资产类别规则第三方资产只用宽松许可证许可证文件放资产旁边如 assets/fonts/OFL-Inter.txt、assets/icons/LICENSE-lucide.txt所有资产含原创登记进 ATTRIBUTION.md路径、标题、作者、来源、许可证字体文件永远不进入本仓库放在外部 craft-fonts 仓库经CRAFT_FONTS_DIR构建输入引用docs/brand/的 ArtCraft Logo不是开源资产受 docs/brand/LICENSE-brand.txt 约束字体的接入方式是可选构建输入crates/text/build.rs 读取$CRAFT_FONTS_DIR/fonts/manifest.txt把字体嵌入为photocraft_text::CRAFT_FONTS未设置时该常量为空构建行为不变一切功能仍须正常工作。该变量必须是绝对路径因为 build.rs 在crates/text下运行相对路径会解析错位置wasm32 构建即使设置了它也不嵌入字体UI 字体单方面就把 release wasm 推过 24 MiB 门禁见 docs/development.md › Fonts# clone craft-fonts 到本仓库旁边后docs/development.md:106 CRAFT_FONTS_DIR$PWD/../craft-fonts cargo run --release -p photocraft对齐边界clean-roomPhotoCraft 是 clean-room 重实现——可以研究和观察 Photoshop 等专有软件的行为与外观但禁止复制其代码、着色器、图标、ICC 配置文件或任何资产。实现依据仅限公开规范Adobe PSD 规范、ICC 标准、ISO 32000 混合模式与观察。这条边界同时约束文档措辞产品名在用户可见文本中恒写作PhotoCraft机器名保持小写crate、二进制、id 如ai.storyteller.photocraft。Never crash 铁律用户把真实工作托付给这个编辑器崩溃意味着工作丢失所以一个畸形文件、一条坏命令参数、一个损坏的偏好文件都必须产生用户或 Agent 可以处理的错误而不是 panic。纪律要点非测试代码禁用unwrap()、expect()、panic!、unreachable!、todo!、unimplemented!错误经Result与?传播文件、参数、选区派生的数字一律视为敌对用get()而非[i]长度与偏移用checked_*/saturating_*。整仓在根 Cargo.toml 第 81 行设置unsafe_code forbid唯一例外是隔离的crates/tabletmacOS 读取数位笔输入需要 AppKit 互操作。这条纪律由工具链强制执行clippy.toml 全文只有四行 allow-in-tests 配置即 unwrap/expect/panic/下标索引只允许出现在测试中干净 crate 携带 crate 级属性crates/engine/src/lib.rs 第 8 行#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]应用壳在命令分发与文件导入导出周围捕获逃逸 panic保住文档每次崩溃修复必须附带一个修复前会 panic 的合成回归测试。把一个新功能从想法变成合入完整路径PhotoCraft 的核心工程约定是「命令而非处理器」一切用户可见行为都是引擎命令UI、CLI、控制通道与 MCP 只是同一注册表的分发端。新增功能的主路径因此固定为七个子步骤锁定 id、落算法、注册、适配上下文、测试、接对话框、刷新报告。锁定命令 id每个命令的 id 与 Photoshop 菜单结构一一对应登记在 crates/ui-egui/src/menu_catalog.rs 的CATALOG表中——一条四元组菜单路径、标签、默认快捷键、命令 id。使用与目录完全一致的 id对应菜单项即刻「活」过来不需要写任何 UI 代码// crates/ui-egui/src/menu_catalog.rs:6 /// (menu path, label or ---, default shortcut, command id) pub static CATALOG: [([str], str, Optionstr, str)] [ ([File], New…, Some(CmdN), file.new), ([File], Open…, Some(CmdO), file.open), ([File], Save, Some(CmdS), file.save), // ……共 777 行每个 Photoshop 菜单项一行 ];⚠️ 没有 Photoshop 菜单入口的内部命令如智能滤镜删除沿用同风格的描述性 id例如layer.smartFilter.delete菜单路径留空menu: []。只有纯视图/窗口状态缩放、面板、屏幕模式才属于 shell 层的ui-egui/src/menus.rsUI_COMMANDS不进引擎注册表。在最低层 crate 落算法算法实现放进放得下的最低层 crate成像算法进algo笔刷进paint路径/形状进vector文字进text色彩进cms并带单元测试。算法函数有三条约定接收位深无关的Surfacephotocraft-raster的 COW 瓦片表面而不是裸[u8]按瓦片工作以利于并行与缓存随机性必须带种子保证导出、测试与回放可复现。以调整类为例数学住在 crates/algo/src/tone.rs引擎模块只负责参数解析与调用——这就是「算法在 L2、命令在 L5」的分层落地// crates/algo/src/tone.rs:22 /// Image › Adjustments › Shadows/Highlights 参数Photoshop 对话框百分比 0..100。 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ShadowsHighlights { pub shadow_amount: f32, pub shadow_tone: f32, pub shadow_radius: f32, // ……其余字段同理Default 实现即 Photoshop 对话框默认值 }⚠️ 位深8/16/32f与色彩模型RGB/Gray/CMYK/Lab是运行时数据公共 API 中禁止引入仅u8的像素路径色彩转换一律走photocraft-cms不得假设 sRGB测试覆盖 8、16、32 三个位深。注册进引擎命令表命令本体放在引擎模块crates/engine/src/area_cmds.rs模块暴露一个specs()函数返回CommandSpec列表crates/engine/src/commands.rs 定义了这个结构体第 14 行起注册列表从第 1497 行开始按功能域逐行扩展例如v.extend(crate::filters::specs());——新模块只需在这个列表加一行。CommandSpec的每个字段都有明确职责// crates/engine/src/commands.rs:14 /// Metadata implementation for one command. pub struct CommandSpec { pub id: static str, pub label: static str, pub menu: static [static str], // 如 [Layer, New]空 不进菜单 pub shortcut: Optionstatic str, // egui 记法CmdShiftNUI 按平台映射 pub params: static str, // 面向人/Agent 的参数文档JSON 风格 pub enabled: Enabled, // fn(Session) - Result(), String置灰菜单项 pub run: Run, // fn(mut Session, Value) - ResultValue pub journal: bool, // 是否记入会话日志查询类命令为 false }填enabled时优先复用现成谓词——commands.rs第 33 行起定义了has_doc、has_layer、has_pixel_layer、has_paintable等返回Err(说明)时对应菜单项置灰并显示该说明。⚠️params文档串不是装饰Agent 经commands/command_list方法读取的就是它CLI 里photocraft-cli commands --filter blur展示的也是它类型、单位、取值范围与默认值都要写清楚参照 crates/engine/src/adjust_cmds.rs 第 541 行的{shadowAmount:0..10035,...}风格。run闭包返回 JSON 结果并恰好记录一步历史——拖拽与打字这类连续会话用coalesce合并否则每次鼠标移动都会产生一条撤销记录。尊重文档上下文命令必须正确处理当前文档的全部上下文维度活动选区含羽化、目标是图层还是蒙版、图层锁、色彩模型与位深。commands.rs的paint_surface第 69 行演示了标准做法参数target:mask时写入蒙版表面并分别检查蒙版锁与像素锁锁定图层返回 Photoshop 措辞的可读错误而不是崩溃// crates/engine/src/commands.rs:93 /// Refuse a layer whose pixels are locked (directly or through a group), with Photoshops message. pub(crate) fn check_pixels_unlocked(doc: Document, id: LayerId) - Result() { let locks doc.effective_locks(id); let l doc.layer(id).ok_or(EngineError::NoLayer(id))?; if locks.pixels || locks.all { return Err(EngineError::Other(format!( Could not complete your request because the layer \{}\ is locked, l.name))); } Ok(()) }⚠️ 上下文检查与 panic_hunt 是同一件事的两面任何上下文组合空文档、无选区、目标层无蒙版、锁定层下命令都应返回Err不存在「先 panic 再处理」的捷径。跑通模块测试与对抗模糊模块内测试覆盖五类正常行为、undo/redo、禁用态enabled为假、坏参数越界/错类型必须得Err、多种位深。模块测试通过后跑对抗参数模糊测试——crates/engine/tests/panic_hunt.rs 对每一条命令注入敌对参数每条命令在独立线程上执行、带 4 秒超时panic 由全局 hook 计数任何一条命令在任何参数与文档状态下 panic 都会让测试变红# 模块测试adjust_cmds 为例可换成你的模块名 cargo test -p photocraft-engine --lib adjust_cmds # 对抗参数模糊opt-in--ignored增改命令后必须跑并保持绿色 cargo test -p photocraft-engine --test panic_hunt -- --ignored⚠️ AGENTS.md 要求每条新命令自带「优雅失败」测试空/越界/错类型参数得到Err而不是崩溃。这条测试与 panic_hunt 互补——前者是文档化的契约后者是穷尽的护栏。接上 schema 驱动的对话框带参数的滤镜与调整使用 schema 驱动的对话框加实时预览代码在 crates/ui-egui/src/filter_dialog.rs 与 crates/ui-egui/src/dialogs.rs参数 schema 必须与注册时的params文档串保持一致滑杆移动即触发预览渲染大文档走ui-egui/src/proxy.rs的降采样代理保证流畅。提交前必须视觉验证离屏快照示例不弹窗口、不抢焦点--script是有序的控制协议调用输入类调用会等应用处理完事件再应答# 离屏渲染完整 UIdocs/development.md:551 cargo run --release -p photocraft-ui-egui --example snapshot -- \ --out ui.png --size 1440x900 --scale 2 --open photo.jpg \ --script [[ui.set, {tool: type}], [ui.menu.invoke, {id: image.canvasSize}]]⚠️ PR 必须附上修改前后的截图对比演示图片必须使用公有领域艺术图不能是个人照片。UI 颜色与圆角一律来自theme::Tokensui-egui/src/theme.rs与widgets::*组件库不得硬编码。刷新 parity 报告菜单覆盖率由cargo xtask parity度量它把menu_catalog.rs的菜单树与实时命令注册表对比重写 docs/parity-checklist.md。新命令使存活命令数上升后把重新生成的文件一并提交——报告是生成物提交它而不是手改它# 新增命令后运行重写 docs/parity-checklist.md并把再生文件提交进 PR cargo xtask parity护栏是 crates/ui-egui/src/parity.rs第 10 行的FLOOR当前值 631是存活命令数的地板测试parity_does_not_regress断言live FLOOR。地板只升不降——parity 上升时把FLOOR抬高绝不下调。合入前的强制检查测试矩阵测试要求按涉及面分项语料测试依赖固定 pin 且经 sha256 校验的语料集项目自有的 Photoshop 制作 oracle、psd-tools、ag-psd、PngSuite 等pin 集中在 xtask/src/corpus_pins.rs语料全部落在corpus/gitignored永远不提交语料文件。corpuscargo feature 默认关闭普通cargo test不编译语料测试feature 打开后缺失语料直接失败而不是静默跳过CI 的corpusjob 每次 PR 与 main 都执行。涉及面必须跑的测试触发条件任何变更cargo test -p crates you touched含模块测试恒触发格式 crateround-trip 与畸形输入测试外加真实文件语料cargo xtask test-corpus触碰 psd、io、codecs、compose、gpu、text、format 任一 crate像素代码8、16、32 位深度下的行为测试引入或改动像素路径命令模块内坏参数「优雅失败」测试 cargo test -p photocraft-engine --test panic_hunt -- --ignored新增或改动命令静态检查与分层风格门禁零容忍cargo fmt之后cargo clippy -- -D warnings风格跟随周边代码注释解释「为什么」而非「是什么」。依赖分层由 xtask/src/layers.rs 的表驱动规则引擎检查——crate 只能依赖更低的层psd、codecs等 Standalone crate 工作区零依赖唯一例外是codecs → heif见第 95 行STANDALONE_EXCEPTIONS同层内还有顺序约束geom → cms → color → raster与compose → gpu第 82 行L6 以下任何 crate 禁止使用UI_CRATES列表中的 egui、eframe、winit、rfd 等库第 104 行UI_MIN_LAYER 6。cargo xtask 分层检查cargo xtask layers是 CI 必过项新增 crate 必须先注册进第 35 行的TABLE否则直接失败cargo fmt cargo clippy -- -D warnings cargo xtask layers # 分层门禁规则源在 xtask/src/layers.rs cargo xtask wasm # 动了 L0–L6 时wasm32-unknown-unknown 目标必须通过 checkcargo xtask wasm的约束来自「永不破坏 wasm」文件系统代码要么cfg(not(target_arch wasm32))要么走平台服务。可观测数字仓库把可测量的数字做成只升不降的地板parity 地板FLOOR、语料地板、性能预算perf/budgets.toml预算达标后把enforce true打开。cargo xtask scorecard从 scorecard/*.toml、perf/budgets.toml、perf/baseline.json与语料地板重新生成 docs/scorecard.mdCI 用--check模式失败于任何过期报告——移动了数字却不提交再生报告流水线会红cargo xtask scorecard # 重新生成 docs/scorecard.md移动数字后必须提交它 cargo xtask scorecard --check # CI 视角报告过期即失败 cargo xtask perf --quick # 碰了热路径时perf --update-baseline 发布完整基线✅ 提交前最后扫一遍cargo test -p 你触碰的 cratecargo clippy -p crate --all-targets -- -D warningscargo xtask layerscargo xtask wasm动了 L0–L6 时cargo xtask parity并提交重新生成的docs/parity-checklist.md新增命令时cargo test -p photocraft-engine --test panic_hunt -- --ignored增改命令时在log/devlog.md追加一条简短 devlog落了什么、数字、还欠什么速查关键路径与命令项目路径或命令获取仓库git clone https://gitcode.com/gh_mirrors/pho/photocraft贡献文档docs/contributing.md黄金规则与 agent 指南AGENTS.md菜单 id 目录crates/ui-egui/src/menu_catalog.rs命令注册入口crates/engine/src/commands.rsv.extend列表第 1497 行起分层规则表xtask/src/layers.rs对抗参数模糊测试crates/engine/tests/panic_hunt.rsparity 报告docs/parity-checklist.mdcargo xtask parity生成语料 pin 与校验xtask/src/corpus_pins.rscargo xtask test-corpus资产登记ATTRIBUTION.mddevloglog/devlog.md本地、gitignored规则见 AGENTS.md 第 5 节离屏 UI 快照cargo run --release -p photocraft-ui-egui --example snapshot改动通过全部门禁后往log/devlog.md追加一条简短记录落了什么、数字、还欠什么并保持每一步树上代码可构建——会话可能随时中断devlog 加绿色的树是下一个接手者人或 Agent的起点。随后推送分支并开 PR附上前后截图对比若改动移动了数字把重新生成的docs/parity-checklist.md与docs/scorecard.md一并放进同一个 PR。【免费下载链接】photocraftPhotoCraft: An open-source, photo editor written by scratch in pure Rust项目地址: https://gitcode.com/gh_mirrors/pho/photocraft创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?