10 Commits
Author SHA1 Message Date
Ian Swope 74cb293570 Large document: full CPU canvas redraws run in the background, visible part first (#4024) (#5001)
When the wgpu compositor can't draw a refresh (layers over its memory
budget, or a feature it doesn't cover), the CPU compositor redrew the
whole document inside the draw call: minutes of Not Responding on a
multi-gigabyte PSB. With background jobs, a refresh of 4 MP or more is
now composited by a worker in 1024 px blocks, the visible ones first;
each frame uploads the finished blocks and the canvas keeps its previous
contents until they land. An edit while it runs draws its own damage and
restarts the worker on the edited document with the blocks still to
draw. The status bar shows Redrawing N%. Web and tests keep the
synchronous path.
2026-10-13 12:49:58 -04:00
1cb3eaa5d2 Free Transform: apply a large layer's warp in the background, with progress and Esc to cancel (#1015) (#4218)
Committing Free Transform on a 300-550 MP layer ran the bicubic warp on the UI
thread: several seconds of a frozen window with no feedback. On a 20000x16000
layer the warp alone took about 6.6 s.

edit.transform is now a background job (edit_job): the warp reports progress per
tile and stops when cancelled. The transform session stays open while it runs,
showing the moving pixels without the box, so the layer doesn't jump back to its
old place until the result lands. The modal progress dialog shows after the
usual delay, and Esc or Cancel cancels the job. A copy or placed layer the
session made is folded into the step, or taken back, in its own document.
Undo and Redo are refused while the commit runs.

Tests and scripts run inline as before.

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 21:57:47 -04:00
Ian Swope 2caa58884c Free Transform: the preview follows edits made while the box is open (#1384) (#2099)
Changing the moving layer's opacity, fill or visibility in the Layers panel
during Free Transform was applied and kept on commit, but the preview showed
the session's starting state until then: the moving pixels kept their first
opacity and the document under them was the copy taken when the box opened.

follow_edits rebuilds that copy from the current document whenever its
revision changes (keeping the lifted pixels out) and takes the tint from the
layer's opacity x fill, or 0 when hidden. canvas::display_doc calls it before
reading the preview, so the canvas never caches a new revision with the old
preview. Lone mask/channel transforms and Transform Selection are unchanged.

Blend modes are still not shown on the moving pixels (an egui mesh blends
normally); that needs the compositor and is left for discussion in #1384.
2026-10-09 18:51:55 -04:00
f3fda94c87 Large document: a notice explains a slow CPU fallback redraw (#1015) (#1427)
* Large document: a notice explains a slow CPU fallback redraw (#1015)

A full refresh that falls back to the CPU compositor blocks the canvas for as
long as it runs, with only a log line to say why. The first one per document
that takes over a second now posts a notice: how long it took, which edits
redraw the whole document, and for the GPU memory budget fallback, that
raising Memory usage raises the budget (up to a quarter of RAM).

* Slow-refresh notice test: check the real "Memory usage" casing; add Polish rows

The last assertion looked for "Memory Usage" while the string says
"Memory usage", so it passed vacuously. The budget case now asserts the
hint is present too. pl.tsv gets rows for the new strings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-08 20:53:39 -04:00
Ian Swope e06c03344a Holding a number key sets one digit's opacity, not a two-digit value (#953) (#1030)
opacity_keys::handle ignored egui's `repeat` flag, so the first auto-repeat
of a held digit arriving within the 800 ms two-digit window read as a
second typed digit: holding 4 set 44%, and holding 0 flipped between 100%
and 0%. Repeats are now consumed without being counted.
2026-10-08 15:41:55 -04:00
Ian Swope 874867461d file.new at 300000x300000 no longer fills ~360 GB of tiles before returning (#705) (#900)
Surface::fill_rect materialized every tile it touched, so a new document's
background cost one 256x256 tile per tile slot: ~360 GB for the 300000x300000
size the command accepts. Fully covered tiles now share one Arc'd tile, so a
solid fill costs one tile plus the tile index; edge tiles are written as
before, and copy-on-write splits a shared tile off when it is edited.
2026-10-08 15:04:57 -04:00
Ian Swope dc256b8b39 A PSD layer with bounds but no channel data no longer aborts on a 360 GB allocation (#755) (#885)
record_surface sized the interleave buffer from the layer rectangle alone,
so a few-hundred-byte file declaring a 300000x300000 layer with no
channels made import allocate ~360 GB and abort. The channel decoders
already bound their output by the bytes present, so a buffer is now built
only when at least one channel decodes; otherwise the layer imports empty
with a warning.
2026-10-08 15:04:40 -04:00
Ian Swope 8f5a0817e4 Applying a text data set re-renders the type layer instead of blanking it (#990) (#1025)
apply_to_doc replaced a text-replacement variable's text and set the layer's
render cache to None, but nothing rebuilt it. The compositor draws a type
layer from that cache, so the text vanished from the canvas after
image.applyDataSet and from every file written by
file.export.dataSetsAsFiles. The layer is now re-rendered with
type_cmds::refresh, as type.edit does.
2026-10-08 14:30:50 -04:00
Ian Swope fe4243ba1f Text fields stop resetting the input method every frame, so keys no longer repeat endlessly on Wayland (#585) (#723)
The command palette and the channel rename field called request_focus on
every frame. egui interrupts IME composition on each request, so egui-winit
disabled and re-enabled zwp_text_input_v3 every frame. With an input method
such as fcitx5 the compositor regrabbed the keyboard each time, a key release
landing in the gap was lost, and winit's client-side repeat never stopped.

Both fields now request focus only when they do not have it, matching the
other rename fields. Tests assert the IME is not interrupted after the first
frame.
2026-10-07 15:59:51 -04:00
51d85c1170 Linux: AppImage update information and a .zsync beside it (#349) (#399)
* Linux: AppImage update information and a .zsync beside it (#349)

appimagetool now embeds
gh-releases-zsync|<owner>|<repo>|latest|photocraft-*-linux-<arch>.AppImage.zsync
(owner/repo from GITHUB_REPOSITORY, default storytold/photocraft), so
AppImageUpdate, AppImageLauncher and the like can find the next release and
fetch only the blocks that changed. With zsyncmake on the host it also writes
the .zsync, into its working directory, so the script runs it in $DIST and
warns when the file is missing.

The release job installs zsync and checks the embedded string and the .zsync
before uploading; docs/releasing.md lists the new asset.

Verified locally with packaging/linux/package.sh --skip-build --formats
appimage and zsyncmake from Ubuntu 22.04: --appimage-updateinformation prints
the string, and the .zsync's URL is the AppImage's basename.

* package.sh: absolute AppDir and appimagetool paths before running in $DIST

appimagetool now runs inside $DIST so zsyncmake writes the .zsync there,
which broke relative CARGO_TARGET_DIR / APPIMAGETOOL paths (AGENTS.md
suggests target/agent-<name>). Resolve both first, remove a stale .zsync
so a missing zsyncmake is reported, and list the .zsync and zsyncmake in
the header.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: bflatastic <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-07 05:26:01 -04:00