poll_render clears the pending develop before it reports the worker's
error, so previewPending turned false with no new frame and settle()
returned as if one had landed. It now fails with the status message
when the dialog reported an error. With the worker forced to fail, all
21 scope tests passed before this change; now the one that settles
after a worker develop fails with "the Camera Raw preview worker
failed".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dialog's own controls develop the preview on a worker (#5964), so after
the scope tone drags its last frame could land after the test recorded
previewRevision and bump it during the hover check that must not re-render.
A settle() helper waits until previewPending is false; the fixture and the
drag test call it before taking a baseline.
With a 20 ms delay added to the worker, the old test failed 9 of 10 runs
with the reviewer's assertion; with settle() all 21 tests passed 10 of 10.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One ToolOptions::sample_all_layers was shared by twelve tools, so ticking
it for the Magic Wand or Paint Bucket also turned it on for Blur, Sharpen
and Smudge. A dab of those on an annotation layer then pasted an opaque
copy of the composite there, which sat above the picture and hid every
later blur of it (the sharp round patch in #5524).
ToolOptions::sample_all is now a SampleAllLayers with one flag per tool,
all off by default: Magic Wand, Quick and Object Selection (and Object
Finder), Paint Bucket, Magic Eraser, Spot Healing, Patch, Content-Aware
Move, Remove, Blur, Sharpen and Smudge. Each options bar and command
reads its own tool's flag, and Reset Tool resets only that tool's. The
new key means older preferences drop the shared flag, so every tool
starts with it off.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The shortcut pass runs before the panels are drawn, and its crop/polygon
branch consumed Esc and Enter even while an egui popup of the options bar
was open (the Ratio list, the overlay and gear menus): Esc cancelled the
crop and left the list open, Enter committed the crop beneath it. Skip the
branch while `egui::Popup::is_any_open` (kept in memory from the last
frame; `ctx.any_popup_open` only sees the current pass).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The stroke preview was white at a quarter opacity, invisible on white
and faint elsewhere. Photoshop shows the area to heal in half-opaque
dark grey; the trail now does too. Other retouch trails are unchanged.
Test: drag_preview_canvas::the_spot_healing_trail_is_a_dark_half_opaque_tint
drags the real canvas offscreen on white and mid-grey and checks the
trail darkens the pixels to 40-60 %; it fails with the old colour.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
On a document over about 5 MP with Low Resolution Previews on, a filter
dialog's live preview ran on a reduced proxy at every zoom, so at 100 % the
canvas showed the image in k-pixel blocks and hid exactly what Unsharp Mask,
High Pass and Smart Blur do.
ensure_filter_preview now caps its factor with proxy::view_factor(k, zoom),
the same rule adjustment previews got in #5764, so from 100 % the preview is
full resolution and stays reduced when zoomed out. The preview already runs on
a cancellable background worker, so the full-resolution path does not block the
UI thread.
Also in this change:
- canvas.rs and filter_preview_ui.rs share one canvas::filter_preview_factor,
so the in-dialog preview window cannot size textures from an uncapped factor
and rebuild them when the first result arrives.
- The cap is gated on cfg!(target_arch = "wasm32"), so the web build keeps its
proxy rather than running a full-resolution filter inline on the UI thread.
- The end-to-end test moved into mod tests and covers Low Resolution Previews
off; a new factor test covers 25/50/75/100/400 %, NaN and zero zoom.
Both harnesses built their device with egui_kittest's .wgpu() (egui's default limits) instead of
gpu_canvas::wgpu_setup() (the adapter's own limits, what the app requests), as #5954 fixed for the
background_redraw and navigator tests. On that device:
- canvas_16f's sixteen_bit_gradient_on_screen and thirty_two_bit_preview_exposes_values_above_one
panicked in egui-wgpu ("Failed to create staging buffer for index data", renderer.rs:984), every
run, single-threaded too;
- arbitrary_rotation_preview's two tests failed "the fixture must use the GPU canvas".
Both binaries pass on the app's device.
* perf(gpu): cap the GPU memory budgets by a discrete GPU's own memory
The compositor and canvas budgets came from system RAM only, so a 16 GB card in a 96 GB machine planned for up to 8 GB of compositor pages plus 24 GB of display textures. Detect a discrete adapter's dedicated memory (DXGI on Windows through winsafe, amdgpu sysfs on Linux) and cap each budget at three eighths of it. PHOTOCRAFT_VRAM_MB simulates a smaller card. Fixes#4536.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* perf(gpu): keep a 16-bit canvas on the GPU in 8-bit when floats don't fit
With the canvas budget capped by a small card's memory, a 16-bit document under the float-canvas pixel limit could need more than the budget as Rgba16Float and fall back to the CPU canvas although its Rgba8Unorm set fits. Choose the float canvas only when it fits the budget, which is now shared atomically so format_for can read it while the renderer is locked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* perf(gpu): detect a card's memory once per adapter, and test the Linux reader
video_memory built a DXGI factory each time it was asked, three times at
startup; it now remembers the answer per (vendor, device), so a GPU retry
that picks another adapter still gets its own. The amdgpu sysfs reader
takes the drm folder as a parameter and is tested against a temporary
folder: the matching card's VRAM, a card without the file, garbage ids, a
malformed size, no match and no folder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix two ui-egui tests broken on main by concurrent landings
- profile_open_tests: #5614 renamed the built-in profile to "(PhotoCraft)" after #5208
hard-coded "(Photocraft)" in the Use-embedded button label.
- smart_object_curves_preview_keeps_its_size_on_a_reduced_proxy (#5779): the reduced
square is in the right place, but its corner pixels are 220/255 after the smart
object's resampling; assert full coverage inside and over half at the corners.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Web: ship the wasm as compressed parts behind a boot module (#5302)
The release site held the app's wasm as one file, which sits at the 25 MiB
per-file cap of Cloudflare Pages, so the 24 MiB gate failed unrelated PRs.
packaging/web/package.sh now gzips the wasm and cuts it into parts of at
most 20 MiB under app/, and ships the page of a new crate,
apps/photocraft-web-boot. That module fetches the parts in order, has the
browser inflate them (DecompressionStream) and passes the stream to the
app's own wasm-bindgen loader, so the browser still compiles while it
downloads. A failed download is retried twice, then the page says so.
The check is now that no file in the site is over 24 MiB. trunk build in
apps/photocraft-web and the Docker image still give one .wasm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Web boot: inflate the parts in the module where the browser can't (#5302)
* Tests: the adjustment-layer drag preview test follows #5778 (#5926)
#5778 caps a live adjustment-layer preview by the view's zoom, so at 100 %
the drag shows full resolution even with Low Resolution Previews on. The
test still expected the reduced copy there. It now checks that at 100 % every
column shows by default, and moves the low-res on/off comparison to 50 %,
where the reduced copy is still used: on, the drag shows only the (inverted)
black columns; off, it matches the committed view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Before this change, Export As always resampled the exported copy with Bicubic, and the preview
used Bilinear. Pixel art and screenshots could not be enlarged with hard edges. Photoshop's
Export As has a Resample menu next to Width/Height/Scale for this.
- engine export_preset_cmds: RESAMPLE lists Photoshop's six choices (Bicubic Automatic, the
default, then Bicubic, Bicubic Smoother, Bicubic Sharper, Bilinear and Nearest Neighbor)
and export_resample() checks a value. An unknown value is now an error. Before, Image Size
quietly treated it as Bicubic.
- Export presets save and restore resample. Older presets load with the default.
- layer.exportAs takes a `resample` param, checked even at 100 %.
- Export As dialog: a Resample dropdown under Image Size. It is remembered with the other
settings, and a bad saved value falls back to the default. export_document passes the
choice to image.imageSize for the exported copy only.
- Preview: the proxy keeps its fast Bilinear path, except for Nearest Neighbor, which is
just as cheap and is what the user wants to see. The preview texture also uses NEAREST
magnification then, so small exports look blocky instead of blurred.
- Translations for the three new labels in all 20 catalogues.
- Scorecard FILE-215-18 is now done. Also fixes a stale doc comment on VectorMask::feather,
because the compositor does feather vector masks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(tests): bound print fuzzing and serialize Windows crop fixtures
* Crop tests: hold the shared GPU lock in every WARP-backed test; drop the print fuzzing change main already has (#4467)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): expose custom paper dimensions in Print Settings
* fix(i18n): keep custom paper labels away from catalog tails
* Custom print paper: Serbian rows; drop the env-gated capture test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: zenjabba <679864+zenjabba@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Canvas: a tool drag past the edge auto-scrolls the view (#4183)
Dragging a marquee, lasso, stroke or crop off the visible part of a zoomed
image now scrolls toward the pointer, as in Photoshop, and stops at the
scroll limits. An axis where the image fits does not scroll, and an
overscrolled view is not pulled back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFAjTZQw6MTyhfpQqjAfoW
* Fix rotated canvas auto-scroll limits
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: jonathnwxh <258340642+jonathanwxh-cell@users.noreply.github.com>
#4093 changed the Auto-Select default to Group, as in Photoshop. The two
layout_perf tests that click layers inside groups with Auto-Select on
expected the layer under the pointer, so they now select the group and
fail on main. They set the target to Layer explicitly, which is what
they test.
* i18n(cs): drop duplicate catalog entries
The Czech catalog listed Tool, Clone Stamp, History Brush, Dodge, Burn,
Sponge and Stroke Path… twice, which fails `cargo xtask i18n-coverage`
and the bundled_catalogs_are_consistent test. Keep one row each; for
Stroke Path… keep the later row, the one the loader already used.
* test(crop): pin the UI language to English in crop_default_mode
The test queries widgets by their English labels ("Ratio", "Swaps height
and width") but its harness followed the system locale, so it failed on a
machine whose locale has a registered catalog. Set interface.language to
"en" in the harness, as paint_bucket_all_layers and export_as_scaling do.
* i18n: add Hungarian (hu) UI translation
A complete Hungarian catalog (hu.tsv, 3908 entries: 3829 plain, 50
contextual, 21 plural, 8 command-id overrides) registered with the
complete-menu languages; hu-HU locales resolve to it.
Plural rule: plural_none. A Hungarian count takes the singular noun
("3 réteg"), so every @plural entry has one form.
Clean room: written from the plain meaning of the English labels with
ordinary Hungarian image-editing vocabulary; no proprietary localisation
resource, string table or product UI was used.
Also listed in the coverage test, LICENSE-translations.txt,
ATTRIBUTION.md and docs/localization.md, with tag, system-locale and
plural tests.
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
On Windows, GPU UI test binaries crashed with STATUS_ACCESS_VIOLATION
whenever two of their tests ran at once: arbitrary_rotation_preview 3
runs in 4 on main, and color_managed_canvas, crop_default_mode,
drag_preview_canvas and full_res_preview at random. egui_kittest
prefers a CPU adapter, which on Windows is WARP over DX12, and a dump
puts the fault inside d3d10warp.dll with two WARP devices rendering on
parallel test threads. Bare egui harnesses with no PhotoCraft code
crash the same way, and Vulkan or GL never does. CI's Windows runners
have no GPU, so they run on WARP too.
Three binaries already held a per-binary lock for the same class of
driver bug (Mesa llvmpipe and RADV, #194; libvulkan with NVIDIA and
lavapipe). That lock now lives once in photocraft-testkit
(`gpu_lock`, with the reasons) and every test in the twelve GPU test
binaries without it takes it on its first line. Under nextest each
test is its own process and the lock is free.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Keep the Color panel selected when switching themes (#3371)
* Scope Camera Raw color checks to its settings panel (#3371)
---------
Co-authored-by: prekabreki <prekabreki@users.noreply.github.com>
The GPU canvas built a full-resolution, mipmapped texture set covering the
whole declared canvas (DocTextures::new) with only per-texture side limits,
so a document's size alone decided how much GPU memory was committed: a
300000x300000 canvas asks for hundreds of gigabytes.
canvas_texture_bytes prices the set in closed form (every tile and mip
level) and DocTextures::new refuses one over canvas_budget (a quarter of
RAM, at least MIN_GPU_BUDGET) without creating anything. refresh reports
"over-budget" and ensure_gpu then draws the document on the reduced CPU
canvas, so a supported large document still displays.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Tests: the marquee ellipse check walks several dash periods of the curve
The old check sampled ~12 positions along a 28 px arc of a 4-on/4-off ants
pattern: a margin of one sample, so renderer, antialiasing or dash phase
decided pass or fail. Walk the true ellipse around each edge midpoint over
±0.3 rad instead, with the ±2 physical px band hugging the curve, so the
check sees the dashes wherever the phase puts them (#253 follow-up).
* rustfmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Pixel grid: fade the line colour across mid-tones instead of switching at one luma
The grid line was a full white line over pixels at or below luma 0.55 and a weaker
black one above it. On an image whose tones sit near that cut, neighbouring cells
landed on either side and the grid showed bright, dark and different-strength lines
in patches that traced the 0.55 contour.
The line is now a white layer and a black layer weighted by a smoothstep over luma
0.35 to 0.75, so mid-tones get a faint line that varies smoothly and dark and light
pixels keep the old white and black lines. The GPU shader and the CPU path
(pixel_grid.rs) use the same band; the CPU share is rounded to eighths so equal-colour
runs still merge into long segments.
Tests: unit tests for the fade and mid-tone evenness, and a GPU render test that
fails on the old shader (grid line 30 levels off its pixel at grey 0x87).
Co-Authored-By: Claude <noreply@anthropic.com>
* Pixel grid: test the line over opaque white too (the #2461 review case)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
View > Pattern Preview only toggled a flag that nothing read. With it on, the
canvas now draws the document repeated around itself as a seamless tile, like
Photoshop, on the GPU canvas and the CPU fallback:
- pattern_preview.rs works out which copies cover the visible area (at most
31 per axis, centred on the view).
- GPU: each copy is another paint callback over the same document texture,
offset by whole document sizes in the shader (ViewParams::offset, the spare
view-uniform slots), so nothing is composited again and seams are exact.
- CPU: the same texture drawn again with a shifted view mapping.
- The drop shadow is off while tiling; selection, guides and other overlays
stay on the document itself.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Compute snapped handles from press-time geometry to avoid feedback from
Auto Center Preview. Paint the image and crop frame with the same camera.
Apply ratio and size options immediately as centred rectangles contained
in the current frame. Respect saved ratios, keep Swap and X consistent,
and let Enter apply output sizes without an initial resize.
Use container padding for the options bar's text allowance so right-aligned
crop actions do not introduce horizontal overflow.
Add regression coverage for handle snapping, rendered frames, preset/Enter
and Swap/X interactions, and options-bar scrolling at multiple UI scales.
Enable public GitHub profile name syncing for TheTeslak's contributor credit.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* perf(gpu): reuse unchanged lower-stack composite chunks
* test(gpu): verify half-float composite checkpoint reuse
* docs: record measured GPU composite reuse gains and limits
* perf(gpu): admit whole grid chunks, serve partial damage as sub-rect copies
Cache entries were keyed by the exact damage rectangle, so brush strokes
(a new partial rect every frame) filled the budget with entries that
never hit again. Entries are now whole chunk-aligned, chunk-sized grid
cells: a capture merges into the cell it overlaps at the rectangle's
offset, a read is served as a sub-rect copy of the covered part, and
coverage is tracked per cell so a resume only happens where every pixel
of the requested rectangle is cached. After a full render, any in-bounds
partial rectangle hits.
Regression test: two series of disjoint strokes inside the same chunks
leave composite_cache_bytes unchanged (no entry-per-rectangle growth),
a full refresh still finds its chunks and a top-layer edit afterwards
reuses them. The old partial-damage test now expects the covered-chunk
hit and checks outside-the-document renders admit nothing.
* docs: record the GPU reuse measurement in target-app-parity.md
The honest assessment moved from roadmap.md to target-app-parity.md on
main; keep main's roadmap and add only the dated measurement.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
View > Pixel Aspect Ratio and its Correction toggle only stored a value:
the canvas transform (ViewXform) had no notion of pixel aspect, so the
image was always drawn with square pixels.
ViewXform now carries the displayed pixel aspect ratio and stretches
document x by it before rotation and flip. Drawing, pointer-to-document
mapping, pan and zoom-about deltas, rulers, grid, Fit/Fill on Screen,
scrollbars, the Navigator's view box and paste-in-place visibility all
go through it. Correction is on by default, as in Photoshop. A corrected
view draws through the CPU canvas path, like a flipped one, since the
GPU shader assumes square pixels. The document's pixels are unchanged.
Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Crop tool: Photoshop's default mode turns the image behind an upright box; Use Classic Mode (P), Auto Center Preview (#1919)
The Crop tool's gear menu gets Use Classic Mode (P toggles it while the tool shows a box; off by
default, as in Photoshop) and Auto Center Preview (on by default). In the default mode the box
stays upright on screen: a drag outside it turns the image about the box's centre (Shift 15 deg
steps, angle readout), a drag inside moves the image under the box, the arrow keys nudge the
image, Straighten turns the image so the line is level, and with Auto Center Preview a drawn or
resized box is re-centred in the canvas. Classic Mode is the previous behaviour.
The crop model and the commit are shared: crop_rect + crop_angle and one image.crop {angle}.
crop_mode.rs only drives the view: while a frame is edited the view's rotation is -crop_angle,
pivoting so the box (or the pressed point) stays put, and the user's view (a Rotate View turn,
its centre) is put back on commit, cancel, leaving the tool or switching to Classic Mode. The
untouched default frame leaves an R turn alone. The options live in ToolOptions.crop_shield
(classic_mode, auto_center_preview; ui.set cropShield). ui.pointer reads a default-mode drag's
points in the view at its press, so an up where the last move was is idempotent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Crop default mode: match Photoshop (view upright on tool pick, live Auto Center, image-turn readout, gear menu order) (#1919)
Checked by hand against Photoshop in its default (non-classic) mode:
- Picking the Crop tool turns a Rotate View (R) turn upright at once (about the view centre),
and the view stays upright after Enter or Esc: the save/restore of the user's view is gone.
Classic Mode still leaves the view alone (not checked against Photoshop).
- The angle beside the pointer while the image turns is the image's turn, clockwise positive.
- Auto Center Preview keeps a box being resized centred live: the handle follows the pointer,
the box grows on both sides, and on the image the opposite edge (with Alt the centre) stays
put. Computed from the press, so the view following the frame can't feed back into the drag.
- Gear menu order as Photoshop's: Use Classic Mode (P), Show Cropped Area (H), Auto Center
Preview, no separator; Classic Mode greys out the other two. Show Cropped Area then acts as on
and H does nothing (the stored choice is kept). German: Classic-Modus verwenden, Automatische
zentrierte Vorschau.
- Crop preview past the canvas in a turned view: each band is drawn as its turned quad (the
screen bounding box stretched band textures and spread the checkerboard). The white corners in
the earlier offscreen snapshot were egui_kittest clamping the repeating checker texture; the
real app shows the checkerboard in both modes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs: regenerate scorecard for the crop default-mode row (#1919)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Crop default mode: the canvas keeps the view the Crop tool moves; a move starts from a centred box (#1919)
Tried by hand in the app: with Auto Center Preview on, a drag inside the box made the box wander
instead of moving the image, and a handle drag didn't keep the box's centre in place (after a
Space pan, too). Root cause: canvas_view works on a copy of the view for the frame and writes it
back at the end, so every camera change the Crop tool made during the frame (the image moving
with the pointer, the live-centred resize, Auto Center on release, the pivot while turning) was
undone, while the frame itself moved in the document. Tests driving tool_event never saw it.
- canvas_view notes the camera before the frame's input and, before writing its copy back,
keeps a centre or rotation a tool changed meanwhile.
- With Auto Center Preview a move inside an off-centre box (after a pan) centres the box first,
then grabs the image point now under the pointer.
- New tests/crop_default_mode.rs drives the full app UI with egui pointer and key events over
several frames (offscreen wgpu): the box stays put while the image follows the pointer, and a
resize after a Space pan keeps the box's centre fixed while the handle follows the pointer. Both
failed before the fix.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Crop default mode: a moved or resized box stays where it is on screen (#1919)
Checked in Photoshop: after a Space (Hand) pan the box isn't snapped back to the middle of
the canvas. A drag inside moves only the image, the box staying wherever it is; a resize keeps the
box's centre where it was at the press (also after a pan), and its release no longer pans the box
to the middle. Auto Center Preview still centres a newly drawn box on release and X still
re-centres. Classic Mode is unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Crop tool: Classic Mode turns a Rotate View turn upright too (#1919)
Checked in Photoshop: with Classic Mode on and the view turned with Rotate View (R), picking
the Crop tool also turns the view upright, and the crop frame stays on the same image area. So the
Crop tool now drives the camera in both modes: default mode keeps it at -crop_angle (the box
upright), Classic Mode at 0 with the frame drawn with its own turn. Picking the tool pivots about
the view centre; commit and cancel leave the view upright in both modes, and P switching modes
never brings the R turn back. Auto Center Preview stays default-mode only.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Regenerate the scorecard; update the crop test's header
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Crop: snap a new box's corner inside the untouched default frame in both modes (#2549 with #2545)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Interface > UI Scale is egui's zoom_factor, folded into pixels_per_point
with the display scale. The canvas divides View::zoom by the full
pixels_per_point (PhotocraftApp::point_zoom) and the GPU shader
multiplies it back, so 100% is one document pixel per physical display
pixel at every UI scale -- fixed by #1978 (Canvas: zoom is physical at
any display scale), which the report predates (v0.5.0). No behaviour
change; the report is stale.
Lock it down with a rendered-output regression test
(tests/ui_scale_canvas.rs): a black document is drawn through the real
app on the GPU and CPU canvas at UI Scale 100/125/150/200% and zoom
100/200%, and its on-screen size is measured in the pixels. It stays
60x60/120x120 physical px at every UI scale, and fails when the canvas
stops using point_zoom (75x75 at 125%) or when the GPU shader stops
multiplying by pixels_per_point (48x48 at 125%). The comments on
View::zoom, PhotocraftApp::ppp and canvas_view now name UI Scale too.
View zoom now means device pixels per document pixel, so 100% shows one image pixel per physical
display pixel on a scaled (125%, 200%) display, as in Photoshop. Fit, Actual Pixels, pan/zoom anchoring,
scrollbars, the navigator, the pixel grid and every hit tolerance go through canvas_ppp / point_zoom.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CPU path drew the pixel grid over the whole view, including transparent areas, while the
GPU shader only lightens or darkens pixels with content. pixel_grid.rs applies the shader's
rule to the composite of the area in view, as merged segments cached per revision and padded
region (capped at 60k segments), sharing the zoom and strength constants with the shader.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Concurrent wgpu instances segfault in libvulkan with NVIDIA and lavapipe both installed; the three
GPU-harness tests in camera_raw_scope now hold a GPU_LOCK, like canvas_16f. Fixes#1855.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test (windows-latest) fails on main in adjustment_layer_drag_previews_at_full_resolution_when_low_res_is_off
with "PollError: The requested Wait timed out before the submission was completed.": egui_kittest
waits at most 10 s for the GPU, and on the Windows runner the adapter is WARP, where the first
full-resolution composite of the 6 MP test document can take longer. The submitted work still
completes, so the pixel read now waits up to three times; the assertions are unchanged.
Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA
Co-authored-by: Claude <noreply@anthropic.com>
Marks TOOL-214-17 done and drops the now-read trackpad rotate preference
from the unread list. The Paint Bucket test mapping carries the view's
camera angle.
Turns the canvas camera around its centre without rewriting pixels.
Reset View zeros the angle; Match Rotation copies it across documents.
Trackpad rotate follows Preferences › Enhanced Controls › Rotate View with Trackpad.
A crop with Delete Cropped Pixels off keeps what it cuts away (and a moved layer can reach past
the edges), but the Crop tool drew only the canvas, so a frame couldn't be pulled back out over
those pixels by eye. Photoshop 25.4 (observed) grows the view to every layer's bounds and the
frame from the first press on the frame: hidden pixels under the shield, transparency as the
checkerboard, no canvas border or shadow, until the crop is committed or cancelled.
- crop_ui: `CropState::editing`, set by the press (before egui's drag threshold) and cleared when
a new default frame is made (commit, Esc) or the tool is left; `shows_beyond_canvas`.
- canvas: `draw_beyond_canvas` composites only the ring between the canvas and every layer's
pixels (`extra_cmds::reveal_all_bounds`, shared with Image > Reveal All), once per document
state, at most MAX_TEXTURE texels per side (from a downsampled proxy past that), and draws it
with the checkerboard out to the frame under the canvas, reaching a point under its edge so the
edge's anti-aliasing has no seam. The drop shadow and the Line border are off meanwhile.
- compose: `render_reduced_rect`, the reduced render of any area of the document plane.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>