Files
photocraft/docs/gaps.md
T
a62ef68ca2 CMYK: Hue/Saturation and Color Balance keep the black ink (#5000)
* Modern Brightness/Contrast: Photoshop's brightness curve

The modern brightness curve was a line rolled off by a v^P term. It sat
up to 4 levels off Photoshop's in the highlights, and up to 2.3 levels
off for lowered brightness.

The curve is refit to the Photoshop ground truth we have:
- the ±50 samples from 4759d12b;
- the photoshop corpus's brightness +25 / contrast +30 files,
  inverted through the (exact) contrast curve.

Raised, it is the line s*x (s = 1.375^(b/50)) up to a knee at 80/255,
then a cubic Hermite to white with end slope s^-6.6. Lowered, it is the
exact inverse of the curve raised by as much (the -50 samples confirm
this to within a level). Every measured point is within about a level.
For strong values the knee moves down so the curve stays monotone
across -150..150.

Brightness then contrast are evaluated through one 4096-entry table,
built in 55 us. The GPU uses that same table through the Levels/Curves
kernel instead of rebuilding the curves in WGSL, so GPU and CPU agree
by construction.

Photoshop oracles: rgb8/16/32 and gray8/16/32 brightness-contrast now
pass (145 -> 151 of 258). The floor goes 133 -> 151.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* CMYK: Brightness/Contrast curves each ink, as Photoshop does

Photoshop applies Brightness/Contrast in a CMYK document to each ink's
stored value, 1 - ink, black included. Both the modern and the legacy
curve do this; the cmyk8 brightness-contrast(-legacy) oracles match it
within a level on every pixel of the files' own inks. Raising
brightness takes ink away. We applied it to the screen colour instead.

- cmyk_channel_adjustment gains Brightness/Contrast. apply_in builds
  the channel curve once (brightness_contrast_lut) and runs each
  separated ink through it (ink_through).
- Destructive Image > Adjustments on a CMYK layer curves the stored
  inks directly, so it is exact; a selection mixes them.
  adjusted_channels now takes the per-pixel operation, and works out
  luma only for CMYK Threshold, which compares it.
- The GPU leaves CMYK Brightness/Contrast to the CPU, like the other
  ink rules.

cmyk8/16 brightness-contrast oracles, bad pixels:
- modern: 72% -> 48-57%, max error 0.19 -> 0.09
- legacy: 71% -> 51-53%

The rest is our separation of the RGB composite, not the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* CMYK Threshold compares the inks' own luma, as Photoshop does

Photoshop's CMYK Threshold splits on the Rec. 601 luma of the inks' plain
complement, (1 - c)(1 - k) and so on, whatever the document's profile.
On the files' own inks this puts every pixel of cmyk8 and cmyk16
threshold-128.psd on the right side of level 128. The profile's RGB luma
missed 100.

stored_channels works the luma out from the inks itself (ink_luma), so it
no longer takes one; the engine's stored-channel path no longer converts
each pixel to RGB at all. Through the separated RGB composite the cmyk
threshold oracles drop from 1.6% to 0.2% bad pixels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* CMYK legacy Brightness/Contrast pivots at 128 in ink

Photoshop's legacy contrast pivots at level 128 of the ink, which is 127
of 255 of the stored value (1 - ink) that the curve runs on, not 128 as
in RGB. On Photoshop's own 16-bit inks (cmyk16
brightness-contrast-legacy.psd) ours sat a constant 0.43 of a level
above. That is exactly the one-level pivot shift times contrast +30's
slope minus one; with the pivot moved, the inks match within 0.01.

brightness_contrast_lut is now ink_brightness_contrast_lut, since its
legacy curve is the CMYK one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* CMYK: adjustment layers composite on the document's own inks

The compositor works in display RGB. In a CMYK document, an adjustment
layer that Photoshop evaluates on the inks could only act on our
separation of the RGB composite, and that separation's black differs
from the file's. So even exact ink rules missed: CMYK Invert was 10%
off, Levels and Curves 50-65%.

The ink-native compositor (compose::native, until now only for
flattening plain raster stacks) now takes adjustment layers too:
- Invert, Posterize, Threshold and Brightness/Contrast (modern and
  legacy), and Levels and Curves on CMYK channels, prepared once
  (InkStack, InkOp) and applied to the straight inks.
- Opacity and an unfeathered layer mask mix the inks linearly, as in
  Photoshop; raster layers' masks scale their coverage the same way.
- A CMYK stack of plain raster layers and such adjustments renders this
  way for display, export and reduced views (render_cmyk, tiles in
  parallel); flattening and merging keep the adjusted inks exactly.
- Stacks of raster layers alone keep the RGB compositor, which the GPU
  matches. The GPU already leaves these adjustments to the CPU.

Photoshop oracles: cmyk8 and cmyk16 invert, posterize-4, threshold-128,
levels, curves, brightness-contrast and brightness-contrast-legacy all
pass (14 files), 151 -> 165 of 258; floor 151 -> 165. psd-tools and io
corpora unchanged. 24 MP CMYK8 with one adjustment renders in 286 ms on
the inks, against 442 ms through the RGB compositor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* CMYK ink compositing inlines open groups

The ink compositor gave up on any group. Now a group at full opacity
without a mask is inlined:
- a pass-through group's children composite straight onto the backdrop,
  so an adjustment inside it reaches the layers below;
- an isolated (Normal) group of raster layers alone composites as its
  children would, since Normal is associative.

An isolated group with an adjustment, which only sees the group's
content, keeps the RGB compositor. So do a translucent group and an
artboard. Nesting is bounded.

psd-tools' CMYK adjustment files keep their swatches in pass-through
groups. With #4948's fair comparison, curves_cmyk drops from 37% to 0.3%
bad pixels and levels_cmyk from 14% to 0.2%. The photoshop corpus is
unchanged at 165.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* CMYK: Hue/Saturation and Color Balance keep the black ink

In a CMYK document Photoshop applies Hue/Saturation (not Colorize) and
Color Balance to C, M and Y as the complement of an RGB colour, and
leaves the black ink alone. On the photoshop corpus's own inks,
cmyk16 hue-saturation.psd follows this on every pixel within half a
level (cmyk8 within two), and color-balance within our RGB Color
Balance's own error. We applied both to the separated screen colour,
which also rewrote K.

- adjust::cmy_adjustment and apply_cmy run the RGB adjustment once per
  band on (1 - c, 1 - m, 1 - y).
- The ink compositor (InkOp::Cmy), the RGB compositor's CMYK branch and
  destructive Image > Adjustments (pixels::adjusted_cmy) use them. The
  GPU leaves these to the CPU in CMYK documents, as it does the other
  ink rules.
- Colorize separates its colour again, black included, so it keeps the
  screen-colour path.

cmyk8/16 oracles, bad pixels:
- hue-saturation: 100% -> 0.4-0.7%
- color-balance: 74% -> 25%

Neither passes yet. Half a level of cyan ink is three levels of red
through the profile at saturated cyan, and our RGB models leave that
much. The pass count stays at 165.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* 32-bit: legacy Brightness/Contrast, Channel Mixer, Photo Filter and B&W tint unclipped

In 32-bit documents Photoshop keeps these adjustments' results above 1
and below 0. On the photoshop corpus's rgb32/gray32 files:
- Legacy Brightness/Contrast pivots its contrast at 127 of 255 (8/16-bit:
  128) and is exact on every pixel.
- Channel Mixer cuts each source percentage to 1/1024 toward zero, counts
  255/512 of the constant and is exact on pure red, green, blue, black
  and white.
- Photo Filter works on the linear samples, negative results included.
- The Black & White tint is the Color blend mode without the clip at 1,
  as the blend mode is there; white tints to (1.05, 1.00, 0.87).

We clipped all four to 0..1. The GPU kernels follow suit under F_HDR
(the legacy pivot and the mixer's steps come from the planner), and the
HDR parity test covers them.

Photoshop oracles: rgb32 and gray32 brightness-contrast-legacy, rgb32
channel-mixer and black-white-tint pass (145 -> 149 of 258); floor
145 -> 149. rgb32 photo-filter improves (0.19 -> 0.035 max); the rest is
its colour (#3790).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Modern brightness: knee at output 126, fit out to -95 and +150

psd-tools' brightnesscontrast_rgb.psd masks four Brightness/Contrast
layers to their own columns. Read through the exact contrast curve, it
gives Photoshop's brightness at -95, -31, +23 and +150, well past the
+/-50 samples the first fit had. The +/-50 curve extrapolated badly
there: 22 levels off at -95 and 25 at +150.

Every setting puts the knee at about the same output level, not input.
The roll-off's end slope falls as 1/(1 + 0.084 b). With the knee at
126/255 and that end slope, the cubic Hermite fits -95, -50, -31, +23,
+25 and +50 within 1.4 levels. Past about +110 no such cubic stays
below white, so there the roll-off is 1 - (1 - y0)(1 - u)^alpha, with
the slope continuous at the knee. That gives +150 within 6 levels,
where every curve family tried bottoms out. Lowered curves are still
the inverse of raised ones.

The photoshop corpus's six brightness-contrast oracles still pass.
psd-tools brightnesscontrast_rgb goes from 0.13 to 0.020 max error and
_grayscale from 0.098 to 0.016. The new measurements are pinned in
modern_brightness_matches_photoshop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Levels: whole levels at every step and Photoshop's shadow toe

In 8 bits Photoshop rounds each of the three Levels steps to a whole
level (half up): the input-range stretch (already modelled), the
midtone gamma and the output range. A narrow output range therefore
merges neighbouring gamma levels: psd-tools levels_rgb maps R 43..48 to
58 through a 0.47 channel gamma and the master's 48..255, where the
unrounded curve gave 56..59 (3 levels off in blue).

The gamma above 1 starts along a toe a*u - b*u^q of slope a = 2^gamma
that joins the power curve with the same value and slope (q = 2.2,
fitted). With the rounding, that is exact on 1116 of the 1118 levels
of gamma 1.16, 1.22, 1.3, 1.78 and 2.0 oracles; the soft-min it replaces
missed 52. Gamma below 1 stays the plain power curve (0.67 and 0.7:
every level exact).

Oracles: psd-tools levels_rgb 0.0118 -> 0.0039 (PASS); photoshop
rgb8/stack-levels-curves-hue-posterize 0.2000 -> 0.0000 (PASS); rgb8 and
gray8 levels.psd 0.0039 -> 0.0000; rgb16/gray16 0.0039 -> 0.0020. Floors:
photoshop 145 -> 146, psd-tools 236 -> 238 (main is at 237).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Masks: feathers blur with exactly the feather's variance

A mask feather is a Gaussian with sigma = feather, which we approximate
with three box passes per axis. Whole-pixel boxes rounded the variance:
a 6 px feather blurred like 6.48 px and a 3 px one like 3.46 px, so
feathered edges were too soft by up to 4 levels against Photoshop
(psd-tools mask-parameters-no-real-channel.psd: a 6 px vector and 3 px
pixel feather).

Each pass is now an "extended box" (Gwosdek et al. 2011): weight 1 on
the whole radius and a fractional weight on the two samples beyond it,
so three passes have variance sigma^2 exactly. It stays O(n), reusing
the window's leaving sample for the low end weight (50 MP at a 1000 px
feather: 180 ms parallel, was 172).

psd-tools: mask-parameters-no-real-channel 0.0168 -> 0.0060 (PASS),
layer_mask_data.psd 26.9 % -> 3.9 % of pixels off (psb 25.9 % -> 1.8 %).
Corpus test 239 -> 240; floor 236 -> 240.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Lab: adjustment layers composite on the stored L, a and b

The compositor reads a Lab document's layers through display RGB, which
clips colours outside its gamut, so the adjustments Photoshop evaluates
on L, a and b (Invert, Posterize, Threshold, Brightness/Contrast, Levels
and Curves with Lab records) only saw the clipped colours: the photoshop
corpus's Lab oracles were up to 11 levels off on saturated colours.

compose::native_lab works a Lab stack of plain layers out on the stored
values instead, like the CMYK ink path: Normal mixes L, a and b
linearly, an opaque layer keeps them exactly, a layer mask scales the
mix, and adjustment layers run adjust::lab_stored_op on each pixel.
Stacks with such adjustments render for display this way; flattening a
plain Lab stack keeps its stored values (exports no longer clip).
Anything else (other blend modes, effects, other adjustments) keeps the
RGB compositor.

Photoshop oracles 145 -> 152: lab16 invert, posterize-4, levels, curves
and brightness-contrast-legacy; lab8 invert and posterize-4. lab8
levels and legacy B/C come within a level (their 8-bit rules are #5143
and #5132). psd-tools and io unchanged; every round trip unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Threshold: split on Photoshop's luminosity of whole samples

Photoshop's Threshold splits on the luminosity 0.30 R + 0.59 G + 0.11 B
(the blend modes' lum, as Gradient Map uses), not the Rec. 601 grey,
taken over the document's whole sample values:

- 8 bits: 30 R + 59 G + 11 B > 100 level - 50, i.e. the luminosity
  rounded to a level with halves down reaches the level;
- 16 bits: 30 R + 59 G + 11 B >= 100 x the level in 16 bits
  (round(level x 32768 / 255): mid grey 16448 is level 128);
- 32 bits: the luminosity reaches the level.

Exact on every pixel of psd-tools threshold_rgb (36432 px; Rec. 601 split
28 the other way), threshold_16bits_rgb (100) and threshold_16bits_
grayscale, and the photoshop corpus's rgb16 threshold-128. The sums are
exact in f32, on the CPU and in the GPU kernel, so ties (grey exactly on
the level) split the same way on both.

psd-tools: threshold_rgb and threshold_16bits_rgb pass; corpus test
239 -> 241, floor 236 -> 241.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Posterize: 16-bit documents bin the 16-bit sample, as Photoshop does

At 16 bits Photoshop doesn't round to 8-bit levels before binning. It
bins the 16-bit sample, floor(v16 * n / 32768), and lands on 8-bit levels
through a 16-bit step: floor(b * 32768 / (n - 1)), converted with
floor((o * 255 + 16384) / 32768). So the middle of 3 levels is 128 where
8 bits give 127, while 42.5 and 25.5 stay 42 and 25.

Exact on every sample of psd-tools posterize_16bits_rgb (levels 3, 7,
13, 21) and posterize_16bits_grayscale (2, 6, 9, 11); the 8-bit rule
missed up to half the range at bin edges. The same rule applies to
CMYK and Lab channels at 16 bits: on the stored inks (#4945's native
path) psd-tools posterize_16bits_cmyk goes from 4.26 % to 0.01 % of
pixels off. 8-bit Posterize is unchanged.

The GPU kernel runs the same rule; 16-bit layers reach it as half
floats, so a sample within their precision of a bin edge can land in
the neighbouring bin there (the CPU, which exports, is exact).

psd-tools 239 -> 241; floor 236 -> 241.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* CMYK: Posterize works on the stored values (1 - ink)

Photoshop posterizes a CMYK channel's stored value, 1 - ink, not the
ink: with levels whose steps are uneven (3, 7, 13, 21) the two differ,
e.g. 3 levels take an ink of 85 to 128, where posterizing the ink drops
it to none. psd-tools posterize_cmyk.psd goes from 13.8 % of pixels off
to exact; psd-tools 241 -> 242, floor 242.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Groups: a pass-through clipping base with an adjustment renders isolated

A pass-through group composites its children straight into the
backdrop, so an adjustment layer inside reaches the layers beneath.
When the group is the base of a clipping group and holds an adjustment
layer, Photoshop renders it isolated instead: in psd-tools
passthrough_clipping_mask_adjustment an Exposure in such a group leaves
the layers beneath alone (we darkened them: half the image 170 levels
off). The same group with a Linear Dodge child and no adjustment keeps
reaching the backdrop (passthrough_clipping_mask_blendmode), and so do
clipping-mask3/4/5, so the rule is the adjustment, not the clipping
alone (isolating every pass-through clipping base broke those two).

CPU compositor and GPU plan alike. psd-tools 239 -> 240; floor 240.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Raise corpus floors to the results of the landed adjustment fixes

Photoshop-authored 165 -> 185 of 258, io 146 -> 148 of 170, psd-tools 242 -> 249 of 309,
measured with the CMYK, Lab, 32-bit, Levels, Threshold, Posterize, feather and
pass-through PRs merged together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 14:12:06 -04:00

26 KiB
Raw Blame History

Where PhotoCraft falls short of Photoshop

Last reviewed: 2026-10-10 · Last updated: 2026-10-13 · Change: minor (G1: CMYK Hue/Saturation and Color Balance keep the black ink; G1: CMYK Posterize on the stored values, psd-tools oracle floor 242/309; G1: CMYK adjustment layers composite on the document's inks, Photoshop-oracle floor 165/258; G1: CMYK Brightness/Contrast on the inks, CMYK Threshold's luma; G1: modern Brightness/Contrast's brightness curve matches Photoshop, Photoshop-oracle floor 151/258; G1: Lab documents composite on their stored L, a, b, Photoshop-oracle floor 152/258; G1: Levels on whole levels at every step, Photoshop-oracle floor 146/258, psd-tools 238/309; G1: 32-bit Brightness/Contrast legacy, Channel Mixer, Photo Filter and Black & White tint unclipped as in Photoshop, Photoshop-oracle floor 149/258; G1: mask feathers blur with their exact variance, psd-tools oracle floor 240/309; G1: Threshold splits on Photoshop's luminosity, psd-tools oracle floor 241/309; G1: pass-through clipping base with an adjustment renders isolated, psd-tools oracle floor 240/309; G1: 16-bit Posterize as Photoshop bins it, psd-tools oracle floor 241/309; G1: Gradient Map driven by the W3C luminosity, Photoshop-oracle floor 147/258; G7: Field Blur's on-canvas pins, #3373; G4: Hue Jitter amount track, #3825 partial; G4: minimum field interaction, #3825 partial; G1: CMYK Invert, Posterize and Threshold on the inks, 16-bit Lab Invert; Photoshop-oracle floor 145/258; G11: .atn import maps 112 events: Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, mask targeting, Step Backward, nudges in points, Bitmap mode; G5: Free Transform H/V skew fields; G9: toolbar tool shortcut editing; refreshed against main: G2, G5, G7, G9, G10, G13, G14, G17 items that landed; G10: DPX read and write, #1049; G4: checkbox-only Brush Settings rows; G11: Load/Save Actions .atn landed, AUTO-219-2 partial; G5: hold-a-key spring-loaded tools landed, #2672; G1: #2465 fixed; stale missing-tool rows corrected: Color Replacement, Red Eye and Add/Delete Anchor Point are live tools, so only Art History Brush is missing; no estimate changed) · Target: Adobe Photoshop 2026

Every known shortfall against Photoshop 2026 (27.11.0), one entry each, ranked by user impact on the way to beta. This is the work list agents pick from: take the highest-ranked entry that nobody is working on, and when you close part of one, update its evidence and estimate here and the matching parity doc. Numbers come from target-app-parity.md; checklist ids (TOOL-…, FILE-…, UI-…) are rows in scorecard.md; #n are GitHub issues.

No gap blocks alpha: the alpha gate passes. "Blocks beta" marks what stands between alpha and beta.

Hours are Opus 5.5 agent-hours, one agent working sequentially, including tests. Human marks what an agent can't finish alone.

Summary

# Gap Kind Impact Estimate Parity doc
G1 PSD fidelity and Photoshop acceptance file format blocks beta 150–250 h file-format-parity
G2 Interactive performance performance blocks beta 200–350 h scorecard
G3 User-reported bug backlog stability, UI blocks beta 250–400 h target-app-parity
G4 Brush feel UI / painting high 100–180 h brush-parity
G5 Transform, handles and modifiers UI high 60–110 h ui-parity
G6 Missing tools (12) features high 60–100 h ui-parity
G7 Workspaces shown as parameter dialogs features high 80–140 h target-app-parity
G8 Type depth features high 120–200 h target-app-parity
G9 Panels, preferences and workspace UI medium-high 150–250 h ui-parity
G10 Missing and partial file formats file format medium 120–220 h file-format-parity
G11 Actions and scripting ecosystem medium 120–200 h target-app-parity
G12 Pen tablets and display hardware hardware medium 80–150 h hardware-parity
G13 Languages: Hindi, Arabic, Vietnamese, RTL, engine messages localization medium 80–140 h + human localization-parity
G14 Camera Raw depth features, formats medium 120–200 h file-format-parity
G15 AI and generative features AI medium (high for some users) 300–600 h + human target-app-parity
G16 Plug-in compatibility (.8BF, UXP) ecosystem medium 250–450 h + human target-app-parity
G17 Platforms and distribution platforms low-medium 30–60 h + human scorecard

Feature gaps

G1. PSD fidelity and Photoshop acceptance

  • Missing: PSDs we write are not always accepted by Photoshop, and Photoshop-authored files don't always render as Photoshop renders them.
  • Evidence: corpus floors (crates/io/tests/corpus.rs, scorecard): Photoshop-authored oracles 185/258 (72%; CMYK and Lab adjustment layers, 32-bit unclipped adjustments, Levels, Threshold, Posterize and mask feathers 2026-10-13; Gradient Map luminosity 2026-10-13; CMYK Invert, Posterize and Threshold on the inks 2026-10-12), io corpus 148/170 (87%), psd-tools 249/309 (81%); round trips ~100% within the corpus. Smart-filter re-render matches 5/30. User reports: a PSD with text layers re-saved unmodified would not reopen (#2374, closed 2026-10-10), Photoshop 26.6 cannot reopen our files (#1281), "PSD not opening in Photoshop" (#2469), a CS2-era smart object unavailable (#2465, fixed 2026-10-12), PSB smart object blank (#1764), perspective placed layers flattened (#579, fixed by #426), PSB > 4 GB fails on Windows (#375). No test opens our exports in a third-party editor (FILE-216-4).
  • Impact: PSD is the format professionals exchange. Beta requires it to be reliable.
  • Estimate: 150–250 h. Human: opening exports in Photoshop on the owner's Mac (or a scripted check there) is the only real oracle.

G6. Missing tools

  • Missing (1 of 68): Art History Brush. Convert Point landed (#3204). Freeform Pen and Curvature Pen landed (#1054); Color Sampler landed (#1046, view.colorSamplers.*); Horizontal and Vertical Type Mask landed (#1055, type.createSelection); Perspective Crop landed (#1043, image.perspectiveCrop); Frame landed (#2914).
  • Evidence: Tool enum (crates/ui-egui/src/state.rs) against the 67 <tool> entries of Photoshop's Default Keyboard Shortcuts.kys plus Remove, Triangle and Curvature Pen.
  • Impact: users look for them by name and shortcut; several are daily tools for designers.
  • Estimate: 60–100 h (2–9 h each, per the calibration).

G7. Workspaces shown as parameter dialogs

  • Missing: Select and Mask workspace layout (TOOL-214-1, Refine Hair #2671; its view modes preview live since #1754, Refine Edge Brush #2972); Content-Aware Fill workspace with a paintable sampling area (TOOL-214-15); Vanishing Point plane editor (TOOL-214-14); Content-Aware Move's live preview of the result (TOOL-213-4; Transform On Drop and Content-Aware Scale through the transform box, TOOL-214-7, are done). Blur Gallery: Field Blur has its on-canvas pins (2026-10-11, #3373); Iris, Tilt-Shift, Spin and Path Blur still edit their pins as parameter dialogs.
  • Evidence: scorecard rows above, all partial or missing.
  • Impact: these are the tools retouchers and compositors use daily; a slider dialog is not a substitute.
  • Estimate: 80–140 h.

G8. Type depth

  • Missing: type on a path (TYPE-218-1), paragraph direction UI (TYPE-218-2), complex-script fallback fonts and golden tests (TYPE-218-3), OpenType Numerals and Stylistic Sets submenus (TYPE-218-5; the Character panel buttons landed 2026-10-11, #3752), live Warp Text preview (TYPE-218-7, #2689), "rasterize and continue" for distort/perspective on type (TYPE-218-9), text free transform (#2630). Done since the re-measure: manual and optical kerning with PSD round trip (TYPE-218-6, #206), variable-font axes (TYPE-218-4, #2985).
  • Evidence: scorecard Type: 3 done, 2 partial, 4 missing. 2026-10-11: 2/3/4/5 clicks select word, line, paragraph and all text, with tests (TYPE-218-8, #3493). The Type tool froze the app for 60 s on one macOS machine (#2518).
  • Estimate: 120–200 h.

G11. Actions and scripting

  • 2026-10-13 evidence: Batch supports output naming templates using {name}, {serial} and a captured UTC {date}, with a starting serial number. Colliding names remain errors; custom output names cannot replace inputs. AUTO-219-5 stays partial.
  • Missing: document-independent action recording (AUTO-219-1), Batch recursion, open-document inputs, progress/cancel and an error-log file (AUTO-219-5), ExtendScript/UXP compatibility. .atn steps outside the mapped events (paths, scripts, Generative Fill, Filter Gallery effects other than Spatter, Duplicate Layers into a new document, Bitmap halftone screens…) load as kept Photoshop steps that fail at playback by name.
  • Done 2026-10-12: Load/Save Actions .atn in the panel menu, batch --actions set.atn and actions.load/actions.save; 112 Photoshop events map to commands both ways (Transform, Apply Image, Save for Web, smart-object edits, Gradient Map, Quick Mask, polygon selections, Place, Open, Image Size in percent, the Paint Bucket and Insert Conditional since the third and fourth PRs; Select and Mask, Filter Gallery Spatter, Ripple, Crystallize, Pattern Fill layers, targeting the layer mask or composite channel, loading the targeted channel, Step Backward/Forward, removing a layer from the selection, nudges in points, Layer From Background, Grayscale and Bitmap mode objects and Image Size with Resample keeping the print size since the fifth), Play, Stop and conditional steps included; layer steps replay by layer name in any document. Four TrueGrit Texture Supply sets (Debaser, Distress Press, Halftone Zine Machine, Transparency) import with 3 of 533 steps unmapped and play end to end. Filter Gallery Spatter's Smoothness now smooths the spray's displacement, so high values tear an edge without detaching specks, as Photoshop's does (calibrated by eye on the Distress Press guide's screenshots). Crystallize fills each cell with the colour at its point instead of the cell's average, so a black-and-white mask keeps hard edges (not yet checked against a Photoshop render). The panel groups actions in set folders, has the dialog toggle column (playback pauses on the step's dialog and resumes on OK) and Insert Stop…; actions.insert, actions.dialog, actions.assignSet for agents (AUTO-219-2 done).
  • Ahead of Photoshop: CLI, MCP and the JSON control channel drive every command.
  • Estimate: 120–200 h. Human: whether to support a JavaScript scripting dialect at all (AGENTS.md forbids JS in the app).

G14. Camera Raw depth

  • Missing: CR3 (detected, unsupported), compressed RAF, lossy DNG (FILE-215-11), .xmp sidecars, masking and calibration in the Camera Raw dialog, per-camera colour for non-DNG cameras, lens profiles.
  • Evidence: crates/raw/src/lib.rs:228 (CR3), scorecard FILE-215-11/12, #50.
  • Progress (2026-10-11): on the embedded-JPEG rendering work in #2135, Camera Raw tuning retains the fitted look and applies user gains afterwards. Three D5100 NEFs establish a guarded 4928 × 3264 image area. This is an estimated rendering, not a measured D5100 colour calibration; validation.
  • Estimate: 120–200 h.

G15. AI and generative features

  • Missing: Generative Fill and Expand, Generative Upscale, Neural Filters, Sky Replacement, Super Zoom, ML Select Subject/Sky. There is no ML runtime (AUTO-219-7). Classical substitutes exist (GrabCut/max-flow selection, Remove tool by non-local patch completion).
  • Evidence: Photoshop's Required/UXP ships the Neural Filters gallery and its .kys has a Neural Filters taskspace; users ask for local models (#2313, #1996, #2682).
  • Estimate: 300–600 h. Human: owner decision #41 (model licences, local vs remote).

G16. Plug-in compatibility

  • Missing: hosting Photoshop .8BF filters (#2647), UXP panels, CEP. PhotoCraft has its own sandboxed WebAssembly plug-in ABI v1 (docs/plugins.md), but ABI v2 (panels, progress, other layers) is missing (AUTO-219-6).
  • Estimate: 250–450 h. Human: .8BF needs native code under the isolated-unsafe rule.

UI / UX gaps

G3. User-reported bug backlog

  • Missing: fixes for 579 open issues (2026-10-10), filed mostly by 0.5/0.6 users. By title: ~150 UI/panels/menus/shortcuts, 94 file/format, 87 layers, 54 painting, 54 selection, 46 type, 41 performance, 34 transform/move, 25 Linux/Wayland, 24 Windows, 20 macOS.
  • Impact: each is a place where a Photoshop user hits something different from what they expect; together they are why ready-for-real-work is ~45% while breadth is ~76%.
  • Estimate: 250–400 h (many overlap with other gaps; count each fix once).

G4. Brush feel

  • Progress (2026-10-13, #3825 partial): Hue Jitter shows a standard themed percentage-amount track rather than a hue spectrum. Other Brush Settings layout and brush-behaviour gaps remain open.
  • Progress (2026-10-13, #3825 partial): Shape Dynamics' Minimum Diameter / Minimum Roundness and Transfer's four Minimum rows disable editing when their Control is Off, retaining the configured values. The other layout and control gaps in #3825 remain open.
  • Progress (2026-10-12, #4283): the five checkbox-only Brush Settings rows keep the open controls selected; Smoothing can be disabled without discarding its amount or mode. This closes that panel interaction gap, not the stroke-quality or performance gaps below.
  • Missing: lag with big brushes (#2619, #1994) and under the cursor (#2566, #1343); jumpy pressure on Windows (#2046) and tap sensitivity (#1933); stair-stepped line weights (#1776), interpolation and smoothing problems (#2138), edge falloff (#2149), pixelated cursor vs Photoshop (#1433), brush rotation not driven by pen rotation (#1301); live preview only for Brush and Eraser (TOOL-213-7); no image file as a brush tip (TOOL-213-12); per-control pressure curves (TOOL-213-11); 4 of Photoshop's symmetry modes.
  • Detail: brush-parity.md.
  • Estimate: 100–180 h. Human: checking feel on real tablets.

G5. Transform, handles and modifiers

  • Missing: the full interpolation list; blend mode/opacity edits during a transform (#1384); path-point nudge; autoscroll at the edge while selecting (#2231); wheel and pinch behaviour (#2485, #2463, #1989); toolbar press-and-hold flyouts (#2301).
  • Done since the re-measure (2026-10-10 to 12): right-click menu during Free Transform (TOOL-214-5); commit/cancel buttons in the options bar (#2442); transform keeps the layer's place in the stack (#2439, #2480); Hand tool no longer interrupts a transform (#2302, #2327); locked layers refused before the drag (#2585, #2609); ⌘-drag temporary Move with box and snapping (#2681, #2684); Auto-Select box follows the picked layer (#2666, #2675); ⌘-arrow layer nudge (#2474, #2556); polygonal lasso Shift angles (#2352, #2396); double-click Zoom tool to 100% (#2508, #2527); tool-key memory in a group (#2608, #2625); hold-a-key spring-loaded tools (#2672); H/V skew fields in the Free Transform options bar.
  • Detail: ui-parity.md.
  • Estimate: 60–110 h.

G9. Panels, preferences and workspace

  • Progress (2026-10-12): toolbar tool shortcuts can be reassigned, removed and reset (#4086), with saved bindings, matching toolbar hints and regression coverage (ui-egui/src/tool_shortcuts.rs).
  • Missing: 27 of 156 preferences do nothing (scorecard prefs audit); panel groups can't float or tear off into OS windows (UI-217-5, #1828, #1213, #2305); Color panel lacks wheel and slider modes (UI-217-17, #2569); more themes (UI-217-11, #2661); layer clipboard (UI-217-23, #2670); tool-specific context menus beyond brushes, selections and Pen (context-menu-parity.md); Libraries, Comments, Contextual Task Bar panels.
  • Estimate: 150–250 h.

G2. Interactive performance

  • Measured progress (2026-10-10): Tilt-Shift's existing per-level halo/copy optimization measures 1.07–1.49× faster across nine local 24 MP cases, with exact finite output bits. The filtered proxy improves for medium/large blur; its default small blur and the 36 MP saturated-field gain are inconclusive. Method and full ranges. Native GUI latency remains unmeasured; no scorecard budget or parity percentage is changed by this work.
  • Missing: 22 of 25 budgeted scenarios are over budget (baseline 2026-10-05, a heavily loaded machine): layer move with styles 321 ms p50 against 25 ms, opacity change 276 ms against 20 ms, paste 12 MP 1.2 s against 100 ms, layer move on 15000×10000 16-bit 2.7 s, Content-Aware Scale 24 MP 954 s against 3 s, Select Subject 559 ms against 100 ms; the 150-layer nudge crashes the GPU compositor (P12); menus slow with thousands of layers (#2460); old machines lag (#2687).
  • Estimate: 200–350 h. Serial in places (compositor architecture).

File-format gaps

G10. Missing and partial file formats

  • Missing: JPEG Options dialog (FILE-215-19), Photoshop PDF read and write and multi-page PDF (FILE-215-10, #2541), AVIF read (#463) and default-build AVIF write, JPEG XL, JPEG 2000 (#1047), Cineon (DPX landed, #1049), DICOM, EPS/DCS, PCX, Pixar, IFF, WBMP, MPO/JPS, PICT, Scitex CT, Photoshop Raw; layered TIFF extra alpha channels (FILE-215-7); spot plates (FILE-215-17); .atn, .asl, .shc, .csh presets (FILE-215-15); C2PA (#1050).
  • Detail: file-format-parity.md.
  • Estimate: 120–220 h.

Hardware gaps

G12. Pen tablets and display hardware

  • Missing: tilt and rotation on Windows (pressure only, via WM_POINTER; no WinTab); native Wayland tablet-v2 (#79; the pen does not click on GNOME 50, #2622, KDE #2297); eraser end on flipped Wacom pens (#2285); HDR/EDR output (Photoshop has "Precise Color Management for HDR Display"); monitor profile detection off macOS and live profile changes; Touch Bar (macOS Intel only, low value); 3D mice.
  • Detail: hardware-parity.md.
  • Estimate: 80–150 h. Human: tablets and HDR displays to test on.

Localization gaps

G13. Hindi, Arabic, Vietnamese, RTL and engine messages

  • Missing: catalogs for Hindi, Arabic and Vietnamese (3 of the 12 key languages); right-to- left UI layout; Devanagari fallback fonts (Thai #1909 and Khmer, Lao and Myanmar #3259 landed); engine error and status messages in English in every language; browser-locale detection and CJK fonts on the web; Japanese IME on Windows (#590); installed Japanese fonts missing from the font list (#2330); Arabic layer names as tofu (#1408, #1693).
  • Evidence (2026-10-11): Czech dynamic UI wrappers, hardware labels and composed messages translate at display sites; engine errors remain English. This does not close G13.
  • Detail: localization-parity.md.
  • Estimate: 80–140 h. Human: native-speaker review of every catalog (none is recorded).

Platform gaps

G17. Platforms and distribution

  • Missing: Windows code signing (material not obtained), in-app update check (DIST-220-1, #2650, #2375), in-app bug report (DIST-220-3), Flathub (#173), starting without a GPU adapter (#2519, #2412), Windows 7 (#2310, out of scope), Intel Macs with OpenCore (#1859).
  • Estimate: 30–60 h. Human: signing certificates.

Revision history

Date Change Summary
2026-10-14 minor Corpus floors raised to the measured results of the CMYK, Lab, 32-bit, Levels, Threshold, Posterize, feather and pass-through fixes landed together: Photoshop-authored 165 → 185 of 258, io 146 → 148 of 170, psd-tools 242 → 249 of 309
2026-10-13 minor G1: CMYK Hue/Saturation and Color Balance work on C, M and Y as inverted RGB with black kept, as Photoshop does; cmyk hue-saturation 100% → 0.4–0.7% bad pixels, color-balance 74% → 25%; floor unchanged
2026-10-13 minor G1: CMYK Posterize works on the stored values (1 − ink); psd-tools posterize_cmyk passes; psd-tools floor → 242 of 309
2026-10-13 minor G1: CMYK stacks with Invert, Posterize, Threshold, Brightness/Contrast, Levels or Curves layers composite on the document's own inks (14 CMYK oracles pass); Photoshop-oracle floor 151 → 165 of 258
2026-10-13 minor CMYK Threshold compares the Rec. 601 luma of the inks' plain complement, as Photoshop does (every pixel of cmyk8/16 threshold-128.psd); oracle bad pixels 1.6% → 0.2%
2026-10-13 minor G1: CMYK Brightness/Contrast (modern and legacy) curves each ink's stored value as Photoshop does; oracle bad pixels 71% → 48–57%, floor unchanged
2026-10-13 minor G1: modern Brightness/Contrast's brightness curve refit to Photoshop (six oracles pass); Photoshop-oracle floor 145 → 151 of 258
2026-10-13 minor G1: Lab documents composite Invert, Posterize, Threshold, Brightness/Contrast and Lab Levels/Curves on their stored L, a, b (no clipped RGB round trip); seven Lab oracles pass; Photoshop-oracle floor 145 → 152 of 258
2026-10-13 minor G1: Levels round every step to whole levels and use Photoshop's shadow toe (slope 2^gamma); levels_rgb and stack-levels-curves-hue-posterize pass; Photoshop-oracle floor 145 → 146 of 258, psd-tools 236 → 238 of 309
2026-10-13 minor G1: 32-bit legacy Brightness/Contrast (pivot 127), Channel Mixer (1/1024 steps, constant ×255/512), Photo Filter and Black & White tint unclipped as Photoshop computes them, CPU and GPU; Photoshop-oracle floor 145 → 149 of 258
2026-10-13 minor G1: mask feathers blur with exactly the feather's variance; psd-tools mask-parameters-no-real-channel passes, layer_mask_data 27 % → 4 % off; psd-tools floor → 240 of 309
2026-10-13 minor G1: Threshold splits on Photoshop's luminosity of whole samples (8- and 16-bit rules exact on four oracles); psd-tools threshold_rgb and threshold_16bits_rgb pass; psd-tools floor → 241 of 309
2026-10-13 minor G1: a pass-through group that is a clipping base and holds an adjustment layer renders isolated; psd-tools passthrough_clipping_mask_adjustment passes; psd-tools floor → 240 of 309
2026-10-13 minor G1: 16-bit Posterize bins the 16-bit sample, as Photoshop does (exact on two oracles); psd-tools floor → 241 of 309
2026-10-13 minor G1: Gradient Map driven by the W3C luminosity (0.3, 0.59, 0.11), as Photoshop is; rgb8/rgb16 gradient-map pass; Photoshop-oracle floor 145 → 147 of 258
2026-10-13 minor G6: stale "missing tool" list corrected against main — Color Replacement (paint.colorReplacement), Red Eye (paint.redEye) and Add/Delete Anchor Point (P flyout, #4144) are live tools, so the toolbox is 67 of 68 and Art History Brush is the only one missing; no percentage or estimate changed
2026-10-13 minor G4: Hue Jitter amount-track appearance (#3825, partial); no readiness percentage or estimate changed
2026-10-13 minor G4: minimum inputs in Shape Dynamics and Transfer follow their Control (#3825, partial); no readiness percentage or estimate changed
2026-10-12 minor G5: Free Transform options bar H/V skew fields done
2026-10-12 minor G11: .atn import maps 112 events (Select and Mask, Spatter, Ripple, Crystallize, Pattern Fill, mask targeting, Step Backward, points, Bitmap)
2026-10-12 minor G9: toolbar tool shortcut editing (#4086); the remaining workspace gaps stay open
2026-10-12 minor G1: #2465 (CS2 PlLd-only smart objects) marked fixed
2026-10-12 minor G1: CMYK Invert, Posterize and Threshold work on the inks, 16-bit Lab Invert keeps greys neutral; Photoshop-oracle floor 133 → 145 of 258 (145 already passed on main)
2026-10-12 minor G5: spring-loaded tool letters done (#2672)
2026-10-12 minor G10: DPX read and write (#1049); Cineon remains open
2026-10-12 minor G4: checkbox-only Brush Settings rows preserve the open controls and Smoothing settings (#4283); no readiness percentage or estimate changed
2026-10-12 minor G11: Load/Save Actions .atn landed (AUTO-219-2 partial); remaining panel work listed
2026-10-12 minor Refreshed against main: G5 items fixed by #2480, #2327, #2609, #2684, #2675, #2556, #2396, #2527, #2625 and TOOL-214-5/#2442; G7 TOOL-214-7 and Transform On Drop done; G2 #2377 fixed (#2417); G9 UI scale (UI-217-9) and Stamp Visible (UI-217-22) done, prefs 27 of 156; G10 #2225 done (#2482); G13 Khmer, Lao, Myanmar fallback (#3259); G14 CR3 line; G17 Windows ARM64 done (DIST-220-2)
2026-10-11 minor G7: Field Blur pins are placed, dragged and tuned on the image (#3373); the other Blur Gallery filters remain dialogs
2026-10-11 minor D5100 image area and Camera Raw tuning validation; G14 remains open
2026-10-11 minor G13: Czech dynamic UI text localized; engine messages and native review remain open
2026-10-11 minor G8: triple/quadruple/five-click type selection done (TYPE-218-8, #3493); Type evidence recounted
2026-10-11 minor G8: Character panel OpenType buttons landed (TYPE-218-5, #3752); Numerals and Stylistic Sets submenus still missing
2026-10-10 minor G2: recorded Tilt-Shift computation/filtered-proxy measurements and limits; gap and estimate remain open
2026-10-10 minor Checked against the alpha gate (no alpha blockers); #2374 marked closed
2026-10-10 major First version, from the full re-measure against Photoshop 2026 27.11.0 and the open issues