DEV Community

Cover image for A PSD carries Photoshop's answer key. The UI has none.
Ian
Ian

Posted on

A PSD carries Photoshop's answer key. The UI has none.

This week I wrote a Photoshop file by hand. 390 bytes, two gray layers, the top one set to Soft Light. I filled the file's saved preview with solid magenta on purpose, so any reader that just showed the preview would get caught.

The reader ignored the magenta, rebuilt the image from the layers, and gave me a gray of 32. The W3C formula behind CSS soft-light gives 15 for the same two layers.

That reader was the command-line tool of PhotoCraft, which calls itself an open-source, clean-room reimplementation of Photoshop in pure Rust. The repo was created on September 30, passed 2,200 stars inside a week, and shipped v0.2.0 on October 5. Its own README labels it early alpha and says it is "not yet a Photoshop replacement for daily professional work."

I spent an evening on one question: how does a project this young get blend math right, while its first users say the basics break?

The answer key inside the file

A PSD does not only hold layers. When Photoshop saves with Maximize Compatibility on, it also writes a flattened copy of the whole image: its own render of those layers. Each such file is a question with its answer stapled to the back.

PhotoCraft's test suite is built on that. crates/io/tests/corpus.rs opens each real-world PSD, flattens the layers with PhotoCraft's own compositor, and compares the result with the merged image Photoshop saved. A pixel passes within 2/255. Files saved without a merged image fall back to the small JPEG thumbnail Photoshop embeds, which is also Photoshop's render.

I checked that from the outside. I took 14 blend-mode test files from the MIT-licensed psd-tools project, all saved by Photoshop, and wrote a short Node script that decodes each file's merged image on its own. Then I had PhotoCraft's CLI render every file from its layers and compared the two. A sample:

pt/multiply.psd      4096 px  exact 3993  max diff   1/255
pt/overlay.psd       4096 px  exact 3353  max diff   1/255
pt/soft-light.psd    4096 px  exact 3346  max diff   2/255
pt/vivid-light.psd   4096 px  exact 3361  max diff   2/255
pt/hue.psd           4096 px  exact 2787  max diff   2/255
Enter fullscreen mode Exit fullscreen mode

All 14 landed within 2/255 of Photoshop's render. Before that impresses you too much: these files sit in PhotoCraft's own test corpus, so a pass is what its tests already promise. The interesting part is what the corpus taught the code.

Clean-room means learning from output

The project's rule, in AGENTS.md and docs/contributing.md, is that Photoshop is studied for behavior and look only: no copied code, shaders, profiles or assets. Implementation comes from public specs (Adobe's PSD spec, ICC, ISO 32000 for blend modes) and from observation. Where the PSD spec is silent, the psd crate's README says it follows the MIT-licensed psd-tools and ag-psd docs.

Observation is not a figure of speech here. crates/compose/src/psblend.rs carries Photoshop-specific versions of Vivid Light and Hard Mix, and its header says they were derived from Photoshop's rendered composites in those psd-tools files. One case: a black Vivid Light layer over a white backdrop. The textbook color-burn branch returns white. PhotoCraft returns black, because that is what Photoshop's saved render shows. I ran that case and got 0.

Soft Light is the same story. crates/color/src/blend.rs keeps a Photoshop Soft Light that differs from the W3C definition in its upper half. Here are my hand-built files, run through the CLI, next to both formulas:

$ node cases.mjs
sLit backdrop   4 source 254 ->  32  css-w3c 15  ps-formula 32
sLit backdrop  26 source 230 ->  71  css-w3c 67  ps-formula 71
sLit backdrop 200 source 230 -> 221  css-w3c 221  ps-formula 221
vLit backdrop 255 source   0 ->   0
Enter fullscreen mode Exit fullscreen mode

I scanned all 65,536 pairs of 8-bit values. The two formulas disagree on 4,579 of them, by up to 17 levels, always on a dark backdrop under a bright layer. If you ever matched a Photoshop mockup with mix-blend-mode: soft-light and the shadows came out wrong, this is one honest reason.

The price of the rule shows up in the camera RAW decoder. Issue #83 explains why Nikon's compressed NEF still opens only as its embedded JPEG: the decoder needs Huffman tables the file does not carry, and the only descriptions the author found were GPL decoder source or write-ups taken from it. So they stopped. That is the project's own line, not a legal opinion, and I am not offering one.

What has no answer key

Here is my claim. PhotoCraft moves fastest exactly where a file can grade it, and breaks where nothing can.

The speed is real. When I cloned it, the repo held about 231,000 lines of Rust by my count, two dozen crates in layers a build check enforces, and a CLI whose registry listed 748 commands. By the repo's own commit trailers, 138 of its 236 commits on main were co-authored with Claude, and its AGENTS.md is written for AI agents working in parallel. A grader that never sleeps suits that way of working.

Then people opened the app. In the HN thread (106 points), several testers say they could not get basic tasks done, and the maintainer says the repos went public before they were ready. The project's roadmap, to its credit, says the same thing in its "honest parity assessment." Users hit a text selection offset, 214 shortcut failures, panels resizing themselves and an immovable crop frame, all of which counted as "live" in its parity list. In its own words: "this week proved our tests miss what users hit."

A crop frame has no merged image. Nothing in a file says how a drag should feel.

Even the file format has a softer side than the pixels. Reading is graded by Photoshop. Writing is graded by whoever opens your file next. In issue #200, a user re-saved 437 public PSDs, and psd-tools could not read 36 of the outputs. The fix landed after v0.2.0, and the README dropped a "byte for byte" claim in the same change.

So mind which number you read. 307 of 309 psd-tools files re-open looking the same after a re-save, which is PhotoCraft agreeing with itself. The enforced floor for matching Photoshop's own render on that set is 229.

The roadmap's next step reads like an answer: script 30 to 50 real tasks end to end and check each against Photoshop's output on every build. That is an answer key for workflows. Whether it can capture how a crop frame feels is the bet I would watch.

What I ran, and what I did not

I did not launch the desktop app or build from source. I downloaded the macOS CLI zip from the v0.2.0 GitHub release, checked its hash against the release's SHA256SUMS.txt, and confirmed it is signed with a Developer ID and notarized. Then, in a scratch folder:

./photocraft-cli --version      # photocraft-cli 0.2.0 (ad8632173, 2026-10-05)
./photocraft-cli info t-soft.psd   # my hand-made file: two layers, "blend": "Soft Light"
./photocraft-cli convert pt/soft-light.psd soft-light.ppm
./photocraft-cli run t2.psd --cmd layer.layerStyle.blendingOptions \
  --params '{"blend":"screen"}' --out t2-screen.ppm
Enter fullscreen mode Exit fullscreen mode

I found no hosted web version. The release ships the WebAssembly build as a zip you host yourself.

Two limits on my numbers. The psd-tools files are in PhotoCraft's own corpus. And my Soft Light check compares PhotoCraft with a formula, not with Photoshop, which I did not run.

When you had to clone behavior with no spec, what did you use as your answer key, and what did you do about the parts that had none?

Top comments (0)