Commit Graph
958 Commits
Author SHA1 Message Date
NetterSilverandClaude Haiku 5.5 fb5896f43f Fix generator spaced size keeping size text in asset file name (#6628)
Found while testing crates/engine/src/web_cmds.rs:1171

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 03:07:45 -04:00
David MannandClaude Opus 5.5 e8ab6846c0 Place Scale Marker: lay out the label in points, not pixels (#5754) (#5755)
fontSize is in points and type.create renders it at resolution/72 px
per point, but the bar height, label height and baseline treated it as
pixels. At 300 ppi a 12 pt label (50 px) overlapped the bar. Lay out in
font_size * dpi / 72 px; type.create still gets points. 72 ppi is
unchanged.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 03:07:40 -04:00
David MannandClaude Opus 5.5 5a9fb94b9b Type: Paste Lorem Ipsum inserts into the type layer named by "layer" (#5659) (#5660)
paste_lorem decided whether to insert into existing text from the active
layer, while with_text edits the layer the "layer" param names. With a
pixel layer active, {"layer": T} made a new paragraph layer and left T
unchanged. An explicit "layer" now always goes through with_text, so a
type layer gets the text and any other layer is an error; without
"layer" the behaviour is unchanged.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 03:07:34 -04:00
David MannandClaude Opus 5.5 20c3e95a6b EXR: the ACEScg preference applies to every way of opening a file (#6782) (#6783)
The "Open color-less EXR as ACEScg" option was only applied by file.openAs; background
opens (File > Open, drag-drop, Open Recent via Session::start_open) and file.openExrParts
kept io's linear-sRGB default. Both now retag through tag_exr_colorspace, start_open before
the Ask When Opening prompt so it sees the ACEScg profile.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 03:07:29 -04:00
Korg1ch 84f326fa25 Actions: a Paint Bucket step keeps its explicit colour through .atn (#7199) (#7208)
* fix(atn): a Paint Bucket step keeps its explicit colour through .atn export and import (#7199)

* test(atn): an explicit Paint Bucket colour survives an .atn round trip (#7199)
2026-10-18 03:07:23 -04:00
Korg1ch f04fc80267 fix(engine): flip and rotate presets turn about the selection's bounds, as Free Transform does (#6928) (#6967)
* fix(engine): flip and rotate presets turn about the selection's bounds, as Free Transform does (#6928)

* test(engine): cover flips and turns about the selection (#6928)
2026-10-18 03:07:18 -04:00
NetterSilverandClaude Haiku 5.5 e66941bde1 Ignore a leading UTF-8 BOM in variable data set CSV import (#6485)
Found while testing crates/engine/src/variables_cmds.rs:285-292 (import_data_sets_text; the file is read in import_data_sets at line 279)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 03:07:13 -04:00
wwew 73cd277a03 fix(engine): check the define-brush tip size multiply before reserving (wasm32) (#6902) 2026-10-18 03:07:08 -04:00
NetterSilverandClaude Haiku 5.5 d722dd8b54 Clamp filter.cameraRaw exposure to its documented -5..5 range (#6876)
Found while testing crates/algo/src/camera_raw.rs:592 (value taken unchecked in crates/engine/src/lens_cmds.rs:137 raw_params)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 03:07:03 -04:00
David MannandClaude Opus 5.5 363d385114 Trap: refuse locked layers (#5623) (#5624)
Image > Trap wrote the active layer's pixels without checking locks, so a
layer under Lock All or the pixel lock (its own or its group's) was trapped
anyway. Check check_pixels_unlocked before writing, like the fill commands.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 03:06:40 -04:00
Korg1ch 36d6b669d4 test(engine): Content-Aware Fill with an empty sampling area is refused and changes nothing (#6532) 2026-10-18 09:46:18 +03:00
Korg1ch 0f12ac151f fix(engine): refuse a Content-Aware Fill with nothing to sample instead of filling black (#6532) 2026-10-18 09:19:41 +03:00
skylar taylor-barrick 2d0f3b61cf Merge pull request #2376 from Unemars/content-aware-workspace
Content-Aware Fill workspace, as Photoshop's
2026-10-17 22:50:54 -06:00
Michael HigginsandClaude Opus 5.5 4d9f163c61 Linux: Reveal never hands xdg-open a file (#7166) (#7168)
Reveal in Finder on Linux opens the linked file's parent with xdg-open,
which launches whatever it is given. A link stored as `tool.jar/a.png`
names a file as its parent, so Reveal launched that file. Reveal now
refuses a parent that is not a folder.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 20:53:59 -06:00
skylar taylor-barrick ead6d2197f Package: report its paths with / on Windows too (#7184)
`file.package` absolutised the caller's `dir` and then built `folder`, `document`
and every `links` entry on it with `join`, which always writes `/`. On Windows
that left one string spelt both ways:
`C:\Users\...\pkg/Print Me/Links/art.png`. The control channel takes `/` and
rejects alternate separators, so a reported path could not be handed back to it.

Spell the absolutised directory with `/` as well, keeping a leading `\\`
(verbatim and UNC prefixes, where Win32 does not accept `/`) intact.
2026-10-17 19:20:33 -04:00
Diego Chagas 235e7578f3 Print through CUPS over IPP and the print portal, so File › Print works in the Flatpak (#2728) (#3017)
Main prints through craft-print, which runs `lp`; a Flatpak has no `lp`,
so File › Print fails there. This keeps craft-print as the route wherever
`lp` is on the path (and on Windows) and adds two more behind it:

- `photocraft-ipp`, a pure-Rust IPP client (CUPS-Get-Printers/Classes/
  Default, Print-Job over HTTP/1.1) that talks to CUPS over its socket or
  TCP; the Flatpak manifests grant --socket=cups for it. A `printServer`
  named in the request always takes this route. `file.print.printers`
  lists the queues.
- The XDG Print portal as the app's spooler (`Session::print_spooler`),
  for a sandbox without the CUPS socket or a system without a printer:
  the system print dialog, seeded with copies, paper and orientation.
  PHOTOCRAFT_PRINT_PORTAL=1 prefers it, =0 removes it.

A route that exists but fails (unknown printer, refused job) is an error;
only a missing route moves on. Results gain `backend` (lp, windows, ipp,
portal). docs/printing.md describes the routes.
2026-10-17 15:55:54 -06:00
skylar taylor-barrick 039b86f8f6 Merge pull request #7149 from skylartaylor/linked-path-checks
Follow linked paths only on this computer or the document's share
2026-10-17 12:13:47 -06:00
ZuujisandClaude Opus 5.5 796ee3b103 Image Assets: two layers naming the same file no longer overwrite each other (#6614) (#6668)
* Image Assets: two layers naming the same file no longer overwrite each other (#6614) [WIP]

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

* Image Assets: two layers naming the same file no longer overwrite each other (#6614)

generate_assets wrote every requested asset, so two layers (or two
default variants) naming one file silently replaced each other and the
result listed the file twice. Each file is now written once per run:
layers go top first, as the Layers panel lists them, the first writer
keeps the name, and later ones are reported in errors. Names are
compared ignoring case, since Logo.png and logo.png are one file on
Windows and macOS.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:55:54 -06:00
David MannandClaude Opus 5.5 c9899e9c29 Duotone: ink curve points must be finite (#7049) (#7050)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:55:31 -06:00
ZuujisandClaude Opus 5.5 1dcaa28b96 Quick Export: Same Folder asks before replacing the document's own file (#6599) (#6632)
With Export Preferences › Location = Same Folder, Quick Export wrote
<name>.<format> beside the document's file with no check, so a document
opened from photo.png was exported over photo.png itself. The app now
opens the save dialog for that case, and file.export.quickExport refuses
it without a `path`. Overwriting an earlier export beside a PSD is kept.

Adds file_cmds::same_file (text, dev+inode on Unix, case-insensitive
canonical path on Windows).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:53:10 -06:00
David MannandClaude Opus 5.5 193f495e5a Perspective Crop and Rotate Arbitrary keep the Background opaque (#7138)
Both filled the Background's uncovered area with the background colour
including its alpha (tools.setColors accepts one), leaving see-through
pixels on a Background. They now use alpha 1, as crop_doc does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:44:29 +02:00
David MannandClaude Opus 5.5 e30cedbb2a Merge main into fix/transform-fill-cache
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 18:24:17 +02:00
David MannandClaude Opus 5.5 cdf7f2c7fe Merge main into fix/gradient-offset-resize
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 18:13:04 +02:00
David MannandClaude Opus 5.5 3096e456dd Merge main into fix/preset-group-rename-unique
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 17:24:08 +02:00
David Mann dc71c6440c Merge origin/main into fix/gif-delay-below-1fps 2026-10-17 17:00:12 +02:00
David Mann f8c7955699 Merge origin/main into fix/artboards-to-files-marks 2026-10-17 16:59:38 +02:00
Josephand00xJ0 351149f0df Fix the build: restore export_presets in load_presets_json (#5344 x #5616) (#6906)
#5344 added the export_presets field to Persisted; #5616 rewrote load_presets_json to read each set with take() and its Persisted initializer lacked the new field, so main stopped compiling (E0063 at presets/mod.rs:180). Read export_presets the same way as the other sets; the existing load step and round-trip test cover it.

Co-authored-by: 00xJ0 <56056764+00xJ0@users.noreply.github.com>
2026-10-17 08:32:54 -06:00
David MannandClaude Opus 5.5 7ebaa94a82 Render Video: GIF frame delay follows timelines under 1 fps (#7045)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:44:43 +02:00
David MannandClaude Opus 5.5 fd2c10bba8 Artboards to Files: paths, notes, slices and marks move with the board (#7010)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:12:17 +02:00
NetterSilver 158dcc6731 Fix engine compile error: load export presets from presets JSON
Fixes #6911

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit bb7a5f2309)
2026-10-17 13:34:00 +03:00
PerchAnton 120cfe19a2 Merge origin/main into fix/finite-command-parameters
Resolve Color to Alpha validation conflict while preserving the upstream array guards and finite color parsing.
2026-10-17 13:34:00 +03:00
MarekSpodBiedry 20013ef9bd Linked smart objects: don't refresh a document while a background job runs on it (#4973) (#5074) 2026-10-16 20:18:17 -06:00
nrq6993-a11yandCursor c61cf9f0bd fix(engine): load each persisted preset set independently (#5616)
One malformed set in the preferences file (gradients, swatches, styles...)
made load_presets_json ignore the whole presets blob, so saved New Document
presets vanished at every launch: the shell merges back only changed keys,
leaving the damaged set on disk. Each set now loads on its own; null and
malformed sets keep their defaults.

Refs storytold/photocraft#5602

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 19:20:06 -06:00
Ivan · VanTan 10fdcbbefc chore: fix workspace Clippy collapsible_match warnings (#5190)
Strict workspace Clippy stopped on collapsible_match in affinity, raw, plugins, engine, ui-egui and xtask. Pure conditions become guarded match arms (each falling through to an existing empty arm), and the ruler drag state and the measurement command result are bound to locals, preserving evaluation order, error messages and fallbacks.

The same lint fixes also appear as a side commit in #5157 (David2766), which can drop it on rebase.
2026-10-16 17:42:36 -07:00
Gert Pellin - GPE 5a33441af3 fix(engine): an unknown layer id or name lists the document's layers, and a name matches regardless of case as a fallback (#5058) (#5133)
A command that fails on a layer id or name the document doesn't have now says which layers there are, top-down as id and name (at most 20, names cut at 64 characters), so an agent can recover from the error alone: ids are not positions, and a freshly opened PNG's Background is layer 2. NoLayer becomes UnknownLayer { id, known } at both command entry points, keeping the 'no such layer' prefix. A layer name that matches no layer exactly matches regardless of case; an exact match always wins.

Fixes #5058.
2026-10-16 17:34:40 -07:00
Gert Pellin - GPE a6a1d2ed22 feat(algo): face detection with libfacedetection's CNN in pure Rust, and image.detectFaces (part of #3603) (#4513)
photocraft_algo::faces::detect finds faces in an RGBA image with a score, a box and five landmarks, through a pure-Rust port of libfacedetection's YuNet network (Shiqi Yu, BSD-3-Clause; licence, NOTICE and ATTRIBUTION rows included). The weights are the library's, converted to half precision (107 KB), parsed defensively. image.detectFaces reports the document's faces to scripts and agents; it is read-only with no history step and no menu entry. The network is compiled out of the browser build, where the command reports itself unavailable. Head outputs are pinned against the C++ reference.

Part of #3603.
2026-10-16 17:31:02 -07:00
wwew dd69f0797d fix(engine): reserve the define-brush tip and selection-mask buffers fallibly (#212) (#5469)
brush.defineFromSelection allocated the sampled tip and the selection mask infallibly over the whole selection, so a large selection could abort the process. Both now go through the engine's fallible allocator and return a normal error that leaves the brush, presets, history and document untouched. A failpoint test asserts all four.

Part of #212.
2026-10-16 17:23:08 -07:00
PerchenkoAnton f360cb0b4a fix(engine): give artboard exports unique indexed filenames (#6655) 2026-10-16 16:57:33 -06:00
David MannandClaude Opus 5.5 1338d78b45 Transform: drop a fill layer's cached render when its frame changes (#6808)
Free Transform and Warp moved a fill layer's linked masks (and so the frame its
gradient is laid out in) but kept the cached render, which the compositor draws
as-is while the Fill is unchanged. Drop the cache in apply_transform and
warp_layer, as transform_layer_geometry and Perspective Crop already do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 23:42:34 +02:00
David MannandClaude Opus 5.5 8c6412bbb2 Plug-ins: running a filter plug-in respects layer locks (#6770)
plugin.run wrote into raster and smart object layers without checking
locks. It now refuses layers with locked pixels or Lock All like the
built-in filters, and with Lock Transparency it keeps the layer's alpha
(as built-in filters do, via filters::keep_alpha).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 23:20:35 +02:00
David MannandClaude Opus 5.5 ec83a1117f Presets: refuse renaming a group to a name another group has (#6768)
renameGroup in the shared preset group editor renamed without checking
that the new name was free, so two groups could share a name and a later
replace import of that name removed both. It now refuses like the
Swatches sibling; renaming a group to its own name stays fine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 23:20:35 +02:00
David MannandClaude Opus 5.5 0fc5660f60 Image Size: a gradient fill's centre offset stays a fraction of the frame (#6778)
map_fill multiplied the offset by the full linear part of the canvas transform, so Image Size
(and Perspective Crop) scaled it: 200% moved a 25% offset to 50%. Each axis of the linear part
is now normalised, so scale drops out while flips and 90° turns still mirror and turn it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 23:19:40 +02:00
David MannandClaude Opus 5.5 e5f1ecf501 Merge main into fix/smart-filter-last-mask
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 22:24:50 +02:00
David MannandClaude Opus 5.5 221af61dea Merge main into fix/count-delete-group-active
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 22:22:55 +02:00
David MannandClaude Opus 5.5 48ed913526 Merge main into fix/video-fps-validate
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 22:19:12 +02:00
David MannandClaude Opus 5.5 0ea2684679 Merge main into fix/adjust-smart-object-locks
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 22:15:01 +02:00
David MannandClaude Opus 5.5 dc71a11d41 Merge main into fix/4809-dual-brush-size
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:25:44 +02:00
aiongg 0a328189aa Smart filters, data sets, video: re-read stored files only when local or on the document's share
Paths these features keep in the document go through the same resolver as
linked smart objects (relative to the document's folder; other computers,
devices and URLs refused):

- A Displace smart filter's `mapPath` and a Color Lookup smart filter's LUT
  `file`, re-read on every re-render: a path that is not followed leaves
  the pixels as they are, as for a missing map or LUT.
- Apply Data Set's Pixel Replacement images.
- Reload Frame of a video layer's footage.

Apply Data Set and Reload Frame say why a path is not followed.
2026-10-16 16:53:28 +00:00
aiongg 348499a5e0 PSD export: embed linked files only when they are local or on the document's share
Save As PSD/PSB, layered TIFF and Package as PSD read a linked smart
object's file to embed it. The path is resolved with the same resolver as
other linked-file reads, against the document's own file (the new
ExportOptions::document_path / PsdExportOptions::document_path, set by the
engine's saves and the app's Save As). A link that is not followed is
written as pixels with a warning that says why, as for a missing file.
Nested smart-object documents resolve against the same document.
2026-10-16 16:53:21 +00:00
aionggandDavid Mann 87918a6c85 Linked smart objects: follow stored paths only to local files or the document's share
A linked smart object's path comes from the document. Every place that
follows it now goes through one resolver (photocraft_io::doc_path, built on
craft-docpath): a relative path is taken from the document's folder, and
only local files or files on the share the document itself is on are
opened. Paths to other computers, device paths and URLs are treated like a
missing file: the layer keeps its last rendering.

- Link watch: such links are not stamped, and no background look starts
  when no link of the document is followed.
- Update Modified Content and other re-renders read only followed files;
  the error names why a link is not followed.
- Package lists such links as missing instead of copying them.
- Reveal in Finder for a linked layer shows only a followed file; the
  document's own Reveal is unchanged. The macOS invocation ends options
  with `--`, and Explorer is not asked to select a path containing a comma
  or a quotation mark.
- Place Linked stores a relative path as an absolute one.

The document path travels to smart-object renders in a thread-local scope
entered by Session::edit, command dispatch and background jobs.

Co-authored-by: David Mann <daaavid.mann@gmail.com>
2026-10-16 16:53:13 +00:00