PhotoCraft は「Adobe Photoshop のクリーンルーム再実装を Rust だけで書く」と名乗る OSS だ。star は11,500、コミットは317本、Rust が23クレート・24万7千行ある。ただし最初のコミットは 2026-09-30、つまり公開から8日しか経っていない。
この手の「〇〇の再実装」で最初に知りたいのはどこまでできているのかで、そこは普通いちばん分からない。READMEは良いところを書くし、issue を全部読むのは現実的ではない。
ところがこのリポジトリには scorecard/ というディレクトリがあって、自分で採点した結果が TOML で置いてある。集計したら、150項目のうち done が12、missing が99だった。
もうひとつ気づいたことがある。AGENTS.md に「every feature drivable by agents(全機能をエージェントから操作できるように)」と書かれていて、そのための JSON 制御チャネルと MCP サーバーが入っている。画像エディタの中に AI が入っているのではなく、AI に運転させる側の設計だ。本記事はその2点を測る。
30秒でわかる
・自己採点150項目のうち done は12(8%)、missing は99(66%)。領域別に TOML で公開されている
・各項目にはissue番号と、根拠のソースファイル名・テストファイル名が付く。検証コミットと日付も明記
・エージェント運転用のMCPツールは20本。コマンドごとにツールを生やさず command_run{id} で呼ぶ設計
・歯止めが数値で入っている。応答8MiB・PNG 5MiB・プレビュー辺2048px・描画元64Mピクセル
・トークン照合は定数時間。接続ごとに最初の1行が認証で、成功まで他のメソッドは一切通らない
・unsafe 禁止は26中25クレートで機械的に有効。例外は macOS タブレット FFI の3箇所だけ
・ビルドは完了できなかった(rustc 1.95 要求+ディスク)。実行を伴う検証は未検証
MCPサーバーそのものの作り方はMCPサーバーの作り方2026年完全ガイド:TypeScript・Python両対応チュートリアルにまとめてある。本記事はその枝として、既存のGUIアプリにエージェントの運転席を足すとどうなるかを見る。
PhotoCraft は何で、何がAIなのか
まず正体の確認から。README の冒頭はこうだ。
Image editing; an open-source, clean-room reimplementation of Adobe Photoshop, rebuilt in pure Rust. Layers, masks, adjustment layers, layer styles, type, vectors, brushes and real PSD files, in a native app written entirely in Rust.
Electron でも Tauri でもない。AGENTS.md は「No Tauri, Electron or webview shells」とまで書いていて、デスクトップは egui/eframe on wgpu のネイティブ、Web 版は同じ Rust を WebAssembly にしたものだ。
| 項目 | 実測値 |
|---|---|
| バージョン | 0.2.0(ワークスペース) |
| クレート数 | 23 |
| Rust ソース | 676ファイル / 246,884行 |
| コミット | 317本(2026-09-30 〜 2026-10-07) |
| ライセンス | MIT OR Apache-2.0(デュアル) |
| star / fork / open issue | 11,500 / 1,500 / 90 |
| 必要な Rust | 1.95 以上 |
8日で24万7千行。コードがどう書かれたかは計測していないので推測は書かない。実務上の含意は、レビュー履歴から成熟度を読み取れないことだ——ただしこのプロジェクトは、その代わりになるものを置いている(後述)。
当サイトは AI 関連の OSS を扱うので、AI 機能の有無を機械で確かめた。openai・anthropic・gpt-・stable-diffusion・onnx・candle・torch での検索結果はこうなった。
・diffusion の該当11件 → すべて error diffusion(誤差拡散ディザ、algo/src/quantize.rs)
・candle の該当9件 → すべて CandleLight(ろうそくの炎ブラシ、algo/src/render2.rs)
・モデル推論の依存 → Cargo.toml に該当なし
モデルを積んでいるという意味でのAIは入っていない。 単語だけ見て飛びつくと両方とも誤判定するので、中身を見て確かめた。
では何が AI 文脈なのか。AGENTS.md の宣言だ。
The goal is 1:1 Photoshop parity (same menus, shortcuts, behaviour and file fidelity) with better performance, and every feature drivable by agents.
そして docs/control-protocol.md が267行かけて、エージェントがアプリを運転するためのJSONプロトコルを定義している。AI が中にいるのではなく、AI に明け渡すための入口が設計されている。
自己採点150項目を集計する
ここが本記事のいちばんの収穫だった。scorecard/ に8つの TOML がある。
ls scorecard/
# automation.toml bugs.toml distribution.toml file_compat.toml
# reliability.toml tools.toml type.toml ui.toml
中身は1項目ずつの到達状況だ。file_compat.toml の冒頭にはこう書いてある。
Statuses must be true on main. Verified against 4b53f62 on 2026-10-05
検証したコミットと日付が書いてある。項目はこういう形をしている。
| フィールド | 例 |
|---|---|
id |
FILE-215-2 |
target |
Smart Filters survive PSD save and re-open |
status |
partial |
issue |
215 |
note |
smart_map.rs: 14 filters mapped both ways, others kept verbatim… PhotoCraft-only filters (Twirl, Liquify…) are left out of filterFX with a warning |
何ができて何ができないかが、根拠のファイル名つきで書いてある。 「14個のフィルタは双方向でマップでき、それ以外はそのまま保持、PhotoCraft 独自のフィルタは警告を出して除外する」——これは宣伝文では出てこない粒度だ。
全部集計した結果がこれになる。
| 領域 | 項目数 | done | partial | missing |
|---|---|---|---|---|
| bugs | 32 | 0 | 3 | 29 |
| tools | 30 | 3 | 14 | 13 |
| file_compat | 27 | 1 | 8 | 18 |
| ui | 23 | 0 | 4 | 19 |
| distribution | 11 | 4 | 3 | 4 |
| automation | 11 | 1 | 3 | 7 |
| type | 9 | 0 | 3 | 6 |
| reliability | 7 | 3 | 1 | 3 |
| 合計 | 150 | 12 | 39 | 99 |
done は8%、missing は66%。 star 11,500 のプロジェクトが、自分でそう書いている。
これをどう読むか。「まだ使えない」は正しい結論だが、本題はそこではないと思う。同じことを書いていない OSS のほうが圧倒的に多いという点だ。「Photoshop の代替」を名乗って、どこまで届いているかを数字で出しているプロジェクトを、少なくとも私は他に思いつかない。
READMEのバッジも「status: early alpha」を掲げていて、採点と矛盾していない。公称と実体が食い違う例をこのところ何件も測ってきたので、一致しているとわざわざ書いておく。
127.0.0.1 のみ"] D --> E{"最初の1行で認証"} E -->|"失敗"| F["他のメソッドは
一切ディスパッチしない"] E -->|"成功"| G["command_run・ui_set
doc_open など"] G --> H["起動中のアプリを操作"] H --> I{"歯止め"} I --> J["応答8MiB
PNG 5MiB"] I --> K["プレビュー辺2048px
描画元64Mピクセル"] I --> L["読み書きルートを
起動時に固定"]
コマンドごとにツールを生やしていない
MCP サーバーは crates/automation(9ファイル・2,838行)にあり、公式の Rust SDK(rmcp)で書かれている。#[tool(description = ...)] 属性を抽出すると 20本だった。
| 分類 | ツール |
|---|---|
| ドキュメント | doc_open doc_new doc_save doc_export doc_inspect doc_render_preview doc_select doc_close |
| コマンド | command_list command_run command_batch |
| ジョブ | jobs_list jobs_cancel |
| UI | ui_inspect ui_screenshot ui_pointer ui_menu_invoke ui_set |
| その他 | session_list control_call |
description の合計は 2,330バイト(heuristic-jp-v1 で約582トークン)。ただしこれは説明文だけで、入力スキーマは Rust の型から実行時に導出されるため含まれていない。他のMCPサーバーの「ツール定義の合計」と直接は比べられない下限値として読んでほしい。
設計の要点は command_run と command_list の組にある。Photoshop 相当のメニュー項目は当然たくさんあるが、それを1つずつ MCP ツールにしていない。command_list で一覧と有効状態を引き、command_run {id, params} で呼ぶ。
ソース中のコマンドid形の文字列を重複なしで数えると827件あった(名前空間別に layer 212・view 86・edit 86・type 85・window 82・filter 82・image 66・file 58・select 34 ほか)。ただしこれは文字列リテラルからの抽出で、メニューidやUI用の識別子を含みうる。正確な数は command_list を実行すれば取れるが、本記事ではビルドが完了しなかったため未検証とする。
当サイトが実測してきたMCPサーバーでは、Backlog MCPとは|62ツール11,710トークンとENABLE_TOOLSETSでの半減を実測のように機能ごとにツールを並べる設計が主流で、そちらは62本で11,710トークンだった(当該記事での計測値。本記事の582トークンは heuristic-jp-v1 による説明文のみの概算なので、トークナイザも計測範囲も違う。桁の比較として読んでほしい)。PhotoCraft は逆に、ツール数を20で固定してコマンドidを引数に逃がしている。
それでも方向は明らかだ。数百のコマンドを数本のツールで扱う構造になっている。当サイトが本日あわせて測ったtregとは|約2,600エンドポイントを13 MCPツールで配る設計と、Apache表示の裏にある追加条項とまったく同じ考え方で、あちらは「カタログはツールではなくデータ」と書いていた。扱うものがAPIエンドポイントでもアプリのコマンドでも、結論は同じところに来るらしい。
MCP サーバーは制御チャネルの薄いラッパーでもある。photocraft-cli mcp だけならウィンドウ無しのヘッドレスで動き、--bridge 127.0.0.1:7878 を付けると起動中のアプリへ転送される。後者だとエージェントが見ている状態と人が見ている画面が一致する。
エージェントに運転させるための歯止め
GUIアプリをエージェントに明け渡すなら、どこで止まるかが肝心になる。ここは具体的だった。
経路。アプリは 127.0.0.1 のみで待ち受ける。起動はこう書かれている。
photocraft --control 7878 --control-token-file /private/path/photocraft-control.token \
--automation-read-root /work/project --automation-write-root /work/project
トークンファイルが無ければ 256bit のトークンを新規作成し、Unix では mode 0600 で置く。--automation-read-root / --automation-write-root で読み書きできるディレクトリを起動時に固定する。
認証。すべてのTCP接続で最初のリクエストが認証でなければならない、と仕様に書かれている。
The first request on every TCP connection must authenticate. No other method is dispatched before this succeeds.
照合。security.rs の token_matches を読んだ。
pub fn token_matches(expected: &str, supplied: &str) -> bool {
if expected.len() != TOKEN_HEX_LEN || supplied.len() != TOKEN_HEX_LEN {
return false;
}
expected.bytes().zip(supplied.bytes()).fold(0u8, |d, (a, b)| d | (a ^ b)) == 0
}
長さを先に確認したうえで、全バイトを XOR で畳んでから最後に比較する定数時間比較だった。先頭が一致した瞬間に return するような実装だとタイミング差から1バイトずつ当てられるが、ここはそうなっていない。小さいことだが、こういう箇所は手を抜いているほうが多い。
容量。budgets.rs に上限が定数で並んでいる。
| 定数 | 値 | 何を止めるか |
|---|---|---|
MAX_RESPONSE_BYTES |
8 MiB | 応答そのものの肥大 |
MAX_PNG_BYTES |
5 MiB | スクリーンショットやプレビューの肥大 |
MAX_PREVIEW_SIDE |
2048 px | 巨大なプレビュー要求 |
MAX_RENDER_SOURCE_PIXELS |
64 Mピクセル | 巨大な元画像の描画 |
加えて read_bounded_line(1行の長さ上限)と ConnectionLimiter(同時接続数)がある。エージェントが「このPSDを全部プレビューして」と言った結果、応答が数百MBになって固まる——という種類の事故を、構造的に止めている。
この構えは、当サイトがこの数日で測ってきたものと一致する。REA は16機能すべてに副作用を宣言させ、Brewery は任意コマンド実行だけを専門家モードに隔離し、d1-studio は読み取り専用で4種の文を403で止めていた。エージェントに渡す道具は、何ができるかより何をしないかを先に決めてあるかどうかで出来が分かれる。
「Rustだけ」「クリーンルーム」は機械で守られているか
規約が書いてあることと、守られていることは別だ。確かめられる範囲を確かめた。
unsafe の禁止。ワークスペースの Cargo.toml に [workspace.lints.rust] unsafe_code = "forbid" がある。forbid は #[allow] で上書きできないので、適用されていればコンパイラが強制する。ただし Rust の [workspace.lints] は各クレートが [lints] workspace = true で明示的に取り込まないと効かない。数えたら crates/ と apps/ の26個中 25個が opt-in していた。
では unsafe の実使用は——3件。すべて crates/tablet/src/macos.rs で、NSEvent の FFI(addLocalMonitorForEventsMatchingMask_handler、removeMonitor、as_ref)だった。そして tablet が、唯一 opt-in していないクレートだった。
つまり「unsafe を使わない」は宣言ではなく機械で効いていて、例外は1クレート3箇所に封じ込められている。ペンタブレットの筆圧を macOS から取るには Objective-C を呼ぶしかないので、これは妥当な割り切りだ。
ついでに注意点をひとつ。「unwrap/expect/panic! を使わない」という規約もあるが、こちらを grep で数えると非テストコードに3,000件以上ヒットしてしまう。実態は clippy.toml の allow-unwrap-in-tests = true などでテストだけ免除する運用で、インラインのテストモジュールが大半を占めている。単純な件数は規約違反の指標にならないので、数字として出すのはやめた。最初にそう書きかけたので、自戒として残しておく。
ブラウザ版のサイズ制約。Cargo.toml のプロファイル定義に、実務的なコメントが残っていた。Web 版は同じ Rust を WebAssembly にしたもので、訪問のたびにダウンロードされるため配信側の上限に当たる——ホストの単一ファイル上限(Cloudflare Pages / Workers は 25 MiB)を超えないよう、packaging/web/package.sh が 24 MiB でビルドを失敗させる。そのために Web 用プロファイルは fat LTO と opt-level = "s" を基本にしつつ、ピクセル処理のクレートだけ opt-level = 3 を維持してフィルタと合成の速度を落とさない構成になっている。コメントには結果も書いてあった——「この対応で wasm は release プロファイルの 26.3 MiB から 18.8 MiB になった(Issue #198)」。配信制約から逆算してプロファイルを分けた記録が残っているのは珍しい。
クリーンルーム。docs/contributing.md の先頭に方針がある。
Clean-room: do not copy code, shaders, icons, ICC profiles or other assets from proprietary software (such as Photoshop). Match behaviour and look by observation and public specs.
非コード資産は ATTRIBUTION.md(77行)に1件ずつ、出所とライセンスつきで列挙される。フォントは別リポジトリ(storytold/craft-fonts)に置き、このリポジトリには入れない、という運用も明記されている。
ただしクリーンルームの実態そのものは外部から検証できない。本記事が確認できたのは規約の存在と、unsafe 禁止が機械で効いていることまでだ。そこは正直に書いておく。
PhotoCraft を導入するかの判断材料
向いている場面
・画像編集をエージェントに任せる仕組みを作りたい場合。制御チャネルとMCPが最初から入っていて、歯止めも数値で定義されている。この用途なら今の完成度でも試す価値がある
・設計を読みたい場合。docs/ と scorecard/ と AGENTS.md の情報量が多く、GUIアプリにエージェントの運転席を足す設計の実例として読める
・PSD の読み書きを Rust でやりたい場合。crates/psd が独立している
向いていない場面
・Photoshop の置き換え。自己採点が missing 99項目と言っている。今日の実務には使えない
・安定性を要する用途。bugs.toml が32項目中29件 missing
・手元の Rust が 1.95 未満の環境。ビルドが通らない
何を代替するかを現時点で言うなら、Photoshop ではなく「画像編集を自動化するために自分で書いていたスクリプト」だと思う。ImageMagick や Pillow でやっていた処理を、レイヤーとマスクを持ったドキュメントモデルの上で、しかもエージェントから叩ける形でやれる——という方向だ。1:1 パリティはその先の話になる。
測り終えて残った印象は、自己採点を公開していることの重さだった。150項目中 done が12というのは、普通なら隠したい数字だ。それを issue 番号とソースファイル名つきで出し、検証コミットと日付まで添えている。当サイトは公称値と実体のずれを何度も記事にしてきたが、このプロジェクトに関してはずれを探す前に本人が先に書いていた。
本記事で測れなかったのは、肝心の「動かしたらどうか」の部分だ。ビルドは rustc 1.95 を入れるところまでは進んだが、依存のコンパイル中に target/ が2.1GBを超え、空きディスクとの兼ね合いで中断した。MCPサーバーの実起動、command_list の実数、アプリの実操作はいずれも未検証のまま残っている。
参照ソース
・storytold/photocraft(GitHub) — 本記事の計測対象。MIT OR Apache-2.0・star 11,500
・docs/control-protocol.md — JSON制御チャネルとMCPブリッジの仕様(267行)
・AGENTS.md — 「every feature drivable by agents」ほか開発規約
・scorecard/ — 本記事が集計した自己採点150項目
計測値は data/measurements/runs/2026-10-07-photocraft-agent-control.json に記録した。環境は Ubuntu 24.04 / x86_64 / rustc 1.95.0。ビルドが完了していないため、実行を伴う検証(MCPサーバーの起動、コマンド数の実測、アプリの操作)はいずれも未検証。